0011 — Capabilities own migration progress
Status: accepted for the clean rebuild; supersedes the global ledger in
ADR 0009. The record is
docs/adr/0011-migration-ownership.md;
this page is its map and its summary and states nothing the record does not.
The map
decisions/0011-migration-ownership in the recordContext
A composed client had reached migration 2003. Adding public migration 23, or
enabling catalog migration 1001, returned success while silently skipping the
new SQL: one scalar version cannot describe independently evolving
capabilities. The old runner, its flattened filesystem, its (version, dirty)
ledger and its down files are removed rather than converted; per-owner copies
of the previous engine would have fixed the numbering but kept separate
dirty-state bookkeeping and needed integrity tracking added beside it.
Decision
db.Migrate accepts ordered MigrationSource values, a stable owner and its
filesystem; kit/app supplies the foundation first, then module manifests in
composition order, and the module name already identifies ownership, so no
registry is added. One schema_migrations table records (owner, version),
filename, SHA-256 and application time for each committed file, and there are
no global version ranges. A database advisory lock serialises the whole
composition, each file executes inside a transaction with its history insert,
and failure or cancellation rolls both back so a retry reads committed history
and resumes.
Consequences
-
All selected histories are validated before pending SQL runs: a changed or missing applied file, a duplicate identity or an insertion before an applied version fails, and earlier successful files remain committed after a failure.
-
An omitted owner retains its data and history for later re-enablement, and the application role receives no privileges on the history table.
-
Files are
<positive-version>_<name>.up.sql, ordered numerically within an owner, and contain transactional PostgreSQL SQL: they must not manage transactions or run nontransactional operations such asCREATE INDEX CONCURRENTLY, and cross-capability schema dependencies follow the application’s composition order. -
This is a fresh baseline: nothing converts the old ledger or preserves old installations, so adopting the rebuild means provisioning a clean database, and once the baseline is used, applied files stay unchanged and corrections are appended as new revisions.
-
A later breaking schema change means stopping old processes before migration; a rolling release needs an explicitly tested schema both running versions can use; an older artifact missing applied migrations fails startup; downgrading an image is not a schema rollback.
Where it lives
-
kit/db/migrate.go—db.Migrateanddb.MigrationSource: the advisory lock, theschema_migrationstable with its grants revoked, the history validation, and one transaction per file with its history row. -
kit/db/migration_files.go— reads every<version>_<name>.up.sqlbefore connecting: a valid, unrepeated owner, a positive unrepeated version, and the SHA-256 of the bytes that run. -
kit/app/migrations.go—app.MigrationSources(mods): the foundation first, then each module’sMigrationsunder itsName, in composition order. -
kit/module/module.go— theMigrations fs.FSfield of a manifest, whose versions are local to the module. -
migrations/embed.go—migrations.Source, the foundation’s append-only history as ownerplatformkit, one source in this change. -
kit/app/app.go—App.Runmigrates in every role before anything listens, as ADR 0005 decides.
Evidence
go test ./kit/db -run 'TestMigrationOwnersAdvanceIndependently|TestFailedMigrationRollsBackAndCanBeRetried'
go test ./kit/db -run 'TestMigrationHistoryIsAppendOnly|TestConcurrentMigrationsApplyEachFileOnce'
go test ./kit/db -run 'TestMigrationCancellationRollsBackAndReleasesTheLock|TestMigrateIsIdempotent'
go test ./kit/app -run 'TestMigrationSourcesFollowComposition|TestBootMigratesAndServes'
The record names kit/db/migrate_test.go
rather than commands: it covers late module installation, upstream advancement,
retained data, disable/re-enable, immutable history, numeric order, concurrent
startup, failure/retry, cancellation and ledger permissions, and
kit/app exercises the same source
collection through a real application boot. What it deliberately does not
claim: the runner is not a SQL parser or a sandbox for untrusted SQL, so the
file contract above is checked in review.