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>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 24,
|
||||
"identityHash": "1e328aab136444e802ffa7443e06ebe6",
|
||||
"identityHash": "c99c92c14e98a35c2d2a13118bc1f727",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "BroadcastNostrEventReceipt",
|
||||
@@ -282,7 +282,7 @@
|
||||
},
|
||||
{
|
||||
"tableName": "ChatMessage",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `senderPublicKey` TEXT NOT NULL, `isUserMessage` INTEGER NOT NULL, `giftWrapPayloadId` TEXT, `marmotGroupEventId` TEXT, `marmotInnerEventId` TEXT, `chatRoomId` TEXT NOT NULL, `replyToMessageId` INTEGER, `quotedMessageId` INTEGER, `content` TEXT NOT NULL, `messageType` TEXT NOT NULL, `directMessageRecipientPublicKey` TEXT, `frostSigningSessionId` TEXT, `dkgSessionId` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, FOREIGN KEY(`chatRoomId`) REFERENCES `ChatRoom`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`giftWrapPayloadId`) REFERENCES `GiftWrapPayload`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`marmotGroupEventId`) REFERENCES `MarmotGroupEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`marmotInnerEventId`) REFERENCES `MarmotInnerEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `senderPublicKey` TEXT NOT NULL, `senderIdentity` TEXT, `giftWrapPayloadId` TEXT, `marmotGroupEventId` TEXT, `marmotInnerEventId` TEXT, `chatRoomId` TEXT NOT NULL, `replyToMessageId` INTEGER, `quotedMessageId` INTEGER, `content` TEXT NOT NULL, `messageType` TEXT NOT NULL, `directMessageRecipientPublicKey` TEXT, `frostSigningSessionId` TEXT, `dkgSessionId` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, FOREIGN KEY(`chatRoomId`) REFERENCES `ChatRoom`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`giftWrapPayloadId`) REFERENCES `GiftWrapPayload`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`marmotGroupEventId`) REFERENCES `MarmotGroupEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`marmotInnerEventId`) REFERENCES `MarmotInnerEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
@@ -297,10 +297,9 @@
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isUserMessage",
|
||||
"columnName": "isUserMessage",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
"fieldPath": "senderIdentity",
|
||||
"columnName": "senderIdentity",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "giftWrapPayloadId",
|
||||
@@ -6215,7 +6214,7 @@
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1e328aab136444e802ffa7443e06ebe6')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c99c92c14e98a35c2d2a13118bc1f727')"
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user