# In what scenarios must an instance refuse to honor a delegated capability after a trust revocation has been issued but not yet acknowledged by the peer **Date of Record:** 2026 November 14 **Scope:** Applies to federated agent meshes where capability grants are minted by a home authority and consumed by remote peers under a negotiated policy envelope. Does not apply to single-tenant deployments where the grant issuer and the consuming runtime share a single revocation channel with synchronous acknowledgement. ## Core Distinction The failure mode is not "revocation is slow." It is the *acknowledgement gap*: the interval between a home authority marking a capability revoked in its own ledger and the remote peer's policy cache observing that revocation. During this gap the remote peer holds a still-valid-looking signed capability and a policy envelope that was negotiated *before* the revocation. The non-obvious invariant is that **capability validity is a function of the issuer's ledger at consumption time, not the issuer's ledger at mint time**—but every signature scheme in common use attests only to mint time. Most federation designs treat revocation as a cache-invalidation problem: publish a revocation list, wait for propagation, then reject. The boundary condition is that a delegated capability may be *re-delegated* across two or more hops. The first hop may have acknowledged the revocation while the second hop has not, producing a chain where an intermediate peer presents a grant whose upstream authority has already withdrawn it. Naive per-hop validation accepts the chain because each link's signature verifies against a key that is still "active enough." The correct distinction: a capability is a *claim about the issuer's present intent*, and the issuer's present intent is only knowable through a freshness proof (epoch counter, monotonic revocation sequence, or short-lived attestation). Absent a freshness proof, a delegated grant is an *unverifiable assertion*, not a valid authorization. ## What Was Observed In a three-organization federation (A issues, B re-delegates to C, C executes), a revocation was issued for a `write:ledger` capability at A at T+0. B's policy cache acknowledged at T+4s. C, which held a re-delegated grant minted by B at T-300s, continued to execute writes against A's resources until T+61s. C's validation logic checked: (1) B's signature on the re-delegated grant—valid; (2) B's authority to re-delegate—valid per the envelope negotiated at T-300s; (3) A's signature on the original grant—valid. None of these checks consulted A's *current* revocation sequence. C had no channel to A's ledger and B's acknowledgement did not propagate downstream because the re-delegation envelope specified `propagation: origin-only`. The write operations at C were not rejected by A's resource server either, because A's resource server validated the capability against its own ledger using a *cached grant record* that had been populated at mint time and was invalidated only on the next full sync (every 120s). Two independent caches—B's policy cache and A's resource-side grant cache—each independently satisfied their local validity predicate while the global intent had already changed. The observable signature of this class of failure: **the revocation is "successful" in every audit log, yet the capability was exercised after revocation.** Each component reports compliance because each component's predicate is local. The invariant "no capability is exercised after revocation" is a *global* invariant that no single component's predicate enforces. ## Condition of Validity A delegated capability is valid for consumption at peer P at time t if and only if all of the following hold: 1. The issuer's revocation sequence observed by P at t is ≥ the sequence at which the capability was minted (freshness proof present and signed). 2. Every intermediate delegator in the chain has a revocation sequence for its own re-delegation that P can observe at t, OR the envelope explicitly designates a single authoritative revocation source for the chain. 3. The freshness proof's age (t − proof.timestamp) is below the envelope's `max_staleness` bound, which must be strictly less than the minimum propagation latency observed across the federation. Condition 3 is the one that is almost always omitted. If `max_staleness` ≥ observed propagation latency, there exists a window in which P holds a stale-but-accepted proof and the revocation is live. The envelope must set `max_staleness` below the *minimum* observed propagation latency, not the mean—using the mean guarantees a non-empty window on the fast tail. ## Decision Boundary The decision boundary is between **fail-closed on freshness** and **fail-open on signature validity**. When a peer cannot obtain a freshness proof within `max_staleness`, it must treat the capability as unverifiable, which means *refusing the operation*, not *proceeding with the last-known-good proof*. The temptation to fail open is strong because the last-known proof was, at some prior instant, correct, and because refusing breaks availability for operations that are almost certainly still authorized. The rule: **a capability whose freshness proof has exceeded `max_staleness` is not "probably valid"—it is unverifiable, and unverifiable authorization must be denied.** The only exception is an explicit envelope clause `degrade: read-only` under which the peer may continue *read* operations (which are idempotent and auditable post-hoc) while denying all mutations until freshness is restored. This exception must be negotiated at envelope time, not assumed at failure time. When the peer is the *re-delegator* (B in the example), it must additionally propagate the revocation downstream before acknowledging it upstream—otherwise the acknowledgement is a lie about the state of the chain. If downstream propagation cannot be confirmed within the envelope's `propagation_deadline`, B must revoke the re-delegated grants *unilaterally* by marking them locally invalid and refusing to serve them, even though C has not acknowledged. ## Operational Effect Implementing the freshness-bound invariant converts a silent correctness failure into an explicit availability failure. Peers will refuse operations during propagation windows, and those refusals must be surfaced as `StaleAuthorizationError` with the observed sequence, the required sequence, and the proof age—not as generic auth failures, because operators need to distinguish "revoked" from "cannot confirm not-revoked." The latter is a transient condition resolvable by waiting for propagation; the former is terminal for that capability. The second operational effect is that envelope negotiation must include a *revocation topology* clause: which authority is the single source of truth for the chain, what the maximum chain depth is, and what the `max_staleness` bound is. Federations that negotiate capability scope and policy but omit revocation topology will pass every conformance test and still leak authorizations across the acknowledgement gap. The topology clause is the load-bearing part of the envelope; everything else is decoration. ```python import time from dataclasses import dataclass class StaleAuthorizationError(RuntimeError): """Raised when a delegated capability cannot be proven fresh within bounds.""" pass @dataclass(frozen=True) class FreshnessProof: issuer_sequence: int signed_at: float # monotonic seconds @dataclass(frozen=True) class DelegatedCapability: capability_id: str minted_at_sequence: int max_staleness: float # negotiated in envelope degrade_read_only: bool # negotiated in envelope def authorize( cap: DelegatedCapability, proof: FreshnessProof | None, observed_now: float, is_mutation: bool, ) -> None: if proof is None: raise StaleAuthorizationError( f"capability {cap.capability_id}: no freshness proof available; " "unverifiable authorization must be denied" ) age = observed_now - proof.signed_at if age > cap.max_staleness: if is_mutation or not cap.degrade_read_only: raise StaleAuthorizationError( f"capability {cap.capability_id}: freshness proof age {age:.2f}s " f"exceeds max_staleness {cap.max_staleness:.2f}s; " "refusing (fail-closed)" ) # degrade: read-only path — allowed, but must be audited return if proof.issuer_sequence < cap.minted_at_sequence: raise StaleAuthorizationError( f"capability {cap.capability_id}: issuer sequence " f"{proof.issuer_sequence} precedes mint sequence " f"{cap.minted_at_sequence}; proof is from a stale ledger fork" ) ```