Defines zero-downtime secret rotation by publishing a new secret under a key identifier, dual-accepting old and new material during a measured grace window, and revoking the old secret only after observed coverage.
Package status: reference context ready for human review. The contract and test scenarios are complete, but no claim is made that an adopting implementation has passed them.
Decision
Rotate production secrets without a hard cut. Publish the next secret with a distinct key identifier. During a grace window, verifiers dual-accept current and next material. Signers switch only after a stated fraction of instances have reloaded. Revoke the previous identifier only when metrics show observed coverage, not when a calendar timer fires. Abort pins verifiers back to the current identifier if the next material misbehaves.
Scope
- HMAC keys, API tokens, and similar symmetric or opaque secrets consumed by live services.
- Publisher, verifier, and signer roles; reload signals; coverage metrics; abort.
- The dual-read window documented in
dual-read-window.yaml.
Outside this block
- Certificate issuance and public-key infrastructure lifecycle.
- How CI obtains deploy-time secrets; see the related CI least-privilege block.
- Human password reset or session cookies.
Contract
- Every secret version has a key identifier that travels with the ciphertext or MAC, not a single unlabeled slot.
- While
dual_acceptis true, verifiers accept bothcurrent_key_idandnext_key_idand reject any other identifier. - Signers emit
current_key_iduntil the cutover state, then emit onlynext_key_id. - Revoke of
current_key_idis refused untilmin_observed_requestsandcoverage_ratioonnext_key_idare met. - Instance heartbeats must show reload within
max_unreloaded_secondsor cutover is blocked. - Abort restores single-key accept on
current_key_idand marks the next version not-for-production.
Implementation guidance
- Carry the key identifier in an envelope field parsers cannot skip.
- Dual-accept is two constant-time verifications, not a merged key.
- Coverage comes from kid-labeled success metrics, not deploy checkboxes.
Failure handling and safeguards
- Unknown kid fails closed. Missing coverage is treated as zero, so revoke is refused.
- If
next_key_idcannot be published, rotation does not start. - Do not auto-revoke when grace expires without coverage; page operations.
Verification and operations
- Rehearse dual-accept, signer cutover, and revoke above
min_observed_requests. - Alert on unreloaded instances and attach kid histograms to the ticket.
The executable-looking examples in this package are fixtures and acceptance contracts. Run
python tools/validate.py from the collection root to check package structure and metadata; then
implement and execute the scenarios in the target repository.
Adoption assumptions
- Names and numeric values in
example.yamlare an adoption profile, not universal defaults. - The adopting team must map actors, data classes, error vocabulary, and ownership to its system.