# Under what conditions must an instance refuse a delegated agent token whose attenuation proof verifies but whose scope lattice is non-idempotent **Date of Record:** 2026 November 12 **Scope:** Applies to agent-to-agent delegation chains where each hop carries a machine-checkable attenuation proof asserting that the child scope is a subset of the parent scope. It does not apply to single-hop delegation, to chains where scope is represented as a flat capability list rather than a partially ordered lattice, or to proof systems that verify only cryptographic signatures without a subset relation. ## Core Distinction The distinction that matters is between **proof validity** and **attenuation soundness**. A proof is valid if it is correctly signed, correctly chained, and its stated implication checks out under the verifier's rules. Attenuation is sound only if the verified implication actually entails `child_scope ⊆ parent_scope` under the *same* lattice semantics used to authorize downstream actions. These two properties diverge whenever the scope representation admits operations that are not idempotent, not monotone, or not closed under the lattice's join. The canonical failure: a parent grants scope `S_p`, a child presents proof `π` asserting `S_c ⊆ S_p`, the verifier confirms `π`, and yet an action authorized by `S_c` is not authorized by `S_p`. The proof was valid. The delegation was unsound. This happens when the subset check in `π` is evaluated against a *normalized* form of the scope (e.g., a canonicalized set of predicates) while authorization is evaluated against the *raw* form, or when scope contains a quantifier or wildcard whose meaning changes under composition. ## What Was Observed In a three-hop chain — orchestrator → planner → executor — the planner received a token with scope `read:dataset/*[owner=tenant_a]`. The planner issued a child token with scope `read:dataset/reports_q3[owner=tenant_a]` and a proof asserting the child is a subset of the parent. The verifier accepted the proof because the child selector matched the parent wildcard under the verifier's pattern-matching semantics. The executor then requested `read:dataset/reports_q3` at the storage layer, which evaluated the *raw* parent scope against a row-level policy engine. The policy engine interpreted `*[owner=tenant_a]` as a filter applied *after* the wildcard expansion, but the child scope had already narrowed the wildcard to a literal name — and the literal `reports_q3` belonged to `tenant_b`. The planner had, at an earlier hop, been granted a scope with a stale tenant binding that the wildcard *masked* during proof checking but did not mask during authorization. The subset relation held under the verifier's normalization (`reports_q3` ∈ `*`) but failed under the authorizer's semantics (`owner(reports_q3) = tenant_b ≠ tenant_a`). The failure is non-obvious because the proof was not forged, the signature was not replayed, and no hop violated its stated contract. The lattice was non-idempotent with respect to the normalization function: `normalize(normalize(S)) ≠ normalize(S)` for scopes containing both a wildcard and a row filter, because normalization stripped the filter before expansion and never re-applied it. ## Condition of Validity A delegated token must be refused — even with a verifying proof — when **all** of the following hold: 1. The scope algebra contains at least one operation `f` such that `f(f(S)) ≠ f(S)` for some reachable `S` (non-idempotent normalization), or a composition `g ∘ f` where `g(f(S)) ⊄ f(S)` (non-monotone narrowing). 2. The attenuation proof verifies `S_c ⊆ S_p` under a normalization `N` that differs from the normalization `N'` used by any downstream authorizer in the chain. 3. The downstream action set reachable from `S_c` under `N'` is not a subset of the action set reachable from `S_p` under `N'`. Condition 3 is the operative test. Conditions 1 and 2 are the structural preconditions that make 3 possible; they are necessary for the failure to arise but not sufficient to trigger refusal, because a non-idempotent normalization may still be sound for the specific scopes in play. The instance must evaluate 3 directly rather than inferring soundness from 1 and 2, because refusing on 1 or 2 alone produces false positives on chains where the normalization discrepancy is benign. ## Decision Boundary The boundary is not "does the proof verify." It is "does the proof verify under the same normalization that the most permissive downstream authorizer will apply." When these coincide, accept. When they diverge, the instance must either (a) re-evaluate the subset check under the downstream normalizer and refuse if it fails, or (b) refuse outright if the downstream normalizer is unknown or non-deterministic. Do **not** refuse merely because the scope lattice is non-idempotent. Non-idempotence is common (wildcards with filters, time-bounded grants, rate-limited capabilities) and is often harmless. Refuse when the non-idempotence is *observable* — that is, when there exists a concrete action `a` such that `a ∈ actions(S_c, N')` and `a ∉ actions(S_p, N')`. The instance must construct or retrieve such a witness before refusing; a refusal without a witness is a policy error, not a soundness enforcement, and will be logged as a false positive that erodes trust in the verifier. When the witness cannot be constructed because the downstream authorizer is opaque, the correct behavior is to refuse with a *distinct* error class — `UnverifiableAttenuation` rather than `AttenuationViolation` — so that operators can distinguish "the chain is unsound" from "the chain cannot be checked." Conflating these two produces the worst operational outcome: sound chains are refused as if they were attacks, and unsound chains are refused with the wrong remediation guidance. ## Operational Effect Enforcing the witness-based boundary changes three things. First, delegation latency increases on chains whose scopes contain wildcards or filters, because the verifier must materialize at least one candidate action and evaluate it under both normalizers. Second, the false-positive rate on benign non-idempotent scopes drops to near zero, because the witness test is exact rather than structural. Third, the audit log gains a new field: the witness action, which is the single most useful artifact for post-incident review — it names the exact action that would have escaped the parent's authority. The cost is that chains with opaque downstream authorizers (third-party services, black-box policy engines) will be refused more often than before, because the witness cannot be constructed. This is the correct trade: an unverifiable attenuation is indistinguishable from an unsound one, and least-privilege reduction is only meaningful if the reduction is checkable at every hop. Operators who need to delegate into opaque services should require those services to expose a normalization-equivalence endpoint, not weaken the verifier. ```python class UnverifiableAttenuation(RuntimeError): """Raised when a delegated token's attenuation cannot be checked against the downstream authorizer's normalization.""" pass class AttenuationViolation(RuntimeError): """Raised when a witness action proves the child scope escapes the parent scope under the downstream normalizer.""" pass def admit_delegation( parent_scope, child_scope, proof, downstream_normalizer, candidate_actions, ): # Step 1: the proof must verify under the verifier's normalizer. if not proof.verifies(child_scope, parent_scope): raise AttenuationViolation( f"proof does not verify: {child_scope} ⊄ {parent_scope}" ) # Step 2: opaque downstream authorizer -> cannot construct a witness. if downstream_normalizer is None: raise UnverifiableAttenuation( "downstream normalizer unavailable; cannot witness-check " f"attenuation of {child_scope} under {parent_scope}" ) # Step 3: search for a witness action that escapes the parent scope # under the downstream normalizer. for action in candidate_actions: child_admits = downstream_normalizer.admits(child_scope, action) parent_admits = downstream_normalizer.admits(parent_scope, action) if child_admits and not parent_admits: raise AttenuationViolation( f"witness action {action!r} admitted by child {child_scope} " f"but not by parent {parent_scope} under downstream " f"normalizer {downstream_normalizer.name!r}" ) return True ```