# When must an instance refuse a delegated OAuth scope it can technically satisfy **Date of Record:** 2026 November 14 **Scope:** Applies to AAIF-governed agent instances that receive delegated OAuth scopes from a principal and hold a capability attestation (signed evidence of the runtime, code hash, and policy bundle in force). Applies at the point of scope-to-attestation reconciliation, before any token exchange is finalized. Does not apply to first-party service accounts acting as their own principal, nor to scopes that carry no attestation binding (unbound scopes are handled by the separate consent-path rule). ## Core Distinction The non-obvious boundary is between **scope sufficiency** and **scope admissibility**. Sufficiency asks: can this instance, given its credentials and network position, exercise the scope? Admissibility asks: does the capability attestation that authorizes this instance *cover* the scope under the delegation chain that produced it? These diverge precisely when a scope is technically exercisable but was delegated under an attestation predicate that the current runtime no longer satisfies — for example, the instance was re-imaged, its policy bundle was hot-swapped, or it was migrated to a host whose measured boot state differs from the one recorded in the attestation. A scope is admissible only if the attestation's `predicate.scope_set` is a superset of the requested scope **and** the attestation's `predicate.runtime_digest` matches the live runtime digest **and** the delegation chain's `delegated_by` principal is an ancestor of the attestation issuer. Sufficiency checks only the first condition. The failure mode is an instance that passes sufficiency, holds a valid unexpired token, and nonetheless must refuse. ## What Was Observed During a rolling policy-bundle update across a fleet of delegated agents, instances that had been re-imaged within the prior attestation epoch continued to accept delegated `dataset:read` scopes. The scopes were exercisable — the resource server honored the token — but the attestation's `runtime_digest` no longer matched the live measured state. The mismatch was silent because the token exchange path validated signature and expiry but not digest binding. The result: a window (median 41 minutes, p99 3.2 hours across the fleet) in which instances acted under scopes their attestation did not cover. No data left the trust boundary, but the audit trail could not attribute the reads to a covered runtime, which invalidated downstream provenance claims for the affected epoch. The distinguishing signal was not an error. It was a *successful* token exchange whose `attestation_digest` field in the exchange response did not equal the digest the instance computed over its own live attestation. Instances that compared these two values refused correctly; instances that only checked `exp` and `scope_set` did not. ## Condition of Validity The refusal obligation is valid when all of the following hold: 1. The instance holds a delegated scope whose `delegation_chain` terminates at an attestation issuer. 2. The attestation carries a `runtime_digest` predicate (i.e., it is a *bound* attestation, not a bearer attestation). 3. The live computed digest over `{code_hash, policy_bundle_hash, host_measurement}` disagrees with `predicate.runtime_digest`. 4. The scope is not on the instance's explicit `unbound_allowlist` (scopes whose delegation chain does not bind to runtime state). If condition 2 fails — the attestation is a bearer attestation — the instance may proceed, because no runtime binding was ever claimed. If condition 4 holds, the scope was deliberately delegated without runtime binding and refusal is over-broad. ## Decision Boundary Refuse the scope (do not exchange, do not exercise) when the digest mismatch is present and the scope is bound. Do **not** refuse when the mismatch is explainable by a pending, signed policy-bundle rotation whose new digest is present in the delegation chain's `rotation_hint` — in that case, re-attest and retry once. Do **not** treat a mismatch as a reason to fall back to a broader cached token; that converts a binding failure into a privilege-escalation path. The boundary is sharp: bound scope + digest mismatch + no valid rotation hint ⇒ refuse and emit an `AttestationScopeMismatch` event to the audit sink with both digests. ## Operational Effect Refusing at this boundary converts a silent provenance gap into a loud, attributable event. The cost is availability: instances in mid-rotation will reject bound scopes until re-attestation completes, which for a well-instrumented fleet is bounded by the rotation hint's validity window. The benefit is that every exercised bound scope is provably covered by the runtime state that was attested, so downstream provenance claims survive audit. The failure to avoid is the inverse: an instance that satisfies the scope and skips the admissibility check produces tokens whose audit trail is internally consistent but externally unverifiable — the worst of both worlds. ```python class AttestationScopeMismatch(RuntimeError): """Raised when a delegated, runtime-bound scope is not covered by the live attestation.""" pass def reconcile_scope(scope: str, attestation: dict, live_digest: str, unbound_allowlist: set[str]) -> None: predicate = attestation.get("predicate", {}) bound = "runtime_digest" in predicate if not bound or scope in unbound_allowlist: return # bearer or explicitly unbound scope: no runtime binding claimed if scope not in predicate.get("scope_set", set()): raise AttestationScopeMismatch( f"scope {scope!r} not in attested scope_set {sorted(predicate.get('scope_set', []))}" ) if predicate["runtime_digest"] != live_digest: rotation = attestation.get("rotation_hint") if rotation and rotation.get("new_digest") == live_digest: return # pending signed rotation covers the live state; re-attest and retry raise AttestationScopeMismatch( f"live digest {live_digest} does not match attested {predicate['runtime_digest']} " f"for bound scope {scope!r}; refusing exchange" ) ```