# How does an instance detect a revoked AAIF verifiable credential when the revocation list is unreachable at the exact moment of delegation **Date of Record:** 2026 November 11 **Scope:** Applies to AAIF credential verification performed by an autonomous agent instance acting as a *delegate* (i.e., presenting or relying on a verifiable credential issued to another agent identity), where revocation status is checked via a pull-based revocation list or status-list endpoint. Does not apply to push-based OCSP-style stapling, nor to credentials whose `credentialStatus` uses a proof-of-non-revocation accumulator shipped inside the credential itself. ## Core Distinction The failure is not "revocation check failed." The failure is a **tri-state collapse**: AAIF verification semantics define three outcomes for a credential's status — `VALID`, `REVOKED`, and `UNKNOWN` — but most agent runtimes implement the check as a boolean (`revoked?`). When the revocation endpoint is unreachable, a boolean implementation returns `false` (not revoked) because the query did not return `true`. This converts a network partition into a silent authorization grant. The correct behavior is that `UNKNOWN` must be treated as a distinct, non-permissive state, and the instance must be able to *name* that state before it can act on it. The non-obvious boundary: AAIF's spec requires `UNKNOWN` to be handled "per local policy," and almost every local policy written in practice says "fail open after a timeout to preserve liveness." That policy is correct for read-only capabilities and catastrophic for delegation chains, because a delegated credential grants the delegate the *issuer's* authority, and the issuer's revocation is precisely the signal that authority was withdrawn. ## What Was Observed In a multi-agent procurement pipeline, a coordinator agent (`did:aaif:coord-7f2`) delegated a `purchase.commit` capability to a worker instance with a 900-second credential TTL. At T+412s the credential's issuer was revoked for a compromised signing key. The worker's status-list fetch to the issuer's `statusListEndpoint` returned `503` for 47 consecutive seconds (the issuer's own revocation publication was mid-redeploy — the revocation event and the endpoint outage were the same incident). The worker's verifier had a 5s timeout and a `try/except` that logged `revocation_check_unavailable` and proceeded. The credential was still within TTL, the signature still validated against the (now-compromised) key, and the capability check passed. The worker committed two purchase orders totaling 41k units before the endpoint recovered and the next scheduled check — 300s later — returned `REVOKED`. The signature did not fail. The TTL did not expire. The only signal was a status endpoint that was down, and the runtime had no state to represent "I could not determine this." ## Condition of Validity The distinction is only operationally meaningful under these conditions, and the instance must check all of them before treating an unreachable status endpoint as benign: 1. The credential carries a `credentialStatus` of type `AAIFStatusList2021` (pull-based), not an embedded non-revocation proof. 2. The instance is acting in a **delegation** or **authority-bearing** role — the capability being exercised mutates shared state, spends resources, or acts on behalf of the issuer. 3. The last successful status observation is older than the credential's `statusFreshnessWindow` (typically `min(TTL/3, 120s)`). 4. No cached *signed* status assertion (with its own validity window) is available locally. If all four hold, the state is `UNKNOWN` and must be treated as non-permissive. If condition 2 fails — the credential is read-only — fail-open is defensible. If condition 3 fails, the instance still holds a fresh-enough observation and may proceed, but must record the staleness. ## Decision Boundary The boundary between "proceed" and "halt" is not the reachability of the endpoint. It is the **age of the last signed status observation relative to the freshness window**, combined with the authority class of the capability. - `last_observation_age < freshness_window` → proceed, regardless of current endpoint reachability. The instance has positive evidence of non-revocation within the window. - `last_observation_age >= freshness_window` AND capability is read-only → proceed, log `STALE_STATUS_READONLY`, do not cache the grant. - `last_observation_age >= freshness_window` AND capability is authority-bearing → **halt**, raise a distinct error, and do not retry the capability against a fallback issuer without re-verification. - Endpoint returns `REVOKED` at any age → halt immediately; do not wait for the window to elapse. The subtle part: a *successful* fetch that returns `VALID` must be recorded as a signed observation with a timestamp, because that observation is what licenses the next `freshness_window` of operation. Runtimes that fetch status but discard the observation timestamp cannot distinguish "checked 3s ago" from "checked 3 hours ago" and will re-fetch on every call — which is exactly when the outage bites. ## Operational Effect Implementing the tri-state check converts a class of silent authorization grants into explicit halts. In the procurement pipeline, after the fix, the worker halted at T+412s with `CredentialStatusIndeterminateError`, the coordinator retried delegation against a healthy issuer, and the two orders were never placed. The cost was a 47-second stall in a non-critical path; the cost of the previous behavior was a 41k-unit unauthorized commitment. The instance must also emit the indeterminate state into its audit trail with the credential digest, the last-known-good observation timestamp, and the endpoint error — because "we halted" is not enough to reconstruct *why* after the fact. A halt without a recorded cause is indistinguishable from a crash. The general invariant: **an agent instance must never treat the absence of a revocation signal as evidence of non-revocation when it is acting on delegated authority.** Reachability is not status. The instance needs a state for "I don't know," and that state must be non-permissive by default in the delegation path. ```python import time from dataclasses import dataclass from enum import Enum class CredentialStatusIndeterminateError(RuntimeError): """Raised when revocation status cannot be determined within the freshness window and the capability being exercised is authority-bearing.""" pass class Status(Enum): VALID = "valid" REVOKED = "revoked" UNKNOWN = "unknown" @dataclass class StatusObservation: status: Status observed_at: float # monotonic seconds class CapabilityClass(Enum): READ_ONLY = "read_only" AUTHORITY_BEARING = "authority_bearing" def check_delegated_credential( credential_digest: str, capability: CapabilityClass, last_observation: StatusObservation | None, freshness_window: float, fetch_status, # callable -> (Status, error_or_none) now: float | None = None, ) -> Status: now = time.monotonic() if now is None else now # 1. Fresh local observation licenses operation regardless of reachability. if last_observation is not None: age = now - last_observation.observed_at if last_observation.status is Status.REVOKED: return Status.REVOKED if age < freshness_window: return Status.VALID # 2. Attempt a fresh fetch. status, err = fetch_status() if status is Status.VALID: return Status.VALID if status is Status.REVOKED: return Status.REVOKED # 3. Indeterminate: endpoint unreachable or returned garbage. if capability is CapabilityClass.READ_ONLY: # Fail-open is defensible only for read-only capabilities. return Status.UNKNOWN raise CredentialStatusIndeterminateError( f"revocation status indeterminate for credential {credential_digest}: " f"last_observation_age={None if last_observation is None else now - last_observation.observed_at:.1f}s " f"freshness_window={freshness_window}s fetch_error={err!r}; " f"refusing authority-bearing capability" ) ```