deckhearth/.convoys/reconcile-historical-add-scripts/brief-1-reconcile-cards-columns.md
Randall Stillwell 1db6d4dd60 convoy(architect): draft B1-B5 + B7 implementer briefs (Finding 1 → A)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 08:52:04 -05:00

10 KiB

convoy brief_number depends_on files
reconcile-historical-add-scripts 1
migrations/1781000000001_reconcile-cards-columns.js

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.jsnew

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.mdcscripts/add-card-columns.js is append-only history. Do not edit it.
  • Style: match the existing migrations under migrations/. Use raw pgm.sql(...) template literals (the migration-tool convoy ratified this in D2 of .convoys/reconcile-historical-add-scripts.md).
  • ESM exports (export const up = ..., export const down = ...); no module.exports. The repo is "type": "module" per package.json line 5.
  • Use IF NOT EXISTS on 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.js exists with the exact filename above (the timestamp 1781000000001 is the reservation token assigned by the architect — do NOT use npm run migrate create, which would call Date.now() and assign a different timestamp).
  • The file's up() adds both columns via IF NOT EXISTS.
  • The file's down() throws with a clear message pointing at the drop-dead-cards-columns follow-up.
  • The file's docstring cites scripts/add-card-columns.js and .convoys/reconcile-historical-add-scripts.md.
  • node --check migrations/1781000000001_reconcile-cards-columns.js passes (syntactic validity).
  • 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 (module loads cleanly).
  • npm run lint exits clean against the baseline (no new lint errors introduced by this file).
  • npm run test:run reports 21/21 passing (no test surface changes).
  • The PR body documents that add-updated-at-column.js is already captured by initial-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 --check exits 0 with no output.
  • The node -e line prints exactly: function function undefined.
  • npm run lint matches the existing baseline (no new errors).
  • npm run test:run reports 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.js or any other file under scripts/ — 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 its pgmigrations row.
  • DO NOT edit scripts/setup-neon-db.js.
  • DO NOT edit package.json (no new deps).
  • DO NOT edit AGENTS.md or docs/SCHEMA_MAP.md — that's Brief 7's job.
  • DO NOT run npm run migrate up against any environment.
  • DO NOT call npm run migrate create to scaffold the file — it uses Date.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.