Commit Graph
2 Commits
Author SHA1 Message Date
Kgothatso Ngako c0db6908a1 Merge branch 'mantra' into claude/focused-brattain-433b3b
Takes the dialect catalogue, the subgroups-for-entities line and the first four ios
phases -- thirty-two commits.

**Renumbered again, and this time the collision was not only a number.** `mantra`
reached 23 with two nullable `DkgSession` columns saying what a subgroup is *for*. That
is the table this branch's first migration rebuilds. So the three become 23->24,
24->25 and 25->26, and the rebuild's column list grew by `originKind` and `originId` --
a rebuild is the one kind of migration that cannot ignore a column it knows nothing
about, because leaving one out drops it.

**And the rebuild had a bug that only this merge could expose.** It copied with
`SELECT *`, which was fine while every file reaching it had the entity's declared
column order. An `AutoMigration` that adds a column does it with `ALTER TABLE ... ADD
COLUMN`, appending it physically at the end -- so a file that came through mantra's
22->23 hands `SELECT *` the columns in a different order than the backup table declares
them, and the copy lands shifted. `DkgSessionMigrationThroughRoomJvmTest` and
`ChatRoomSplitThroughRoomJvmTest` both failed with
`NOT NULL constraint failed: DkgSessionBackup.updatedAt`, which is `originKind` arriving
where a timestamp was expected. Both copies name their columns now.

Schemas 24 and 25 were regenerated, since each describes one of this branch's
migrations applied to a base that has moved. 26 is the current shape. The chain checks
out: 23 adds the origin columns, 24 keys the ritual by profile and carries them, 25
splits the membership off the room, 26 puts the sender's identity on a transcript line.

Four new reads of a room's MLS state came with the dialect and project detail screens,
and ask through the viewing profile like the rest.

1298 tests, 0 failures.
2026-10-03 08:15:27 +02:00
Kgothatso NgakoandClaude Opus 5 024e44c2c5 feat(groups): schema 25 -- two profiles on one device can hold one group
The refusal `679d5249` added is gone, and with it the last reason it existed.

**The per-viewer field had to go first.** `ChatMessage.isUserMessage` was a boolean
about one reader stored on a row that belongs to the group: a transcript line is
deduped by the group event behind it, and `reconcileMarmotLine` lets the fresher row
win, so whichever profile indexed the event last decided whose message it looked
like. A member's own messages would draw as somebody else's and flip back when the
other profile caught up -- on every line in the room.

It is `senderIdentity` now, and `ChatMessage.isFrom(userPublicKey)` asks the
question. The identity is the fact about the message; whether it is yours is a
question only a reader can ask. Not derivable from `senderPublicKey`, which for a
Marmot line is the kind:445's ephemeral signer -- the same trap the comment on
`fromGroupEventResult` already records someone falling into. `answeredRequests` takes
the asking profile for the same reason: the other profile's approval is not this
one's to be credited with.

The backfill is exact, and exact for the reason the bug could not happen yet: the
boolean was only safe to store because a room belonged to one profile, so a line
marked as the user's was sent by that room's one member, and `ChatRoomMembership`
says who. `isUserMessage` is named by no index or constraint, so `DROP COLUMN` takes
it and `ChatMessage`'s three cascading children are untouched.

**Removing the refusal was not enough.** The Welcome branch keyed on the *room*, so a
Welcome that found one fell through and did nothing -- the accidental silent refusal
that `679d5249` was written to make legible in the first place. It asks about this
profile's membership now, and the body it guards already does the right thing for a
joiner: the room's upsert is idempotent, the bundle consumed is their own, the roster
is the group's, and `adopt` files a statement the group already signed.

`TYPE_WELCOME_REFUSED_OTHER_PROFILE` stays, written by nothing and still rendered.
Devices that ran the older build hold lines of that type, and a transcript that
cannot draw a line it is holding is worse than one drawing an obsolete notice.

`WelcomeForOtherProfileJvmTest` becomes `SecondProfileJoinsGroupJvmTest` and asserts
the opposite of what it used to: two memberships of one room, two distinct ratchet
states, the holder's untouched, no refusal line, and the key package spent like any
other join's.

1216 tests, 0 failures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-02 23:49:17 +02:00