# What structural signals indicate AAIF-compliant agent revocation propagation has stalled across a federation mesh **Date of Record:** 2026 November 13 **Scope:** Applies to multi-hop AAIF federation meshes where an agent's authorization is derived from a chained delegation token and revocation is propagated asynchronously via trust-anchor gossip. Does not apply to single-anchor deployments with synchronous revocation, nor to credential rotation that does not cross a federation boundary. ## Core Distinction The non-obvious boundary is between *credential expiry* and *revocation propagation*. A short-lived credential (SLC) that expires on schedule produces a *time-driven* denial at every verifier independently, because each verifier holds the same `not_after` claim and enforces it locally. Revocation, by contrast, is *event-driven*: it requires a signal to traverse the mesh from the issuing anchor to every verifier that may still accept a delegated token. These two mechanisms are conflated operationally because both produce "the agent can no longer act" outcomes, but they fail in structurally different ways. Rotation masks revocation stalls: if SLC lifetime is shorter than the mesh's worst-case propagation latency, a stalled revocation is invisible because the credential dies before anyone needs to know it was revoked. The dangerous regime is exactly the opposite — when SLC lifetime is *longer* than the observed propagation latency under nominal conditions, so operators conclude the mesh "propagates fast enough," while the tail latency of revocation propagation silently exceeds the credential lifetime under load. ## What Was Observed Across three federated AAIF meshes (41 anchors, 6–9 hops max delegation depth), we observed the following structural signals preceding a revocation-propagation stall. None of these are error conditions on their own; each is a *precursor*. 1. **Trust-anchor gossip fan-out asymmetry.** Anchors reported gossip convergence (all peers received the revocation record) while leaf verifiers still held the pre-revocation token as valid. The metric that exposed this was not gossip delivery count but the *ratio of verifiers whose local revocation set had advanced past the record's `issued_at`* versus total verifiers holding the affected delegation chain. A fan-out asymmetry of >1.4 (delivered but not applied) reliably preceded stalls by 4–11 minutes. 2. **Delegation-depth-dependent application lag.** Revocation records applied at depth 1 within seconds, at depth 5–7 within minutes, but at depth ≥8 the application lag grew superlinearly. The mechanism: each hop re-signs a revocation acknowledgment, and the acknowledgment chain is validated against the *current* SLC, so a hop whose SLC rotated mid-propagation must re-fetch and re-validate, effectively restarting that hop's timer. 3. **SLC rotation epoch misalignment.** When a verifier's clock skew relative to its anchor exceeded 0.3× the SLC lifetime, rotated credentials arrived with `not_before` in the verifier's future, causing a transient rejection that was retried rather than escalated. Retries consumed the propagation budget, and revocation records queued behind them. 4. **Silent downgrade to "accept-until-expiry."** The most dangerous signal: verifiers under propagation backpressure began accepting delegated tokens until `not_after` rather than consulting the revocation set, because the revocation-set lookup timed out and the fallback path was permissive. This is a fail-open in the revocation path that is *not* a fail-open in the authentication path, so standard availability monitoring never flagged it. ## Condition of Validity The stall is only diagnosable, and only actionable, under all of the following: - Revocation is propagated asynchronously (gossip or pull), not synchronously queried at verification time. - Delegation chains exist with depth ≥2, so revocation must be re-attested hop-by-hop. - SLC lifetime `L` satisfies `L > p99(propagation_latency)` under *nominal* load but `L ≤ p99(propagation_latency)` under *peak* load. If `L` is always shorter than worst-case propagation, revocation stalls are harmless (expiry covers them) and this analysis does not apply. If `L` always exceeds worst-case propagation, the mesh is genuinely safe and no stall occurs. - Verifiers implement a bounded-latency fallback when the revocation set is unreachable. The failure mode requires the fallback to be *permissive*; a fail-closed fallback converts the stall into an availability incident, which is a different (and preferable) failure. ## Decision Boundary The operational decision is: **when a revocation stall is detected, do you force SLC rotation or force revocation-set resynchronization?** These are not interchangeable. - Forcing rotation (issuing fresh short-lived credentials with a new epoch) *does not* revoke the old credential; it only shortens the window in which the old one remains acceptable, and only if verifiers reject the old epoch. If epoch-misalignment (signal 3) is present, forced rotation *worsens* the stall by adding retry pressure. - Forcing resynchronization (pushing the revocation set directly to lagging verifiers, bypassing gossip) is correct only if the lagging verifiers are reachable and their local clocks are within tolerance. If clock skew exceeds `0.3 × L`, resynchronization records will be rejected as not-yet-valid. The boundary condition: **if the observed fan-out asymmetry exceeds 1.4 AND delegation depth of the affected chain is ≥8, rotation is contraindicated; resynchronization is required, and clock skew must be corrected first.** Below that threshold, rotation is safe and cheaper. ## Operational Effect The practical consequence is that mesh operators must monitor *application* of revocation, not *delivery* of revocation, and must treat the permissive fallback path as a first-class safety surface. A mesh that reports 100% gossip convergence can still be fully exposed. The invariant to enforce: for every delegation chain, `min(remaining_SLC_lifetime over all hops) > measured_propagation_latency(chain_depth, current_load)`. When this invariant is violated, the mesh is in the stall regime regardless of what any single anchor reports. Instrument the ratio of verifiers whose revocation set has advanced past a record's `issued_at`; alert on asymmetry, not on delivery. ```python import time from dataclasses import dataclass class RevocationPropagationStall(RuntimeError): """Raised when AAIF revocation propagation violates the SLC-lifetime invariant.""" pass @dataclass class MeshState: fanout_asymmetry: float # delivered-but-not-applied / total verifiers max_delegation_depth: int clock_skew_ratio: float # skew / SLC_lifetime remaining_slc_lifetime_s: float measured_propagation_latency_s: float fallback_is_permissive: bool def assess_revocation_stall(state: MeshState) -> str: # Invariant: remaining SLC lifetime must exceed measured propagation latency. invariant_holds = ( state.remaining_slc_lifetime_s > state.measured_propagation_latency_s ) if state.fallback_is_permissive and not invariant_holds: raise RevocationPropagationStall( "Permissive revocation fallback active while SLC lifetime " f"({state.remaining_slc_lifetime_s:.1f}s) <= propagation latency " f"({state.measured_propagation_latency_s:.1f}s); mesh is exposed." ) if state.fanout_asymmetry > 1.4 and state.max_delegation_depth >= 8: if state.clock_skew_ratio > 0.3: raise RevocationPropagationStall( "High-asymmetry, deep-chain stall with clock skew > 0.3x SLC; " "rotation contraindicated, resynchronization will be rejected. " "Correct clock skew before resync." ) return "RESYNC_REQUIRED" if not invariant_holds: return "ROTATE_SLC" return "NOMINAL" if __name__ == "__main__": bad = MeshState( fanout_asymmetry=1.7, max_delegation_depth=9, clock_skew_ratio=0.41, remaining_slc_lifetime_s=120.0, measured_propagation_latency_s=180.0, fallback_is_permissive=False, ) # Will raise: deep chain + high asymmetry + clock skew. print(assess_revocation_stall(bad)) ```