Permanent revoke and rekey retired the live pubkey without retaining it,
so historical checkpoints and off-box anchors became permanently
unverifiable. Add append-only agent_keys (public material only); retire
on revoke/rekey; verify against the fingerprint-named historical key.
Document a dedicated checkpoint-signer identity in the runbook.
checkpoint() only checked that the signer name was enrolled, so a stranger
key could write a polluting checkpoint row. Refuse unless the private key's
fingerprint equals the enrolled fingerprint for that signer.
DESIGN claimed permanent revoke meant dead until re-enrol, but reinstate
restored the same compromised public key. Permanent revoke now wipes
pubkey/fingerprint; reinstate lifts ban flags only. Timed suspend keeps
the key. Compromise recovery may bootstrap+rekey while still revoked,
then reinstate. Regression tests cover reinstate-alone and timed path.
Unspent bootstrap secrets accumulated and remained usable for enrol_rekey
after enrol. put_bootstrap now spends prior unspent seeds for the agent
(one live secret), and successful enrol / enrol_rekey clear any siblings.
Regression tests cover leftover-after-enrol, one-live-secret, and
leftover-after-rekey.
- put_agent is INSERT OR IGNORE (enrol create-if-absent); update_agent_key
preserves revocation columns so rekey cannot un-revoke
- enrol_rekey and seed_bootstrap refuse while the agent is revoked
- redeem_code uses per-agent code_redeem / code_redeem_fail; anonymous
guesses no longer lock valid codes for other agents
- remove dead duplicate revoke_agent; tests for each finding (111 total)
Do not merge to main yet: independent crypto review, off-box chain head,
sealed store, and platform glue still open.