existing storage migrations #
core implementations live under src/albedo/daemon/migrations/;
extension implementations live under their own migrations/ directories and
contribute MigrationPlugin values. the host applies them, without importing
extension-specific migration modules.
startup order #
runtime.start installs the extension registry before server.prepare_storage.
for each installed extension, extension.install first calls its table
initialiser, then applies its contributed SchemaMigration callbacks on the
store-owned SQLite connection, in plugin order. work contributes cwd.apply;
paperclips contributes title.apply, scope.apply, and reply.apply. their ledgers create tables but do not run
upgrades themselves. all installed owners upgrade, even when disabled for
sessions; session selection and live reload never rerun migrations.
server.prepare_storage then runs before sessions start:
conversation.initialisecreates the core tables, then callsconversation_columns.apply: the existing session columns first, transcript columns second. it then runs its existing domain-local session recovery (missing activity/title from transcript), then createssessions_activity.- quota, mail, and family initialise their tables, unchanged.
migrations.run(ledger, backup)runs the coreimage_store.run, which first externalizes legacy transcript images, then converts image-table TEXT data to decoded BLOB. It then runstranscript_classes.runto classify old transcript rows.runtime.migrate(host, backup)then collects installed extensions'DataMigrationcallbacks in registry/plugin order and applies them with the same backup path. python contributescell_images.run, which requires the core image and marker tables. callbacks report named row counts; the server logs nonzero extension results asmigration <name>: <n> rows. a failed callback stops startup and prevents later migrations from running.- the existing best-effort filesystem credential migration runs; then provider assignment from legacy configuration runs, unchanged.
schema creation is still owned by each subsystem. work/migrations/cwd.apply
checks PRAGMA table_info(work), adds the existing reserved __albedo_legacy__
cwd default when absent, then creates work_cwd_id;
paperclips/migrations/title.apply adds the existing non-null empty-default title
column, paperclips/migrations/scope.apply drops the now-unused workspace
index left from the cwd-scoped vent era, and
paperclips/migrations/reply.apply adds the non-null empty-default reply
column that makes a /paperclips answer durable. these upgrades run immediately
after their owner's table creation, so later extension initialisers can use
the upgraded schema. embedding hosts call
runtime.migrate after preparing core storage and before opening sessions.
compatibility and interruption #
conversation_columns.applyadds nullabletranscript.row_class, checked againstuser,image_fit, andother. Live writes set it in the same transaction as the payload; forks copy it with the retained prefix.transcript_classes.runclassifies only NULL rows in 128-row transactions. An invalid payload fails the current batch and startup; completed batches survive interruption. The backfill neither rewrites payloads nor adds a migration marker. It runs after the image migration.transcript_pending_classindexes unclassified sequence IDs.transcript_usersindexes(session,seq)for user and image-fit rows;transcript_fitsindexes(session,seq)for image-fit rows alone. Completed databases have no pending entries, so subsequent startups do not decode transcript payloads for classification.- the existing
migrations(name,applied_at)table remains unchanged. the exact markers areimage_storeandcell_images, recorded withunixepoch(). transcript migration skips when its marker exists; cell migration skips when its marker exists, and does nothing without a cells table. - TEXT-to-BLOB migration deliberately has no marker. it checks
typeof(data)='text'on every run and converts pending rows, even when theimage_storemarker already exists. invalid legacy base64 fails without replacing that row. - transcript and cell scans retain their original cursor order and 16-row pages; BLOB conversion retains its 16-row limit. each page uses the existing transaction helper. there is no new all-migrations transaction or transaction policy change. markers are written at the original completion points, and interrupted runs recheck remaining rows.
- core
backup.gleamand python's cell migration preserve the two existing backup call patterns: transcript and BLOB migration check for an existing file before directory creation andVACUUM INTO, retaining theimage store backup failed:error prefix; cell migration ensures the directory first and copies on the first page containing inline images, outside its transaction. the server still supplies one sharedhome/backups/albedo-before-image-store-<timestamp>.sqlitepath. no backup is overwritten and backup triggering is unchanged (the transcript candidate check can include tool outputs without inline images). - live writes and transcript migration share
image_payloads.insert, the unchanged decoded-BLOB insertion helper. image hashes, serialized formats, image validation, and legacy inline fallback reads are unchanged. the existing Erlang conversion and cell packing functions remain the FFI implementation.
migration inventory #
| previous implementation | current implementation |
|---|---|
conversation.initialise: session/transcript add_columns |
migrations/conversation_columns.apply |
| transcript row classification | migrations/transcript_classes.run, after core image upgrades |
images.migrate / migrate_legacy / transcript pages |
migrations/image_store.run / migrate_legacy / transcript pages |
images.migrate_blobs / BLOB pages |
migrations/image_store.migrate_blobs / BLOB pages |
images.backup_before_migration |
migrations/backup.image_store |
python/cells.migrate_images / cell pages |
python/migrations/cell_images.run / cell pages |
python/cells.backup_before_migration |
python/migrations/cell_images.backup_before_migration |
work/ledger.initialise: cwd alteration and index |
work/migrations/cwd.apply, contributed by work |
paperclips/ledger.initialise: title addition |
paperclips/migrations/title.apply, contributed by paperclips |
paperclips/ledger.initialise: paperclips_cwd index creation |
dropped from the schema; paperclips/migrations/scope.apply removes it from existing stores |
paperclips/ledger.initialise: reply column |
paperclips/migrations/reply.apply, added with the global ledger |
images.migrate and cells.migrate_images are removed rather than wrapped:
the migration implementations are separate from the live domain readers and
writers. startup discovers extension upgrades through plugins, not direct calls
to their implementation modules.
intentionally not extracted #
- session recovery stays in
conversation: it uses the domain's transcript decoding/title helpers and still runs at exactly the same point. core schema bootstrap and index creation remain with their owners. - rolling compaction's legacy item-count-to-user-cut conversion remains lazy in
rolling/extension.resume. it needs the current prepared history and source, validates the old fingerprint (including legacy image payload fingerprints), and only writes a cut when the history matches and its tail begins with a user message. treating this as an unconditional startup migration would change its timing and semantics; its projections are untouched. - credentials migration is filesystem/auth, not SQLite. legacy configuration's provider assignment likewise remains in the existing server startup path.
- no zstd, compression, schema-version, offline storage upgrade, new dependency, or unrelated transaction fix is part of this refactor.