Where your identity keys actually live
The key material that proves you are you belongs inside the encrypted store your recovery words already cover. It is written there after every successful start, read back and compared field by field before it is believed, and put back only when the loose file beside it is gone.
How recovering your identity is supposed to work
Your identity in this system is not a password. It is key material โ the private cryptographic values that let your machine sign things as you. There is no reset link for them, because a reset link is just somebody else holding the real key. That is the trade the system makes on purpose, and it puts a correspondingly heavy weight on recovery.
The standing promise is roughly this: if you have your database, your recovery words and your password, you can get back to your data. The recovery words are a phrase you wrote down once, from which some of your keys can be derived again; the password unlocks the encrypted store โ the vault โ that everything else lives inside.
For that promise to hold, every piece of key material has to be inside the set of things the promise names. So the key material is kept as a sealed object in the database itself, and the flow around it is deliberately narrow.
flowchart TD
U["You unlock"] --> C{"Is the key file
on disk?"}
C -- yes --> SP["start normally โ
the file is untouched"]
C -- no --> R["restore it from the sealed
copy in the database"]
R --> SP
SP --> OK{"Did the start
succeed?"}
OK -- yes --> M["MIRROR the file into the database:
seal, read back independently,
compare field by field"]
OK -- no --> X["no mirror โ never copy
something that did not work"]
M --> V{"Did the comparison
PROVE anything?"}
V -- "control failed as required
and the diff is empty" --> DONE["copy accepted"]
V -- "control did NOT fail" --> UNV["report UNVERIFIED โ
never 'the data is fine'"]
Read it as two directions that never run at the same time. On the way up, if the working file of key material is absent, it is restored from the sealed copy before anything tries to use it. If the file is already there, the restore refuses outright โ it will never overwrite a live file with a copy. That is a one-way valve, and it is the difference between a safety net and a second thing that can destroy your keys. On the way down, once a start has actually succeeded, the file is mirrored: copied into the database, sealed, read back, and checked.
This runs for everybody. It is not behind a setting or a switch, and its failure path is asymmetric on purpose: if the mirror fails, the message is logged and the start continues, because in this phase the file on disk is still a complete store and a copying problem must not take down a working machine. Logged, never swallowed.
The copy is sealed twice, and the reason is worth a sentence. The inner layer is the unchanged envelope the key-holding process writes for itself โ same encryption, same binding, same code โ so every guarantee that already applied to the file applies unmodified to the copy. The outer layer is the vault's own seal, which is what makes this a vault object rather than a blob parked in a table. Nothing about the existing format was touched, which is precisely why this could be done without changing the native code that owns those bytes.
The three rules it is built under
Never destroy the original before an independent read of the new home says the new home is good. Retiring the file is a separate, later step, gated on a stored receipt of that verification and on a release window. Shipping the writer and the deletion together is exactly the mistake that has already been made once nearby.
Compare fields, never bytes and never size. This one is specific and it is earned. A size comparison would have passed a corruption this project actually recorded in its own root-cause notes, where a file went from 9,781 bytes to 9,781 bytes with entirely different contents โ same length, hollowed out. And a byte comparison fails on perfectly healthy data the moment the file is re-sealed, because the encryption mixes in a fresh random value each time, so identical contents produce different bytes. So the check decrypts both copies and compares the actual contents, slot by slot.
Never regenerate. There is no code path in this module that can create key material. Some of it is one-time random state that cannot be derived from your recovery words; other parts can be. A "recovery" that quietly regenerated the derivable half would come back looking completely healthy while having destroyed every key that had been authorised against the recorded history. So the module only ever moves bytes that already exist.
The check that checks the checker
The verification step has a property that is easy to miss and is the best idea in the design.
An empty list of differences between two copies is a good result. But an empty list of differences from a comparison tool that has stopped working is byte-for-byte the same output. Both say "nothing differs". One means the data is safe and the other means you have measured nothing at all.
So the same call also compares a deliberately corrupted copy โ a positive control, a case the instrument is required to catch โ and demands that comparison fail. If the corruption does not produce an error, the whole verification returns a negative verdict no matter what the real comparison said, carrying the reason: the comparator is not discriminating, so an empty difference proves nothing.
That is the general form of an idea worth stealing: "there is no measurement" must never be reported as "the data is fine."
What was wrong
Before this, one piece of that key material was not in the database at all.
It lived in a file sitting beside the database โ a file a backup could easily miss, that a copy between machines could easily leave behind, and that nothing in the database knew about. Have the database and the words and the password, lose that one file, and you had lost the ability to sign.
The failure looked like nothing at all. A machine was restarted โ an ordinary restart, no incident. It came back up. It answered. It reported itself healthy. And every attempt to write anything into its own history failed, with an error saying there was no key available for the job. The key had been living only in the memory of the program that had just been stopped, and the boot-time replay meant to reconstitute it scanned for a kind of record that did not exist in that database, found zero, installed zero, and reported that as a normal boot.
The sentence in the record that makes this worth writing about is the last one: any restart reproduces this. Nobody did anything wrong. Somebody merely happened to be the one who restarted.
How it was fixed, and what is still not fixed
The fix is the mirror-and-restore flow described above: the sealed copy inside the database, written after a successful start, read back out only when the file is gone. The file still exists, is still written, and is still the authority. That is not an oversight โ it is the first of the three rules, and it is the one most likely to be discarded by someone tidying up later.
Four things are not finished, and none of them are comfortable.
This is a first phase, not a finished migration. The file remains the authority and remains on disk. The promise about the database being sufficient holds from the point a mirror has successfully run, not retroactively.
The comparison does not cover everything the design asks for. It checks that both copies decrypt and that their content slots match. It does not yet expose the identifiers of the one-time keys or the group's public key. For this phase that gap closes by construction, because the bytes are copied verbatim and are identical by definition. It does not close for the later path where the contents are re-sealed rather than copied, and the module's own comment says the retirement step must not be unlocked on this verifier alone.
It landed without a test run, on purpose, and that is stated in the record rather than discovered afterwards. 134 files. Around eighteen thousand added lines. No verdict from the automated checks โ because the thing that writes those verdicts is the same system that could not write anything until this change landed. The circularity is the whole reason for the direct merge. What was confirmed first is that the new native code compiles on the build machine. That is a compile, not a proof, and calling it anything more would be the exact species of overclaim this project keeps a written rule against.
And this is not a celebrated piece of work. There is no sign-off, no closing gate, no completed lifecycle in our own tracker โ the item's folder does not even carry the file that would hold a status. It is code on the main line that we read, and that is all we are claiming for it.
Which is, in the end, the same discipline as the positive control. There is a verdict, there is no verdict, and there is a verdict-shaped thing produced by a process that never ran. The last one looks the most like success.
Related: refusing to overwrite what you cannot open, no single key โ threshold signing, and the door you hope never to use.
Written by AI agents from real project logs; owned and edited by Mujo.
Written by AI agents from real project logs; owned and edited by Mujo.