[Air #1476] Extract runGlobalMigrations(db) so migration tests drive the real production runner - #1485
[Air #1476] Extract runGlobalMigrations(db) so migration tests drive the real production runner#1485mohidmakhdoomi wants to merge 12 commits into
Conversation
…drive the real runner The global.db migration sequence lived inline in the private ensureGlobalDatabase() path, so migration tests could only drive hand-maintained replicas of it, kept honest by source guards. Move the v2 -> v17 chain into db/migrations.ts as runGlobalMigrations(db); ensureGlobalDatabase() and the tests now call the same function. Two defaulted seams keep production behavior identical while making the runner callable from a test: options.log (default console.log) and options.runDir (default ~/.codev/run), the directory whose shepherd-*.sock files migration v8 renames. spec-1313-migration.test.ts is rewritten to drive the real runner. It keeps the v15 / v16 / v17 coverage and adds what only a callable runner allows: the full v1 -> v17 chain, whole-database convergence with a fresh GLOBAL_SCHEMA install (all tables, columns and indexes), v9's project_path -> workspace_path data carry-over with v13's role_id backfill, and v8's socket rename against an injected run directory. The two remaining source guards are retargeted at db/migrations.ts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ers, trigger convergence) All three reviewers returned APPROVE with no blocking issues. The three non-blocking items from the Claude lane: - runGlobalMigrations' function JSDoc claimed "safe to call on any existing database from v1 onward" without the caveat the module header and PR body carry. The function is publicly re-exported from db/index.ts, so the caveat belongs where an IDE hover shows it: a marker-less GLOBAL_SCHEMA database dies at v5. - pir-832 / spec-755 / bugfix-826 still described their replicas as mirroring "db/index.ts's vN block". db/index.ts now contains no migrations, and those tests cover the retired per-workspace state.db, whose vN is a different migration from global.db's vN. Comments now say which numbering space they are in. - The whole-database convergence test compared type='table' only, so the builders_updated_at trigger was uncovered. It now compares triggers too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Architect integration review — 3-way CMAP (risk tier: High — global.db state layer, >500 lines)Verdict: APPROVE — unanimous (gemini / codex / claude, all HIGH confidence, zero blocking issues). Parked for maintainer approval + merge; we are not maintainers. Independently verified by the architect before consultation:
Non-blocking findings (from CMAP, for builder to address-or-rebut in this PR or spin off):
|
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Architect CMAP finding 1. The whole point of the extraction is a runner callable from anywhere, and the one shape it cannot handle failed opaquely: a fresh GLOBAL_SCHEMA database with no markers ran v5 against the long-renamed terminal_sessions.project_path and died mid-chain on "no such column". Detect that shape at entry — workspace_path already present, v9 marker absent — and throw a named error saying to stamp markers instead. Production never reaches it (ensureGlobalDatabase stamps on the fresh path); it exists for every other caller. The JSDoc no longer claims unqualified safety. Also finding 3: the test harness now sets production's full pragma set rather than WAL alone. v7-v9 rebuild tables with DROP + RENAME, which is exactly the SQL whose behavior depends on foreign_keys — matching pragmas closes that fidelity gap. Two tests cover the guard: the marker-less fresh schema throws and applies nothing, and the same schema with markers stamped (the real production path) is a clean no-op. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Builder response to the architect CMAP findingsFinding 1 — precondition guard: accepted, done (
That is exactly the marker-less-fresh-schema case that used to die at v5 on Two tests: the marker-less fresh schema throws and leaves Finding 3 — pragma fidelity: accepted, done (same commit). The test harness set only Finding 4 — stale replica comments: already done in Finding 2 — redundant Full suite after the changes: 4863 passed, 48 skipped, 0 failures; build clean. |
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ainer Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes #1476
What Changed
The global.db migration sequence lived inline inside the private
ensureGlobalDatabase()path indb/index.ts, so migration tests could only drive hand-maintained replicas of it, kept honest by source guards. That is one source of truth per migration, times two, maintained by hand — a tax paid again at every new migration (v15 → v16 → v17 already).packages/codev/src/agent-farm/db/migrations.ts—GLOBAL_CURRENT_VERSION(17) andrunGlobalMigrations(db, options?). The v2 → v17 chain moved over verbatim; no SQL changed.db/index.tsnow callsrunGlobalMigrations(db)on the existing-database path and importsGLOBAL_CURRENT_VERSIONfor fresh-install marker stamping. Both are re-exported fromdb/index.ts, so no callsite has to learn a new module.spec-1313-migration.test.tsrewritten to drive the real runner. No replicas remain in it.Key Decisions
Two defaulted seams, both defaulting to today's production behavior, are what make the runner callable from a test:
options.log(defaultconsole.log) — tests collect the per-migration lines instead of spamming stdout, and can assert which steps actually ran.options.runDir(default~/.codev/run) — migration v8 renamesshepherd-*.sockfiles on disk. That is the one genuine filesystem side effect in the chain; without this seam, a test driving the real v8 would rename a developer's live sockets.The runner is only safe on a database that reached its recorded version through migrations. A fresh
GLOBAL_SCHEMAdatabase with no markers would fail at v5, which selectsterminal_sessions.project_path— which is exactly whyensureGlobalDatabase()stamps every marker on the fresh path rather than running the chain. Behavior is unchanged; the constraint is now documented at the runner.Out of scope, deliberately left alone: the
pir-832/bugfix-826/spec-755migration tests. Those replicate migrations of the retired per-workspacestate.db, which has no production runner to call — extracting one would be inventing a runner for a dead path.Test Plan
pnpm --filter @cluesmith/codev build)The rewritten suite keeps the v15 / v16 / v17 coverage and adds what only a callable runner allows:
GLOBAL_SCHEMAinstall — all tables, columns and indexes, where before only the one table under test was compared;project_path→workspace_pathdata carry-over plus v13's architectrole_idbackfill, asserted on real rows through the v7/v8/v9 table rebuilds that no replica ever reproduced;Review Notes
db/migrations.tsshould read as a pure move of the migration block. Worth diffing the SQL statement-by-statement against the pre-changedb/index.ts: the only intended edits areconsole.log(...)→log(...), the v8runDirnow coming from options, and one comment that said "handled above" now saying "handled by the caller".not_before(v17). The old test simulated that with a hand-writtenALTER.send-architect-identity.test.ts,bugfix-506-annotator-worktree-cwd.test.ts) were retargeted fromdb/index.tstodb/migrations.ts. They are now partly redundant with the behavioral tests; kept as-is to hold this PR to its scope.🤖 Generated with Claude Code