Files
mantra-kmp/docs
Kgothatso Ngako 4de87edf12 fix: tell one proposal's transcript lines from another's
A room with two proposals open showed "Review" on both, and then dropped it from
both the moment either one was decided. The second proposal was still waiting on
the reader, still had a decision in it, and had nowhere left to be reached from.

Two proposals at once is not a corner case any more: a chapter and the
translation scaffolding beside it are proposed as separate sessions, on purpose,
and they run at the same time. Both write the same line types into the same
stretch of transcript.

`answeredRequests` matched a request against any later line of the fulfilling
type, and `settledRequests` against any later ending. That reads a room signing
one thing at a time exactly right -- the nonce after the request is the answer to
it, because there is nothing else it could be an answer to -- and a room signing
two things at once exactly wrong. Nothing else on the row could separate them:
same type, same room, same minute, and `ChatMessage` carried no session.

So the session goes on the row. `ChatMessage.frostSigningSessionId` is nullable,
added as schema v11 through `AutoMigration(10, 11)`, and stamped by
`FrostSigningManager.announce` -- the one place every FROST line is written, so
there is no line that can be forgotten. Both rules read it when both rows have
one and fall back to the clock when either does not.

The fallback is not a compromise, it is the right reading of the rows it applies
to. A line written before this column has no session and never will, and the
rooms that wrote those lines could not run two sessions at once, so the clock is
the whole truth there. A ceremony line falls back too and always will: a room
runs one ritual at a time, and a DKG step is either taken or still waited on.

**This reverses a call `FrostSigningRoute` argued for.** Its note said a chat row
carrying a session id was "a poor trade for a lookup the screen can do". That was
right when the lookup could only be wrong about which of one session it meant.
The batch work made two sessions ordinary, and the lookup and the rules both
became guesses at the same moment. A column on the table every message uses is
the cost; two proposals, one of them unreachable, was the alternative.

**Tests.** Three in TranscriptRequestStateTest for what the column buys: a nonce
answers its own session's request and not the other's, one session completing
settles nothing in the other, and a line naming no session is still read by the
clock. TranslationBatchProposalJvmTest proves the other half against a real
two-session proposal -- every FROST line the manager writes names its own
session, and neither session's lines are attributed to the other. The rule is
tested on rows and the stamping is tested on a database, because a rule that is
right about rows nothing writes correctly is worth nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 12:41:01 +02:00
..

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.