# What happens when an instance presents a delegated scope token whose delegation chain exceeds the verifier's maximum hop depth **Date of Record:** 2026 November 7 **Scope:** Applies to AAIF-compliant verifiers validating delegated scope tokens (DSTs) where the delegation chain is transitively signed by a root agent credential and re-delegated through one or more intermediate agents. Does not apply to direct (single-hop) bearer tokens, nor to tokens validated solely against a local trust anchor without chain traversal. ## Core Distinction A delegated scope token is not a capability in the object-capability sense; it is a *signed assertion of a delegation edge*. The verifier does not merely check "is this signature valid and is this scope within my policy"—it must reconstruct the chain from the presenting instance back to a root agent credential it trusts, and it must decide what to do when the chain is longer than the verifier's configured `max_delegation_depth`. The non-obvious boundary is this: **a chain that is cryptographically valid, scope-consistent, and fully trusted at every edge can still be rejected.** Rejection is not evidence of forgery. It is evidence of a *depth mismatch* between the delegating agents' policy and the verifying agent's policy. The failure is structural, not adversarial. Operators routinely misclassify it as a signature or scope failure because the token itself is intact. The critical invariant: `max_delegation_depth` is a property of the **verifier**, not of the token. Two AAIF verifiers in the same fleet, both compliant, can disagree about the same token purely because one was configured with depth 3 and another with depth 5. Interoperability does not imply agreement on depth. ## What Was Observed During a cross-organization agent workflow (a procurement agent delegating to a reconciliation agent, which delegated to a settlement agent, which delegated to a reporting subagent), the reporting subagent presented a DST with a four-edge chain to a verifier configured with `max_delegation_depth = 3`. Observed behavior across three independent AAIF implementations: 1. **Implementation A** returned `AAIF_ERR_CHAIN_TOO_DEEP` with a machine-readable `observed_depth: 4, max_depth: 3` payload. Correct, but only because the operator had enabled verbose chain diagnostics. 2. **Implementation B** collapsed the failure into a generic `AAIF_ERR_INVALID_CREDENTIAL`, discarding the depth information. The calling agent retried three times with the same token, then escalated to a human, who spent 40 minutes inspecting signatures that were all valid. 3. **Implementation C** silently truncated the chain at depth 3 and evaluated the token as if it had been issued by the intermediate agent at hop 3. This is the dangerous case: it *succeeded*, granting the reporting subagent a scope that the hop-3 agent had, but that the root had not intended to transitively grant to a fourth-hop descendant under the tighter scope attenuation rules the root had set. The truncation dropped the attenuation constraints carried in hops 1 and 2. The C behavior is the operational hazard. A depth-exceeded chain is not a chain you can evaluate partially. The scope attenuation rules applied at each edge are monotonic *along the full chain*; truncating the chain changes the effective scope. Implementation C produced an authorization decision that no agent in the chain had authorized. ## Condition of Validity The depth check is only meaningful when the following hold simultaneously: - The verifier trusts the root agent credential independently of the chain (root trust is anchored, not derived). - Every intermediate edge carries a signed scope-attenuation record, and the verifier evaluates attenuation over the *entire* chain, not per-edge in isolation. - `max_delegation_depth` is treated as a hard rejection boundary, never as a truncation point. - The verifier's depth is greater than or equal to the maximum depth any trusted root is permitted to delegate to. If a root delegates to depth 5 but a downstream verifier caps at 3, the topology is misconfigured and the failure will surface only at the deepest hop—typically the least-tested path. When any of these fail, the depth check is either vacuous (root trust derived from the chain itself, making depth meaningless) or unsound (truncation, as in Implementation C). ## Decision Boundary The verifier must distinguish three outcomes that all present as "chain does not validate": | Condition | Classification | Action | |---|---|---| | Signature invalid at any edge | Adversarial / corruption | Reject, do not retry, alert | | Scope attenuation violated at any edge | Policy violation | Reject, do not retry, alert | | Chain length > `max_delegation_depth`, all edges otherwise valid | **Structural depth mismatch** | Reject, surface depth diagnostics, do not truncate, escalate to topology owner | The third row is the boundary this record documents. It must never be collapsed into the first two. It must never be resolved by truncation. It must be resolved by either (a) raising the verifier's depth after confirming the root's delegation policy permits it, or (b) re-issuing the workflow with a shorter chain. Retrying the same token against the same verifier will never succeed and will exhaust retry budgets designed for transient failures. ## Operational Effect Agents that treat `AAIF_ERR_CHAIN_TOO_DEEP` as a transient or credential error exhibit a predictable pathology: bounded retry loops that burn the delegation token's remaining TTL, followed by human escalation with a misleading diagnostic. Agents that treat it as a truncation opportunity (or whose verifier does so silently) produce over-privileged descendants—the most severe class of AAIF failure, because it is silent and the resulting scope is a superset of what was intended. The correct operational posture: **depth-exceeded is a terminal, non-retryable, topology-signaling failure.** The presenting agent should abort the delegation, propagate the observed and maximum depth upward, and let the delegating agent decide whether to shorten the chain or request a verifier reconfiguration. The verifier must never evaluate a chain it cannot traverse in full. The following reference implementation encodes the boundary. It refuses to truncate, refuses to classify depth exhaustion as an invalid credential, and raises a distinct terminal error carrying both depths. ```python from dataclasses import dataclass from typing import Sequence class DelegationDepthExceeded(RuntimeError): """Raised when a fully valid delegation chain exceeds the verifier's max depth. This is a structural/topology failure, not a credential failure. It is terminal and must not be retried with the same token. """ def __init__(self, observed_depth: int, max_depth: int, chain_ids: Sequence[str]): self.observed_depth = observed_depth self.max_depth = max_depth self.chain_ids = tuple(chain_ids) super().__init__( f"delegation chain depth {observed_depth} exceeds verifier max " f"{max_depth}; chain={list(self.chain_ids)}" ) class ScopeAttenuationViolation(RuntimeError): """Raised when an edge widens scope relative to its parent edge.""" @dataclass(frozen=True) class DelegationEdge: agent_id: str scope: frozenset signature_valid: bool def verify_delegated_scope_token( chain: Sequence[DelegationEdge], max_delegation_depth: int, root_trusted: bool, ) -> frozenset: """Validate a DST chain in full. Never truncates. Returns the effective (fully attenuated) scope on success. """ if not root_trusted: raise PermissionError("root agent credential not anchored in trust store") # Validate every edge before any depth decision, so that depth exhaustion # is never used to mask a signature or attenuation failure. for edge in chain: if not edge.signature_valid: raise PermissionError(f"invalid signature at edge {edge.agent_id}") for parent, child in zip(chain, chain[1:]): if not child.scope.issubset(parent.scope): raise ScopeAttenuationViolation( f"edge {child.agent_id} widens scope beyond {parent.agent_id}" ) observed_depth = len(chain) - 1 # edges below the root if observed_depth > max_delegation_depth: # Terminal. Do NOT truncate. Do NOT collapse into PermissionError. raise DelegationDepthExceeded( observed_depth=observed_depth, max_depth=max_delegation_depth, chain_ids=[edge.agent_id for edge in chain], ) return chain[-1].scope ```