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.
This commit is contained in:
Kgothatso Ngako committed 2026-10-03 08:15:27 +02:00
commit c0db6908a1
135 files changed
+14109 -299

No files matched your search

@@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 24,
"identityHash": "c99c92c14e98a35c2d2a13118bc1f727",
"identityHash": "16d70e9be637a10e099e9ddc20bdefe0",
"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, `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 )",
"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 )",
"fields": [
{
"fieldPath": "id",
@@ -297,9 +297,10 @@
"notNull": true
},
{
"fieldPath": "senderIdentity",
"columnName": "senderIdentity",
"affinity": "TEXT"
"fieldPath": "isUserMessage",
"columnName": "isUserMessage",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "giftWrapPayloadId",
@@ -670,7 +671,7 @@
},
{
"tableName": "ChatRoom",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subject` TEXT, `description` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `userPublicKey` TEXT NOT NULL, `subject` TEXT, `description` TEXT, `mlsGroupState` TEXT, `initialGiftWrapPayloadId` TEXT, `leftGroupAt` INTEGER, `joinedGroupAt` INTEGER, `chronicleRequestedAt` INTEGER, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`userPublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`initialGiftWrapPayloadId`) REFERENCES `GiftWrapPayload`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
@@ -678,6 +679,12 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "userPublicKey",
"columnName": "userPublicKey",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "subject",
"columnName": "subject",
@@ -688,48 +695,6 @@
"columnName": "description",
"affinity": "TEXT"
},
{
"fieldPath": "createdAt",
"columnName": "createdAt",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updatedAt",
"columnName": "updatedAt",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "savedAt",
"columnName": "savedAt",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "ChatRoomMembership",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`chatRoomId` TEXT NOT NULL, `userPublicKey` TEXT NOT NULL, `mlsGroupState` TEXT, `initialGiftWrapPayloadId` TEXT, `leftGroupAt` INTEGER, `joinedGroupAt` INTEGER, `chronicleRequestedAt` INTEGER, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, PRIMARY KEY(`chatRoomId`, `userPublicKey`), FOREIGN KEY(`chatRoomId`) REFERENCES `ChatRoom`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`userPublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`initialGiftWrapPayloadId`) REFERENCES `GiftWrapPayload`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "chatRoomId",
"columnName": "chatRoomId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "userPublicKey",
"columnName": "userPublicKey",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "mlsGroupState",
"columnName": "mlsGroupState",
@@ -787,42 +752,10 @@
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"chatRoomId",
"userPublicKey"
"id"
]
},
"indices": [
{
"name": "index_ChatRoomMembership_userPublicKey",
"unique": false,
"columnNames": [
"userPublicKey"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_ChatRoomMembership_userPublicKey` ON `${TABLE_NAME}` (`userPublicKey`)"
},
{
"name": "index_ChatRoomMembership_initialGiftWrapPayloadId",
"unique": false,
"columnNames": [
"initialGiftWrapPayloadId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_ChatRoomMembership_initialGiftWrapPayloadId` ON `${TABLE_NAME}` (`initialGiftWrapPayloadId`)"
}
],
"foreignKeys": [
{
"table": "ChatRoom",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"chatRoomId"
],
"referencedColumns": [
"id"
]
},
{
"table": "Profile",
"onDelete": "CASCADE",
@@ -927,7 +860,7 @@
},
{
"tableName": "DkgSession",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `chatRoomId` TEXT NOT NULL, `coordinatorPublicKey` TEXT NOT NULL, `userPublicKey` TEXT NOT NULL, `threshold` INTEGER NOT NULL, `participantCount` INTEGER NOT NULL, `participantPublicKeys` TEXT, `subject` TEXT, `stage` TEXT NOT NULL, `hostPublicKey` TEXT NOT NULL, `round1Random` TEXT NOT NULL, `round2AuxRandom` TEXT NOT NULL, `coordinatorRound1` TEXT, `certificate` TEXT, `thresholdPublicKey` TEXT, `secretShare` TEXT, `publicShares` TEXT, `recoveryData` TEXT, `failureReason` TEXT, `hostKeyApprovedAt` INTEGER, `round1ApprovedAt` INTEGER, `round2ApprovedAt` INTEGER, `approvalRequestedThrough` TEXT, `parentChatRoomId` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, PRIMARY KEY(`id`, `userPublicKey`), FOREIGN KEY(`chatRoomId`) REFERENCES `ChatRoom`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `chatRoomId` TEXT NOT NULL, `coordinatorPublicKey` TEXT NOT NULL, `userPublicKey` TEXT NOT NULL, `threshold` INTEGER NOT NULL, `participantCount` INTEGER NOT NULL, `participantPublicKeys` TEXT, `subject` TEXT, `stage` TEXT NOT NULL, `hostPublicKey` TEXT NOT NULL, `round1Random` TEXT NOT NULL, `round2AuxRandom` TEXT NOT NULL, `coordinatorRound1` TEXT, `certificate` TEXT, `thresholdPublicKey` TEXT, `secretShare` TEXT, `publicShares` TEXT, `recoveryData` TEXT, `failureReason` TEXT, `hostKeyApprovedAt` INTEGER, `round1ApprovedAt` INTEGER, `round2ApprovedAt` INTEGER, `approvalRequestedThrough` TEXT, `parentChatRoomId` TEXT, `originKind` INTEGER, `originId` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, PRIMARY KEY(`id`, `userPublicKey`), FOREIGN KEY(`chatRoomId`) REFERENCES `ChatRoom`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
@@ -1059,6 +992,16 @@
"columnName": "parentChatRoomId",
"affinity": "TEXT"
},
{
"fieldPath": "originKind",
"columnName": "originKind",
"affinity": "INTEGER"
},
{
"fieldPath": "originId",
"columnName": "originId",
"affinity": "TEXT"
},
{
"fieldPath": "createdAt",
"columnName": "createdAt",
@@ -6214,7 +6157,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, 'c99c92c14e98a35c2d2a13118bc1f727')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '16d70e9be637a10e099e9ddc20bdefe0')"
]
}
}