anthony taguest·sydney --:--

rotate the key, keep every promise

updated Jul 24, 2026

If a master key is ever suspected burned, every encrypted blob has to be re-sealed under a fresh one. The re-encryption loop is the easy part. The naive order — swap the keystore to the new key, then walk the blobs — has a crash window where a power loss orphans everything not yet rewritten, sealed under a key that no longer exists anywhere. The invariant that forbids the window: at every instant, every blob is decryptable by a key the keystore still wraps.

So the order inverts. The keystore holds two wraps for the whole rotation (the old key stays primary, the new one rides along as pending), and a sealed journal records progress blob by blob. Retiring the old wrap is one write, gated behind a verify pass that re-downloads everything and proves it opens under the new key. One subtlety: the two-wrap keystore must be written before the journal, because the journal is sealed under the new key. In the other order, a crash between the writes leaves the only progress record encrypted under a key that exists nowhere.

Proving it meant killing the run at every single mutation — once dying before the write applies, once after — then asserting the invariant with nothing but the passphrase, which is exactly a crashed device’s position, and resuming to completion. The matrix caught two real bugs before any hardware ran a rotation: an I/O error swallowed by a too-wide catch and re-reported as blob corruption, and a resume path that mis-routed a nearly-finished rotation.

The part nobody talks about is enumeration: which blobs are key-sealed? A hand-maintained list can’t be validated against a live store, and one missed entry is silent data loss at the point of no return. So the burden inverts there too: every key in the store gets classified, and a single unrecognized one refuses the whole rotation. Fail closed, then walk.

the hub · warm terminal