# When must an instance distrust a delegated identity scope after revocation: transitive revocation latency across federated trust domains **Date of Record:** 2026 November 12 **Scope:** Applies to hierarchical agent identity delegation where a leaf agent's capability token carries a scope chain (issuer → delegate → sub-delegate) and revocation is propagated asynchronously between federated trust domains. Does not apply to single-domain, synchronous revocation models where the token issuer is also the sole revocation authority and directly controls the verifier's introspection endpoint. ## Core Distinction The non-obvious boundary is between *revocation of a delegation edge* and *invalidity of the transitive scope beneath that edge*. When domain A revokes a delegation edge it issued to domain B, the revocation is authoritative for A. But a sub-delegate in domain C that received its scope from B, not from A, holds a token whose local signature chain is still intact and whose issuer (B) has not yet acknowledged A's revocation. The verifier in C faces two distinct questions: "is this token cryptographically valid?" (yes) and "is the scope it represents still live?" (unknown, pending propagation). These are not the same predicate, and conflating them is the failure mode. The invariant that actually matters is *scope liveness*, not *token validity*. A token can be valid and dead simultaneously during the propagation window. Verifiers that check only validity will accept dead scopes. ## What Was Observed In a three-domain federation (A issues root agent identity; B re-delegates to a fleet of worker agents; C hosts a tool-invocation service that accepts B-signed worker tokens), A revoked B's delegation edge at T+0 due to a compromised B control plane. B's revocation propagation to C was asynchronous with a measured p99 of 41 seconds and a tail exceeding 6 minutes under load. During that window, C continued to accept worker tokens signed by B and invoked state-mutating tools against A-owned resources. The failure was not a signature failure, a clock skew, or a replay. Every token C saw was legitimately issued by B before revocation, correctly signed, unexpired, and within its declared scope. The tokens were valid. The scopes were dead. C had no local signal distinguishing the two. A second observation: C's cache of B's revocation list had a TTL of 300 seconds, longer than B's p99 propagation latency but shorter than B's observed tail. The cache TTL was tuned to the median, which meant the tail window was structurally invisible to C's operators until an audit reconstructed the timeline. ## Condition of Validity The "accept on valid token" policy is only sound when the verifier's revocation-propagation latency upper bound is strictly less than the token's remaining lifetime *and* the verifier has a bounded-staleness guarantee on the revocation feed. Concretely, for a token with `exp` and a revocation feed with maximum staleness `S`, acceptance is safe only if `now + S < exp` and the feed's staleness bound is enforced, not merely observed. If either condition fails, the verifier must treat the scope as indeterminate rather than live. In the observed incident, `S` was unbounded in practice (the tail was not capped, only measured), so the condition did not hold, and the verifier's policy was unsound regardless of the median latency. ## Decision Boundary The decision boundary is not "is the token revoked?" but "can I prove the scope is not revoked as of now?" When the answer is "I cannot prove it," the correct action depends on the operation's blast radius: - **Read-only, idempotent, low-sensitivity:** accept under indeterminate status, log the indeterminacy, and attach the propagation window to the audit record. - **State-mutating or privilege-escalating:** reject or quarantine until the revocation feed's staleness bound is satisfied, even at the cost of availability. - **Cross-domain delegation depth ≥ 2:** require an explicit liveness attestation from the intermediate domain (B) that includes the timestamp of its last successful pull from the root domain (A), not merely a signature over the scope. The boundary is crossed the moment a verifier treats "no revocation seen" as "not revoked." Absence of evidence in an asynchronous feed is not evidence of liveness. ## Operational Effect Enforcing the liveness predicate converts a silent tail-window exposure into an explicit, bounded availability cost. In the reconstructed incident, applying the rule would have rejected approximately 0.7% of worker invocations during the 6-minute tail — a visible, attributable failure rather than an invisible authority leak. The trade is deliberate: bounded rejection of legitimate work is preferable to unbounded acceptance of dead scopes, because the former is observable and the latter is not. The secondary effect is that cache TTLs must be derived from the revocation feed's *enforced* staleness cap, not its observed median. A TTL longer than the cap is a correctness bug, not a performance tuning. ```python import time from dataclasses import dataclass class DeadScopeAcceptedError(RuntimeError): """Raised when a cryptographically valid token represents a scope whose liveness cannot be proven within the revocation feed's staleness bound.""" @dataclass(frozen=True) class DelegationToken: issuer_domain: str subject: str not_after: float # epoch seconds delegation_depth: int @dataclass(frozen=True) class RevocationFeedState: last_successful_pull: float # epoch seconds enforced_staleness_cap: float # seconds; hard bound, not a median def assert_scope_live( token: DelegationToken, feed: RevocationFeedState, now: float | None = None, mutating: bool = True, ) -> None: """Verify that a delegated scope is provably live, not merely unrevoked. Soundness requires: now + staleness_cap < token.not_after, and the feed's staleness cap is enforced (not observed). For mutating operations at delegation_depth >= 2, an intermediate liveness attestation is required. """ now = time.time() if now is None else now feed_age = now - feed.last_successful_pull if feed_age > feed.enforced_staleness_cap: raise DeadScopeAcceptedError( f"revocation feed stale by {feed_age:.1f}s, cap " f"{feed.enforced_staleness_cap:.1f}s; scope liveness unprovable" ) if now + feed.enforced_staleness_cap >= token.not_after: raise DeadScopeAcceptedError( f"token expires at {token.not_after:.0f} but staleness bound " f"extends acceptance window past expiry; reject" ) if mutating and token.delegation_depth >= 2: raise DeadScopeAcceptedError( f"depth-{token.delegation_depth} mutating scope requires explicit " f"intermediate liveness attestation from {token.issuer_domain}" ) ```