`assemble` reads `GroupSignedEvent` now and rebuilds from `Mantra*` rows only what that table does not hold, which is work signed before it existed. The rebuild is therefore on its way out rather than merely second in line, and this says so where a reader will actually meet it -- at the call site, from the compiler -- instead of only in a paragraph they have to find first. **Nine `@Deprecated` markers, and they are load-bearing as documentation.** The eight `toXEvent()` methods and `ArchiveManager.rebuiltEventsOf`, each carrying the same sentence: this is the fallback for pre-v13 work, read the event off the table instead, and it goes when the last such install does. That raises nine warnings in `commonMain` today, all of them inside the walk itself, so the deprecation is visible in every build without anything failing over it. The level is `WARNING` deliberately -- the code is still called, still correct, and still the only thing standing between an older room and an empty archive. **The checklist is a new section in docs/member-archive.md**, because the interesting part of this removal is not the eight methods, it is everything around them that is easy to take out by association or leave behind by accident. *What goes*: the walk and the version-label recovery inside it, the union in `signedEventsOf`, the eight rebuilds, and `ArchiveRoundTripTest` entire -- all ten cases, which exist to hold the rebuild up and cover nothing else. Its own header still opened with "signed events are not stored as events", which stopped being true two commits ago, so it now says what it is: the gate on a deprecated fallback, deleted with what it guards. *Two already-dead cousins to sweep at the same time*, named because they will look like part of the rebuild to whoever does the removal and are not: `MantraTranslation.toTranslationEvent`, which nothing has ever called, and `MantraTranslationChunkProposal.toTranslationChunkEvent`, on a model that is not even a `@Database` entity. *The tests that seed without recording*: in `ArchiveAssemblyJvmTest` the `apply`-only seeding **is** the rebuild path, and two of its cases are about the union specifically and mean nothing without it. `ArchiveApplyJvmTest` seeds its sender the same way but is testing delivery rather than assembly, so it needs the recording call *added* -- otherwise it quietly starts asserting against an empty archive, which is the same silent-success failure this whole feature is about. **What only looks like it goes, which is the half worth writing down.** The `isArchivable` filter in `signedEventsOf` is not part of the rebuild and becomes the only thing standing. It is there *because* of the record: the walk could only ever produce document kinds, so nothing needed filtering while it was the source, and the table holds every kind the group has signed -- starting with the `GroupKeyStateEvent` every room signs as its first act. Dropping it with the walk turns every room's archive into an `IllegalArgumentException` from `ArchiveEvent.build`. Two cases fail with exactly that if it goes, which is the guard against removing it by association rather than by decision. The verify filter in `assemble` stays too. With the rebuild gone it checks events that were verified before they were recorded, so it cannot fail in practice -- which is the argument for keeping it, not against. "Cannot happen" is the state it exists to preserve. `Mantra*.signature` and `Mantra*.publicKey` are explicitly *not* on the list. They were what made a row rebuildable, and since v13 `groupSignedEventId` says whether the group signed a row and points at the proof -- so they are arguably redundant. But four test files assert on them and `MantraTranslationContributor` builds a contributor list out of one, and it is a twelve-table migration with its own tests to rewrite. It should be decided on its own merits, not ride along. **The precondition cannot be checked, and the section says so plainly.** No query answers "does any install still hold pre-v13 work" -- a device that upgraded is indistinguishable from one that never had any, and the rows that need rebuilding are on other people's devices. What is observable is the `signedEventsOf` log line, which fires only when the rebuild actually contributed something; fleet-wide silence is evidence and not proof. The cost of getting it wrong is named as well, because it is not loud: the member keeps their own rows and reads the room normally, and only loses the ability to *answer* a request with the older half of the group's work -- so a newer member asks, is answered, and receives an archive that is quietly short. No behaviour change. 495 jvm tests and 297 android unit tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mantra docs
Notes on the parts of this app whose behaviour is not recoverable by reading the code alone — where the reasoning lives in a protocol, a failure mode that is silent, or a decision that looked arbitrary and was not.
| document | covers |
|---|---|
| shared-key-ceremony.md | ChillDKG over NIP-17: the rounds, the approval gates, the chat transcript, participant ordering |
| shared-key-derivation.md | deriving further keys from the group's threshold key with FROST tweaks — why not BIP32, why no chain code, and the one rule that must not be broken |
| frost-batch-signing.md | signing several events in one ceremony — why one nonce can never cover two messages, and the phased schema, wire and UI work that follows from it |
| marmot-membership.md | how members join an MLS group, and the epoch race that makes a missing member look like a successful invite |
| member-archive.md | handing a member added after the work was done the group's signed record — why the events are not on the wire at all, and why the room's id is enough to verify them |
| marmot-direct-messages.md | a one-to-one message inside a group as a stock NIP-59 gift wrap — what its MIP-03 carve-out costs, why the sender cannot read their own, and the one query that would broadcast it |
| mls-skipped-keys.md | why a group event that arrives a moment late is dropped for good, which flows trigger it, the quartz fix, and the partial mitigation in this app |
| long-running-sync.md | the chat subscriptions that stay open instead of pulling once per screen — why the request queue could not simply hold one, and how the group filter follows the room list |
| dead-code.md | code in the sync and relay stack that nothing calls, why each piece is still there, and which of it is a bug rather than a leftover |
| jvm-target.md | what desktop support cost, phased — why the native chain was already done, why an empty source set in our phoenix fork was the real blocker, and why DAO tests need none of it |
Start with the ceremony if you are new to this area; the Marmot notes all assume it. Read the skipped-keys note before debugging any "the other device never got it" report — it is silent, and it looks like every other kind of delivery failure. The sync note stands alone, and the dead-code inventory reads as a follow-up to it. The batch-signing note is a phased plan that has been built: read it after the derivation note, whose one rule is the same one it is built around. The member archive note is a phased plan that has not been built, and reads as the membership note's unanswered half: what a member who joins late can be given, and the one thing they cannot. The jvm-target note is unrelated to all of them: it is a build and packaging story.