Actor Key Succession — Unforgeable Old→New Rotation Records
Accepted
Context
ADR-023/024 make the witness the unit of provenance: every authoritative mutation is signed by an actor's Ed25519 key, and external verifiers check the signature against the actor's published public key (`.ontoref/actors.ncl`). Until now actor keys were static — there was no way to rotate a key without stranding every witness it had ever signed.
Two failure modes follow from static keys:
- A compromised or lost key has no recovery path. Abandoning it abandons the verifiability of all prior witnesses signed under it. - Routine hygiene (periodic re-keying) is impossible without breaking the audit chain.
The seam at risk is `witness-as-axis-seam` (Axiom, invariant): the witness binds the ontology axis (what IS — the signed state) to the reflection axis (the act). A key change that orphans prior witnesses tears that seam for every deposit made under the old key.
Decision
ACCEPTED. Key rotation is recorded as an unforgeable succession entry in the oplog, modeled as `OpPayload::KeySuccession { actor_id, new_key, old_key }`. The record MUST be signed by the OLD key: the enclosing `OpBody.actor` equals `old_key`, and the operation signature verifies under it. Only the holder of the current key can therefore authorize its successor. The record is anchored in the oplog DAG (parents = current heads, monotonic HLC) and is state-neutral — the commit, triple, and substrate reducers assert nothing from it.
Verification chains trust backwards from the actor's current trusted key (`actors.ncl`) through the succession records: a key is valid for an actor if it is the current key or a verified predecessor of a valid key. A record only extends the chain when its declared `old_key` equals its signer AND its signature verifies — a forged record (signed by anyone but the declared predecessor) is silently ignored. A witness signed by a superseded key therefore still verifies: the chain proves that key was once authoritative for the actor.
The trust-chain resolver (`ontoref_types::succession::valid_keys_for_actor`) is a pure function over `&[Operation]` — no IO, no oplog handle — so any verifier (daemon, CLI, remote peer) applies it offline. Emission (generate new key, append the succession, republish the public key) is a tier-2 substrate act, gated behind the `substrate` feature; tier-0/1 projects pay no cost.
Constraints
- Hard A KeySuccession record MUST be signed by its declared old_key (OpBody.actor == old_key) and that signature MUST verify; chain resolution MUST reject any record where the signer is not the declared predecessor. Only the holder of the current key may authorize its successor.
- Hard Verification MUST treat a witness signed by a superseded key as valid when a chain of verified succession records links that key to the actor's current key. Reversing this (invalidating prior witnesses on rotation) breaks the witness-as-axis-seam.
Alternatives considered
- Static keys (status quo) — rejected: No recovery path; a compromised key strands every witness it signed. Rejected because it leaves the witness-as-axis-seam brittle across the inevitable key lifecycle.
- Sign the succession with the NEW key — rejected: Anyone can mint a new key and claim to be an actor's successor. Authorization must come from the party being superseded, so the record is signed by the old key.
- A central key-authority service that issues/revokes actor keys — rejected: Reintroduces a trusted third party and a runtime dependency, contradicting ADR-001 minimal adoption and ADR-027 P2P-pure direction. The oplog-anchored, self-authorizing chain needs no authority.