refactor: call it a chronicle, and keep "archive" for what a user does to a chat
Archiving a chat is an ordinary thing a user will want to do to a conversation, and it is not this. This is the group's signed record, handed to a member who joined after the work was done so their room stops being empty. Two unrelated meanings of one word in one app is a bug waiting to be written, and `ChatRoom.archiveRequestedAt` is exactly where they would have met: a column on the chat row, named for the thing that is not the chat. So the whole feature is Chronicle now -- `press.mantra.compose.nostr.chronicle`, `ChronicleEvent` (30327), `ChronicleRequestEvent` (30328), the three tags, `ChronicleManager`, `docs/member-chronicle.md`. The kind numbers do not move; only the words do. **The wire tags move too**, `archiveId` -> `chronicleId` and `archivePage` -> `chroniclePage`, which is free exactly once. Both kinds are new and there is no old build to stay compatible with -- the design note says so in as many words -- so the alternative was carrying the old spelling on the wire forever to save a rename that costs nothing today. The recipient tag stays `p`; it was never ours. **Schema v14, because two things had the old word written into stored data.** `ChatRoom.archiveRequestedAt` becomes `chronicleRequestedAt`, renamed rather than dropped and re-added: while it is set it is the only record that a device with an empty room has already asked the group for its history, and a device that lost it mid-flight would ask again on its next launch, and the one after that. The three `ChatMessage.messageType` strings become their `chronicle*` spellings, rewritten rather than left to a legacy constant the way `dkgApprovalNeeded` was. These lines cannot be regenerated -- a chronicle is announced once, when it is requested, sent and applied -- and an unrecognised type is not skipped by the transcript. It renders as an ordinary chat bubble, so "Caught up on 12 items" would come back attributed to a member as something they said. `MIGRATION_13_14` does both, because Room can rename a column and cannot rewrite rows in the same breath. `ALTER TABLE ... RENAME COLUMN` needs SQLite 3.25, which `getRoomDatabase` guarantees by pinning `BundledSQLiteDriver`, and the column is in no index, no foreign key, and there is not a view or trigger in the database -- so nothing has to move with it. Five tests hold the two halves apart: the value survives, the column keeps its position, a room that never asked still reads as never having asked, the three types are rewritten, and every other type is left alone. **`isArchivable` is `isChroniclable`**, on the "recyclable" pattern, and it keeps its job unchanged: the allowlist that stands between a replayed `GroupKeyStateEvent` and the apply path. No behaviour change beyond the rename. 797 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a69d80d38f
commit
ea11e8b233
33 files changed
+6506
-606
No files matched your search
@@ -177,7 +177,7 @@ val GENESIS_AT = Instant.fromEpochMilliseconds(1231006505000L)
|
||||
UnsignedNostrEvent::class,
|
||||
Zap::class
|
||||
],
|
||||
version = 13,
|
||||
version = 14,
|
||||
autoMigrations = [
|
||||
// v2 only adds the DkgSession/DkgParticipantMessage tables, so Room can
|
||||
// generate the migration itself — nothing existing changes shape.
|
||||
@@ -232,12 +232,13 @@ val GENESIS_AT = Instant.fromEpochMilliseconds(1231006505000L)
|
||||
// a room that could only propose one thing at a time cannot have written
|
||||
// two sessions' lines into the same stretch of transcript.
|
||||
AutoMigration(from = 10, to = 11),
|
||||
// v12 adds the nullable ChatRoom.archiveRequestedAt, which stops a device
|
||||
// with no signed work in a room asking the group for its history on every
|
||||
// launch while an answer is in flight. Rooms written before it read back
|
||||
// null, meaning "never asked" -- true of all of them, and harmless: the
|
||||
// request only goes out for a room that holds no signed work, and a room
|
||||
// that holds some will not ask.
|
||||
// v12 adds the nullable ChatRoom.archiveRequestedAt -- renamed to
|
||||
// chronicleRequestedAt in v14 -- which stops a device with no signed work
|
||||
// in a room asking the group for its history on every launch while an
|
||||
// answer is in flight. Rooms written before it read back null, meaning
|
||||
// "never asked" -- true of all of them, and harmless: the request only
|
||||
// goes out for a room that holds no signed work, and a room that holds
|
||||
// some will not ask.
|
||||
AutoMigration(from = 11, to = 12),
|
||||
// v13 adds the GroupSignedEvent table and the nullable
|
||||
// `groupSignedEventId` on every row a signed event turns into. A new
|
||||
@@ -246,7 +247,14 @@ val GENESIS_AT = Instant.fromEpochMilliseconds(1231006505000L)
|
||||
// hold the event behind this row" -- true of all of them, since nothing
|
||||
// kept it. They are not backfilled: the events are gone, and inventing
|
||||
// an id for one would point a row at a signature nobody can produce.
|
||||
AutoMigration(from = 12, to = 13)
|
||||
AutoMigration(from = 12, to = 13),
|
||||
// v14 changes one column name and three stored strings, all of which say
|
||||
// "archive" about the group's signed record. The word was wanted back for
|
||||
// the ordinary thing a user does to a chat, and a column called
|
||||
// `archiveRequestedAt` sitting on ChatRoom is exactly where the two
|
||||
// meanings would have met. Room can rename a column and cannot rewrite the
|
||||
// rows in the same breath, so this is a manual migration passed to the
|
||||
// builder rather than an entry here. See MIGRATION_13_14.
|
||||
]
|
||||
)
|
||||
@ColumnTypeConverters(MantraConverters::class)
|
||||
|
||||
Reference in new issue
Block a user