CP02 ยท Implemented

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.

Upgrade discipline

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.