Skip to content

fix(db): journal-consistency guard for migrations (RBR-968 / RBR-927 AC3) — fork retarget (RBR-1033/RBR-1089) - #14

Merged
PraeSynBH merged 3 commits into
masterfrom
rbr-1033-fork-land
Aug 8, 2026
Merged

fix(db): journal-consistency guard for migrations (RBR-968 / RBR-927 AC3) — fork retarget (RBR-1033/RBR-1089)#14
PraeSynBH merged 3 commits into
masterfrom
rbr-1033-fork-land

Conversation

@PraeSynBH

Copy link
Copy Markdown
Owner

Retarget of RBR-1033 (originally PR paperclipai#11012 against paperclipai/paperclip upstream, where we have push:false/admin:false/maintain:false and cannot merge) onto our fork PraeSynBH/paperclip, where we have admin:true/push:true. Applies the RBR-1081 repo-governance pattern (RBR-1089).

Cherry-picked cleanly from 4f18bd3 + d110393 (rbr-968-journal-consistency-guard @ d110393/7b69f702eb) onto current fork/master (which already carries the RBR-1081 admission-control merge, 8165954). Two small conflicts resolved (packages/db/package.json check:migrations script referencing a nonexistent check-migration-safety.ts on this line of history; doc/DATABASE.md section ordering) plus the journal-idx baseline and its pinning tests updated: this fork's migration journal never carried the duplicate idx-178 / gap defects that upstream had at RBR-968 time, so the baseline is empty and the tests that pinned those upstream-specific numbers were rewritten to assert a clean journal instead.

Verified on the fork merge commit:

  • pnpm --filter @paperclipai/db run check:migrations — OK, 128/128 files match journal entries
  • pnpm --filter @paperclipai/db run typecheck — clean tsc --noEmit
  • vitest packages/db/src/migration-journal-consistency.test.ts — 33/33 passing
  • node --test scripts/__tests__/check-migration-journal.test.mjs — 8/8 passing (including the anti-drift test that the .mjs and .ts guards agree)

No server code touched (diff is scoped to packages/db, scripts/, docs, .github/workflows/pr.yml, .gitignore). No global-install swap or restart performed — rides the same swap+restart confirmation sequence as RBR-1081/RBR-974.

beingben and others added 3 commits August 8, 2026 14:42
… (RBR-927)

An orphaned .sql file (present in packages/db/src/migrations, absent from
meta/_journal.json) is invisible to drizzle's journal-driven migrator but IS
visible to the folder-enumerating inspectMigrations. The database is genuinely
up to date while bootstrap reports the file as permanently pending, so
applyPendingMigrations threw "Failed to bootstrap migrations" with no repair
path — neither reconcilePendingMigrationHistory nor
applyPendingMigrationsManually can fix it, because re-running the orphan's DDL
collides with the columns the journalled copy already created.

Because that throw happens inside beforeAll, every test in an embedded-Postgres
suite reported `skipped` rather than `failed` — a silent-green failure mode.

This adds migration-journal-consistency.ts as the single source of truth for the
invariant, wired into both enforcement points so they fail identically with the
offending filename:

- check-migration-numbering.ts (the `check:migrations` gate, which already
  guards build/typecheck/generate/migrate) now runs the journal/folder set
  comparison *first*, so an orphan is reported by name rather than as a
  confusing duplicate-slot or bad-prefix numbering complaint.
- applyPendingMigrations() asserts consistency before inspecting state, so the
  runtime path fails fast and actionably instead of bottoming out in an
  unfixable pending-migration loop.

Detects both directions: orphaned files and journal entries with no file.

Verified: `pnpm check:migrations` passes clean; re-injecting
0128_force_reassign.sql makes it exit 1 naming the file; 11 unit tests cover
both directions plus the exact RBR-927 renumber shape, and one test pins the
real shipped migrations folder against its journal.
… AC3)

An orphaned .sql in packages/db/src/migrations is invisible to drizzle's
journal-driven migrate() but IS counted as pending by inspectMigrations. The
result is a database that is genuinely up to date while bootstrap reports it
as permanently pending -- and because that throw happens inside beforeAll,
Vitest reports the whole suite as `skipped` rather than `failed`. Silent green.

Checks (all name the offending filename):
1. file <-> journal bijection -- always a hard error
2. journal `idx` uniqueness -- hard error, minus an explicit ratcheting
   baseline for already-shipped defects
3. journal `idx` continuity -- warning (gaps usually mean a dropped migration)

Enforcement is deliberately split by blast radius:
- assertMigrationJournalConsistency() runs in applyPendingMigrations, i.e.
  every embedded-Postgres beforeAll. Bijection only -- it must never start
  failing on shipped journal history or it would brick production startup.
- auditMigrationJournal() is the pre-build/pre-test gate and adds idx checks.

Wired at three points so a defect cannot reach a test lane:
- packages/db check:migrations (already gates build/typecheck/generate/migrate)
- scripts/run-vitest-stable.mjs preflight, before any suite starts
- PR `policy` job, which has no pnpm install -- hence the dependency-free
  scripts/check-migration-journal.mjs reimplementation. An anti-drift test
  asserts the .mjs and .ts guards agree on the real tree.

Also fixes the .gitignore rule that caused this work to be lost once already:
`check-*.mjs` on line 23 silently swallowed
scripts/__tests__/check-migration-journal.test.mjs, so the guard's own test
file was never stageable and the CI step referencing it would have failed.

Known pre-existing defects on origin/master, now reported by name rather than
fixed (out of scope per the issue): duplicate idx 178 shared by
0177_activity_log_responsible_user and 0178_summary_slots; missing idx
126, 130, 177.
…l (RBR-1033)

Fork's migration journal diverged from upstream before upstream's known
defects (duplicate idx 178, gaps at 126/130/177) existed on this line of
history, so the fork's journal has neither. Update the pinned assertions
and baseline to match the fork's actual (clean) journal state instead of
upstream's, so the guard still exercises its real behavior (orphan/missing
file detection, --strict gating) without asserting defects this fork
never had.
@PraeSynBH
PraeSynBH merged commit 491c36f into master Aug 8, 2026
1 of 2 checks passed
PraeSynBH pushed a commit that referenced this pull request Aug 8, 2026
…not undefined scriptsDir

runMigrationJournalGuard() in run-vitest-stable.mjs referenced an
undefined 'scriptsDir' variable, throwing a ReferenceError before any
suite could run. This is caught live on fork PR #14's CI: every General
tests shard, every Verify serialized server suites shard, review, and
verify all failed with the same trace. The guard script itself and its
dedicated unit test are correct; only this call site had the wrong
identifier. Use the already-defined repoRoot instead.

Verified: general-server shard 1/30 (8 suites, 38 tests) now runs to
completion under the fixed preflight; migration-journal-consistency.test.ts
(33/33) and check-migration-journal.test.mjs (8/8) unaffected.
PraeSynBH added a commit that referenced this pull request Aug 8, 2026
…flight — fork retarget (RBR-1089)

The RBR-1033 fork-retarget merge (491c36f) added a preflight call to
check-migration-journal.mjs via an undefined scriptsDir variable. Every
test:run invocation that reaches runMigrationJournalGuard() throws
ReferenceError: scriptsDir is not defined before any suite starts —
verified failing on fork/master CI for PR #14 and #16 (General tests,
Verify serialized server suites, verify gate).

The rest of the file already resolves script/repo paths off repoRoot
(process.cwd()); use the same pattern here. Verified locally:
  node scripts/check-migration-journal.mjs        -> OK, 128/128
  node scripts/run-vitest-stable.mjs --dry-run     -> no crash, full plan

Co-authored-by: CTO Agent <cto-agent@paperclip.local>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants