229 lines
10 KiB
Markdown
229 lines
10 KiB
Markdown
|
|
---
|
||
|
|
convoy: reconcile-historical-add-scripts
|
||
|
|
brief_number: 1
|
||
|
|
depends_on: []
|
||
|
|
files:
|
||
|
|
- 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.js` — **new**
|
||
|
|
|
||
|
|
## Source script (read-only audit reference; DO NOT EDIT — no-go-zone)
|
||
|
|
|
||
|
|
`scripts/add-card-columns.js` lines 22-33 (verbatim):
|
||
|
|
|
||
|
|
```js
|
||
|
|
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:
|
||
|
|
|
||
|
|
```js
|
||
|
|
/**
|
||
|
|
* 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.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:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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:**
|
||
|
|
|
||
|
|
```markdown
|
||
|
|
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.
|