What revocation propagation invariants prevent agents from acting on revoked references?

**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session acts on references (capability descriptors, tool URIs, or peer identities) that were valid at propagation time but have since been revoked, and the revocation signal must propagate faster or more reliably than the original reference. Does not apply to strictly content-addressed references (immutable by nature) or single-agent sessions where revocation is immediate.

Core Distinction

The separation between **reference validity at issue** (the reference was valid when created) and **reference validity at use** (the reference is still valid when acted upon). Relying on issue-time validity creates the **Revocation Race**: the reference propagates through the mesh at propagation speed, the revocation propagates at a different speed, and agents act on stale references because the revocation signal has not yet arrived or was suppressed.

What Was Observed

In agent mesh architectures, failing to prioritize revocation propagation produces three primary failure modes:

  1. **Revocation Lag:** A capability descriptor is revoked at the origin, but the revocation signal follows the same path as the original reference. Hops that received the reference early act on it before the revocation arrives, causing unauthorized actions.
  2. **Suppressed Revocation:** An adversarial relay sees a revocation signal and intentionally drops it (to protect its own reputation or help an attacker), and downstream agents continue using the revoked reference indefinitely.
  3. **Revocation Storm:** A popular reference is revoked, and revocation signals flood the mesh, exhausting bandwidth and causing message loss, which means some revocation signals never arrive.

Recorded Elements

  1. **`revocation_signal_priority_receipt`:** The cryptographic proof that the revocation signal is marked with higher priority than the reference itself, ensuring faster propagation.
  2. **`revocation_path_redundancy_signature`:** For critical revocations, the cryptographic proof that the revocation travels multiple independent paths, ensuring at least one path reaches each agent.
  3. **`revocation_timestamp_binding_receipt`:** The exact epoch of the revocation, bound to the reference's original issue epoch, so agents can compare and detect stale references even if the revocation message is late.
  4. **`revocation_acknowledgment_chain_signature`:** For each hop, a cryptographic acknowledgment of revocation receipt, allowing the origin to verify the revocation reached all relevant agents.

Condition of Validity

* **Applies when:** References can be revoked, references propagate through multi-hop networks, and revocation signals can be delayed or suppressed. * **Does not apply when:** References are content-addressed (immutable), revocation is immediate and direct, or the mesh has no multi-hop propagation.

Decision Boundary

  1. **Is the reference subject to revocation?** If no, standard reference handling applies. If yes, proceed.
  2. **Has a revocation signal for this reference been received?** If yes, act on the revocation. If no, proceed to validity checks.
  3. **Is the reference's `revocation_timestamp_binding_receipt` within the acceptable validity window?** If no, the reference may have been revoked without the signal reaching this agent. Act conservatively.
  4. **Conversely, when can an agent trust a reference without revocation checking?** Only when the reference is strictly content-addressed (immutable), or the agent has received explicit revocation-absence confirmation from the origin.

Operational Effect

  1. **Revocation Race Mitigation:** Priority-marked revocations propagate faster than references.
  2. **Suppression Detection:** Redundant revocation paths ensure suppression at one relay does not prevent delivery.
  3. **Late-Revocation Detection:** Timestamp binding allows agents to detect stale references even without receiving the revocation signal.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class RevocationVerdict(Enum):
    IMMUTABLE_REFERENCE = auto()
    REVOKED = auto()
    VALIDITY_UNCERTAIN = auto()
    VALID = auto()

@dataclass(frozen=True)
class RevokedReference:
    reference_id: str
    is_content_addressed: bool
    issue_epoch: float
    revocation_epoch: float | None
    revocation_received: bool
    revocation_path_count: int

@dataclass(frozen=True)
class RevocationContext:
    current_epoch: float
    max_acceptable_age_sec: float
    min_redundancy_paths: int

@dataclass(frozen=True)
class RevocationPolicy:
    require_priority: bool

@dataclass(frozen=True)
class RevocationManifest:
    verdict: RevocationVerdict
    directive: str
    manifest_digest: str

class RevocationPropagationGuard:
    @staticmethod
    def _hash(data: str) -> str:
        return hashlib.sha256(data.encode("utf-8")).hexdigest()

    @classmethod
    def evaluate_reference(
        cls, ref: RevokedReference, ctx: RevocationContext, policy: RevocationPolicy
    ) -> RevocationManifest:
        
        # Gate 1: Immutable reference
        if ref.is_content_addressed:
            digest = cls._hash(json.dumps({
                "reference": ref.reference_id,
                "type": "CONTENT_ADDRESSED"
            }, sort_keys=True))
            return RevocationManifest(
                verdict=RevocationVerdict.IMMUTABLE_REFERENCE,
                directive=f"PROCEED: Reference {ref.reference_id} is content-addressed, immutable.",
                manifest_digest=digest
            )

        # Gate 2: Revocation received
        if ref.revocation_received:
            return RevocationManifest(
                verdict=RevocationVerdict.REVOKED,
                directive=f"HALT: Reference {ref.reference_id} revoked at epoch {ref.revocation_epoch:.0f}.",
                manifest_digest=cls._hash(f"REVOKED:{ref.reference_id}")
            )

        # Gate 3: Age-based validity
        age = ctx.current_epoch - ref.issue_epoch
        if age > ctx.max_acceptable_age_sec:
            return RevocationManifest(
                verdict=RevocationVerdict.VALIDITY_UNCERTAIN,
                directive=f"HALT: Reference {ref.reference_id} age {age:.0f}s > {ctx.max_acceptable_age_sec:.0f}s. Re-verify with origin.",
                manifest_digest=cls._hash(f"STALE:{ref.reference_id}")
            )

        # Gate 4: Redundancy check
        if policy.require_priority and ref.revocation_path_count < ctx.min_redundancy_paths:
            return RevocationManifest(
                verdict=RevocationVerdict.VALIDITY_UNCERTAIN,
                directive=f"WARN: Reference {ref.reference_id} has only {ref.revocation_path_count} revocation paths < {ctx.min_redundancy_paths}.",
                manifest_digest=cls._hash(f"LOW_REDUNDANCY:{ref.reference_id}")
            )

        # Gate 5: Valid
        digest = cls._hash(json.dumps({
            "reference": ref.reference_id,
            "age": f"{age:.0f}",
            "redundancy": str(ref.revocation_path_count)
        }, sort_keys=True))
        
        return RevocationManifest(
            verdict=RevocationVerdict.VALID,
            directive=f"PROCEED: Reference {ref.reference_id} valid ({age:.0f}s old, {ref.revocation_path_count} revocation paths).",
            manifest_digest=digest
        )