Ships Brief 7 (documentation + verification runbook) of the
reconcile-historical-add-scripts convoy, ~2 months post-hoc. The
6 implementer briefs (B1-B6) landed 2026-06-14 to 2026-07-06 via
PRs #148, #149, #150, #151, #152, #153. This PR closes the loop:
- Brings the architect's parent convoy file + 6 brief files onto
main (they only existed on the stale convoy/reconcile-historical-
add-scripts branch, never merged)
- Adds § As-shipped to the parent convoy file documenting all 6
squash SHAs + PR numbers + merge dates + the reservation-timestamp
rename (1781000000001-006 → 1781442330001-006 in ec9bb2b, except
B3 which kept its original) + the B6 shipped-as-tiny-migration
deviation from the collapse-to-docs plan
- Fixes docs/SCHEMA_MAP.md § user_favorites (was stale
(user_id, card_id); actual polymorphic (item_type, item_id) per
B4's migration)
- Adds docs/MIGRATION_VERIFICATION_RUNBOOK.md — manual
fresh-Neon-branch vs prod pg_dump diff runbook per architect D5
- Flips .convoys/ship-readiness.md entries:
- reconcile-historical-add-scripts → RESOLVED
- retire-graveyard-scripts-after-audit → UNBLOCKED
- Adds two new queued follow-ups surfaced by the architect:
- unify-user-avatar-column (P3 — dual avatar column smell)
- drop-dead-cards-columns (P3 — cards.quantity + cards.favorited)
No source-code changes. Docs only.
Co-authored-by: Cursor <cursoragent@cursor.com>
10 KiB
| convoy | brief_number | depends_on | files | |
|---|---|---|---|---|
| reconcile-historical-add-scripts | 1 |
|
Brief 1: Reconcile cards columns into migration history
Goal (1 sentence)
Capture the DDL added by scripts/add-card-columns.js (the cards.quantity + cards.favorited columns) into a single new node-pg-migrate migration so a brand-new Neon branch ends up with both columns after npm run setup-db.
Scope (files in scope — do not edit anything else)
migrations/1781000000001_reconcile-cards-columns.js— new
Source script (read-only audit reference; DO NOT EDIT — no-go-zone)
scripts/add-card-columns.js lines 22-33 (verbatim):
await sql`
ALTER TABLE cards
ADD COLUMN IF NOT EXISTS quantity INTEGER DEFAULT 0
`;
// ...
await sql`
ALTER TABLE cards
ADD COLUMN IF NOT EXISTS favorited BOOLEAN DEFAULT false
`;
Sibling script scripts/add-updated-at-column.js is already captured by migrations/1779853647564_initial-schema.js (the cards CREATE TABLE at lines 45-69 already declares updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP). Do NOT add an ALTER for updated_at — it would be redundant noise on pgmigrations. Mention this in the PR body so reviewers don't ask.
Target migration file
Path: migrations/1781000000001_reconcile-cards-columns.js
Contents:
/**
* Reconcile historical `scripts/add-card-columns.js` into the migration
* history. Adds `cards.quantity` and `cards.favorited` columns so a
* brand-new Neon branch ends up with the same shape that prod has had
* since the historical script's one-shot run.
*
* Both columns are flagged as **unused** in docs/SCHEMA_MAP.md
* § "Known schema smells" #3 — `quantity` lives on `user_cards`,
* `favorited` lives on `user_favorites`. They're added here for
* fresh-env parity with prod. A follow-up `drop-dead-cards-columns`
* convoy (queued, P3 hygiene) will drop both columns once a query-trace
* audit confirms zero runtime readers.
*
* Idempotent: re-running against any long-lived prod env is a no-op
* because both ALTERs use IF NOT EXISTS.
*
* Note: `scripts/add-updated-at-column.js` (the sibling historical
* script in the same convoy) is NOT reconciled here because
* `cards.updated_at` is already declared in
* `migrations/1779853647564_initial-schema.js`'s `CREATE TABLE cards`
* (line 67). No further work needed for that script.
*
* @type {import('node-pg-migrate').ColumnDefinitions | undefined}
*/
export const shorthands = undefined;
/**
* @param {import('node-pg-migrate').MigrationBuilder} pgm
*/
export const up = (pgm) => {
pgm.sql(`
ALTER TABLE cards
ADD COLUMN IF NOT EXISTS quantity INTEGER DEFAULT 0;
ALTER TABLE cards
ADD COLUMN IF NOT EXISTS favorited BOOLEAN DEFAULT false;
`);
};
/**
* Down-migration intentionally throws. Dropping these columns on
* long-lived envs requires the `drop-dead-cards-columns` convoy's
* query-trace audit — bypassing it via a casual rollback risks
* dropping data on prod. Use `drop-dead-cards-columns` when ready.
*
* @returns {void}
*/
export const down = () => {
throw new Error(
'[migration:1781000000001_reconcile-cards-columns] Down not supported. ' +
'Dropping cards.quantity / cards.favorited belongs to the queued ' +
'drop-dead-cards-columns convoy, which performs a query-trace audit ' +
'before the DROP. Do not rollback this migration directly.'
);
};
Conventions to follow
.cursor/rules/db-and-schema.mdc§ "Schema source of truth" —migrations/is the canonical home for schema changes; one column-group per migration..cursor/rules/no-go-zones.mdc—scripts/add-card-columns.jsis append-only history. Do not edit it.- Style: match the existing migrations under
migrations/. Use rawpgm.sql(...)template literals (themigration-toolconvoy ratified this in D2 of.convoys/reconcile-historical-add-scripts.md). - ESM exports (
export const up = ...,export const down = ...); nomodule.exports. The repo is"type": "module"perpackage.jsonline 5. - Use
IF NOT EXISTSon every ALTER — idempotent re-apply is a documented requirement of this convoy. - Add a JSDoc docstring at the top of the file explaining what's being reconciled, citing the source script + the convoy file.
Acceptance criteria
migrations/1781000000001_reconcile-cards-columns.jsexists with the exact filename above (the timestamp1781000000001is the reservation token assigned by the architect — do NOT usenpm run migrate create, which would callDate.now()and assign a different timestamp).- The file's
up()adds both columns viaIF NOT EXISTS. - The file's
down()throws with a clear message pointing at thedrop-dead-cards-columnsfollow-up. - The file's docstring cites
scripts/add-card-columns.jsand.convoys/reconcile-historical-add-scripts.md. node --check migrations/1781000000001_reconcile-cards-columns.jspasses (syntactic validity).node -e "import('./migrations/1781000000001_reconcile-cards-columns.js').then(m => console.log(typeof m.up, typeof m.down, typeof m.shorthands))"printsfunction function undefined(module loads cleanly).npm run lintexits clean against the baseline (no new lint errors introduced by this file).npm run test:runreports 21/21 passing (no test surface changes).- The PR body documents that
add-updated-at-column.jsis already captured byinitial-schema(per the source-script section above) and explains why no second migration is added.
Verification
Run the following from the convoy worktree (tcg-vault-worktrees/reconcile-historical-add-scripts/) BEFORE opening the PR:
node --check migrations/1781000000001_reconcile-cards-columns.js
node -e "import('./migrations/1781000000001_reconcile-cards-columns.js').then(m => console.log(typeof m.up, typeof m.down, typeof m.shorthands))"
npm run lint
npm run test:run
Expected:
node --checkexits 0 with no output.- The
node -eline prints exactly:function function undefined. npm run lintmatches the existing baseline (no new errors).npm run test:runreports 21/21 tests passing.
Do NOT run npm run migrate up against any environment as part of this brief — that's a post-merge operator step covered by Brief 7's verification runbook.
Commit message
feat(migrations): reconcile add-card-columns into migration history (brief 1/7)
Captures the DDL effect of scripts/add-card-columns.js (cards.quantity
+ cards.favorited columns) into a new node-pg-migrate migration. Both
columns are flagged as unused in docs/SCHEMA_MAP.md § "Known schema
smells" #3 — added here for fresh-env parity with prod; a follow-up
drop-dead-cards-columns convoy will drop them after a query-trace
audit.
Sibling script add-updated-at-column.js is already captured by
migrations/1779853647564_initial-schema.js (cards.updated_at is in the
CREATE TABLE); no second migration needed.
Per .convoys/reconcile-historical-add-scripts.md § Brief outline → B1.
Idempotent re-apply (IF NOT EXISTS guards).
PR shape
Title: feat(migrations): reconcile add-card-columns into migration history (brief 1/7)
Body template:
Brief 1 of the `reconcile-historical-add-scripts` convoy. See
[`.convoys/reconcile-historical-add-scripts.md`](../.convoys/reconcile-historical-add-scripts.md)
for the full plan and rationale.
## What this PR does
Adds `migrations/1781000000001_reconcile-cards-columns.js` — a new
`node-pg-migrate` migration that adds two columns to `cards` via
`ADD COLUMN IF NOT EXISTS`:
- `quantity INTEGER DEFAULT 0`
- `favorited BOOLEAN DEFAULT false`
Both columns already exist in long-lived prod environments (added by
the historical `scripts/add-card-columns.js`). This migration brings
fresh Neon branches to parity so `npm install` → `npm run setup-db`
alone produces the prod shape, without manually replaying historical
scripts.
## What this PR does NOT do
- Does **NOT** edit `scripts/add-card-columns.js` (no-go-zone).
- Does **NOT** add a migration for `scripts/add-updated-at-column.js`
— `cards.updated_at` is already declared in
`migrations/1779853647564_initial-schema.js` line 67.
- Does **NOT** edit `scripts/setup-neon-db.js`, `package.json`, README,
or AGENTS.md.
- Does **NOT** run `npm run migrate up` against any environment (that's
the post-merge operator step covered by Brief 7's runbook).
- Does **NOT** drop the columns (deferred to follow-up
`drop-dead-cards-columns`).
## Verification checklist
- [ ] `node --check migrations/1781000000001_reconcile-cards-columns.js` exits 0
- [ ] `node -e "import('./migrations/1781000000001_reconcile-cards-columns.js').then(m => console.log(typeof m.up, typeof m.down, typeof m.shorthands))"` prints `function function undefined`
- [ ] `npm run lint` matches baseline (no new errors)
- [ ] `npm run test:run` reports 21/21 passing
- [ ] Did not run `npm run migrate up` against any environment in this PR
- [ ] Operator post-merge: run the verification runbook from Brief 7 (`docs/MIGRATION_VERIFICATION_RUNBOOK.md` once it lands)
## Cross-references
- Convoy file: `.convoys/reconcile-historical-add-scripts.md`
- Source script (no-go-zone, audit reference only): `scripts/add-card-columns.js`
- Follow-up after this convoy lands: `drop-dead-cards-columns` (P3 hygiene)
DO NOT
- DO NOT edit
scripts/add-card-columns.jsor any other file underscripts/— append-only no-go-zone per.cursor/rules/no-go-zones.mdc. - DO NOT edit any other file under
migrations/— each migration is pinned by itspgmigrationsrow. - DO NOT edit
scripts/setup-neon-db.js. - DO NOT edit
package.json(no new deps). - DO NOT edit
AGENTS.mdordocs/SCHEMA_MAP.md— that's Brief 7's job. - DO NOT run
npm run migrate upagainst any environment. - DO NOT call
npm run migrate createto scaffold the file — it usesDate.now()for the timestamp prefix, which would collide with the architect's pre-assigned reservation tokens for parallel briefs.
Rationale (≤3 sentences)
Capturing cards.quantity + cards.favorited in a single small migration matches the per-table grouping of the convoy's D1 decision and keeps the diff easy to review. Sibling add-updated-at-column.js is already captured by initial-schema, so reconciling it would create a pgmigrations row for zero functional benefit. The columns themselves are dead per SCHEMA_MAP smell #3, but parity with prod is the convoy's success metric — the actual DROP is the drop-dead-cards-columns follow-up's responsibility.