From 0a010758c3451a89521a2f903e15e81ab6ed858f Mon Sep 17 00:00:00 2001 From: Randall Stillwell Date: Sun, 14 Jun 2026 08:02:37 -0500 Subject: [PATCH] feat(migrations): reconcile cards columns missed by initial-schema backfill (B1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Folds `scripts/add-card-columns.js` into the migration history as B1 of the `reconcile-historical-add-scripts` convoy (architect plan at commit 22ebef2). Adds two columns to `cards` that the initial-schema backfill (1779853647564) did not capture in its bootstrap CREATE TABLE: - cards.quantity INTEGER DEFAULT 0 - cards.favorited BOOLEAN DEFAULT false Both columns exist in every long-lived env (the historical script ran pre-migration-tool) but were missing from fresh-env onboarding via `npm run setup-db` until now. They are flagged "Unused" in docs/SCHEMA_MAP.md § "Known schema smells" #3; the follow-up `drop-dead-cards-columns` convoy will retire them once a query-trace audit confirms zero readers. Reproduced verbatim here to bring fresh envs to prod-parity per the convoy's D3 ratification. Idempotency (D2): both statements use ADD COLUMN IF NOT EXISTS, so the migration is safe to run against fresh Neon branches, long-lived prod envs where add-card-columns.js already ran, or re-applications. Matches the raw `pgm.sql()` style of `1779853647564_initial-schema.js`. `down()` is a hard-stub throw consistent with the rest of the migration corpus's reconciliation/destructive guards. Static idempotency proof — the `cards` CREATE TABLE block in initial-schema (lines 45-69) does NOT contain `quantity` or `favorited`; the three `quantity` hits in that file at lines 76, 103, 127 are on `user_cards`, `collection_cards`, and `deck_cards`. No other migration mentions either column: $ rg -n "quantity|favorited" migrations/ migrations/1779853647564_initial-schema.js:76: quantity INTEGER DEFAULT 1, migrations/1779853647564_initial-schema.js:103: quantity INTEGER DEFAULT 1, migrations/1779853647564_initial-schema.js:127: quantity INTEGER DEFAULT 1, PR #32's NEW post-architect migration `1781440700404_add-scryfall-bulk-columns.js` adds 13 unrelated Scryfall bulk columns (oracle_id, illustration_id, color_identity, keywords, legalities, flavor_text, artist, released_at, layout, edhrec_rank, reserved, reprint, finishes) — verified to not include quantity/favorited; no scope reduction required. Verification: - `node --check migrations/1781000000001_reconcile-cards-columns.js` → exit 0 - `npm run lint` → 0 errors, 1 pre-existing warning on main (components/CollectionsPageView.js, unrelated to this change) - `npm run test:run` → 131/131 tests pass across 26 files - End-to-end `npm run migrate up` against a fresh Neon branch: deferred to operator post-merge verification per D5 (D5 runbook lives in .convoys/reconcile-historical-add-scripts.md § Verification plan) Refs: - Architect plan: .convoys/reconcile-historical-add-scripts.md (commit 22ebef2) - Historical script (no-go-zone, not edited): scripts/add-card-columns.js - SCHEMA_MAP smell entry: docs/SCHEMA_MAP.md § "Known schema smells" #3 Co-authored-by: Cursor --- .../1781000000001_reconcile-cards-columns.js | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 migrations/1781000000001_reconcile-cards-columns.js diff --git a/migrations/1781000000001_reconcile-cards-columns.js b/migrations/1781000000001_reconcile-cards-columns.js new file mode 100644 index 0000000..30ebfb7 --- /dev/null +++ b/migrations/1781000000001_reconcile-cards-columns.js @@ -0,0 +1,82 @@ +/** + * Reconcile cards columns missed by initial-schema backfill (B1 of + * reconcile-historical-add-scripts convoy). + * + * Folds the effects of `scripts/add-card-columns.js` into the migration + * history. That historical script ran against every long-lived environment + * pre-migration-tool and added two columns to the `cards` table that the + * initial-schema backfill (`1779853647564_initial-schema.js`) did NOT + * capture in its bootstrap `CREATE TABLE cards`: + * + * - cards.quantity INTEGER DEFAULT 0 + * - cards.favorited BOOLEAN DEFAULT false + * + * Both columns are flagged as "Unused" in docs/SCHEMA_MAP.md § "Known + * schema smells" #3 (the live quantity/favorited semantics live on + * `user_cards` and `user_favorites`). They are reproduced here verbatim + * so a brand-new Neon branch onboarded by `npm run setup-db` ends up + * structurally equivalent to prod. The follow-up `drop-dead-cards-columns` + * convoy will retire these columns once a query-trace audit confirms zero + * readers — see .convoys/reconcile-historical-add-scripts.md § Follow-ups. + * + * Idempotency (D2): every statement uses `ADD COLUMN IF NOT EXISTS`, so + * this migration is safe to run against: + * + * (a) A fresh Neon branch where initial-schema just ran → columns do + * not exist → ALTER adds them. + * (b) A long-lived env where `scripts/add-card-columns.js` ran + * pre-migration-tool → columns already exist → ALTER is a no-op + * (only the `pgmigrations` row insert is recorded). + * (c) Re-application of this migration → ALTER is a no-op as in (b). + * + * Note on overlap with sibling migrations on the `cards` table: + * - `1781440700404_add-scryfall-bulk-columns.js` (PR #32, post-architect + * plan) adds 13 unrelated Scryfall bulk-data columns to `cards` + * (oracle_id, illustration_id, color_identity, keywords, legalities, + * flavor_text, artist, released_at, layout, edhrec_rank, reserved, + * reprint, finishes). Verified to not include quantity/favorited; + * no scope reduction required. + * - `add-updated-at-column.js` (script #7 in the convoy inventory) is + * already fully captured by initial-schema's `updated_at` column on + * `cards` (line 67), so it is intentionally NOT folded here. + * + * Down-migration is a hard stub. These are reconciliation migrations: the + * prod schema state is the source of truth, and rolling back would create + * fresh-vs-prod inconsistency. If the columns ever need to be dropped, the + * follow-up `drop-dead-cards-columns` convoy will write a new dated + * migration with its own real `down()` — do NOT remove this stub. + * + * @type {import('node-pg-migrate').ColumnDefinitions | undefined} + */ +export const shorthands = undefined; + +/** + * @param {import('node-pg-migrate').MigrationBuilder} pgm + * @returns {void} + */ +export const up = (pgm) => { + pgm.sql(` + ALTER TABLE cards + ADD COLUMN IF NOT EXISTS quantity INTEGER DEFAULT 0 + `); + + pgm.sql(` + ALTER TABLE cards + ADD COLUMN IF NOT EXISTS favorited BOOLEAN DEFAULT false + `); +}; + +/** + * Down-migration is a hard stub. See top-of-file docstring for rationale. + * + * @returns {void} + */ +export const down = () => { + throw new Error( + '[migration:1781000000001_reconcile-cards-columns] Refusing to drop reconciled cards columns. ' + + 'This migration folds historical scripts/add-card-columns.js into the migration history; ' + + 'rolling it back would create fresh-vs-prod schema inconsistency. If cards.quantity / ' + + 'cards.favorited need to be retired, ship a new dated migration via the queued ' + + '`drop-dead-cards-columns` convoy (see .convoys/reconcile-historical-add-scripts.md § Follow-ups).' + ); +}; -- 2.45.2