[E00-S03-T03] Migration ledger created #392
No Reviewers
Labels
Clear labels
agent/analyst-drafted
agent/analyst-drafted
needs/human-decision
needs/human-decision
needs/security-review
needs/security-review
tier/t0
tier/t1
tier/t2
tier/t3
kind
bug
kind
bug
kind
epic
kind
epic
kind
initiative
EPPP programme initiative
kind
story
kind
story
kind
task
EPPP engineering card/task decomposed from a story
kind
toil
kind
toil
loop
1
loop
1
loop
2
loop
2
loop
3
loop
3
risk
agent-full
risk
agent-full
risk
human-gated
risk
human-gated
risk
human-only
risk
human-only
size
l
size
l
size
m
size
m
size
s
size
s
status
blocked
status
blocked
status
done
Workflow: Done
status
in-progress
status
in-progress
status
proposed
status
proposed
status
ready
status
ready
status
review
status
review
stream
checkout
stream
checkout
stream
onboarding
stream
onboarding
stream
platform
stream
platform
trivial — implementer only, auto-merge
standard — implementer + reviewer + tester
complex — security if triggered, human merge
critical — full chain + security, human merge
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: Fabrika/PersonalBlog#392
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What changed
Implements [E00-S03-T03] Migration ledger created (#178): the migration ledger that records applied migrations now exists in the
database-postgrespackage and is exercised against a real database.packages/database-postgres/src/ledger.ts— newMigrationLedgerover the package-ownedpgPool:ensure()creates the ledger table (schema_migrations,version text PRIMARY KEY+applied_at timestamptz NOT NULL DEFAULT now()) with idempotent DDL (CREATE TABLE IF NOT EXISTS);record()inserts an applied migration with an idempotent, parameterized statement (INSERT … VALUES ($1) ON CONFLICT (version) DO NOTHING— a rerun never double-applies, and the version is always a bound parameter, never interpolated);has()/applied()read the ledger back in apply order (ORDER BY applied_at, version). Rollback:DROP TABLE schema_migrationsresets migration state (the issue's rollback note).packages/database-postgres/src/index.ts— driver boundary re-export:MigrationLedger+MIGRATION_LEDGER_TABLEare re-exported from the package entrypoint, so no other workspace package needs thepgdriver to touch migration state (isolation E00-S03-T02 stays intact — the new module imports onlyimport type { Pool }and lives inside the owner package).tests/database-postgres-ledger.test.mjs— new suite locking in both acceptance criteria: static assertions on the committed source (idempotent table DDL withversionPK +applied_at, idempotent parameterizedrecord(),has()/applied()queries, driver-boundary re-export, CI enforcement) each backed by a mutation probe proving non-vacuousness (removingPRIMARY KEY,IF NOT EXISTS,ON CONFLICT, or$1binding, or dropping the boundary re-export fails loudly). The real-stack probe implements the issue's test plan — "migrate an empty database and confirm the ledger exists": it starts the committed composedbservice (isolated projecteppp-ledger-probe+ host port 55432 so it never collides with the compose-config suite), drops any leftover ledger table for a clean slate, executes the committed ledger module against the empty database, and asserts the ledger exists (to_regclass), both fixtures are recorded in apply order,has()answers correctly, and a second run records nothing twice (the story's "second migration run is idempotent"). Cross-checks from inside the db container via psql (information_schematable count, recorded versions). Skipped cleanly where Docker or Node ≥ 23.6 (type stripping) is unavailable..gitea/workflows/ci.yml— newdatabase-postgres-ledgerjob runsnode --test tests/database-postgres-ledger.test.mjson every PR so the ledger criterion gates merges. (Pipeline tripwire: additive job only, no existing job modified, same action majors, nosecrets:context, no untrusted interpolation — matches the security-reviewed #390/#391 precedent.)docs/development/non-container.mdpackage table andpackages/database-postgres/package.jsondescription updated;.gitignoreignores the transient probe file the real-stack probe writes into the package (removed in itsfinally).Explicitly out of scope per the brief, not touched: pg/Kysely isolation (E00-S03-T02), advisory lock (E00-S03-T04), failure diagnostic (E00-S03-T05).
Criterion → test table
tests/database-postgres-ledger.test.mjs— "the ledger module exists in the driver-owner package and defines the schema_migrations table" and "the ledger is created by idempotent DDL (migration ledger is created)" (source definesCREATE TABLE IF NOT EXISTS schema_migrationswithversion text PRIMARY KEY+applied_at timestamptz NOT NULL DEFAULT now()); mutation probes "removing the version primary key …" and "removing the idempotent create …" prove non-vacuousness; real-stack probe "migrating an empty database creates the ledger and records applied migrations (real stack)" runs the committed module against an empty database and confirmsschema_migrationsexists (to_regclass+information_schemacount 1 from inside the container)tests/database-postgres-ledger.test.mjs— "record() records applied migrations with an idempotent, parameterized insert" (ON CONFLICT (version) DO NOTHING,$1binding, no${version}interpolation) and "has() and applied() read the ledger back through the pool"; mutation probes "removing ON CONFLICT …", "interpolating the version into the SQL …", "dropping the ledger re-export …", "a placeholder ledger module …"; real-stack probe records two fixtures, asserts them in apply order,has()true/false, androwCountstays 2 after a re-run (idempotent), plus psql cross-check of the recorded versions inside the db containertests/database-postgres-ledger.test.mjs— "the ledger criterion is enforced in CI" (root test glob covers the suite and.gitea/workflows/ci.ymlruns it);.gitea/workflows/ci.yml—database-postgres-ledgerjob runsnode --test tests/database-postgres-ledger.test.mjson every PRTest plan executed
node --test tests/database-postgres-ledger.test.mjs→ 12 pass / 0 fail / 1 skip (the docker-gated real-stack probe skips where no Docker daemon is available).node --test "tests/**/*.test.mjs", Node 24.20.0 + pnpm 11.23.0, frozen install) → 134 pass / 0 fail / 10 skip.pnpm install --frozen-lockfile→ passes ("Already up to date");pnpm build/pnpm typecheckover the 5-project workspace → exit 0.to_regclass→schema_migrations), both fixtures recorded in order,has()correct,rowCount2 after re-run,information_schemacount 1, versions readable via psql;DROP TABLErollback verified.Risks / notes
pgthrough the package's own dependency links).-p eppp-ledger-probe) and host port 55432 so it never collides with the compose-config suite's default-project containers or the default 5432 binding when both run on the same host.Refs #178