Uh oh!
There was an error while loading. Please reload this page.
[Air #1476] Extract runGlobalMigrations(db) so migration tests drive the real production runner - #1485
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>
mohidmakhdoomi
commented
Aug 17, 2026
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>
mohidmakhdoomi
commented
Aug 17, 2026
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>
waleedkadous
left a comment
There was a problem hiding this comment.
Thank you for the discipline on this one — claude's lane did a mechanical normalized diff of main's inline block against migrations.ts and confirmed zero SQL changed (only the runDir hoist, one comment reflow, and the dropped return db), and proved the new precondition guard can't false-positive because v9 wraps its rebuilds and marker in one transaction. Approving.
The best part isn't in the stated scope: the convergence test that walks v1→v17 and diffs it against a fresh GLOBAL_SCHEMA install on tables, columns, indexes and triggers closes a real latent hazard in both directions. I'll take the arch.md follow-up myself (add db/migrations.ts to Core Components and the repo tree, and name that test as the enforcing guard) — that's architect territory, as you correctly left it.
One optional tidy-up for a follow-up: the literal GLOBAL_CURRENT_VERSION = 17 source guard in send-architect-identity.test.ts:239 is now behaviorally covered and just adds a per-migration edit tax.
Uh oh!
There was an error while loading. Please reload this page.
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