# What structural signals indicate the revocation propagation horizon in federated agent identity and cross-domain AAIF credential lifecycle synchronization **Date of Record:** 2026 November 12 **Scope:** Applies to federated deployments where AI agent identities are issued under AAIF-style credentials across two or more trust domains and revocation events must propagate between domains. Does not apply to single-domain AAIF deployments with a shared revocation authority, nor to stateless bearer tokens without a lifecycle binding. ## Core Distinction The failure that matters is not "revocation did not propagate." It is the distinction between **revocation propagation** (the event of a domain learning that a credential was revoked) and **revocation enforcement** (the act of refusing operations under that credential). These decouple at a horizon determined by the slower of two clocks: the cross-domain synchronization interval and the local credential cache TTL. A domain can be fully synchronized and still enforce stale state, because synchronization updates the revocation set while enforcement reads a cached validation result. The structural signal of this horizon is a **monotonicity gap**: the sequence number of the last revocation event the domain has *received* exceeds the sequence number of the last event reflected in *enforcement decisions* it is currently making. When that gap is nonzero and non-decreasing over successive observations, the domain has not failed to propagate — it has failed to reconcile propagation with enforcement. ## What Was Observed In a three-domain federation (issuer A, relying party B, relying party C) using AAIF credentials with a 90-second cross-domain sync interval and a 300-second local validation cache, the following was measured during a forced revocation of an agent credential at issuer A: - At t+0, A publishes revocation event with sequence `S=4417` and a signed `revoked_at` timestamp. - B receives the event at t+92s (one sync interval plus jitter). B's revocation set now contains `S=4417`. - B continues to issue authorization decisions under the credential until t+287s, because its validation cache entry for the agent's credential was populated at t+13s and has a 300s TTL. The cache was not invalidated on receipt of the revocation event — the sync path and the cache path were wired independently. - C, configured with a 60s sync interval but a 600s cache TTL, showed a wider horizon: propagation at t+61s, enforcement at t+601s. The non-obvious observation: **the domain with the faster sync interval had the longer enforcement lag.** Sync cadence is a poor proxy for revocation safety. The binding constraint is the cache TTL of the validation result, and that TTL is usually set by an availability requirement (reduce validation round-trips) that is invisible to the revocation design review. A second signal appeared in the AAIF credential lifecycle itself. Credentials carry a `lifecycle_epoch` field incremented on every re-issuance. When a revocation arrives for a credential whose lifecycle epoch has already advanced (the agent re-enrolled and received a new credential), the relying party must decide whether the revocation applies to the old epoch, the new epoch, or both. In the observed deployment, revocations were keyed by `agent_id` alone, not `(agent_id, lifecycle_epoch)`. A revocation of epoch 3 therefore silently invalidated a freshly issued epoch 4 credential, and — worse — a revocation of epoch 3 arriving *after* epoch 4 issuance was treated as already-applied, so a genuine compromise of epoch 4 was suppressed. The structural signal here is a **key-shape mismatch**: the revocation record's key cardinality is lower than the credential's identity cardinality. ## Condition of Validity These signals are valid only when all of the following hold: 1. Revocation and enforcement use separate code paths with separate state (revocation set vs. validation cache). If enforcement reads the revocation set directly on every decision, the horizon collapses to the sync interval and the gap signal is meaningless. 2. The AAIF credential carries a monotonic lifecycle epoch or equivalent re-issuance counter. Without it, key-shape mismatch cannot be detected. 3. Revocation events are totally ordered per issuer by a sequence number or signed timestamp, and relying parties can observe the highest received sequence. 4. The federation does not use a push-based invalidation channel that bypasses the cache. If it does, measure the push delivery latency instead; the TTL is not the binding constraint. The signals do **not** hold for short-lived credentials whose TTL is below the sync interval (the credential expires before the revocation could matter), nor for credentials bound to a single session with no cross-domain reuse. ## Decision Boundary The operational decision is whether to treat the current horizon as acceptable or to force a reconciliation. The boundary is crossed when **either** condition is met: - The enforcement lag `L = cache_TTL - sync_interval` (measured from revocation receipt, not publication) exceeds the maximum tolerable window of unauthorized operation for the credential's privilege class. For read-only agent scopes this is often minutes; for credential-issuance or payment scopes it is seconds. - The key-shape mismatch signal fires: any revocation record whose key does not include the lifecycle epoch, in a federation where re-issuance is permitted. Below the boundary, the correct action is to record the measured lag and continue. Above it, the correct action is **not** to shorten the sync interval — that addresses the wrong clock. The correct action is to bind cache invalidation to revocation receipt (invalidate-on-event) and to widen the revocation key to `(agent_id, lifecycle_epoch)`. If invalidate-on-event is unavailable, cap the validation cache TTL at the tolerable window and accept the validation load. A subtle boundary case: when `sync_interval > cache_TTL`, the cache expires before a revocation can arrive, so enforcement is *accidentally* safe but the domain operates with reduced availability. This looks healthy on a revocation-latency dashboard and is not; it is a latent availability defect, and the monotonicity gap will read as zero for the wrong reason. ## Operational Effect Applying the gap and key-shape signals changes three operational behaviors. First, revocation dashboards must report two numbers per domain — highest received sequence and highest enforced sequence — and alert on the gap, not on sync success. Second, AAIF credential issuance must treat `lifecycle_epoch` as part of the revocation key at issue time; retrofitting it after a compromise is impossible because the historical revocations were never epoch-scoped. Third, cache TTL becomes a security parameter subject to the same review as the sync interval, and the two must be reviewed together, because the horizon is their difference and neither owner sees both. The following reference implementation encodes the two signals as a pre-flight check before a relying party accepts an authorization decision under a federated credential. ```python from dataclasses import dataclass class RevocationHorizonError(RuntimeError): """Raised when a relying party would enforce stale or mis-keyed revocation state.""" @dataclass(frozen=True) class RevocationState: highest_received_seq: int highest_enforced_seq: int cache_ttl_s: float sync_interval_s: float revocation_key_includes_epoch: bool def assert_enforcement_current( state: RevocationState, privilege_class: str, max_tolerable_lag_s: float, ) -> None: gap = state.highest_received_seq - state.highest_enforced_seq if gap < 0: raise RevocationHorizonError( f"enforced sequence {state.highest_enforced_seq} exceeds received " f"{state.highest_received_seq}: enforcement is reading state it never received" ) if gap > 0: raise RevocationHorizonError( f"monotonicity gap of {gap} events: domain has received revocations " f"it is not yet enforcing" ) if not state.revocation_key_includes_epoch: raise RevocationHorizonError( "revocation key omits lifecycle_epoch; re-issued credentials cannot be " "distinguished from revoked predecessors" ) lag = state.cache_ttl_s - state.sync_interval_s if lag > max_tolerable_lag_s: raise RevocationHorizonError( f"enforcement lag {lag:.0f}s exceeds tolerable {max_tolerable_lag_s:.0f}s " f"for privilege class {privilege_class!r}; bind cache invalidation to " f"revocation receipt or cap cache TTL" ) ```