Batch signing landed on mantra while this branch was open, and it makes the argument this change was built on obsolete as written. MantraChunk.chunksOf said the chunks "cannot be events proposed on their own -- that would cost a quorum per paragraph". They can now: proposeSigningBatch would carry the chapter and a chunk per paragraph through one quorum, and every row would hold a signature of its own. Weighed and declined, and the KDoc now says so rather than leaning on a reason that stopped being true. MAX_BATCH_SIZE is 64, which caps a batched chapter at 63 paragraphs and fails an ordinary one outright; the text would go on the wire twice, whole on the chapter and again split across the chunks, and the proposal is the term that cap is sized against; and all-or-nothing over k items would make a long chapter less likely to be signed than a short one, for no reason a member could see. The signature it would buy is redundant besides -- the appendix rejects the manifest shape because an item then needs a lookup to be checked, and here that lookup is a foreign key: a chunk is a pure function of its chapter and cannot be stored without it. docs/frost-batch-signing.md records this under the slot Phase 7 leaves open -- "deciding *what* to batch" -- because the next caller will reach for the same shape. The rule it leaves behind: batch siblings, not derivations. Events that could each have been authored separately are worth a batch; events that are a function of another event in the same batch are worth deriving instead. **The merge.** Only SignedChapterTest broke: the five per-item columns moved off FrostSigningSession onto FrostSigningItem, so it builds an item and calls signedEvent(item, sig), which is how SignedArtifactTest was ported in the same commit. Nothing in the flow itself moved -- proposeSigning kept its signature as the one-event form, and complete() applies each signed event through ChatMessage.applyInnerEvent, so the chapter's chunk derivation works the same whether the chapter arrives alone or as one item of somebody else's batch. 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 |
| 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 jvm-target note is unrelated to all of them: it is a build and packaging story.