⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.
Context
detectColumnCollisions (src/db/migration-column-extraction.ts:211) replays every migration's schema events
in order and reports any (table, column) pair defined by more than one file. It is the only pre-merge gate for
the failure #2551 exists to catch: two independently-numbered migrations that each add the same column, which
produces no git conflict, a clean mergeable_state, and a hard failure at wrangler d1 migrations apply time
— after merge.
extractSchemaEvents (line 161) recognizes five statement shapes. It does not recognize
ALTER TABLE <old> RENAME TO <new>. The renameColumnMatch regex on line 166 requires a literal TO after the
captured identifier, so ALTER TABLE orb_signals_new RENAME TO orb_signals matches nothing and the statement
yields zero events.
That statement is not hypothetical — it is the repo's established pattern for widening a CHECK
constraint, which SQLite cannot ALTER. Two migrations already use it:
migrations/0176_orb_signals_superseded_reversal.sql:33 — ALTER TABLE orb_signals_new RENAME TO orb_signals;
migrations/0201_ledger_anchor_bittensor.sql:24 — ALTER TABLE decision_ledger_anchors_new RENAME TO decision_ledger_anchors; (its own comment calls this "the standard rebuild-and-rename")
Replaying either file leaves the tracker in a wrong state: the DROP TABLE orb_signals event clears every
column tracked for orb_signals, the CREATE TABLE orb_signals_new event tracks the columns under
orb_signals_new, and the rename is invisible — so from that file onward the checker believes orb_signals
and decision_ledger_anchors have no columns at all.
Verified by executing the module directly:
files: [0195 CREATE TABLE t(id, backend)], [0201 CREATE t_new(id, backend); DROP TABLE t; ALTER TABLE t_new RENAME TO t], [0202 ALTER TABLE t ADD COLUMN backend]
detectColumnCollisions(...) -> [] <-- collision NOT detected
same corpus without the rebuild-and-rename file:
detectColumnCollisions(...) -> [{table:"t", column:"backend", files:["0195_a.sql","0202_c.sql"]}]
So a future migration that re-adds an existing column to orb_signals or decision_ledger_anchors ships
green and breaks the deploy — the exact regression class this module exists to prevent, and the same family as
the already-fixed #8368 (RENAME COLUMN/DROP COLUMN without the literal COLUMN keyword).
scripts/check-migrations.ts imports this module directly (line 34) and so does the live premerge recheck, so
both are blind in the same way.
Requirements
SchemaEvent gains a { type: "rename_table"; from: string; to: string } variant.
extractSchemaEvents emits that event for ALTER TABLE <old> RENAME TO <new> (case-insensitive, tolerant of
arbitrary whitespace), and continues to emit the existing remove_column + define_column pair for
ALTER TABLE <t> RENAME [COLUMN] <a> TO <b>. The table-rename form must not be misparsed as a column rename,
and the column-rename form must not be misparsed as a table rename.
detectColumnCollisions handles rename_table by re-keying every column currently tracked under from to
to (dropping anything already tracked under to, since the rename target was just dropped by the
preceding DROP TABLE in this pattern), so the rebuilt table carries its column set forward.
- Already-recorded collisions stay recorded permanently, exactly as documented at lines 205-209 — a rename must
not clear a collision that was already detected.
⚠️ Required pattern: mirror the existing drop_table handling in detectColumnCollisions
(src/db/migration-column-extraction.ts:218-222) — iterate tracked and rewrite matching entries, rather
than introducing a second parallel table-alias map consulted at lookup time. What does NOT satisfy this
issue: special-casing the _new suffix (nothing requires that naming); making check-migrations.ts
skip files containing RENAME TO; or adding the rename handling only in scripts/check-migrations.ts
instead of in the shared pure module (check-migrations.ts's own header, lines 9-13, states that CI and the
Worker must never disagree about what counts as a collision).
Deliverables
All Deliverables above are required in a single PR. A PR that satisfies only some of them — for example adding
the rename_table event in extractSchemaEvents without the re-keying in detectColumnCollisions, so the
detector is still blind — does not resolve this issue.
Test Coverage Requirements
This repo enforces 99%+ Codecov patch coverage, branch-counted, on src/**, and
src/db/migration-column-extraction.ts is inside coverage.include. Every new regex branch and every arm of
the new rename_table handling needs a test, including the negative arms (a column-rename statement must NOT
produce a rename_table event, and a rename_table for an untracked table must be a harmless no-op). The
three-file fixture test in Deliverable 3 is the required named regression test.
Expected Outcome
A migration that re-adds a column to a table previously rebuilt via DROP TABLE + CREATE … _new +
ALTER TABLE … RENAME TO is caught by npm run db:migrations:check pre-merge, instead of passing CI and
failing at wrangler d1 migrations apply after merge.
Links & Resources
Context
detectColumnCollisions(src/db/migration-column-extraction.ts:211) replays every migration's schema eventsin order and reports any
(table, column)pair defined by more than one file. It is the only pre-merge gate forthe failure #2551 exists to catch: two independently-numbered migrations that each add the same column, which
produces no git conflict, a clean
mergeable_state, and a hard failure atwrangler d1 migrations applytime— after merge.
extractSchemaEvents(line 161) recognizes five statement shapes. It does not recognizeALTER TABLE <old> RENAME TO <new>. TherenameColumnMatchregex on line 166 requires a literalTOafter thecaptured identifier, so
ALTER TABLE orb_signals_new RENAME TO orb_signalsmatches nothing and the statementyields zero events.
That statement is not hypothetical — it is the repo's established pattern for widening a
CHECKconstraint, which SQLite cannot
ALTER. Two migrations already use it:migrations/0176_orb_signals_superseded_reversal.sql:33—ALTER TABLE orb_signals_new RENAME TO orb_signals;migrations/0201_ledger_anchor_bittensor.sql:24—ALTER TABLE decision_ledger_anchors_new RENAME TO decision_ledger_anchors;(its own comment calls this "the standard rebuild-and-rename")Replaying either file leaves the tracker in a wrong state: the
DROP TABLE orb_signalsevent clears everycolumn tracked for
orb_signals, theCREATE TABLE orb_signals_newevent tracks the columns underorb_signals_new, and the rename is invisible — so from that file onward the checker believesorb_signalsand
decision_ledger_anchorshave no columns at all.Verified by executing the module directly:
So a future migration that re-adds an existing column to
orb_signalsordecision_ledger_anchorsshipsgreen and breaks the deploy — the exact regression class this module exists to prevent, and the same family as
the already-fixed #8368 (
RENAME COLUMN/DROP COLUMNwithout the literalCOLUMNkeyword).scripts/check-migrations.tsimports this module directly (line 34) and so does the live premerge recheck, soboth are blind in the same way.
Requirements
SchemaEventgains a{ type: "rename_table"; from: string; to: string }variant.extractSchemaEventsemits that event forALTER TABLE <old> RENAME TO <new>(case-insensitive, tolerant ofarbitrary whitespace), and continues to emit the existing
remove_column+define_columnpair forALTER TABLE <t> RENAME [COLUMN] <a> TO <b>. The table-rename form must not be misparsed as a column rename,and the column-rename form must not be misparsed as a table rename.
detectColumnCollisionshandlesrename_tableby re-keying every column currently tracked underfromtoto(dropping anything already tracked underto, since the rename target was just dropped by thepreceding
DROP TABLEin this pattern), so the rebuilt table carries its column set forward.not clear a collision that was already detected.
Deliverables
extractSchemaEventsinsrc/db/migration-column-extraction.tsreturns a singlerename_tableeventfor
ALTER TABLE a RENAME TO b, asserted by a new case intest/unit/coveringmigrations/0201_ledger_anchor_bittensor.sql's exact statement text.extractSchemaEventsstill returnsremove_column+define_columnfor bothALTER TABLE t RENAME COLUMN a TO bandALTER TABLE t RENAME a TO b, asserted by existing-or-new casesso the two forms are provably disambiguated.
detectColumnCollisionsre-keys tracked columns across arename_table, asserted by a named regressiontest using the three-file fixture above (
CREATE t→ rebuild-and-rename →ALTER TABLE t ADD COLUMN)that expects exactly one collision on
t.backend.migrations/corpus still reports zero collisions after the change(so the fix does not turn
0176/0201themselves into false positives).All Deliverables above are required in a single PR. A PR that satisfies only some of them — for example adding
the
rename_tableevent inextractSchemaEventswithout the re-keying indetectColumnCollisions, so thedetector is still blind — does not resolve this issue.
Test Coverage Requirements
This repo enforces 99%+ Codecov patch coverage, branch-counted, on
src/**, andsrc/db/migration-column-extraction.tsis insidecoverage.include. Every new regex branch and every arm ofthe new
rename_tablehandling needs a test, including the negative arms (a column-rename statement must NOTproduce a
rename_tableevent, and arename_tablefor an untracked table must be a harmless no-op). Thethree-file fixture test in Deliverable 3 is the required named regression test.
Expected Outcome
A migration that re-adds a column to a table previously rebuilt via
DROP TABLE+CREATE … _new+ALTER TABLE … RENAME TOis caught bynpm run db:migrations:checkpre-merge, instead of passing CI andfailing at
wrangler d1 migrations applyafter merge.Links & Resources
src/db/migration-column-extraction.ts(lines 154-241)scripts/check-migrations.ts(lines 34, 189-198)migrations/0176_orb_signals_superseded_reversal.sql:33,migrations/0201_ledger_anchor_bittensor.sql:24