Database migrations
Numbered application migrations run before the service becomes ready and record their name and UTC application time.
Atomic upgrades
Each current migration executes in a transaction. A failed statement rolls the migration back, leaves the schema version unchanged, closes the database, and prevents the server from listening.
Version behavior
A fresh database applies every migration in order. Restarting an up-to-date database is a no-op. The applied version is derived from validated migration history; on SQLite, PRAGMA user_version is only a compatibility mirror that a valid history may restore. A database with a schema version newer than the executable, or with history that is missing, non-contiguous, misnamed or in disagreement with the marker, is rejected rather than guessed at. The startup error explains the recovery step: restore the database from a backup before starting Trestle again.
Registration policy migration
Migration 15 adds a constrained singleton application-registration policy row plus email-bound invitation and access-request tables. The initial policy is recorded once from the durable pre-migration schema version: a brand-new database starts closed and is finalized by the first-run policy selection, while an existing pre-v15 deployment starts open to preserve its historical behavior - regardless of user count, and never re-derived from current rows. Restoring a pre-v15 portable backup restores open; a v15+ backup restores the saved policy. Restore and cross-provider migration revoke genuinely outstanding activation invitations (used, already-revoked and expired states are preserved).
Current schema
Trestle-owned tables store migration history, system metadata, identities, events, jobs, audit facts and integration state. Collection validation prevents user schemas from colliding with the reserved namespace.
CP22 provides checksum-verified binary update and rollback, while CP23 exercises every retained schema boundary and restore path. Back up first: executable rollback never promises an automatic database downgrade.
Offline cross-provider migration
An explicit offline trestle migrate command moves data between SQLite and the PostgreSQL provider using the portable logical format. It opens the source read-only, requires an empty destination, provider and schema preflight, a zero-write dry run, the explicit --confirm-migration flag, stable IDs, counts and a content checksum, and destination verification before cutover. Operator pg_dump and managed-service backup remain separate responsibilities.