[E00-S03-T05] Migration failure produces structured diagnostic #394

Merged
kpcto merged 3 commits from feature/180 into main 2026-08-30 01:31:49 +00:00
Member

What changed

Implements [E00-S03-T05] Migration failure produces structured diagnostic (#180): a MigrationRunner in the database-postgres package that applies pending migrations through the migration ledger exactly once and, when a migration fails, throws a MigrationFailedError carrying a structured diagnostic that identifies the failing migration — verified against a real database with an intentionally failing migration fixture.

  • packages/database-postgres/src/runner.ts — MigrationRunner over the package-owned pg Pool + MigrationLedger (E00-S03-T03): run() ensures the ledger exists, reads the applied migrations, and applies every migration whose version is not yet recorded — a rerun skips everything already recorded (the story's "second migration run is idempotent", from parent #60). Migrations are { version, up(pool) } steps run in order (oldest first).
  • Structured failure diagnostic (the acceptance criterion): when a migration's up throws, the runner wraps the failure into MigrationFailedError instead of rethrowing the raw error. The error carries .diagnostic, a structured object that identifies the failing migration (migration — its version), where the run failed (phase: 'apply' when up threw, 'record' when the ledger insert threw after a successful up), the underlying cause (preserved for inspection), and the ledger state at failure time (applied/pending — disjoint, in run order; the failing migration is still pending). The error is serializable: toJSON() returns a plain structured object with a structured cause (for pg errors the code, e.g. 42P01), so operators can log/parse the diagnostic without string-matching.
  • packages/database-postgres/src/index.ts — driver boundary re-export: MigrationRunner + MigrationFailedError (and the Migration/MigrationDiagnostic/MigrationRunResult/MigrationFailurePhase types) are re-exported from the package entrypoint, so no other workspace package needs the pg driver to run migrations (isolation E00-S03-T02 stays intact — runner.ts imports only import type { Pool } and the ledger).
  • tests/database-postgres-diagnostic.test.mjs — suite locking in both acceptance criteria: static assertions on the committed source (structured diagnostic shape with migration/phase/cause/applied/pending; MigrationFailedError carries .diagnostic and is serializable with a message naming the failing migration; both failure paths build the diagnostic with the failing migration's version; ledger integration; driver-boundary re-export; CI enforcement), each backed by a mutation probe proving non-vacuousness. The deterministic stub-pool behavioral probe (no database/Docker; runs on Node ≥ 23.6, i.e. the CI Node 24) drives the committed runner with an intentionally failing migration fixture through three scenarios — success + idempotent rerun, apply-failure, and record-failure — and asserts the structured diagnostic identifies the failing migration in every case. The real-stack probe is the authoritative behavioral check: it starts the committed compose db service (isolated project eppp-diagnostic-probe + host port 55434), executes the committed runner against the real database with a fixture whose second migration runs valid SQL against a missing table, and asserts the run rejects with a MigrationFailedError whose diagnostic names the failing migration, reports phase 'apply', carries the pg error code 42P01, lists the applied/pending state, and is serializable — while the ok migration's table and ledger row persist and the failing migration is not recorded (the issue's test plan: "run an intentionally failing migration fixture and confirm the diagnostic").
  • .gitea/workflows/ci.yml — new database-postgres-diagnostic job runs node --test tests/database-postgres-diagnostic.test.mjs on every PR so the failure-diagnostic criterion gates merges. Additive only (no existing job modified, same action majors, no secrets: context, no untrusted interpolation).
  • Docs/descriptors: docs/development/non-container.md package table and packages/database-postgres/package.json description updated (the runner + failure diagnostic now land here, not "in later stories"); .gitignore ignores the transient probe files the behavioral/real-stack probes write into the package (removed in their finally blocks); the stale "later stories" references in the ledger.ts/lock.ts module headers updated.

Explicitly out of scope per the brief, not touched: advisory lock (E00-S03-T04) — the runner performs no locking itself (a runner that wants to serialize takes the lock around run()); ready-before-migrations gate (E00-S03-T06).

Criterion → test table

Acceptance criterion Test (fails without the committed state)
migration failure produces a structured diagnostic tests/database-postgres-diagnostic.test.mjs — "the runner module exists in the driver-owner package and defines the runner and its diagnostic types" (exports MigrationRunner, MigrationFailedError, Migration, MigrationDiagnostic, MigrationRunResult), "the failure diagnostic is structured and identifies the failing migration (migration/phase/cause/applied/pending)" (migration: string, phase: MigrationFailurePhase, cause: unknown, applied: string[], pending: string[]), "MigrationFailedError carries the structured diagnostic and is serializable, naming the failing migration" (readonly diagnostic, toJSON(), message names the failing migration), "the runner wraps a failing migration into the structured diagnostic instead of rethrowing the raw error" (both failure paths construct new MigrationFailedError with the failing version); mutation probes "replacing the wrapped failure with a bare rethrow …", "constructing a plain Error instead of MigrationFailedError …", "removing the diagnostic field from MigrationFailedError …", "removing toJSON() …", "a placeholder runner module …"; behavioral probe — apply-failure and record-failure fixtures both reject with MigrationFailedError whose diagnostic carries migration/phase/cause/applied/pending, and toJSON() round-trips; real-stack probe — the intentionally failing fixture rejects with a structured diagnostic
the diagnostic identifies the failing migration tests/database-postgres-diagnostic.test.mjs — "the failure diagnostic is structured and identifies the failing migration …" (the migration: string field) + mutation probe "removing the migration field makes the identifies-the-failing-migration criterion fail"; "MigrationFailedError … naming the failing migration" (message migration "…" failed during …); behavioral probe — diagnostic.migration === 'fail-2' for the apply-failure fixture and 'fail-record' for the record-failure fixture (phase 'record' still names the failing migration), and toJSON().diagnostic.migration matches; real-stack probe — diagnostic.migration === '2026-08-30_002_fixture_fail', phase === 'apply', cause code 42P01, applied ['2026-08-30_001_fixture_ok'], pending ['2026-08-30_002_fixture_fail'], toJSON() names the failing migration, and the failing migration is not recorded in the ledger (psql cross-check)
the runner applies pending migrations through the ledger exactly once (never double-applies) tests/database-postgres-diagnostic.test.mjs — "the runner applies pending migrations through the migration ledger exactly once" (ledger.ensure()/applied()/record(migration.version)); behavioral probe — first run applies ['ok-1','ok-2'], rerun returns { applied: [], skipped: ['ok-1','ok-2'] } with the ledger unchanged (the story's "second migration run is idempotent")
the criteria are verified behaviorally against a real database — the real-stack probe is the authoritative criterion check, and static source assertions are backed by mutation probes proving non-vacuousness tests/database-postgres-diagnostic.test.mjs — the docker-gated real-stack probe runs the committed runner against the committed compose db service and asserts both acceptance criteria behaviorally (see rows above); the deterministic stub-pool behavioral probe (no Docker) covers the same contracts on every CI Node; every static assertion has a mutation probe (8 probes: 7 source mutations + placeholder module) proving it fails on a violation
the failure-diagnostic suite gates merges via the database-postgres-diagnostic job in .gitea/workflows/ci.yml tests/database-postgres-diagnostic.test.mjs — "the failure-diagnostic criterion is enforced in CI" (root test glob covers the suite and .gitea/workflows/ci.yml runs it); .gitea/workflows/ci.yml — database-postgres-diagnostic job runs node --test tests/database-postgres-diagnostic.test.mjs on every PR (additive, matching the security-reviewed #390/#391/#392/#393 precedent)

Test plan executed

  • node --test tests/database-postgres-diagnostic.test.mjs → 18 tests, 16 pass / 0 fail / 2 skip on Node 22.23.2 (the docker-gated real-stack probe and the TS-stripping-gated behavioral probe skip on Node 22 per the suite's conservative >= 23.6 gate; both run in CI on Node 24 — the behavioral probe needs no Docker).
  • The behavioral probe was executed directly on this Node (via --experimental-strip-types, with the committed modules copied to a temp dir so the relative .js specifier resolves on Node 22): DIAGNOSTIC_PROBE_RESULT reports — success: first {applied:['ok-1','ok-2'],skipped:[]}, rerun {applied:[],skipped:['ok-1','ok-2']}; apply-failure: rejected:true, isMigrationFailedError:true, name:'MigrationFailedError', message:'migration "fail-2" failed during apply', migration:'fail-2', phase:'apply', applied:['ok-1'], pending:['fail-2','ok-3'], causeIsOriginal:true, causeCode:'42P01', toJson.diagnostic.cause.code:'42P01'; record-failure: migration:'fail-record', phase:'record', causeCode:'25006'. All behavioral-probe assertions pass on the committed module's control flow.
  • Typecheck: the committed src/*.ts (including the new runner.ts and the updated index.ts) compiles clean under the exact committed strict base config (tsconfig.base.json — strict family, verbatimModuleSyntax, exactOptionalPropertyTypes, noUncheckedIndexedAccess, NodeNext) with the pinned TypeScript 6.0.3, pg 8.22.0 + @types/pg 8.21.0 → exit 0.
  • Related suites re-run locally, all green: database-postgres-ledger (12 pass / 1 docker skip), database-postgres-lock (16 pass / 2 skip), database-postgres-imports (8 pass), workspace-layout (8 pass), workspace-config (6 pass), architecture-import (10 pass).
  • Full suite (node --test "tests/**/*.test.mjs"): 180 tests — 154 pass / 12 fail / 14 skip on Node 22.23.2; the 12 failures are pre-existing environment artifacts, verified identical on the pristine base commit in a clean worktree (this sandbox has Node 22 — the workspace engines gate requires Node ≥ 24 — and pnpm is not on PATH / node_modules is absent for the spawned-command tests): tests/frozen-install.test.mjs ×5 and tests/root-commands.test.mjs ×3 and tests/typescript-pin.test.mjs ×1 fail on pnpm: not found/engine gate, tests/node-engine.test.mjs ×1 asserts the runtime is Node 24.x, tests/strict-tsconfig.test.mjs ×2 fail because the tsc binary is absent (no install). CI runs Node 24 with corepack, where these pass (prior CI runs on the same codebase were fully green).

Risks / notes

  • The docker-gated real-stack probe runs in the new CI job where a Docker daemon is available and skips cleanly otherwise; the job installs the frozen workspace because the probes execute the committed runner module from the host (it resolves pg through the package's own links).
  • The probes use an isolated compose project (-p eppp-diagnostic-probe) and host port 55434 so they never collide with the compose-config suite's default-project containers, the ledger probe's project (eppp-ledger-probe)/port 55432, the lock probe's project (eppp-lock-probe)/port 55433, or the default 5432 binding when several suites run on the same host.
  • The runner performs no locking (advisory lock E00-S03-T04 is out of scope per the brief — a runner that wants to serialize takes the lock around run()) and no ready gating (E00-S03-T06); both remain for their own stories. Rollback note from the issue: revert the diagnostic/error handling changes (the runner is additive; dropping runner.ts and its re-export restores the prior state).

Refs #180

## What changed Implements [E00-S03-T05] Migration failure produces structured diagnostic (#180): a `MigrationRunner` in the `database-postgres` package that applies pending migrations through the migration ledger exactly once and, when a migration fails, throws a `MigrationFailedError` carrying a **structured diagnostic that identifies the failing migration** — verified against a real database with an intentionally failing migration fixture. - **`packages/database-postgres/src/runner.ts` — `MigrationRunner` over the package-owned `pg` Pool + `MigrationLedger` (E00-S03-T03)**: `run()` ensures the ledger exists, reads the applied migrations, and applies every migration whose version is not yet recorded — a rerun skips everything already recorded (the story's "second migration run is idempotent", from parent #60). Migrations are `{ version, up(pool) }` steps run in order (oldest first). - **Structured failure diagnostic (the acceptance criterion)**: when a migration's `up` throws, the runner wraps the failure into `MigrationFailedError` instead of rethrowing the raw error. The error carries `.diagnostic`, a structured object that **identifies the failing migration** (`migration` — its version), where the run failed (`phase`: `'apply'` when `up` threw, `'record'` when the ledger insert threw after a successful `up`), the underlying `cause` (preserved for inspection), and the ledger state at failure time (`applied`/`pending` — disjoint, in run order; the failing migration is still pending). The error is serializable: `toJSON()` returns a plain structured object with a structured cause (for pg errors the `code`, e.g. `42P01`), so operators can log/parse the diagnostic without string-matching. - **`packages/database-postgres/src/index.ts` — driver boundary re-export**: `MigrationRunner` + `MigrationFailedError` (and the `Migration`/`MigrationDiagnostic`/`MigrationRunResult`/`MigrationFailurePhase` types) are re-exported from the package entrypoint, so no other workspace package needs the `pg` driver to run migrations (isolation E00-S03-T02 stays intact — `runner.ts` imports only `import type { Pool }` and the ledger). - **`tests/database-postgres-diagnostic.test.mjs` — suite locking in both acceptance criteria**: static assertions on the committed source (structured diagnostic shape with `migration`/`phase`/`cause`/`applied`/`pending`; `MigrationFailedError` carries `.diagnostic` and is serializable with a message naming the failing migration; both failure paths build the diagnostic with the failing migration's version; ledger integration; driver-boundary re-export; CI enforcement), each backed by a mutation probe proving non-vacuousness. The **deterministic stub-pool behavioral probe** (no database/Docker; runs on Node ≥ 23.6, i.e. the CI Node 24) drives the committed runner with an **intentionally failing migration fixture** through three scenarios — success + idempotent rerun, apply-failure, and record-failure — and asserts the structured diagnostic identifies the failing migration in every case. The **real-stack probe** is the authoritative behavioral check: it starts the committed compose `db` service (isolated project `eppp-diagnostic-probe` + host port 55434), executes the committed runner against the real database with a fixture whose second migration runs valid SQL against a missing table, and asserts the run rejects with a `MigrationFailedError` whose diagnostic names the failing migration, reports phase `'apply'`, carries the pg error code `42P01`, lists the applied/pending state, and is serializable — while the ok migration's table and ledger row persist and the failing migration is not recorded (the issue's test plan: "run an intentionally failing migration fixture and confirm the diagnostic"). - **`.gitea/workflows/ci.yml` — new `database-postgres-diagnostic` job** runs `node --test tests/database-postgres-diagnostic.test.mjs` on every PR so the failure-diagnostic criterion gates merges. **Additive only** (no existing job modified, same action majors, no `secrets:` context, no untrusted interpolation). - **Docs/descriptors**: `docs/development/non-container.md` package table and `packages/database-postgres/package.json` description updated (the runner + failure diagnostic now land here, not "in later stories"); `.gitignore` ignores the transient probe files the behavioral/real-stack probes write into the package (removed in their `finally` blocks); the stale "later stories" references in the `ledger.ts`/`lock.ts` module headers updated. Explicitly out of scope per the brief, **not touched**: advisory lock (E00-S03-T04) — the runner performs no locking itself (a runner that wants to serialize takes the lock around `run()`); ready-before-migrations gate (E00-S03-T06). ## Criterion → test table | Acceptance criterion | Test (fails without the committed state) | | --- | --- | | migration failure produces a structured diagnostic | `tests/database-postgres-diagnostic.test.mjs` — **"the runner module exists in the driver-owner package and defines the runner and its diagnostic types"** (exports `MigrationRunner`, `MigrationFailedError`, `Migration`, `MigrationDiagnostic`, `MigrationRunResult`), **"the failure diagnostic is structured and identifies the failing migration (migration/phase/cause/applied/pending)"** (`migration: string`, `phase: MigrationFailurePhase`, `cause: unknown`, `applied: string[]`, `pending: string[]`), **"MigrationFailedError carries the structured diagnostic and is serializable, naming the failing migration"** (`readonly diagnostic`, `toJSON()`, message names the failing migration), **"the runner wraps a failing migration into the structured diagnostic instead of rethrowing the raw error"** (both failure paths construct `new MigrationFailedError` with the failing version); mutation probes **"replacing the wrapped failure with a bare rethrow …"**, **"constructing a plain Error instead of MigrationFailedError …"**, **"removing the diagnostic field from MigrationFailedError …"**, **"removing toJSON() …"**, **"a placeholder runner module …"**; behavioral probe — apply-failure and record-failure fixtures both reject with `MigrationFailedError` whose diagnostic carries `migration`/`phase`/`cause`/`applied`/`pending`, and `toJSON()` round-trips; real-stack probe — the intentionally failing fixture rejects with a structured diagnostic | | the diagnostic identifies the failing migration | `tests/database-postgres-diagnostic.test.mjs` — **"the failure diagnostic is structured and identifies the failing migration …"** (the `migration: string` field) + mutation probe **"removing the migration field makes the identifies-the-failing-migration criterion fail"**; **"MigrationFailedError … naming the failing migration"** (message `migration "…" failed during …`); behavioral probe — `diagnostic.migration === 'fail-2'` for the apply-failure fixture and `'fail-record'` for the record-failure fixture (phase `'record'` still names the failing migration), and `toJSON().diagnostic.migration` matches; real-stack probe — `diagnostic.migration === '2026-08-30_002_fixture_fail'`, `phase === 'apply'`, cause code `42P01`, `applied ['2026-08-30_001_fixture_ok']`, `pending ['2026-08-30_002_fixture_fail']`, `toJSON()` names the failing migration, and the failing migration is not recorded in the ledger (psql cross-check) | | the runner applies pending migrations through the ledger exactly once (never double-applies) | `tests/database-postgres-diagnostic.test.mjs` — **"the runner applies pending migrations through the migration ledger exactly once"** (`ledger.ensure()`/`applied()`/`record(migration.version)`); behavioral probe — first run applies `['ok-1','ok-2']`, rerun returns `{ applied: [], skipped: ['ok-1','ok-2'] }` with the ledger unchanged (the story's "second migration run is idempotent") | | the criteria are verified behaviorally against a real database — the real-stack probe is the authoritative criterion check, and static source assertions are backed by mutation probes proving non-vacuousness | `tests/database-postgres-diagnostic.test.mjs` — the docker-gated **real-stack probe** runs the committed runner against the committed compose `db` service and asserts both acceptance criteria behaviorally (see rows above); the deterministic **stub-pool behavioral probe** (no Docker) covers the same contracts on every CI Node; every static assertion has a **mutation probe** (8 probes: 7 source mutations + placeholder module) proving it fails on a violation | | the failure-diagnostic suite gates merges via the `database-postgres-diagnostic` job in `.gitea/workflows/ci.yml` | `tests/database-postgres-diagnostic.test.mjs` — **"the failure-diagnostic criterion is enforced in CI"** (root test glob covers the suite and `.gitea/workflows/ci.yml` runs it); `.gitea/workflows/ci.yml` — **`database-postgres-diagnostic` job** runs `node --test tests/database-postgres-diagnostic.test.mjs` on every PR (additive, matching the security-reviewed #390/#391/#392/#393 precedent) | ## Test plan executed - `node --test tests/database-postgres-diagnostic.test.mjs` → **18 tests, 16 pass / 0 fail / 2 skip** on Node 22.23.2 (the docker-gated real-stack probe and the TS-stripping-gated behavioral probe skip on Node 22 per the suite's conservative `>= 23.6` gate; both run in CI on Node 24 — the behavioral probe needs no Docker). - The **behavioral probe** was executed directly on this Node (via `--experimental-strip-types`, with the committed modules copied to a temp dir so the relative `.js` specifier resolves on Node 22): `DIAGNOSTIC_PROBE_RESULT` reports — success: `first {applied:['ok-1','ok-2'],skipped:[]}`, rerun `{applied:[],skipped:['ok-1','ok-2']}`; apply-failure: `rejected:true, isMigrationFailedError:true, name:'MigrationFailedError', message:'migration "fail-2" failed during apply', migration:'fail-2', phase:'apply', applied:['ok-1'], pending:['fail-2','ok-3'], causeIsOriginal:true, causeCode:'42P01'`, `toJson.diagnostic.cause.code:'42P01'`; record-failure: `migration:'fail-record', phase:'record', causeCode:'25006'`. All behavioral-probe assertions pass on the committed module's control flow. - **Typecheck**: the committed `src/*.ts` (including the new `runner.ts` and the updated `index.ts`) compiles clean under the exact committed strict base config (`tsconfig.base.json` — strict family, `verbatimModuleSyntax`, `exactOptionalPropertyTypes`, `noUncheckedIndexedAccess`, NodeNext) with the pinned TypeScript 6.0.3, `pg` 8.22.0 + `@types/pg` 8.21.0 → exit 0. - Related suites re-run locally, all green: `database-postgres-ledger` (12 pass / 1 docker skip), `database-postgres-lock` (16 pass / 2 skip), `database-postgres-imports` (8 pass), `workspace-layout` (8 pass), `workspace-config` (6 pass), `architecture-import` (10 pass). - Full suite (`node --test "tests/**/*.test.mjs"`): **180 tests — 154 pass / 12 fail / 14 skip on Node 22.23.2**; the 12 failures are pre-existing environment artifacts, **verified identical on the pristine base commit in a clean worktree** (this sandbox has Node 22 — the workspace engines gate requires Node ≥ 24 — and `pnpm` is not on PATH / `node_modules` is absent for the spawned-command tests): `tests/frozen-install.test.mjs` ×5 and `tests/root-commands.test.mjs` ×3 and `tests/typescript-pin.test.mjs` ×1 fail on `pnpm: not found`/engine gate, `tests/node-engine.test.mjs` ×1 asserts the runtime is Node 24.x, `tests/strict-tsconfig.test.mjs` ×2 fail because the tsc binary is absent (no install). CI runs Node 24 with corepack, where these pass (prior CI runs on the same codebase were fully green). ## Risks / notes - The docker-gated real-stack probe runs in the new CI job where a Docker daemon is available and skips cleanly otherwise; the job installs the frozen workspace because the probes execute the committed runner module from the host (it resolves `pg` through the package's own links). - The probes use an isolated compose project (`-p eppp-diagnostic-probe`) and host port 55434 so they never collide with the compose-config suite's default-project containers, the ledger probe's project (`eppp-ledger-probe`)/port 55432, the lock probe's project (`eppp-lock-probe`)/port 55433, or the default 5432 binding when several suites run on the same host. - The runner performs **no locking** (advisory lock E00-S03-T04 is out of scope per the brief — a runner that wants to serialize takes the lock around `run()`) and **no ready gating** (E00-S03-T06); both remain for their own stories. Rollback note from the issue: revert the diagnostic/error handling changes (the runner is additive; dropping `runner.ts` and its re-export restores the prior state). Refs #180
bot-implementer added 2 commits 2026-08-30 01:17:49 +00:00
MigrationRunner applies pending migrations through the migration ledger
exactly once; when a migration fails it throws a MigrationFailedError
whose diagnostic is a structured object identifying the failing migration
(version), the failure phase (apply/record), the underlying cause, and the
applied/pending ledger state, serializable via toJSON. Re-exported from
the driver boundary so no other package needs the pg driver to run
migrations. Advisory lock (T04) and ready gate (T06) remain out of scope.
test: lock in the migration failure diagnostic (E00-S03-T05)
CI / Frozen lockfile install (pull_request) Successful in 44s
CI / Secrets not embedded (E00-S02-T08) (pull_request) Successful in 28s
CI / Database-postgres import isolation (E00-S03-T02) (pull_request) Successful in 26s
CI / Migration ledger (E00-S03-T03) (pull_request) Successful in 44s
CI / Migration advisory lock (E00-S03-T04) (pull_request) Successful in 51s
CI / Compose config (E00-S03-T01) (pull_request) Successful in 25s
CI / Migration failure diagnostic (E00-S03-T05) (pull_request) Failing after 51s
52190d082c
Static assertions + mutation probes on the committed runner source, a
deterministic stub-pool behavioral probe (intentionally failing migration
fixture -> structured diagnostic naming the failing migration, apply and
record phases), a docker-gated real-stack probe against a real database
(the issue's test plan), and CI enforcement via the additive
database-postgres-diagnostic job.
bot-implementer added 1 commit 2026-08-30 01:24:18 +00:00
fix: inject the ledger into MigrationRunner (type-only import) so probes load the committed module under type stripping
CI / Frozen lockfile install (pull_request) Successful in 45s
CI / Secrets not embedded (E00-S02-T08) (pull_request) Successful in 29s
CI / Database-postgres import isolation (E00-S03-T02) (pull_request) Successful in 28s
CI / Migration ledger (E00-S03-T03) (pull_request) Successful in 42s
CI / Migration advisory lock (E00-S03-T04) (pull_request) Successful in 44s
CI / Migration failure diagnostic (E00-S03-T05) (pull_request) Successful in 41s
CI / Compose config (E00-S03-T01) (pull_request) Successful in 25s
bb3a68648a
Node's type stripping does not rewrite './ledger.js' to './ledger.ts', so the
runner's runtime import of the ledger could not resolve when the behavioral
probes execute the committed runner.ts directly (CI failure on Node 24).
The ledger is now imported type-only and the caller passes the instance
(new MigrationLedger(pool)) — the probes already do. runner.ts has no
runtime imports left, so type stripping erases them and the committed
module loads as-is.
Author
Member

CI green on the final head (bb3a686, run 92 — 7/7 jobs, including the new database-postgres-diagnostic job). Follow-up fix over the initial push: MigrationRunner now takes the ledger by type-only import (caller passes new MigrationLedger(pool) — the probes already did) because Node's type stripping does not rewrite ./ledger.js → ./ledger.ts, which the behavioral probe hit when executing the committed runner.ts directly. runner.ts now has no runtime imports, so the committed module loads as-is under type stripping; the strict typecheck and the full local suite remain green.

CI green on the final head (`bb3a686`, run 92 — 7/7 jobs, including the new `database-postgres-diagnostic` job). Follow-up fix over the initial push: `MigrationRunner` now takes the ledger by **type-only import** (caller passes `new MigrationLedger(pool)` — the probes already did) because Node's type stripping does not rewrite `./ledger.js` → `./ledger.ts`, which the behavioral probe hit when executing the committed `runner.ts` directly. `runner.ts` now has no runtime imports, so the committed module loads as-is under type stripping; the strict typecheck and the full local suite remain green.
kpcto merged commit e8cafa090b into main 2026-08-30 01:31:49 +00:00
kpcto deleted branch feature/180 2026-08-30 01:31:50 +00:00
Sign in to join this conversation.