Repository navigation
Give every migration a version the ledger can record - #417
Merged
Merged
Conversation
supabase_migrations.schema_migrations has a PRIMARY KEY on version, so it
physically cannot hold two rows for 0010. This repository carried 17 duplicate
version numbers, and that made the table unusable for it:
ERROR: duplicate key value violates unique constraint "schema_migrations_pkey"
Key (version)=(0010) already exists.
It blocked Supabase branching outright, and it sits directly in the path of the
production ledger repair (LB-016 §4), which replays the history into that same
table. The duplicates were dormant only for as long as nothing tried to record
them.
Every colliding group is renamed to a unique version that preserves apply order,
using the scheme scripts/ci-dedupe-migration-versions.mjs already applied in the
disposable CI checkout and proves order-preserving:
0010_blog_posts.sql -> 00100_blog_posts.sql
0010_support_tickets_admin_users.sql -> 00101_support_tickets_admin_users.sql
"00100" < "00101" < "0011" and > "0009", so nothing moves relative to its
neighbours. 35 files across 17 groups; 291 migrations, unchanged in content.
Why this was safe to do NOW and would not have been later: the audit warned that
renaming risks drift against a production database that "records applied
versions by that prefix". Production's ledger holds only 0001-0003, so none of
these 17 versions is recorded anywhere — there is nothing to drift from. Once
the ledger is repaired that stops being true, so the rename had to come first.
KNOWN_DUPLICATE_MIGRATIONS is now empty and stays exported: the collision check
still runs, so a NEW duplicate fails the audit rather than being absorbed.
migration-version-safety now asserts there are no duplicates at all, that the
escape hatch is empty, and that every version is unique — replacing the test
that asserted the collisions matched an approved list.
nextVersion needed fixing to survive this. It took Math.max over Number(version),
and Number('01421') is 1421, which would have pointed the next migration at 1422.
It now reads the first four digits — the generation — so the 5th digit that
orders files within a generation does not leak into the numbering.
Two tests opened a renamed migration by name and failed to load; both now open
the new name. Swept the repo for other hardcoded paths: the remaining misses are
synthetic fixtures and three stale references in production-migration-contract
that predate this change on main.
Also adds docs/audit/migration-ledger-state.sql, a read-only pre-flight for the
ledger repair, and rewrites LB-016 §4 into an actual procedure. That section
previously said only "repair the ledger baseline". It now says what the repair
is (let db push replay from 0004 — every migration is additive and idempotent
and CI replays all 291 against real Postgres, so the ledger repairs itself
rather than being stamped by hand), what its prerequisite was (this rename), and
records a window nobody had flagged: a replay runs 0006's is_family_member
finance policies BEFORE 0267 narrows them and 0275 sweeps, so production is
briefly open mid-replay. End state correct, intermediate state not — do it in a
maintenance window, not against live traffic.
tsc --noEmit clean, 900 files / 7,700 tests pass, migration audit reports next
version 0276.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
NewWorldVenture
marked this pull request as ready for review
September 7, 2026 18:46
NewWorldVenture
added a commit
that referenced
this pull request
Sep 7, 2026
A French visitor was getting a translated header above an English page, and a
raw catalogue key in the footer reading siteFooter.acceptableUse. This makes the
whole public site render in the reader's language, and fixes the tooling that
reported it clean the entire time.
What was showing English: the site footer (every heading and link, on every
marketing page), the Knowledge Center block, all of /mobile, all four legal
pages (123 strings), the FAQ page, and inLanguage: 'en-US' in the JSON-LD, which
told every crawler the French page was English.
Why nothing caught it — three defects in the i18n tooling, each fixed:
* walk() skipped any directory named mobile/ios/android at ANY depth. The
intent was the sibling native projects at the repo root, so app/(marketing)/mobile
— a real public page — was invisible to the gate. Root-anchored now.
* the scanner matched copy against an allowlist of property names, so every
gap had to be found by reading English off a shipped page. It now recognises
copy by VALUE, with a list of properties that are never copy — wrong in the
over-reporting direction, which is the direction to be wrong in.
* i18n-lift stored the string exactly as it sat between the quotes, so a JS
escape survived into the catalogue and 29 values held a literal backslash.
Repaired, and a catalogue test now fails on any value carrying one.
`everything` is removed from the gated set, deliberately. It was gated on a zero
from a scanner that could not see data literals, could not see props outside its
allowlist, and skipped every directory named mobile. Widening it turned that
zero into 2,343 real strings, so the promise had never been true. It goes back
when it scans clean — a surface gated while dirty teaches everyone to ignore the
gate.
Brought up to date with main twice during review, across #414, #416, #417, #419
and #420. Conflicts in site-footer, structured-data and en-US resolved by
keeping main's titleKey/labelKey naming (the shape that makes a key in a
copy field visible on sight, which is what this PR exists to fix) and main's
relocated social row, while keeping BOTH sides in structured-data: the cached
social-links reader and this branch's translations.
The merged tree then failed catalogue-key-not-rendered-raw with 20 offenders,
and the test was right: those lists were half-lifted, a key beside a literal in
the same field. The render sites were already correct, so the literals were the
defect. Nine lifted — Children's privacy, two security commitments, the GDPR /
HIPAA / CCPA labels, two aiShowcase strings and three pricing tier labels.
Footer spacing: the language pill and social row share one wrapping flex row,
and gap-y-2 left them reading as one crowded block once wrapped. gap-y-4.
Known gaps, stated rather than hidden: 293 lifted keys are en-US only and render
English by the documented fallback; 2,343 strings remain across app/(app) and
components/modules; and the FAQ answers are database rows that no amount of code
translation reaches.
All six checks green. E2E needed one re-run: 9 iPad page.goto timeouts, no
assertion failures, 395 passing at three other viewports — the same signature
seen on #412, which contained no application code at all.
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
supabase_migrations.schema_migrationshas a PRIMARY KEY onversion, so it physically cannot hold two rows for0010. This repo carried 17 duplicate version numbers, which made that table unusable for it:That blocked Supabase branching outright, and it sits directly in the path of the production ledger repair (LB-016 §4), which replays the history into the same table. The duplicates were dormant only while nothing tried to record them.
The rename
Every colliding group gets a unique version preserving apply order — the scheme
scripts/ci-dedupe-migration-versions.mjsalready used in the disposable CI checkout and proves order-preserving:"00100" < "00101" < "0011"and> "0009", so nothing moves relative to its neighbours. 35 files across 17 groups; 291 migrations, contents unchanged.Why now, and not later
The audit warned that renaming risks drift against a production database that "records applied versions by that prefix". Production's ledger holds only
0001–0003, so none of these 17 versions is recorded anywhere — there is nothing to drift from. Once the ledger is repaired that stops being true, so the rename had to come first. This was the free window.Guardrails
KNOWN_DUPLICATE_MIGRATIONSis now empty and stays exported — the collision check still runs, so a new duplicate fails the audit instead of being absorbed.migration-version-safetynow asserts there are no duplicates at all, that the escape hatch is empty, and that every version is unique, replacing the test that asserted collisions matched an approved list.nextVersionneeded fixing to survive this: it tookMath.maxoverNumber(version), andNumber('01421')is1421— which would have pointed the next migration at 1422. It now reads the first four digits (the generation), so the 5th digit that orders files within a generation doesn't leak into numbering. Audit now reports0276.Ledger repair (the other half)
Adds
docs/audit/migration-ledger-state.sql— a read-only pre-flight — and rewrites LB-016 §4 from "repair the ledger baseline" into an actual procedure:migrations-are-additive.test.ts), and CI replays all 291 against real Postgres, so lettingdb pushrun from0004repairs the ledger by doing the work. Writing rows by hand asserts work was done without doing it, and a migration wrongly marked applied is skipped forever.0006'sis_family_memberfinance policies before0267narrows them and0275sweeps. End state correct; intermediate state briefly open. Maintenance window, not live traffic.Verification
tsc --noEmitclean · 900 files / 7,700 tests pass · migration audit reports next version0276· dedupe script now finds 0 files to rename.Two tests opened a renamed migration by name and failed to load; both fixed. Swept for other hardcoded paths — the remaining misses are synthetic fixtures and three stale references in
production-migration-contractthat predate this change onmain.🤖 Generated with Claude Code
https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
Generated by Claude Code