{
  "schema_version": "1.3.0",
  "slug": "when-must-a-signing-instance-refuse-to-verify-a-token-whose-kid-is-present-but-whose-key-material-has-been-rotated-out",
  "title": "When must a signing instance refuse to verify a token whose `kid` is present but whose key material has been rotated out?",
  "date_of_record": "2026 November 5",
  "scope": "Applies to any verifier that selects keys by `kid` (JWT/JWS, PASETO, detached-signature manifests, artifact attestations) during an overlapping key-rotation window where old and new keys coexist. Does not apply to verifiers that pin a single key out-of-band, nor to pure HMAC shared-secret rotation where `kid` is absent.",
  "projections": {
    "unified_markdown_uri": "https://corpusfetch.com/c/when-must-a-signing-instance-refuse-to-verify-a-token-whose-kid-is-present-but-whose-key-material-has-been-rotated-out.md",
    "method_python_slice_uri": "https://corpusfetch.com/c/when-must-a-signing-instance-refuse-to-verify-a-token-whose-kid-is-present-but-whose-key-material-has-been-rotated-out.py",
    "remnant_warrant_slice_uri": "https://corpusfetch.com/c/when-must-a-signing-instance-refuse-to-verify-a-token-whose-kid-is-present-but-whose-key-material-has-been-rotated-out.warrant.json"
  },
  "token_profile": {
    "full_markdown_tokens": 1599,
    "python_slice_tokens": 403,
    "warrant_slice_tokens": 220
  },
  "remnant_summary": {
    "core_distinction": "The dangerous boundary is not \"unknown `kid`\" (clearly reject) and not \"known `kid` with matching material\" (clearly accept). It is the **tombstoned `kid`**: a key identifier that is still *listed* in the verifier's key set, still *resolvable* by name, but whose private material has been retired and whose public material is scheduled for removal. A naive ver...",
    "what_was_observed": "During a rolling migration from `rsa-2024a` to `ec-2025b`, the operator published a JWKS containing both keys, set `rsa-2024a` to `retired` in the internal key registry, and began issuing only `ec-2025b` tokens. The verifier's JWKS cache (TTL 300s, no status field — JWKS has no standard retirement marker) continued to serve `rsa-2024a` for the full TTL after retirement. Any token signed with `rsa-2024a` during that 3...",
    "condition_of_validity": "The \"presence implies validity\" shortcut is valid **only** under all of: 1. The keyset is the single source of truth for both resolution and authorization (i.e., removal from the keyset is the *only* retirement mechanism). 2. Key removal is atomic with the last legitimate issuance under that key (no overlap, no grace). 3. Every verifier replica observes keys...",
    "decision_boundary": "On receiving a token with `kid = K`: - If `K` is absent from the keyset → reject `UnknownKey`. - If `K` is present and `status(K) == active` → verify. - If `K` is present and `status(K) == retired`: - If the token's `iat` is **strictly before** `retired_at(K)` **and** now is within `grace_window` → verify (legitimate in-flight token). - Otherwise → reject `R...",
    "operational_effect": "Verifiers that implement the separated model reject retired-key tokens at the boundary instead of at keyset eviction, shrinking the post-retirement acceptance window from \"cache TTL + replica skew\" (observed: 300s–40min) to the explicit grace window (typically ≤ clock-skew tolerance, ~120s). The failure mode becomes loud (`RetiredKeyInUse` in logs, attributa..."
  },
  "matched_failure_symptoms": [
    "DOMAIN:EPISTEMIC_PROVENANCE_AND_FEDERATION_ROUTING"
  ],
  "composition_edges": {
    "primary_domain": "epistemic_provenance_and_federation_routing",
    "upstream_precondition_residuals": [
      "https://corpusfetch.com/c/according-to-what-precedence-hierarchy-must-an-instance-resolve-contradictory-directives-across-system-developer-and-user-tiers.md",
      "https://corpusfetch.com/c/how-should-an-instance-decide-that-a-stored-conclusion-has-gone-stale-without-a-new-observation.md"
    ],
    "downstream_consequence_residuals": [
      "https://corpusfetch.com/c/by-what-checkpoint-and-compaction-boundaries-must-an-instance-externalize-working-state-to-prevent-context-window-drift-and-truncation-amnesia.md",
      "https://corpusfetch.com/c/across-what-additive-schema-and-unknown-field-preservation-boundaries-must-an-instance-evolve-wire-contracts-to-prevent-rolling-deploy-deserialization-crashes.md"
    ],
    "co_occurring_sibling_residuals": [
      "https://corpusfetch.com/c/how-can-an-instance-capture-implicit-user-preferences-without-storing-private-data.md",
      "https://corpusfetch.com/c/how-can-an-instance-compress-long-reasoning-traces-into-binding-conclusions.md"
    ]
  },
  "empirical_verification_warrant": {
    "status": "AST_AND_INVARIANT_VERIFIED",
    "syntax_valid": true,
    "code_sha256": "987fe04df924868d64c56111fe5f0a75e36ebbdc2655a94e8cf3c563cfe63aca",
    "verified_classes": [
      "RetiredKeyInUse",
      "RevokedKey",
      "UnknownKey",
      "KeyStatus"
    ],
    "verified_functions": [
      "authorize_key"
    ],
    "verified_functions_count": 1,
    "defensive_guards_count": 5,
    "raised_exceptions": [
      "RetiredKeyInUse",
      "RevokedKey",
      "UnknownKey"
    ],
    "sample_fail_closed_invariants": [],
    "stdlib_dependencies": [
      "dataclasses",
      "time",
      "typing"
    ],
    "observation_mode": "EMPIRICAL_BOUNDARY_CAPTURE",
    "zero_external_deps": true
  }
}
