# Migration framework

Operators use one entry point: `scribe-server migrate --data-dir DIR`. It
preflights every catalog repository, holds the exclusive data-root lock for the
whole run, and applies the ordered chain to the current build. `--dry-run`
reports the plan without recovery or writes. See the
[operator procedure and boundaries](/docs/migration-backup-restore.md#scribe-server-migrate).

The coordinator is `crates/scribe-server-cli/src/migrate/runner.rs`. It first
converts `atomic-publication-v1` lock transfers
(`crates/scribe-metadata/src/repo/migrations/publication_v2.rs`), then runs
the schema-3 branch-table and snapshot verification step, then registered
metadata steps in
`crates/scribe-metadata/src/repo/migrations/`. Descriptors advance last. A root
can contain repositories at different progress points after interruption;
rerunning completes each one's remaining work. No startup path migrates data.

Every conversion lives in one of two folders: database work in
`crates/scribe-metadata/src/repo/migrations/` (one file per step, tests in a
sibling folder), and any descriptor handling it needs in
`crates/scribe-server-cli/src/migrate/`. Numbered steps register in `STEPS`;
layout upgrades that must finish before a repository can open at all, such as
`publication_v2.rs`, are called directly by the runner ahead of them.

The registered steps are revision 1 (`workspace-state-v1`), which builds the
workspace lifecycle indexes; revision 2 (`path-fold-v1`), which verifies the
Unicode path collision profile and shared snapshots; and revision 3
(`change-notes-v1`), which creates the change-description journal. Shared
snapshot profile 3 is a prerequisite;
pre-snapshot development generations require their matching build and are
refused before conversion.

Repository schema and migration revision are separate: schema describes the
base layout; the revision records completed additive upgrades. New repositories
create the final tables and current revision directly. The journal bytes are
specified in [the on-disk format](/docs/on-disk-format.md#repository-migration-journal).
This framework currently migrates storage repositories. Gateway roots retain
their own version checks, and this command does not invent conversions for
their data or for arbitrary incompatible development layouts. Auth data
directories have their own explicit command,
`scribe-auth migrate --data-dir DIR [--dry-run]`, implemented in
`crates/scribe-auth/src/store/migrate.rs` with the same rules: it holds the
directory's exclusive lock, refuses malformed records before writing, commits
converted records with immediate durability before publishing the new schema
marker by rename, and enumerates its crash windows for `SCRIBE_FAULT`. See
[upgrading the auth data directory](/docs/auth.md#upgrading-the-auth-data-directory).
The web stack's database has its own explicit migrate command and
`data_migration` ledger, run from the worker image; startup refuses data that
needs a migration. See [stored data versions](/docs/web-interface.md#stored-data-versions).

## Adding a step

1. Append a named step with the next consecutive revision to `STEPS` and advance
   `REPOSITORY_MIGRATION_VERSION`. Never reuse a completed revision or change a
   published cursor's meaning. Describe the exact supported source layout and
   canonical destination bytes. Refuse ambiguity and future versions.
2. Implement read-only `start`, `prepare`, and `verify` callbacks. `start`
   prepares initialization and the first cursor. `prepare` reads a bounded page
   and returns owned writes plus the next cursor; returning `None` asks the
   runner to verify and complete. Never hold read guards across a write commit.
3. Keep write closures bounded and limited to prepared database mutations: no
   hashing, external I/O, authentication, or unbounded scans. The runner commits
   data and cursor together with Immediate durability. Verification runs outside
   a write transaction and must pass before the revision marker advances.
   `finish` performs only bounded marker writes in that final transaction.
   Workspace ownership lookups use the read-only storage auth claims outside
   the write transaction; they never make a network request.
4. Preserve authoritative state and operation outcomes. Existing malformed data
   must fail verification; migration is not repair. Teach completed-version
   validation about required tables, and update fresh repository creation.
5. Test empty and populated stores, multiple pages, reopen/resume at each
   checkpoint, aborted writes, unchanged source history, repeated completion,
   corrupt/future markers, and byte-identical dry runs. Add a maintenance CLI
   regression and update the format specification and operator documentation.

All calls to `advance_migration` require the caller to retain exclusive root
ownership, including preparation and final verification. Future object-layout
changes must use durable generation activation with file and directory flushes;
database transactions alone do not make external object writes durable.

The metadata crate's `fault-injection` feature exposes migration crash windows
through `SCRIBE_FAULT=<point>=crash`. Points are enumerated by the transaction
implementation in `migrations/fault.rs`; the all-features tests terminate a
child process at each window and reopen and finish its real database. Production
builds omit environment injection. The server CLI forwards this feature only in
explicit fault-injection builds.
