---
convoy: reconcile-historical-add-scripts
brief_number: 7
depends_on: [1, 2, 3, 4, 5]
files:
  - 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 install` → `npm run setup-db` alone and verify the result.

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

- `docs/SCHEMA_MAP.md` — **modified**
- `docs/MIGRATION_VERIFICATION_RUNBOOK.md` — **new**
- `AGENTS.md` — **modified** (small Gotcha #6 cross-reference update only)
- `.convoys/ship-readiness.md` — **modified** (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`:

   ```markdown
   ### 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:

   ```markdown
   ### 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:

   ```markdown
   ### 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:

    ```markdown
    ## 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:

```markdown
# 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 install` → `npm run setup-db` alone. Operator
> verification runbook at
> [`docs/MIGRATION_VERIFICATION_RUNBOOK.md`](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-scripts`** — **RESOLVED 2026-06-14** by [convoy](../.convoys/reconcile-historical-add-scripts.md) (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 install` → `npm run setup-db` alone. Operator verification at [`docs/MIGRATION_VERIFICATION_RUNBOOK.md`](../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

```bash
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:**

```markdown
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.
