mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
A cordn room's history lived in a LinkedHashMap and nowhere else, while the cursor that fetches it was persisted pointing past everything. So a relaunch opened every room empty and no amount of syncing refilled it: the one handle to history had already been spent. Rewinding it would not have helped either — both seal keys are epoch-derived, so the copy the coordinator still holds cannot be opened once the group has ratcheted. Ingestion is the only moment the message is in the clear. **EncryptedAppendLog moves to a neutral package.** It takes encrypt and decrypt lambdas and stores opaque strings; nothing in it knows what Marmot is. It only lived under `marmot` because that is where it was written first, and the independence guard reads that as a dependency, so cordn could not touch it. Now `commons.storage`, used by both, owned by neither — which makes the two features more separable, not less. Marmot changes by one import line. **The write.** `CordnDeliveredMessageCodec` stores the envelope plus its cursor: the cursor is the coordinator's sequence, which the room orders on and the unread divider compares against, and `created_at` is the sender's clock, which is a claim. `FileCordnGroupStore` appends a segment per message rather than rewriting the conversation, so receiving costs the same on message ten thousand as on message one. **The ordering is the correctness property.** `persist` writes messages *before* the cursor. Crash between them and the message is on disk while the cursor still points before it: the next catch-up re-delivers it and dedup drops it. The other order loses it permanently. That is why the write lives next to the cursor write rather than at the call sites. Dedup keys on the envelope id, not the stored entry: a re-delivery carries the same envelope with a different cursor, so the bytes differ while naming one message. **The read, in two parts.** The conversation loads when a room opens, through `addAll`, so the annotation fold and ordering are rebuilt by the same code that handles live delivery. A one-entry summary per group loads at login for the inbox line — which is why every cordn room read "No messages yet" after a relaunch however much had been said. Deleting a group takes the log and the summary with it. History that outlived its MLS state would be the plaintext of an end-to-end encrypted conversation left behind after the key that read it was destroyed. Mutation-checked: inverting the write order, dedup'ing on the whole entry instead of the id, and forgetting to drop history on delete each fail exactly one test and nothing else. Not yet carried into backup or device migration — the plan (commons/plans/2026-09-24-cordn-message-store.md) recommends migration yes, backup no, and that is still an open call. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012BfD4txdnsaPRXmNXbup9n