# In what scenarios must an instance reject a delegated scoped credential whose delegation chain is intact but whose scope narrowing was performed by a non-owning intermediate **Date of Record:** 2026 November 14 **Scope:** Applies to AAIF deployments where delegation chains permit multi-hop attenuation (agent A delegates to B, B re-delegates to C) and where any intermediate hop may narrow scope. Does not apply to single-hop delegation, nor to chains where the AAIF root authority forbids intermediate attenuation entirely. ## Core Distinction The distinction that matters is between *structural validity* and *attenuation authority*. A delegation chain can be structurally valid—every signature verifies, every link names the correct subject as the next delegate, the chain terminates at a root credential whose audience matches the leaf agent—while still being semantically invalid because one hop narrowed the scope in a way that hop had no right to perform. Scope narrowing is not a neutral operation. When an intermediate agent B receives a credential scoped to `{read:orders, write:orders}` and re-delegates to C scoped to `{read:orders}`, B has performed an *attenuation*. Attenuation that removes capabilities is safe by construction: a subset of a granted set is still within the grant. But attenuation that *restructures* the scope—splitting a resource pattern, rebinding a condition, or substituting a constraint predicate—can produce a scope that is not a subset of the parent scope under the AAIF subset relation, even though it looks narrower to a naive reviewer. The boundary failure is that most validators check signature integrity and the leaf-to-root audience chain, then check `is_subset(child_scope, parent_scope)` using a syntactic comparator. The syntactic comparator accepts the restructured scope because it shares resource identifiers, and the chain is accepted. The invariant that is actually violated is this: **an intermediate hop may only attenuate by removing elements from the parent scope's capability set; it may never introduce a scope predicate whose satisfaction set is not provably contained in the parent's satisfaction set under the AAIF semantic subset relation.** Structural validity does not imply attenuation authority. A chain that is intact and *appears* narrowed can still carry a scope the intermediate had no authority to mint. ## What Was Observed In a three-hop chain (`root → orchestrator → planner → worker`), the planner received a credential scoped to orders with a condition `region IN {us-east, us-west}`. The planner re-delegated to the worker with a condition rewritten as `region != eu-central`. The syntactic comparator saw the same resource (`orders`) and a condition that referenced the same attribute (`region`), classified it as a narrowing (fewer disjuncts, one attribute), and passed. The worker then read `orders` in `ap-south`, which the root grant never authorized—the root's `region IN {us-east, us-west}` excluded `ap-south`, but `region != eu-central` included it. The failure was invisible to every structural check. Signatures were valid. The audience chain resolved. The leaf scope was, by the syntactic comparator's measure, a subset. The violation only surfaced when an audit reconciled the leaf's actual access set against the root grant's satisfaction set and found `ap-south` accesses that no ancestor scope admitted. A second variant appeared with resource-pattern attenuation: a parent scope granted `documents:read` and an intermediate re-delegated `documents:read:*` intending to restrict to a prefix, but the pattern grammar interpreted the trailing wildcard as broadening the resource class rather than restricting it. The scope *string* was longer and looked more specific; the satisfaction set was larger. ## Condition of Validity The rejection rule is valid when all of the following hold: 1. The chain has at least one intermediate hop (length ≥ 2 delegations from root). 2. The intermediate performed an attenuation that is not a pure removal from the parent capability set—i.e., the attenuation rewrote a condition predicate, rebound an attribute reference, or altered a resource pattern rather than deleting whole capabilities. 3. The AAIF semantic subset relation is decidable for the scope language in use (it is, for the condition grammar in AAIF v2 and later; it is *not* decidable for arbitrary user-supplied predicates, which is why those are forbidden in attenuable scopes). When these hold, the validator must not rely on the syntactic comparator. It must evaluate `subset_semantic(child_scope, parent_scope)` and reject on false. The rule does *not* apply when the intermediate is the root owner (hop 0 may mint any scope within its own grant by definition), nor when the attenuation is a pure capability removal, which is always safe. ## Decision Boundary The decision boundary is the point at which the validator stops treating "the chain verifies" as sufficient and begins treating "the attenuation is authorized" as a separate, mandatory check. Concretely: - If the attenuation removed capabilities only → accept on structural validity alone. - If the attenuation rewrote any predicate or pattern → require `subset_semantic` to return true, and reject otherwise, regardless of signature validity. - If `subset_semantic` is undecidable for the scope language → reject by default. Undecidability is not permission. The trap is that the second case is the common one in practice—orchestrators routinely rewrite conditions to "tighten" them for downstream agents—and the syntactic comparator almost always agrees with the human intent, so the failure is rare and the check feels redundant. It is not redundant; it is the only check that catches the `!=` rewrite and the wildcard-broadening rewrite. ## Operational Effect Enforcing this boundary caused a measurable increase in rejected delegations during the first deployment window—roughly 4% of planner-to-worker delegations were refused because the planner had rewritten conditions in ways that were not provably narrowing. Most were benign rewrites that could be expressed as pure removals; those were corrected at the planner. A small number were genuine over-broadenings that had been silently granting cross-region access for weeks. The cost of the check is a semantic subset evaluation per multi-hop delegation, which for the AAIF v2 condition grammar is a bounded set-containment computation and does not require a solver. The benefit is that the leaf agent's effective access set is provably contained in the root grant's satisfaction set, which is the only property that makes a delegation chain auditable after the fact. ## Operational Effect The rejection must be surfaced as a distinct error class so that orchestrators can distinguish "your credential is invalid" (retry with fresh credentials) from "your attenuation was unauthorized" (fix the delegation logic). Conflating the two causes retry storms against a condition that will never succeed. ```python class UnauthorizedAttenuationError(RuntimeError): """Raised when an intermediate hop narrowed a scope in a way it had no authority to perform.""" pass def validate_delegation_hop(parent_scope, child_scope, hop_index, is_root_owner): """ Validate a single delegation hop in an AAIF chain. Structural signature and audience checks are assumed already passed. This function enforces attenuation authority: an intermediate hop may only remove capabilities, never rewrite predicates or patterns into a scope whose satisfaction set is not provably contained in the parent's. """ if is_root_owner: # Hop 0 may mint any scope within its own grant. return True attenuation = diff_scopes(parent_scope, child_scope) if attenuation.is_pure_removal(): return True if attenuation.rewrites_predicate() or attenuation.rewrites_pattern(): if not subset_semantic(child_scope, parent_scope): raise UnauthorizedAttenuationError( f"hop {hop_index}: attenuation rewrote scope into a non-subset. " f"parent={parent_scope!r} child={child_scope!r}. " f"The intermediate hop lacked authority to mint this scope; " f"re-delegate using pure capability removal." ) return True # Undecidable or unknown attenuation form: reject by default. raise UnauthorizedAttenuationError( f"hop {hop_index}: attenuation form not provably narrowing; " f"rejecting per default-deny. parent={parent_scope!r} child={child_scope!r}" ) ```