deckhearth/.convoys/reconcile-historical-add-scripts/brief-7-documentation-and-verification.md
varutasu 0d52858bbd
docs: post-hoc B7 closure for reconcile-historical-add-scripts (#163)
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>
2026-08-15 16:31:37 -05:00

25 KiB

convoy brief_number depends_on files
reconcile-historical-add-scripts 7
1
2
3
4
5
docs/SCHEMA_MAP.md
docs/MIGRATION_VERIFICATION_RUNBOOK.md
AGENTS.md
.convoys/ship-readiness.md

Brief 7: Documentation + verification runbook

Goal (1 sentence)

Update the four documentation surfaces that describe the post-convoy schema and onboarding state — docs/SCHEMA_MAP.md (correct doc bugs + cross-reference new migrations), docs/MIGRATION_VERIFICATION_RUNBOOK.md (new — manual operator runbook from D5), AGENTS.md Gotcha #6 (cross-reference this convoy as the closing follow-up), .convoys/ship-readiness.md (flip this convoy's queued entry to RESOLVED + unblock retire-graveyard-scripts-after-audit + add new follow-ups) — so the next operator onboarding a fresh Neon branch can do so by npm installnpm run setup-db alone and verify the result.

Scope (files in scope — do not edit anything else)

  • docs/SCHEMA_MAP.mdmodified
  • docs/MIGRATION_VERIFICATION_RUNBOOK.mdnew
  • AGENTS.mdmodified (small Gotcha #6 cross-reference update only)
  • .convoys/ship-readiness.mdmodified (queued convoys section)

This brief is sequenced last because it cross-references the 5 new migration files that B1-B5 add; it cannot land before B1-B5 are merged. The architect's slice_dependencies block in the convoy file declares depends_on: [1, 2, 3, 4, 5].

Per-file scope

1. docs/SCHEMA_MAP.md — modified

Targeted edits (keep all other content as-is):

  1. Preamble update. The current preamble (lines 1-18) notes that "the initial backfill captures only the post-setup-neon-db.js shape. … A follow-up convoy (reconcile-historical-add-scripts) will fold the historical effects into the migration history; until then this file remains the curated reference for the full prod shape." Replace with a sentence noting the convoy has landed, the migration history now captures the full prod shape, and the operator verification runbook lives at docs/MIGRATION_VERIFICATION_RUNBOOK.md. Bump the "Last reviewed" date.

  2. ### users notes column updates (lines 43-57): each row references add-user-profile-columns.js / add-user-profile-fields.js — leave those references in place (they're historical context); ADD a single line at the end of the table noting "All columns above are now captured by migrations/1781000000005_reconcile-user-profile.js (B5 of reconcile-historical-add-scripts, 2026-06-14)."

  3. ### cards notes column updates (lines 78-79): for the quantity and favorited rows, change the "Unused; consider dropping" annotation to "Unused; captured by migrations/1781000000001_reconcile-cards-columns.js for fresh-env parity. Drop tracked as queued drop-dead-cards-columns follow-up."

  4. ### user_cards index/constraint notes (lines 90-102): add a brief note clarifying the canonical constraint is UNIQUE(user_id, card_id, is_foil) (3-col) from initial-schema, and that scripts/fix-user-cards-constraints.js's stricter 2-col variant was either never-applied or reverted (Finding 1 of the reconcile convoy → Outcome A, 2026-06-14). This is the SCHEMA_MAP equivalent of the Drift Finding 1 resolution.

  5. ### user_favorites table (lines 104-111): FIX THE DOC BUG. Current shape lists only (user_id, card_id). Replace with the actual polymorphic shape per the runtime in pages/api/favorites.js:

    ### user_favorites
    
    | Column | Type | Notes |
    | --- | --- | --- |
    | `id` | `SERIAL PK` | |
    | `user_id` | `INTEGER FK users(id) ON DELETE CASCADE` | |
    | `item_type` | `VARCHAR(50) NOT NULL` | `'card' | 'collection' | 'deck'` — polymorphic |
    | `item_id` | `INTEGER NOT NULL` | FK depends on `item_type`; not enforced at DB level |
    | `created_at` | `TIMESTAMP` | |
    | | | **UNIQUE(user_id, item_type, item_id)** |
    
    Indexes: `idx_user_favorites_user_id`, `idx_user_favorites_item_type`,
    `idx_user_favorites_item_id`, `idx_user_favorites_user_type
    (user_id, item_type)` — all in `migrations/1781000000004_reconcile-favorites-system.js`.
    
  6. ### collections notes: add a line at the bottom noting "visibility, tcg, tags, slug (+ idx_collections_slug unique + check_slug_format CHECK), image are now captured by migrations/1781000000002_reconcile-collections-columns.js."

  7. ### collection_permissions + ### collection_activity sections: add a line at the bottom of each noting "Captured by migrations/1781000000003_reconcile-collaboration-tables.js."

  8. NEW SECTION: ### user_settings (currently a one-liner at line 217). Expand to a proper column table matching the actual schema:

    ### user_settings
    
    Per-user key/value store for settings that don't warrant a column on `users`.
    Captured by `migrations/1781000000005_reconcile-user-profile.js`.
    
    | Column | Type | Notes |
    | --- | --- | --- |
    | `id` | `SERIAL PK` | |
    | `user_id` | `INTEGER FK users(id) ON DELETE CASCADE` | |
    | `setting_key` | `VARCHAR(100) NOT NULL` | |
    | `setting_value` | `JSONB NOT NULL` | |
    | `created_at`, `updated_at` | `TIMESTAMP` default now | |
    | | | **UNIQUE(user_id, setting_key)** |
    
    Indexes: `idx_user_settings_user_id`, `idx_user_settings_key`.
    
  9. ### user_avatars expansion (currently one paragraph at lines 222-223). Replace with:

    ### user_avatars
    
    Tracks uploaded avatar history. Captured by
    `migrations/1781000000005_reconcile-user-profile.js`. Older avatars
    are typically deleted from blob storage; verify the cleanup job runs.
    
    | Column | Type | Notes |
    | --- | --- | --- |
    | `id` | `SERIAL PK` | |
    | `user_id` | `INTEGER FK users(id) ON DELETE CASCADE` | |
    | `filename` | `VARCHAR(255) NOT NULL` | |
    | `original_name` | `VARCHAR(255)` | |
    | `mime_type` | `VARCHAR(100)` | |
    | `file_size` | `INTEGER` | |
    | `file_path` | `TEXT NOT NULL` | |
    | `is_active` | `BOOLEAN DEFAULT true` | |
    | `created_at`, `updated_at` | `TIMESTAMP` default now | |
    
    Indexes: `idx_user_avatars_user_id`, `idx_user_avatars_active (user_id, is_active)`.
    
  10. ## Known schema smells section updates (lines 226-232):

    • Smell #2 (is_public vs visibility): add a sentence noting both columns are now captured by separate migrations (initial-schema for is_public, B2 for visibility); resolution lives in a future unify-collection-visibility follow-up.
    • Smell #3 (cards.quantity + cards.favorited): add the cross-reference to the queued drop-dead-cards-columns follow-up.
    • NEW: Smell #7 — users.profile_image_url vs users.avatar_url (the parity smell that B5 perpetuates intentionally). Both columns are present for prod parity; cleanup is the queued unify-user-avatar-column follow-up.
  11. ## Regeneration section update (lines 234-245): the manual-regeneration instructions can be removed entirely since the migration history is now authoritative. Replace with:

    ## Regeneration
    
    The migration history under `migrations/` is the authoritative source
    of truth. To verify this file matches a live env (prod, preview, or a
    fresh Neon branch), use the operator runbook at
    [`docs/MIGRATION_VERIFICATION_RUNBOOK.md`](MIGRATION_VERIFICATION_RUNBOOK.md).
    
    When you add a new migration, update the relevant table section here
    in the same PR. New tables get a new `###` section with the same
    column-table shape.
    

2. docs/MIGRATION_VERIFICATION_RUNBOOK.md — new

Lift verbatim from .convoys/reconcile-historical-add-scripts.md § Verification plan (D5), with a small intro framing it as the canonical operator runbook (not just a convoy artifact). Suggested skeleton:

# Migration verification runbook

How to verify that the migration history under `migrations/` produces
the same schema as a long-lived environment (prod, preview, or
similar). Use this runbook:

- **After this repo's `reconcile-historical-add-scripts` convoy** (the
  initial reconciliation), to confirm a fresh Neon branch reaches
  parity with prod via `npm install``npm run setup-db` alone.
- **After any new migration lands on `main`**, to spot-check that
  applying the migration to prod (via the operator's
  `POSTGRES_URL=<prod> npm run migrate up`) produced the intended
  effect.
- **When suspecting drift** between an env's actual schema and the
  migration history (rare; the migration history is authoritative).

This runbook is the manual precursor to the automated check planned
in the queued `wire-migrate-into-ci` follow-up (see
`.convoys/ship-readiness.md` § "Queued convoys").

## Prerequisites

- `pg_dump` (PostgreSQL 16+) installed locally.
- `POSTGRES_URL` for the env you're verifying.
- `ADMIN_INITIAL_PASSWORD` (for the fresh-branch onboarding step) —
  see `AGENTS.md` § 5 "Running locally".
- A Neon account with permission to create a branch (or any other
  way to spin up a fresh Postgres DB on the same major version as
  prod).

## Procedure

### Step 1 — Snapshot the reference env's structural shape

Run against the env you consider canonical (usually prod):

\`\`\`bash
POSTGRES_URL=<reference-url> pg_dump --schema-only --no-owner --no-acl \
  --schema=public > /tmp/reference-schema.sql
\`\`\`

### Step 2 — Create a fresh DB and onboard via `setup-db`

Create a clean Neon branch from an **empty** parent (or any other
fresh Postgres DB on the same major version):

\`\`\`bash
POSTGRES_URL=<fresh-branch-url> \
ADMIN_INITIAL_PASSWORD=$(openssl rand -base64 24) \
  npm run setup-db
\`\`\`

`setup-db` runs `npm run migrate up` (applying every migration in
`migrations/` in timestamp order) and seeds the admin user.

### Step 3 — Snapshot the fresh DB's structural shape

\`\`\`bash
POSTGRES_URL=<fresh-branch-url> pg_dump --schema-only --no-owner --no-acl \
  --schema=public > /tmp/fresh-schema.sql
\`\`\`

### Step 4 — Diff

\`\`\`bash
diff <(sort /tmp/reference-schema.sql) <(sort /tmp/fresh-schema.sql)
\`\`\`

**Expected non-material differences** (acceptable; do not chase):

- Constraint or index NAME differences. Prod constraints created via
  the historical `scripts/add-*` / `scripts/fix-*` jobs may have
  autogenerated tuple-UNIQUE names that differ from the migrations'
  explicit names.
- Column-ORDER differences. Prod has columns in historical-script-ALTER
  order; fresh envs have them in migration-order.

**Material differences** (bug — fix before declaring verified):

- A column type, default, or NULL/NOT NULL state that differs.
- A missing or extra table.
- A missing or extra CHECK constraint that changes accepted values.
- A missing or extra index that changes query plan shape.

## Supplementary spot-checks

For the highest-risk surfaces (frequently-edited tables), run these
information-schema queries against both envs and confirm the
column-count / constraint-count / index-count totals match exactly:

\`\`\`sql
-- Every column on every table
SELECT table_name, column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema = 'public'
ORDER BY table_name, ordinal_position;

-- Every constraint
SELECT table_name, constraint_name, constraint_type
FROM information_schema.table_constraints
WHERE table_schema = 'public'
ORDER BY table_name, constraint_name;

-- Every index
SELECT tablename, indexname, indexdef
FROM pg_indexes
WHERE schemaname = 'public'
ORDER BY tablename, indexname;
\`\`\`

A mismatch in column count, constraint count, or index count is a
material difference and indicates a bug.

## What to do if you find a material difference

1. **Identify which env is canonical.** Usually prod. If the diff
   surfaces a missing column on prod that's in the migration history,
   the migration was never applied to prod — run
   `POSTGRES_URL=<prod> npm run migrate up` to catch up.
2. **If the diff surfaces a column on prod that's NOT in the
   migration history**, you've found a drift bug. Write a new
   reconciliation migration (under `migrations/`) that captures the
   prod column, following the pattern in `.convoys/reconcile-historical-add-scripts/`.
3. **Do not edit existing migrations** — they're pinned by
   `pgmigrations` rows. Always write a new dated migration to correct
   schema.

## Cross-references

- `AGENTS.md` § 4 Gotcha #6 — migration tool adoption history.
- `.convoys/migration-tool.md` — the convoy that adopted `node-pg-migrate`.
- `.convoys/reconcile-historical-add-scripts.md` — the convoy that folded
  the 13 historical scripts into the migration history.
- `.cursor/rules/db-and-schema.mdc` — schema-change conventions.

3. AGENTS.md — modified (small Gotcha #6 cross-reference)

Gotcha #6 is already "RESOLVED" per migration-tool (2026-05-26). This brief adds one sentence to the end of Gotcha #6 cross-referencing this convoy as the closing follow-up. Verbatim addition (insert immediately before the - **#7** line at AGENTS.md line 120):

Post-reconcile-historical-add-scripts (2026-06-14), the migration history additionally captures the full effect of the 13 historical scripts/add-*.js / scripts/fix-*.js / scripts/seed-*.js jobs (where applicable — pure-DML seed scripts stay out per D4 of that convoy). A brand-new Neon branch can now be onboarded by npm installnpm run setup-db alone. Operator verification runbook at docs/MIGRATION_VERIFICATION_RUNBOOK.md. The 13 historical scripts remain no-go-zones until the queued retire-graveyard-scripts-after-audit (P3) cleanup convoy lands; that convoy is now unblocked.

Do NOT flip Gotcha #6's RESOLVED marker — it's already RESOLVED by the right convoy (migration-tool). Just append the cross-reference.

4. .convoys/ship-readiness.md — modified (queued convoys section)

Find the existing line under § "Queued convoys" that reads:

  • reconcile-historical-add-scripts (priority: P1 quality — needed for fresh-env onboarding). Surfaced 2026-05-26 by migration-tool (PR #32). Fold the effects of the 27 historical scripts/add-*.js / fix-*.js / seed-*.js jobs … Documented in .convoys/migration-tool.md § R1.

Replace with:

  • reconcile-historical-add-scriptsRESOLVED 2026-06-14 by convoy (PRs #XX-#XX). Captured 7 of the 13 historical scripts' effects into 5 new migrations under migrations/ (B1-B5); 2 scripts were already captured by initial-schema + 1780378340194_system-collection-description; 3 are DML-only and stay as dev fixtures; 1 (fix-user-cards-constraints.js) is a no-op on prod per Finding 1 (Outcome A — the canonical 3-col UNIQUE(user_id, card_id, is_foil) from initial-schema is what prod has, and fix-user-cards-constraints.js's stricter 2-col variant is not present). A brand-new Neon branch now onboards via npm installnpm run setup-db alone. Operator verification at docs/MIGRATION_VERIFICATION_RUNBOOK.md. Entry kept (not deleted) for audit trail.

Find the existing line:

  • retire-graveyard-scripts-after-audit (priority: P3 polish; blocked on reconcile-historical-add-scripts). …

Replace the **blocked on reconcile-historical-add-scripts** marker with **UNBLOCKED 2026-06-14** and leave the rest of the description intact.

Add two new queued entries (driven by Findings 2 + 3 of the reconcile convoy):

  • unify-user-avatar-column (priority: P3 hygiene). Surfaced 2026-06-14 by reconcile-historical-add-scripts Finding 2. users.profile_image_url (added by scripts/add-user-profile-columns.js) and users.avatar_url (added by scripts/add-user-profile-fields.js) coexist on prod and in the migration history (both columns are needed for parity per B5). Pick one canonical column, migrate the other's data to it, drop the loser, and sweep runtime readers in pages/api/user/avatar*.js + UI surfaces. Requires a query-trace audit first.
  • drop-dead-cards-columns (priority: P3 hygiene). Surfaced 2026-06-14 by reconcile-historical-add-scripts Finding 3. cards.quantity INTEGER and cards.favorited BOOLEAN are documented unused (per docs/SCHEMA_MAP.md § "Known schema smells" #3); reconciled into the migration history by B1 for parity, but the actual semantics live on user_cards / user_favorites. After a query-trace audit confirms zero runtime readers, ship a migration that DROPs both columns with a real down() that recreates them.

(Do NOT add the withdrawn add-system-collection-on-register or unify-user-cards-foil-tracking entries — both were withdrawn during the reconcile convoy. See .convoys/reconcile-historical-add-scripts.md § Follow-ups.)

Conventions to follow

  • .cursor/rules/no-go-zones.mdc — no edits to scripts/add-* / fix-* / seed-*.
  • .cursor/rules/db-and-schema.mdc — schema-change conventions; SCHEMA_MAP is updated alongside any migration.
  • Markdown style: match the existing tone of docs/SCHEMA_MAP.md (compact tables, "Notes" column explains intent not type semantics) and AGENTS.md (numbered Gotchas, cross-reference convoy files).
  • For .convoys/ship-readiness.md: match the existing entry style under § "Queued convoys" (one bullet per convoy, leading - **name**, RESOLVED entries get the "Entry kept (not deleted) for audit trail" closer when applicable).

Acceptance criteria

  • docs/SCHEMA_MAP.md preamble bumped (post-convoy state); "Last reviewed" date is 2026-06-14.
  • docs/SCHEMA_MAP.md § user_favorites shape is the polymorphic (item_type, item_id) — NOT the previous (card_id) shape.
  • Every reconciled table/column has a "captured by migrations/<file>" cross-reference.
  • ### user_settings and ### user_avatars sections are expanded from one-liners to full column tables matching B5's migration.
  • New schema smell #7 (profile_image_url vs avatar_url) is added to § "Known schema smells".
  • docs/MIGRATION_VERIFICATION_RUNBOOK.md exists with the 4-step procedure from D5 + the spot-check queries + the "material vs non-material differences" guidance.
  • AGENTS.md Gotcha #6 gains a single appended paragraph cross-referencing this convoy + the new runbook + the unblocked retire-graveyard-scripts-after-audit follow-up. Gotcha #6's existing "RESOLVED by migration-tool" marker is NOT changed.
  • .convoys/ship-readiness.md § "Queued convoys" → reconcile-historical-add-scripts entry is flipped to RESOLVED 2026-06-14 with a one-paragraph summary; retire-graveyard-scripts-after-audit is marked UNBLOCKED 2026-06-14; two new entries (unify-user-avatar-column, drop-dead-cards-columns) are added.
  • No edits to any file under scripts/, migrations/, pages/, lib/, components/, test/, or package.json.
  • npm run lint matches baseline (these are markdown-only edits; lint should be unaffected).
  • npm run test:run reports 21/21 passing (these are markdown-only edits; no test surface).

Verification

npm run lint
npm run test:run

Both must match baseline / be green. No additional verification for this brief — the migrations themselves are exercised by Brief 7's verification runbook AFTER an operator runs it manually post-merge.

For the SCHEMA_MAP edits, eyeball the diff against the prior state and confirm every cross-reference to a migration file is accurate (the file actually exists, and the timestamp matches the assigned reservation).

Commit message

docs(reconcile): update SCHEMA_MAP, verification runbook, AGENTS, ship-readiness (brief 7/7)

Closes the reconcile-historical-add-scripts convoy:

  - docs/SCHEMA_MAP.md: cross-reference 5 new migrations from B1-B5;
    fix user_favorites doc bug (polymorphic shape, not (card_id));
    expand user_settings + user_avatars one-liners to full column tables;
    add schema smell #7 (profile_image_url vs avatar_url redundancy).
  - docs/MIGRATION_VERIFICATION_RUNBOOK.md (new): canonical operator
    runbook for verifying fresh-env vs prod schema parity (lifted from
    D5 of the convoy).
  - AGENTS.md Gotcha #6: append cross-reference to this convoy + the
    new runbook + the now-unblocked retire-graveyard-scripts-after-audit
    follow-up. Existing "RESOLVED by migration-tool" marker unchanged.
  - .convoys/ship-readiness.md: flip reconcile-historical-add-scripts
    entry to RESOLVED 2026-06-14; mark retire-graveyard-scripts-after-audit
    UNBLOCKED; add new queued unify-user-avatar-column (Finding 2) +
    drop-dead-cards-columns (Finding 3).

Per .convoys/reconcile-historical-add-scripts.md § Brief outline → B7.
Depends on B1-B5 having merged (this brief cross-references their files).

PR shape

Title: docs(reconcile): update SCHEMA_MAP, verification runbook, AGENTS, ship-readiness (brief 7/7)

Body template:

Brief 7 (final) 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.

This is the closing brief — depends on B1-B5 having merged (PR
references below).

## What this PR does

- **`docs/SCHEMA_MAP.md`** — cross-reference all 5 new migrations from
  B1-B5; fix the `user_favorites` doc bug (the table uses the
  polymorphic `(item_type, item_id)` shape, not `(card_id)`); expand
  `user_settings` + `user_avatars` from one-liners to full column
  tables matching the B5 migration; add a new "Known schema smell" #7
  for the `profile_image_url` / `avatar_url` redundancy.
- **`docs/MIGRATION_VERIFICATION_RUNBOOK.md`** (new) — canonical
  operator runbook for verifying fresh-env vs prod schema parity.
  Pulls verbatim from D5 of the convoy file. Useful both for the
  one-time reconciliation verification and for ongoing post-migration
  spot-checks.
- **`AGENTS.md`** — append one paragraph to Gotcha #6 cross-referencing
  this convoy as the closing follow-up. The "RESOLVED by `migration-tool`"
  marker is unchanged (that's still the right resolution attribution
  for the gotcha itself).
- **`.convoys/ship-readiness.md`** § "Queued convoys" — flip the
  `reconcile-historical-add-scripts` entry to RESOLVED 2026-06-14;
  flip `retire-graveyard-scripts-after-audit` from "blocked on
  reconcile-historical-add-scripts" to UNBLOCKED 2026-06-14; add two
  new queued entries (`unify-user-avatar-column` from Finding 2,
  `drop-dead-cards-columns` from Finding 3).

## Prerequisite PRs (all must be merged first)

- B1 #XX — reconcile cards columns
- B2 #XX — reconcile collections columns
- B3 #XX — reconcile collaboration tables
- B4 #XX — reconcile favorites system
- B5 #XX — reconcile user profile

## What this PR does NOT do

- Does **NOT** edit any file under `migrations/`, `scripts/`, `pages/`,
  `lib/`, `components/`, `test/`, or `package.json`.
- Does **NOT** run the verification runbook itself — that's the
  operator's post-merge job; see the runbook's "Procedure" section.

## Verification checklist

- [ ] `npm run lint` matches baseline
- [ ] `npm run test:run` reports 21/21 passing
- [ ] Every "captured by `migrations/<file>.js`" cross-reference in
  SCHEMA_MAP points at a file that actually exists in `migrations/`
  (eyeballed against `ls migrations/`)
- [ ] `AGENTS.md` Gotcha #6 still has its original RESOLVED marker
  (we only APPEND to it, not rewrite it)
- [ ] `.convoys/ship-readiness.md` § "Queued convoys" has the flipped
  `reconcile-historical-add-scripts` entry, the UNBLOCKED
  `retire-graveyard-scripts-after-audit` marker, and the two new
  follow-up entries
- [ ] Operator post-merge: run `docs/MIGRATION_VERIFICATION_RUNBOOK.md`
  against prod + a fresh Neon branch and confirm the diff is
  non-material

## Cross-references

- Convoy file: `.convoys/reconcile-historical-add-scripts.md`
- Per-brief files: `.convoys/reconcile-historical-add-scripts/brief-{1,2,3,4,5}-*.md`

DO NOT

  • DO NOT edit any file under scripts/, migrations/, pages/, lib/, components/, test/.
  • DO NOT edit package.json, README, next.config.js, or any rule under .cursor/rules/.
  • DO NOT change AGENTS.md Gotcha #6's "RESOLVED by migration-tool" marker — append the new paragraph; don't rewrite the existing resolution attribution.
  • DO NOT add a B6 reference anywhere — that brief was removed (Finding 1 → Outcome A).
  • DO NOT add add-system-collection-on-register or unify-user-cards-foil-tracking to the ship-readiness queued list — both were withdrawn during this convoy.
  • DO NOT remove the existing wire-migrate-into-ci entry from ship-readiness — it's still queued and unrelated to this convoy.
  • DO NOT run the verification runbook itself (that's the operator's post-merge step).

Rationale (≤3 sentences)

Sequencing this brief last lets every documentation cross-reference point at a real file under migrations/ rather than a placeholder. Bundling all four doc surfaces into one PR keeps the convoy's "as-shipped" record consistent (the migrations, the operator runbook, the AGENTS gotcha, and the ship-readiness ledger all flip together). The docs/MIGRATION_VERIFICATION_RUNBOOK.md extraction promotes a one-time convoy artifact into an evergreen operator tool that future migrations can reuse.