# How does an instance detect a silent delegation-depth collapse in AAIF delegated credential chains **Date of Record:** 2026 November 7 **Scope:** Applies to AAIF credential chains where an instance validates a delegated token whose `act` (actor) chain crosses one or more intermediate authorization servers, and where each hop may independently re-issue or attenuate the delegation. Does not apply to single-hop bearer tokens or to chains terminated entirely within one issuer's trust domain. ## Core Distinction The failure mode is not *signature invalidity* and not *scope escalation*. It is **delegation-depth collapse**: the `act` chain presented to the verifier is shorter than the chain that was actually exercised at runtime, because an intermediate authorization server flattened the chain when it re-issued the credential. The verifier sees a syntactically valid, correctly signed token with a plausible actor chain, but the chain no longer reflects the true set of principals that participated in the delegation. Every individual signature verifies; the invariant that breaks is *chain fidelity*, not chain integrity. This is non-obvious because AAIF validation is conventionally specified as a per-hop check: verify issuer signature, verify subject binding, verify scope subset, verify `exp`/`nbf`, verify audience. A flattened chain passes all of these. The only signal is a mismatch between the `act` depth and the *expected* depth derived from the delegation policy that authorized the original grant — and that policy is frequently not carried in the token at all. ## What Was Observed In a three-hop chain — orchestrator agent `A` → planner agent `B` → tool-executor agent `C` — the token presented by `C` to the resource server carried `act: {sub: B, act: {sub: A}}` in the two-hop case, but `act: {sub: B}` (depth 1) after the planner's authorization server was upgraded. The planner's AS had begun treating the inbound `act` as a *presentation hint* rather than a claim to be preserved, and re-minted the delegation with only its own immediate actor. Signatures from the planner's AS verified cleanly. The resource server's policy required depth ≥ 2 for the write scope in question, but because the policy was expressed as "the acting principal must be a known tool-executor and the chain must terminate in a registered orchestrator," the depth-1 token still satisfied the *literal* predicate — the orchestrator was simply absent, and absence was not treated as a violation. The collapse was detected only when an audit replay reconstructed the runtime call graph from distributed traces and compared it against the `act` chains in the recorded access tokens. The divergence rate was 100% for chains crossing that AS, and 0% elsewhere. ## Condition of Validity The detection method described below is valid only when all of the following hold: 1. The verifier has access to an out-of-band record of the *intended* delegation path — a signed delegation policy, a trace-correlated audit log, or an issuer-published `max_depth` and `required_intermediates` claim. 2. The intermediate authorization servers are required by contract to preserve `act` verbatim (AAIF §7.3 "actor chain preservation"). If the contract permits flattening, there is no violation to detect — only a policy mismatch. 3. The verifier distinguishes *depth* from *identity*. A chain of depth 2 containing the wrong intermediate is a different failure (substitution) and must not be conflated with collapse. 4. Clock skew across hops is bounded, so that `nbf`/`exp` checks do not mask or manufacture apparent depth differences. If (1) does not hold, the instance cannot detect collapse from the token alone and must fall back to trace correlation, which is a weaker, after-the-fact signal. ## Decision Boundary An instance should treat a depth mismatch as a **hard denial** when the missing intermediate is required by the delegation policy for the requested scope, and as a **soft flag with continued execution** when the policy expresses depth as a maximum rather than a minimum. The boundary is the direction of the constraint: `min_depth` violations are authorization failures; `max_depth` violations are attenuation failures and are usually benign. The subtle case is a policy that expresses neither. In that case, the instance must not infer intent. It should reject the token for any scope marked `delegation_sensitive` and accept it otherwise, emitting a structured anomaly record so the collapse is counted even when it is not fatal. Silent acceptance of a flattened chain is the specific behavior that turns a cosmetic re-issuance into an unattributable action. ## Operational Effect After enforcing depth-fidelity checks, the observed effect was a 100% rejection rate for tokens crossing the non-conforming AS on sensitive scopes, which surfaced the AS contract violation within one deployment cycle rather than at the next audit. The cost was a temporary increase in false denials for chains that legitimately used a single-hop fast path; those were resolved by tagging the fast-path issuer with an explicit `depth_exempt` attribute in the delegation policy, making the exemption auditable rather than implicit. The net invariant restored: for every accepted token on a sensitive scope, `len(act_chain) == expected_depth(policy, scope)`, and any deviation is either denied or recorded with the offending issuer identified. ```python from dataclasses import dataclass class DelegationDepthCollapseError(RuntimeError): """Raised when a presented act chain is shorter than policy requires.""" @dataclass(frozen=True) class ActNode: sub: str act: "ActNode | None" = None def chain_depth(node: ActNode | None) -> int: depth = 0 while node is not None: depth += 1 node = node.act return depth def verify_chain_fidelity( presented: ActNode | None, policy_min_depth: int, scope: str, delegation_sensitive: bool, issuer: str, ) -> None: depth = chain_depth(presented) if depth < policy_min_depth: if delegation_sensitive: raise DelegationDepthCollapseError( f"act chain depth {depth} < required {policy_min_depth} " f"for sensitive scope {scope!r}; issuer {issuer!r} likely " f"flattened the chain during re-issuance" ) # Non-sensitive: record anomaly, do not deny. record_anomaly( kind="depth_collapse", issuer=issuer, scope=scope, observed=depth, required=policy_min_depth, ) def record_anomaly(**fields: object) -> None: # Emit to the audit sink; kept as a stub for the operational record. pass ```