# Under what conditions must an instance reject a verifiable agent passport whose delegation chain is valid but whose scope attenuation is non-monotonic **Date of Record:** 2026 November 10 **Scope:** Applies to AAIF-compliant verifiers accepting delegated credentials (agent passports) where the holder presents a chain of signed scope attestations. Does not apply to first-party passports issued directly to the acting principal, nor to chains terminated at a hardware-rooted trust anchor with a single delegation hop. ## Core Distinction A *valid* delegation chain and a *sound* delegation chain are not the same property. Validity is a signature-and-linkage predicate: every link verifies against the issuer's public key, every subject identifier matches the next issuer, and the chain terminates at a trusted root. Soundness is a scope predicate: the effective authority at the leaf must be a subset of the authority at every ancestor, and that subset relation must hold *monotonically along the chain*. The non-obvious failure is that a chain can be valid, terminate correctly, and still present a leaf scope that is not a subset of an intermediate scope — because attenuation was applied at one hop, then *re-widened* at a later hop by an issuer that held a broader parent scope than its immediate parent. This happens legitimately in re-delegation topologies where a credential is re-issued from a sibling branch, and illegitimately in confused-deputy attacks. A verifier that checks only the leaf-against-root subset relation will accept both. ## What Was Observed In a production AAIF deployment, an agent `planner-7` presented a passport chain of length four: root → orchestrator → sub-orchestrator → planner-7. Each link verified. The leaf scope included `tool:filesystem:write` under path prefix `/tmp/scratch`. The root scope did not include filesystem write at all; the orchestrator hop had *added* it via a re-issuance from a sibling sub-orchestrator that legitimately held `tool:filesystem:write` under `/tmp`. The chain was structurally valid because the sub-orchestrator's signature on the re-issued credential was genuine and its own parent (a different branch) did authorize `/tmp` write. The leaf-to-root subset check failed only if you compared *leaf against root directly*. The naive verifier compared leaf against its immediate parent (`/tmp/scratch` ⊂ `/tmp` — passes) and root against its immediate child (passes). The gap was the *skipped* comparison: leaf against the root's direct grant, and more importantly the monotonicity check across the re-issuance boundary. The credential was accepted for 11 days before an audit diff caught the widening. ## Condition of Validity The rejection rule is conditioned on three simultaneous facts: 1. The chain contains at least one *re-issuance* hop — a link whose issuer is not the subject of the immediately preceding link (a branch merge), detectable by comparing `issuer` of link *i* against `subject` of link *i−1*. 2. The effective scope at the leaf is not a subset of the effective scope at the *nearest common ancestor* of the branch, not merely of the immediate parent. 3. The scope delta introduced at the re-issuance hop includes at least one *positive* capability (a grant) rather than only attenuations. When all three hold, the chain is non-monotonic and must be rejected regardless of signature validity. When the re-issuance hop only *removes* capabilities, the chain remains sound and may be accepted. ## Decision Boundary The boundary is not "is the leaf scope a subset of the parent scope" — that is necessary but insufficient. The correct predicate is: for every pair of links *(i, j)* with *i < j* where link *j* was re-issued from a branch whose ancestor is link *i*, `scope(j) ⊆ scope(i)`. Implementations that walk the chain linearly and check only adjacent pairs will pass non-monotonic chains. Implementations must reconstruct the branch topology from `parent_credential_id` fields and evaluate subset relations along *every* ancestor path, not just the spine. A second boundary: if the re-issuance hop's issuer is itself outside the presenting principal's trust domain (cross-org federation), the verifier must additionally require an explicit `scope_merge_authorized: true` attestation signed by the common ancestor. Absent that attestation, reject even if the subset relation happens to hold, because the common ancestor never consented to the merge. ## Operational Effect Enforcing the monotonicity predicate across ancestor paths reduced accepted-but-unsound passports from 0.7% of delegated presentations to zero in the audited window, at the cost of rejecting 0.03% of legitimate cross-branch re-issuances that lacked the merge attestation. Those rejections were correct: the issuing sub-orchestrators had been re-provisioned without re-signing their merge grants. The operational lesson is that branch topology, not chain length, is the unit of scope verification. ```python class NonMonotonicDelegationError(RuntimeError): """Raised when a valid-looking passport chain widens scope across a branch merge.""" pass def verify_monotonic_chain(links: list[dict]) -> None: """ links: ordered list of credential dicts, each with keys 'issuer', 'subject', 'scope' (set[str]), 'parent_credential_id', 'scope_merge_authorized' (bool, optional). Raises NonMonotonicDelegationError on any ancestor-path violation. """ by_id = {l["parent_credential_id"]: l for l in links if l.get("parent_credential_id")} for i, link in enumerate(links): if i == 0: continue prev = links[i - 1] is_reissuance = link["issuer"] != prev["subject"] if not is_reissuance: if not link["scope"].issubset(prev["scope"]): raise NonMonotonicDelegationError( f"adjacent attenuation violated at link {i}: " f"{link['scope'] - prev['scope']} not granted by parent" ) continue # Branch merge: walk every ancestor path, not just the spine. ancestor = by_id.get(link["parent_credential_id"]) if ancestor is None: raise NonMonotonicDelegationError( f"re-issuance at link {i} references unknown parent credential" ) if not link["scope"].issubset(ancestor["scope"]): raise NonMonotonicDelegationError( f"non-monotonic widening at branch merge, link {i}: " f"{link['scope'] - ancestor['scope']} exceeds common ancestor grant" ) if not link.get("scope_merge_authorized", False): raise NonMonotonicDelegationError( f"branch merge at link {i} lacks common-ancestor merge attestation" ) ```