Files
mantra-kmp/composeApp/schemas
Kgothatso Ngako 4cba5b9a69 feat(groups): schema 23 -- a membership is a row, so a group is the group's
`ChatRoom` was two things in one row keyed by the group id: the group's own facts --
its id, name and description -- and the asking profile's membership of it, which is
its MLS ratchet state, when it joined and left, whether it asked for a chronicle,
and whether it has been archived. A second profile joining the same group wrote over
the first's ratchet state, which breaks both members in the shape of "the other
device never got it". `679d5249` refuses that Welcome; this removes the reason it
had to.

**Split rather than a wider key.** Keying the room by (`id`, `userPublicKey`) was the
obvious reading and the worse schema. Of the nineteen tables referencing a room,
fourteen hold the *group's* signed work -- `GroupSignedEvent`, `GroupKeyState` and
the twelve `Mantra*` tables -- and two profiles in one group read the same chapters.
Those fourteen would have had to carry a `userPublicKey` that misrepresents them or
drop their foreign key and the cascade with it; `GroupSignedEvent` could not have
carried one at all, its key being the event's hash. Splitting leaves all nineteen
pointing at a room that is unambiguously the group's.

`ChatRoomDao` follows from that: a listing joins the membership, because "mine" and
"archived" are things one profile says about a group, while a lookup by id stays the
group's and filters nobody's `deletedAt` -- a manager asking for the room it is about
to act in wants the group, whether or not somebody tidied it away.

**The migration turns on one fact, and the fact is now measured.** Rebuilding
`ChatRoom` is unavoidable (a foreign key names two of the columns to remove, so
`DROP COLUMN` will not), and with foreign keys enforced no rebuild survives: the drop
fires all nineteen cascades, and `RENAME TO` rewrites the children's clauses to
follow it -- `PRAGMA legacy_alter_table` does **not** prevent that, which I checked
rather than assumed. A migration cannot disable foreign keys itself either, inside
Room's transaction. So this depends on Room disabling them, exactly the kind of
dependency `MIGRATION_9_10` warns against taking on trust -- and
`ChatRoomSplitThroughRoomJvmTest` asserts it against a real v22 file through
`getRoomDatabase`, with rows seeded in four of the nineteen. An earlier draft of this
migration lost every child row; that test is what caught it.

The replacement is also built *beside* the original and renamed into place after it
is gone, so nothing is ever renamed that the children point at.

The acting profile now comes from where it is actually known. `DatabaseChatRepository`
and `DatabaseDkgRepository` read it off the `activeIdentity` they already hold, the
inbound MLS path derives it from the `activeKeyPair` it is already given, and
`ChatMessageListViewModel` takes it from the screen that opened it. Leaving a group
and archiving one are a membership's acts now, so one profile doing either leaves the
other's list alone.

1008 tests, 0 failures. Schema export committed beside its predecessors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-02 21:46:35 +02:00
..