deckhearth/.convoys/pick-a-name.md
Randall Stillwell d640795959 docs: post-convoy cleanup for pick-a-name — first post-P0 P1 convoy
Reflects the merged pick-a-name convoy (PR #21, squash commit 9abbab6)
in repo documentation. **This is the first post-P0 cleanup** —
add-rate-limiting (squash 708ef45) closed the LAST P0 ship-blocker on
2026-05-24, and pick-a-name immediately follows as the first post-P0
P1 quality-bar convoy, closing P1 #12 (brand-consistency — the
inconsistency `AGENTS.md` line 5 had flagged since project setup).
Two file-disjoint briefs landed serially (B1 commit ac8c998
display + comment sweep, B2 commit 1c18d21 infrastructure + email
migration); five canonical-string D-decisions ratified verbatim at
gate-1 plus Risk 4 PRESERVE on the negative regression-lock literal.
Architect commit 50ce9ab; no scope expansions and no implementer
deviations from the verbatim spec beyond the parallel-vs-serial
dispatch choice (parallelizable in theory; serial in practice for
lockfile-regen + migration-script audit cleanliness).

.convoys/pick-a-name.md:
  - frontmatter Status: in-progress -> shipped (added Shipped:
    2026-05-24 line, matching the file's existing **Status:** /
    **Opened:** markdown-line shape rather than YAML frontmatter).
  - new ## As-shipped section replacing the placeholder
    "(To be appended post-merge by role-doc-writer.)" stub. Opens
    with the post-P0 transition language pointing at ship-readiness's
    "8 of 8 RESOLVED" baseline + this convoy's role as the first
    P1 quality-bar closure. Decisions section captures all five
    D-ratifications verbatim (D1 Deck Hearth two-word internal-cap;
    D2 deck-hearth kebab matching the existing CSS class + Node
    convention; D3 deckhearth single-token for IDs/Redis; D4
    admin@deckhearth.com placeholder TLD pre-domain-acquisition,
    swept across admin/alice/bob uniformly; D5 full deckhearth
    Redis prefix not abbreviated dh, with the architect's
    self-documenting-prefix-vs-memory-savings rationale preserved)
    plus the Risk 4 PRESERVE on test/lib/permission-middleware.test.js
    line 87's negative regression-lock literal — including the
    project-pattern justification (Gotchas #2/#3/#4/#5/#8 in
    AGENTS.md all preserve historical-shape locks for the same
    audit-trail reason). As-shipped surface broken into B1/B2 split
    with file-disjoint inventory + the serial-in-practice dispatch
    note (the conductor's parallel-via-multitask path stayed open as
    fallback). As-shipped diff captures B1 (7+/7-) + B2 (133+/30-)
    + lockfile STOP-on-churn confirmation (only the two `name` lines
    diffed; npm ci clean against regen). Empirical CI metrics from
    post-merge run 26387909625 (Aggregate gate 10s, Lint 45s 128
    baseline, forbidden-endpoints + forbidden-cors-headers 4s each,
    Playwright smoke 1m4s 3/3, Screenshot diff 1m5s continue-on-error
    swallow per Decision 4, vitest 21/21 in 35s with Risk 4 PRESERVE
    test green). Cross-validation finding: 4th convoy in a row where
    the same 3-test smoke spec defends the auth surface through a
    sweeping change (PR #15 Layout -> #19 CORS -> #20 rate-limit ->
    #21 brand-rename). Implementer-vs-architect calibration note on
    the B2 forecast paragraph vs § Full surface inventory — both
    correct, cross-reference between them could be tighter for the
    next architect. Operator-action-required-going-forward: run the
    new migration script before the next admin login attempt
    (idempotent + UNIQUE-collision-safe). What did NOT change audit
    trail (~25 lines of historical .convoys/ tcgvault references stay
    byte-identical per no-go-zones, plus seed-collections-*.js
    historical scripts with stale WHERE-clauses that now no-op
    harmlessly post-migration, plus repo-name references deferred to
    rename-repo-and-vercel-project, plus the docs/agent-context
    + add-api-route SKILL.md repo-name references that sweep with
    the same downstream convoy).

.convoys/ship-readiness.md:
  - § Status summary: P1-quality-bar-work-in-progress line added
    below the existing 8/8 RESOLVED milestone footer, citing
    pick-a-name as the first such convoy.
  - § P0 launch sequence step 5 (pick-a-name): marked RESOLVED
    2026-05-24 with the squash commit + brief shape + smoke metrics
    + operator-action callout inline, mirroring the format of step 4
    (add-rate-limiting RESOLVED) introduced by the previous
    doc-writer pass.
  - § Queued convoys: four new entries surfaced by pick-a-name
    inserted as a logical group after purge-weak-creds-from-helpers
    (the natural neighbor — all brand-bound + credential-bound
    post-pick-a-name follow-ups):
    - rename-repo-and-vercel-project (P2 polish; GitHub repo +
      Vercel project rename; auto-redirects make low-urgency; lists
      the 8 architect-verified literal-repo references that sweep
      together; Risk 5 .agent-context-manifest.yml source-tag
      re-evaluation noted).
    - point-domain-at-deckhearth (P2 polish; blocked on domain
      acquisition; DNS pointer + Vercel domain claim + admin email
      TLD swap if non-`.com`; current placeholder-email rationale
      preserved — auth uses email as identity not as mail target).
    - regenerate-brand-assets (P2 polish; favicon, OG images, social
      cards; requires design pass; cross-references the original
      Role-design-system-auditor finding about unifying DH logo +
      AnimatedFireLogo).
    - convert-reset-db-to-esm (P2 hygiene; CJS-in-ESM bug at
      scripts/reset-db.js:142; fold-into-purge-weak-creds-from-helpers
      noted since both convoys touch the same file).
  - seed-visual-baselines-on-linux entry: extended with the
    AFTER-pick-a-name ordering note (now satisfied — 9abbab6 merged
    2026-05-24); first Linux baseline will capture Deck Hearth strings
    not pre-rename TCG Vault strings.
  - purge-weak-creds-from-helpers entry: scope narrowed — the
    @tcgvault.com email-half is now satisfied by pick-a-name B2;
    remaining scope is the admin123/alice123/bob123 weak password
    literals only (which survived pick-a-name deliberately per
    Brief 2's scope boundary). Fold-into-convert-reset-db-to-esm
    suggested (overlapping file).

AGENTS.md:
  - line 5 (top-of-file branding note): rewritten in full from the
    B1-installed transitional shape ("Deck Hearth is the canonical
    product brand (ratified 2026-05-24...)") to the post-merge
    documented shape with the squash commit + PR # citation + the
    Vercel-project parenthetical + the Risk 4 PRESERVE caveat
    citing test/lib/permission-middleware.test.js. **This is the
    showstopper change** — it's the line that's been flagged as
    inconsistent since project setup; the rewrite finally closes
    the brand-consistency loop end-to-end.
  - line 13 (§ 1 Project overview Auth bullet): stale
    `admin@tcgvault.com` reference updated to `admin@deckhearth.com`
    to match the post-B2 seed state. Per Brief 2's "Files explicitly
    out of scope" line: "AGENTS.md line 13 = doc-writer scope".
  - Gotcha #4 (Default admin credentials in the seed): extended
    with a new paragraph below the existing operator-caveat copy
    documenting the post-pick-a-name email rename + the migration
    script + the migration-FIRST-then-setup-db ordering rule + the
    UNIQUE-collision-safe failure mode. Existing drop-public-setup
    operator-caveat copy preserved verbatim above the new content.
  - Gotcha #12 (Rate-limit env vars): per-class limiter table
    extended with a Redis-prefix column showing the renamed
    `deckhearth:*` values (was implicit/missing); one-liner about
    the rename + accepted counter reset added immediately below the
    table. Original env-var contract paragraph
    (KV_REST_API_URL / KV_REST_API_TOKEN, fail-closed-in-prod /
    warn-and-noop-in-dev) preserved verbatim above the new table.
    The defensive THROW + gate-ordering-rule + sixth-class-shape
    paragraphs below stay byte-identical from the
    add-rate-limiting milestone doc-writer pass.

.cursor/rules/api-routes.mdc:
  - § Rate limiting opening lineage line: extended to add the
    pick-a-name attribution (squash 9abbab6, PR #21, 2026-05-24)
    and to note the rename is namespace-only — call shape, return
    shape, and gate-ordering rules below are byte-identical
    post-rename. The convoy-attribution lineage now reads:
    fix-auth-bypass Brief 4 (auth class) -> add-rate-limiting (the
    other four classes + 7 routes) -> pick-a-name (Redis prefix
    rename). No example code blocks in this rule had `tcgvault:`
    literals to swap (verified via re-read); the per-class table
    earlier in the rule doesn't enumerate prefix values either,
    so the lineage line is the only edit needed.

Files explicitly NOT changed: package.json, package-lock.json,
lib/rate-limit.js, lib/**, pages/**, components/**, scripts/**,
test/**, tests/**, .github/workflows/**, README.md, TESTING_GUIDE.md,
playwright.config.js, eslint.config.mjs,
scripts/migrations/2026-05-24-rename-admin-email.js. Doc-writer
pass touches docs only. The four queued downstream convoys
(rename-repo-and-vercel-project, point-domain-at-deckhearth,
regenerate-brand-assets, convert-reset-db-to-esm) own the remaining
brand-bound + helper-bound source-code work; this commit only
records them in the queued-convoys ledger.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 03:04:29 -05:00

909 lines
50 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# pick-a-name (P1 — pre-launch brand consistency)
**Status:** shipped
**Priority:** P1 (pre-launch; not a security blocker, but a brand-consistency
blocker that touches every user-facing surface)
**Convoy owner:** parent (orchestration); `role-architect` then
`role-implementer(s)` then `role-doc-writer`
**Opened:** 2026-05-24
**Shipped:** 2026-05-24
**Operator decision (gate-0, already ratified):** **Deck Hearth wins.**
## Problem
`AGENTS.md` line 5 has called this out since project setup: the repo, README,
seed data, package name, infrastructure, and admin email all say **"TCG
Vault"** (and the kebab `tcg-vault` / camel `tcgvault` variants), while the
actual rendered header brand in `components/Layout.js` says **"Deck Hearth"**.
Two distinct names, two distinct visual brands (the `AnimatedFireLogo.js`
component is also affected). Pre-launch this MUST be one name end-to-end.
## Operator's brand decision (gate-0)
**Deck Hearth wins.** Rationale (operator-stated): more distinctive, more
ownable, and the brand the human-facing surface (Layout header) already
renders. The trade is real: this is the higher-cost path because the
already-shipped infrastructure ID is the *losing* name. The architect's job
is to scope and minimize that cost — NOT to re-litigate the brand decision.
## Initial surface inventory (architect to expand)
Pre-architect rough hit-count from `rg`, scoped to non-vendor files:
| Surface | Files (sample) | "TCG Vault" or `tcg(-)?vault` hits |
| --- | --- | --- |
| Marketing copy | `README.md`, `pages/index.js`, `pages/login.js`, `pages/signup.js` | 4 + 5 + 4 + 1 |
| Brand components | `components/AnimatedFireLogo.js`, `components/ShareModal.js` | 4 + 1 |
| Seed scripts (admin email) | `scripts/setup-neon-db.js`, `scripts/create-test-users.js`, `scripts/seed-collections-*.js` | 2 + 7 + 6 |
| Test guides | `TESTING_GUIDE.md` | 7 |
| **Infrastructure-bound (rename has external blast)** | `package.json`, `lib/rate-limit.js` (Redis prefix), `.github/workflows/*.yml` | 1 + 5 + 3 |
| **Already-correct (Deck Hearth)** | `components/Layout.js`, `styles/globals.css`, `components/AnimatedFireLogo.js` partial | n/a (these stay) |
## Hard scoping rules
- **Strictly NO re-litigating the brand decision.** Deck Hearth wins; the
architect ratifies *how* to rename, not *whether* to.
- **Keep the repo `tcg-vault` for now.** GitHub rename is a separate concern
(auto-redirects work, but Vercel-link / `gh` configs / local remotes /
bookmarks all need touching). Track as a queued follow-up convoy
`rename-repo-and-vercel-project`. This convoy renames the **product brand
in-app**; the repo/Vercel rename is a downstream convoy.
- **Redis prefix migration is in scope.** `lib/rate-limit.js` uses
`tcgvault:auth/search/upload/generate/import` as Redis key prefixes. These
must rename to `deckhearth:*` (or similar) atomically with the convoy.
Accept the one-time rate-limit-state reset (counters are per-15-min /
per-1-hour anyway; no user-perceptible impact).
- **Admin email migration is in scope.** `admin@tcgvault.com` rename to
`admin@deckhearth.com` (or similar — architect proposes the canonical
email, operator ratifies). Includes the in-DB UPDATE for any existing
`admin@tcgvault.com` row (write a one-off `scripts/migrations/YYYY-MM-DD-
rename-admin-email.js` per the no-go-zones rule for new migrations).
- **`package.json` name field:** rename to `deck-hearth` in this convoy
(npm package name; we don't publish, so blast is contained to lockfile +
any local script reference).
- **Lockfile regeneration:** the architect ratifies whether to regenerate
`package-lock.json` in this convoy or accept the rename mismatch
short-term. (Recommended: regenerate; cheap; matches package.json shape.)
- **DNS / domain:** out of scope — separate convoy
`point-domain-at-deckhearth` (you don't own a `deckhearth.*` domain yet
per operator's pre-convoy statement; if/when acquired, that triggers DNS).
- **Favicon / OG images / social cards:** in scope IF source files exist;
defer to follow-up convoy `regenerate-brand-assets` if they need new design.
## Architect deliverables
1. **Full surface inventory** — every occurrence of `TCG Vault`, `tcgvault`,
`tcg-vault`, plus every occurrence of `Deck Hearth` / `deck-hearth` /
`deckhearth` (the latter set tells you what's *already* correct and what
shape collisions need handling, e.g., if Layout.js has `Deck Hearth` but
AnimatedFireLogo.js has `DECKHEARTH` — pick canonical casing).
2. **Canonical-string ratification:** propose the canonical product name
("Deck Hearth" with internal-cap; or "Deckhearth" one word; or other);
the canonical kebab (`deck-hearth`); the canonical camel/lower
(`deckhearth`); the canonical admin email (`admin@deckhearth.com` or
variant); the canonical Redis prefix (`deckhearth` or `dh`). Mark each as
D1-D5 (operator gate-1 for all five — they're naming choices, not
architectural ones).
3. **Slice plan:** probably 2-3 briefs. Suggested split (architect can
change): Brief 1 = in-repo string sweep + AnimatedFireLogo brand update
(mechanical); Brief 2 = Redis prefix migration in `lib/rate-limit.js` +
admin-email DB migration script + package.json rename + lockfile
regen (operationally riskier — needs careful testing). Brief 3 (if
needed) = visual baseline reset (because the smoke + visual-diff specs
will all fail with the new brand).
4. **Boot-the-brief verification** for each: confirm specs compile, lint
passes, vitest passes, smoke can still run against a preview.
5. **Out-of-scope queued follow-ups:** explicit list (repo rename, Vercel
project rename, DNS / domain acquisition, brand asset regeneration,
social card refresh).
## Known constraints
- **Cross-cutting smoke/visual diff blast.** PR-#18's Playwright smoke spec
("sign-in page renders") + the visual-diff baseline are both predicated on
the *current* brand strings. Brief 2 (or Brief 3 if split) MUST update
the smoke spec expectations and either regenerate visual baselines or
accept temporary baseline drift (the `adopt-playwright-smoke` Decision 4
`continue-on-error` swallow is still in effect, so visual-diff drift
won't fail CI — but the queued `seed-visual-baselines-on-linux` convoy
becomes harder to ship until *after* this convoy lands).
- **Smoke spec lives at `tests/smoke/app.smoke.spec.ts`.** Likely needs
one-or-two-character edits to text matchers.
- **`AGENTS.md` line 5's branding note** is the canonical historical record
of the inconsistency; the doc-writer pass MUST update it from "Pick one
before launch" to "Deck Hearth (decided 2026-05-24 in `pick-a-name`
convoy; commit X)."
- **Operator-action-required pre-merge:** smoke-test the rebranded preview
manually before merge (every public page should render "Deck Hearth";
login + register should still work; admin import UI should still
function with the renamed admin user).
## Out of scope (queued follow-ups)
- `rename-repo-and-vercel-project` — GitHub repo rename + Vercel project
rename + local git remote update. P2 polish. Auto-redirects make this
low-risk-low-urgency.
- `point-domain-at-deckhearth` — DNS / domain. Blocked on domain
acquisition (operator pre-convoy: doesn't own `deckhearth.*` yet).
- `regenerate-brand-assets` — favicon, OG images, social cards. P2
polish; requires design pass.
- `migrate-existing-user-emails` — if any existing user accounts have
`@tcgvault.com` emails (besides admin), propose a migration. Architect
to surface count via a `rg`/DB query during inventory.
## Owns
`role-architect` (decisions D1-D5, slice plan) → `role-implementer(s)`
(per brief) → `role-doc-writer` (AGENTS.md line 5 + this file's
As-shipped + ship-readiness Status summary footer).
## Architecture (role-architect, 2026-05-24)
**Status:** architect-complete; awaiting operator gate-1 ratification on D1-D5
before implementer dispatch.
**No blocking findings.** No surfaced naming collision with existing npm
packages (we don't publish; the `name` field is a local identifier).
`deckhearth.com` domain is unowned per operator's pre-convoy statement —
queued in `point-domain-at-deckhearth`, downstream of this convoy. Redis
counter reset is an acknowledged one-time cost (per-15-min and per-1-hour
windows; no user-perceptible impact). All other costs are mechanical sweeps.
**Scope summary:** 16 source files modified across two parallel briefs +
1 new migration script. ~75-110 lines of net diff (excluding `package-lock.json`
regen, which is its own opaque artifact). Two briefs touch DISJOINT file
sets and can run in parallel via `/multitask`.
### Operator-ratified decisions (gate-0, captured)
- **Brand winner:** **Deck Hearth.** Operator's rationale: more distinctive,
more ownable, and the brand the human-facing surface (Layout header)
already renders. The architect's job is to scope and minimize the cost of
getting there — NOT to re-litigate the brand decision.
### Architect-proposed decisions (operator gate-1 required, ALL FIVE)
These five naming choices are NOT architect-self-ratifiable. They establish
the canonical strings the entire codebase will be swept to. **All five
require operator gate-1 ratification before Brief 1 OR Brief 2 dispatch.**
#### D1 — Canonical display string (operator gate-1)
**Recommendation:** **"Deck Hearth"** (two words, internal cap, space-separated).
Trade-off considered: "Deckhearth" (one word) is brand-simpler and gives a
cleaner email TLD (`deckhearth.com` reads as one syllable). But every
already-shipped user-facing surface in the repo renders the two-word form:
- `components/Layout.js` lines 623 + 716 → `Deck Hearth`
- `components/AnimatedFireLogo.js` line 13 → `Deck Hearth Logo`
- `pages/index.js` lines 64, 96, 266, 299, 315 → `Deck Hearth`
- `pages/login.js` lines 76, 158 → `Deck Hearth`
- `pages/signup.js` line 215 → `Deck Hearth`
- `components/ShareModal.js` line 116 → `Deck Hearth`
- `styles/globals.css` line 15 → `Deck Hearth Fire-Inspired`
Choosing "Deckhearth" would require RE-sweeping all seven currently-correct
surfaces — net negative-cost choice. Recommendation: **ratify "Deck Hearth"**
to make the in-progress strings the canonical strings.
#### D2 — Canonical kebab string (operator gate-1)
**Recommendation:** **`deck-hearth`** (matches existing CSS class
`deck-hearth-logo-container` in `components/AnimatedFireLogo.js` line 8 +
the conventional hyphenated lowercase used by package.json `name` fields and
file naming).
Trade-off: `deckhearth` (no hyphen) would match D3 below but conflicts with
both Node ecosystem convention (`@scope/kebab-name`) and the existing CSS
class. Recommendation: **ratify `deck-hearth`**.
#### D3 — Canonical camel/lower string for IDs (operator gate-1)
**Recommendation:** **`deckhearth`** (single token, no separators).
Used for: Redis key prefixes, env-var-style identifiers, internal symbol
names. Distinct from D2 because IDs lack convention-bound separators (the
Upstash `prefix:` option is a single string token; convention is colon-
separated namespace, e.g. `deckhearth:auth:1.2.3.4`). Recommendation: **ratify
`deckhearth`**.
#### D4 — Canonical admin email (operator gate-1)
**Recommendation:** **`admin@deckhearth.com`** (placeholder TLD pending the
queued `point-domain-at-deckhearth` convoy).
Trade-off: the operator does not yet own `deckhearth.com` (or any
`deckhearth.*` TLD) per the convoy seed § *"point-domain-at-deckhearth …
blocked on domain acquisition"*. The seed admin row's email is a STRING
literal in the database — it doesn't need to resolve to a real mailbox to
function (auth uses the email as a unique identifier, not as a mail target).
Picking `.com` now matches the most-likely future domain purchase; if the
operator instead buys `.app` or `.gg`, a future one-line migration can sweep
the TLD without touching this convoy's structure.
**Apply to all three current `@tcgvault.com` accounts:** admin, alice, bob
(the test users created by `scripts/create-test-users.js`). The migration
script in Brief 2 sweeps all three uniformly via a `REPLACE(email,
'@tcgvault.com', '@deckhearth.com')` UPDATE.
Recommendation: **ratify `admin@deckhearth.com` + sweep all `@tcgvault.com`
emails to `@deckhearth.com`** in the same migration. If the operator wants
a different TLD, swap `.com` for the chosen TLD in the migration script and
the seed scripts before Brief 2 implementer dispatch — that's a one-line
change confined to B2's file set.
#### D5 — Canonical Redis prefix (operator gate-1)
**Recommendation:** **full `deckhearth`** (NOT abbreviated `dh`).
Trade-off considered: `dh` saves ~8 bytes per key (vs. `deckhearth`). At peak
load with say 100k active rate-limit identifiers, total savings would be
~800KB of Redis memory — negligible against an Upstash plan that bills by
request count, not per-byte storage. The self-documenting `deckhearth:auth:…`
prefix is materially easier to debug when reading Redis console output. (And
the `cors-tighten` Decision-style precedent applies: short-and-cryptic IDs
tend to attract "what does this mean?" comments in code, longer-and-clear IDs
explain themselves.)
Recommendation: **ratify full `deckhearth`**.
### Full surface inventory
Architect-verified `rg` sweep results, grouped by classification. Files
under `.convoys/**` (append-only convoy history per `.cursor/rules/no-go-
zones.mdc`) are listed at the bottom for completeness but are NOT in any
brief's `files:` list — they stay byte-identical.
#### User-facing display strings (must rename)
| File | Line | Current | Brief |
| --- | --- | --- | --- |
| `README.md` | 1 | `# TCG Vault` | 2 (file already in B2 for email) |
| `TESTING_GUIDE.md` | 1 | `# 🎯 TCG Vault Collaboration Testing Guide` | 2 (file already in B2 for emails) |
| `scripts/README.md` | 1 | `# TCG Vault Bulk Import Scripts` | 1 |
#### Brand-bound infrastructure IDs (must rename; has external blast)
| File | Line | Current | Blast | Brief |
| --- | --- | --- | --- | --- |
| `lib/rate-limit.js` | 10-14 | `tcgvault:auth/search/upload/generate/import` | Orphans existing Redis counters at deploy time; new counters start at zero | 2 |
| `package.json` | 2 | `"name": "tcg-vault"` | Lockfile regen required | 2 |
| `package-lock.json` | 2, 8 | `"name": "tcg-vault"` (×2) | Generated; regen via `npm install` after `package.json` change | 2 |
| `scripts/setup-neon-db.js` | 148 | `VALUES (${'admin@tcgvault.com'}, …)` | Seeds NEW deploys' admin email; needs migration for existing deploys | 2 |
| `scripts/setup-neon-db.js` | 157 | `console.log(' Admin user ready (email: admin@tcgvault.com)')` | Stdout copy | 2 |
| `pages/api/cards/import-lorcana.js` | 14 | `'User-Agent': 'TCG-Vault/1.0'` | External HTTP identity sent to Lorcana API | 1 |
| `pages/api/cards/import-pokemon.js` | 14 | `'User-Agent': 'TCG-Vault/1.0'` | External HTTP identity sent to Pokémon TCG API | 1 |
| `scripts/import-lorcana.js` | 20 | `'User-Agent': 'TCG-Vault/1.0'` | External HTTP identity (CLI variant) | 1 |
#### Email-bound infrastructure (must rename; in-DB migration required)
| File | Lines | Current | Brief |
| --- | --- | --- | --- |
| `scripts/setup-neon-db.js` | 148, 157 | `admin@tcgvault.com` | 2 (also infra above) |
| `scripts/reset-db.js` | 147, 155 | `admin@tcgvault.com` | 2 |
| `scripts/create-test-users.js` | 18, 21, 27, 30, 34-36 | `alice@tcgvault.com`, `bob@tcgvault.com`, `admin@tcgvault.com` | 2 |
| `pages/login.js` | 172, 184 | `alice@tcgvault.com`, `bob@tcgvault.com` (quick-login fixtures) | 2 |
| `README.md` | 132 | `Admin email: the seed creates admin@tcgvault.com` | 2 |
| `TESTING_GUIDE.md` | 7-9, 24, 46, 53 | admin/alice/bob `@tcgvault.com` emails in account table + workflow | 2 |
| `test/lib/permission-middleware.test.js` | 87 | `email: 'admin@tcgvault.com'` (NEGATIVE regression assertion) | 2 (see B2 decision on whether to preserve or update — leaning preserve) |
| `scripts/migrations/2026-05-24-rename-admin-email.js` | NEW | (new file) | 2 |
#### Internal references in docs / rules / agent context (rename for consistency)
| File | Line | Current | Brief |
| --- | --- | --- | --- |
| `AGENTS.md` | 5 | `Branding note: the repo, README, and seed data say "TCG Vault" …` | 1 |
| `.cursor/rules/ui-and-theming.mdc` | 61 | `The repo says "TCG Vault" everywhere except …` | 1 |
| `.cursor/rules/auth-and-permissions.mdc` | 2 | `description: Auth model + permission model for tcg-vault (…)` | 1 |
#### Repo / Vercel project name (explicitly OUT OF SCOPE per convoy seed)
These references match the literal repo name `tcg-vault`, which stays for now
per the convoy seed § *"Keep the repo `tcg-vault` for now."* When the
follow-up convoy `rename-repo-and-vercel-project` ships, these update
together with the repo rename. **Do NOT touch in this convoy.**
| File | Line | Reference |
| --- | --- | --- |
| `README.md` | 30 | `cd tcg-vault` (git clone instructions) |
| `README.md` | 105 | `tcg-vault/` (directory tree diagram) |
| `AGENTS.md` | 1 | `# AGENTS.md — AI collaboration (tcg-vault)` |
| `.github/workflows/ci.yml` | 12 | `# NOTE: tcg-vault is JavaScript (not TypeScript). …` |
| `.github/workflows/ci.yml` | 123 | `# The tcg-vault frontend and API are served from the same Vercel …` |
| `.github/workflows/visual-diff.yml` | 5 | `# Paths are tcg-vault-specific (pages router, JS).` |
| `.agent-context-manifest.yml` | 84, 89, 99, 104, 109, 114 | `source: "tcg-vault-local"` (internal source tag, re-evaluated by `sync-agent-context` skill at repo rename time) |
#### Already-correct (Deck Hearth — stays byte-identical)
These render the canonical D1 string and have no `tcgvault` baggage. Do NOT
touch:
- `components/Layout.js` (lines 623, 716 — sidebar headers)
- `components/AnimatedFireLogo.js` (lines 8, 13, 25, 29 — CSS class +
alt text + style scope)
- `components/ShareModal.js` (line 116 — social share title)
- `pages/index.js` (lines 64, 96, 266, 299, 315 — landing page)
- `pages/login.js` (lines 76, 158 — welcome copy; emails at 172/184 are in B2)
- `pages/signup.js` (line 215 — signup heading)
- `styles/globals.css` (line 15 — theme comment)
#### Convoy artifacts + manifest (no-go per `.cursor/rules/no-go-zones.mdc`)
`.convoys/**/*.md` are append-only history — they describe the state at
write-time. Per the no-go zone for append-only artifacts and `scripts/seed-
*.js` historical jobs, these are NOT renamed in this convoy:
- `.convoys/fix-auth-bypass/brief-2-remove-admin-bypass.md` (4 hits — describes the BUG that returned the OLD synthetic-admin email)
- `.convoys/fix-auth-bypass/brief-5-vitest-and-auth-tests.md` (2 hits — same)
- `.convoys/drop-public-setup.md`, `.convoys/drop-public-setup/brief-*.md` (multiple hits — historical decision record)
- `.convoys/add-rate-limiting.md`, `.convoys/add-rate-limiting/brief-*.md` (8 hits — Redis prefix references reflect the *pre-rename* state at convoy-close; this convoy is what changes them; doc-writer pass at THIS convoy's close updates the gotcha / rule, NOT the historical convoy file)
- `.convoys/cors-tighten/brief-1-sweep-wildcard-cors.md` (2 hits — historical)
- `.convoys/fix-layout-default-user.md`, `.convoys/fix-layout-default-user/brief-*.md` (3 hits — historical)
- `.convoys/ship-readiness.md` (4 hits — historical milestone log; doc-writer pass appends a NEW "12. RESOLVED" entry rather than rewriting line 244)
- `.convoys/bump-next-js.md`, `.convoys/fix-vercel-deployment-protection-in-ci.md` (each 1 hit — historical)
- `scripts/seed-collections-with-cards.js`, `scripts/seed-collections-alice-bob.js` (3 hits each — historical seed jobs per no-go-zone rule "`scripts/seed-*.js` — historical migration / seed jobs already executed"; the email migration in B2 covers the in-DB rename, so these scripts' WHERE-clause literals would simply match zero rows post-migration if re-run, which is a harmless no-op)
- `components/Layout.js.backup` (1 hit — legacy snapshot per no-go-zone rule)
### API surface
**No new or modified API routes.** No request / response shape changes. The
Redis prefix rename in `lib/rate-limit.js` is internal to the lib module —
the five exported `check*RateLimit(req[, userId])` functions retain their
byte-identical Brief-4-and-`add-rate-limiting`-Brief-1 contracts: same name,
same args, same `{ allowed, remaining, reset }` return shape. The seven
gated handlers do NOT need editing.
### Schema diff
**No DDL changes.** The only schema-adjacent operation is the in-DB UPDATE
of `users.email` for at most 3 rows (admin + alice + bob), performed by
the new dated migration script `scripts/migrations/2026-05-24-rename-admin-
email.js`. No new tables, no new columns, no new indexes, no new
constraints. `docs/SCHEMA_MAP.md` requires no update (column shapes and
table inventory are unchanged).
The new `scripts/migrations/` folder is created with this convoy's first
migration file. Per `.cursor/rules/no-go-zones.mdc` § *"if the change
touches DDL strings or `INSERT` semantics, file a migration; if it only
touches Node-module behavior or pre-flight validation, edit in place and
document why in the convoy"*, the seed scripts' literal email INSERT values
in `scripts/setup-neon-db.js` + `scripts/reset-db.js` + `scripts/create-test-
users.js` ARE INSERT-semantics changes; the migration script is the
required artifact to accompany those edits. The architect ratifies this:
the in-place edits keep new-deploy seeding correct; the migration handles
the existing-deploy update.
### Test plan
**No new vitest unit tests** in this convoy (decoupled from the rebrand).
Per `add-rate-limiting` Decision 6 precedent — new handler / unit tests are
queued under `fill-vitest-handler-coverage`. The only test edit is the
update to `test/lib/permission-middleware.test.js` line 87's NEGATIVE
regression assertion (see B2 acceptance criteria).
**Vitest 21/21 must still pass** after Brief 1 and Brief 2 land. Brief 1
touches zero files referenced by any test. Brief 2 touches three test-
adjacent files (`pages/login.js` quick-login fixtures, `test/lib/permission-
middleware.test.js` line 87, and the test-user creation scripts) — none of
these break the unit-test suite. The negative regression assertion's
`.toBeNull()` check is the canonical assertion; the `.not.toEqual({...})`
soft check is incidentally about the historical literal email value and
will continue to pass against any email.
**Playwright smoke 3/3 must still pass** after both briefs. Verified at
architect time: `tests/smoke/app.smoke.spec.ts` text matchers are `'sign in'`
(case-insensitive regex), `/api/health`, and HTTP status < 500. NONE of
these text matchers reference `TCG Vault` or `Deck Hearth` brand strings;
the smoke spec is brand-agnostic and needs **zero edits**. (This is the
"audit" the prompt requested confirmed via direct read of the spec file.)
**Playwright visual baseline**: verified at architect time that
`tests/visual/__screenshots__/` does NOT yet exist (no committed baselines).
Per the `adopt-playwright-smoke` Decision 4 `continue-on-error` swallow and
the queued `seed-visual-baselines-on-linux` convoy, the visual-diff
workflow's first-run failure is the documented end state. **No baseline
drift to manage in this convoy.** When `seed-visual-baselines-on-linux`
eventually lands, it MUST run AFTER `pick-a-name` so the seeded baseline
captures the Deck Hearth brand strings; the architect updates the
`seed-visual-baselines-on-linux` queued entry below to record this
ordering constraint.
**Manual verification** (operator pre-merge):
- Boot `npm run dev`. Visit `/`, `/login`, `/signup`, `/cards`. Every brand
string should render `Deck Hearth` (verify in browser DevTools "Find").
- Click the "Alice" / "Bob" quick-login buttons on `/login`. Expected
behavior post-migration: each button fills the email field with
`<name>@deckhearth.com` and the password `<name>123`. With the migration
run, login succeeds and redirects to dashboard. WITHOUT the migration
run (e.g., op forgot), login 401s that's the expected failure mode and
the operator's signal to run the migration script.
- Run `node scripts/migrations/2026-05-24-rename-admin-email.js` against
a staging DB. Expected: prints the count of `@tcgvault.com` rows found
(0-3), UPDATEs them, prints the success count. Re-run: prints "Nothing
to migrate. Exiting." (idempotency check).
- Visit a Vercel preview. Verify all public surfaces render Deck Hearth.
Quick-login test (admin / alice / bob with the new emails) only works
if the preview's DB is the staging Neon with the migration run.
### Risk list
1. **Operator forgets to run the migration script post-deploy.** The
admin email and Alice/Bob test-user emails would remain `@tcgvault.com`
in the DB while the seed scripts and login.js fixtures reference
`@deckhearth.com`. Symptom: admin login attempts with the new email
401 because the DB still has the old email. **Mitigation:** the
`pick-a-name` PR description MUST include a "Post-merge operator
action" callout naming the migration script and the
`node scripts/migrations/...` command verbatim. (Same defense pattern
as `drop-public-setup`'s "operator caveat" for the weak-hash
rotation.) Doc-writer pass at convoy close adds this to `AGENTS.md`
Gotcha #4's "Operator caveat" paragraph as a sibling caveat.
2. **Redis counter reset surprises a heavy active user.** Rare in
pre-launch. The five sliding windows (15-min auth, 1-min search,
1-hour upload/generate/import) all expire within an hour anyway
the worst-case "loss" is that an attacker who had burned 4/5 of the
auth budget at the moment of deploy gets reset to 0/5. This is
defensively neutral: the legitimate user's prior counter also resets
(they too get a fresh 5/15-min budget). **Mitigation:** none needed;
the convoy seed explicitly accepts this trade.
3. **Lockfile regen surfaces unrelated dependency churn.** Running
`npm install` after a `package.json` `name` change SHOULD only update
the `name` field at the top of `package-lock.json` plus the
`packages.""` entry inside it. But npm might opportunistically
refresh stale `integrity` hashes or pull in metadata updates.
**Mitigation:** B2 acceptance criteria require the implementer to
inspect the `package-lock.json` diff and confirm it touches only
`name`-related entries; if any unrelated `dependencies` / `version`
churn appears, the brief instructs the implementer to STOP and flag
for a separate `bump-deps-cleanup` convoy rather than landing
accidental dependency updates inside the rebrand PR.
4. **The historical synthetic-admin negative regression assertion in
`test/lib/permission-middleware.test.js` line 87.** The literal email
`admin@tcgvault.com` is the OLD bug's exact shape. Two options: (a)
preserve the historical literal so the test continues to encode "the
helper does NOT return THIS SPECIFIC pre-`fix-auth-bypass` shape"; or
(b) update to `admin@deckhearth.com` so the test's literal matches
the current admin email. Recommendation: **preserve (a)** and add a
comment explaining the historical preservation. The `.toBeNull()`
assertion two lines below is the strong contract; the
`.not.toEqual({...})` is the soft historical-shape check. B2's
acceptance criteria documents the preserve-(a) recommendation but
leaves the final call to the implementer.
5. **The `.agent-context-manifest.yml` `source: "tcg-vault-local"` tag.**
Architect-verified at brief time that 6 lines reference this string
as an internal source identifier for the `sync-agent-context` skill's
drift detection. Renaming to `deck-hearth-local` could break the
sync skill's behavior (it tracks "what shipped from this source").
**Mitigation:** leave as-is in this convoy; flag for re-evaluation
in `rename-repo-and-vercel-project` when the repo's literal name
actually changes.
6. **Email is the unique constraint on `users` collision is impossible
but admin-row recreation is.** If the operator runs `setup-neon-db.js`
AFTER the migration runs, the seed's `INSERT … ON CONFLICT (email)
DO NOTHING` would NOT recreate the admin row (because the row exists
with the NEW email post-migration). That's the correct behavior. If
the operator runs `setup-neon-db.js` BEFORE the migration runs and
the existing admin row has the OLD email, the seed inserts a NEW row
with the NEW email, creating TWO admin rows (one with each TLD).
**Mitigation:** the migration script's UPDATE is idempotent and
handles both cases (it sweeps any remaining `@tcgvault.com` row to
`@deckhearth.com`, which would collide with the existing
`@deckhearth.com` row's UNIQUE constraint and fail loudly). The
architect recommends the operator run the migration FIRST, then
confirm via `SELECT email FROM users WHERE role = 'admin'` returns
exactly one row before running `setup-neon-db.js`. B2 acceptance
criteria document this ordering.
7. **B1 and B2 are parallelizable BUT both depend on operator gate-1
ratification of D1-D5 BEFORE dispatch.** This is a single gate at the
convoy level, not a per-brief gate. Once the operator says "approved,
run implementers" with the D1-D5 choices ratified, the conductor can
dispatch B1 + B2 in parallel via `/multitask` since their `files:`
sets are disjoint.
### Decomposition
| Brief # | Title | Files | Depends on | Estimated PR size |
| --- | --- | --- | --- | --- |
| 1 | In-repo display + comment sweep (User-Agent strings, branding notes, rule descriptions) | 7 files | none (gate-1 ratification of D1+D2+D3 required) | ~10-15 lines net |
| 2 | Infrastructure + email migration (Redis prefix rename, package.json + lockfile regen, admin/alice/bob email rename in seed scripts + login fixtures + migration script + regression test) | 10 files + 1 new migration script | none (gate-1 ratification of D1-D5 required); operator pre-merge action: run migration | ~65-95 lines net + opaque package-lock.json regen |
### Slice dependencies (multitask-ready)
```yaml
slice_dependencies:
- brief: 1
depends_on: []
files:
- AGENTS.md
- .cursor/rules/ui-and-theming.mdc
- .cursor/rules/auth-and-permissions.mdc
- scripts/README.md
- pages/api/cards/import-lorcana.js
- pages/api/cards/import-pokemon.js
- scripts/import-lorcana.js
- brief: 2
depends_on: []
files:
- lib/rate-limit.js
- package.json
- package-lock.json
- README.md
- TESTING_GUIDE.md
- scripts/setup-neon-db.js
- scripts/reset-db.js
- scripts/create-test-users.js
- pages/login.js
- test/lib/permission-middleware.test.js
- scripts/migrations/2026-05-24-rename-admin-email.js
```
**Parallelism:** B1's 7 files and B2's 11 files are DISJOINT. The
conductor can dispatch both in parallel via `/multitask` once the operator
ratifies D1-D5 at gate-1. Neither brief blocks the other.
### Out-of-scope queued follow-ups (reaffirmed + 2 new)
Reaffirms the convoy seed's four:
- **`rename-repo-and-vercel-project`** (P2 polish) GitHub repo rename
(`tcg-vault` `deck-hearth`) + Vercel project rename + local git
remote update + the 8 architect-verified `tcg-vault` references that
cite the literal repo (README cd/tree, AGENTS.md line 1, the three
workflow YAML comments, `.agent-context-manifest.yml` source tags).
Auto-redirects make this low-risk-low-urgency.
- **`point-domain-at-deckhearth`** DNS / domain. Blocked on domain
acquisition (operator pre-convoy: doesn't own `deckhearth.*` yet).
If the operator buys a TLD other than `.com`, this convoy also
sweeps the email TLD via a one-line REPLACE migration.
- **`regenerate-brand-assets`** favicon, OG images, social cards.
`public/favicon.ico` exists today (unverified what brand it is out
of scope to inspect binary in this convoy). P2 polish; requires
design pass.
- **`migrate-existing-user-emails`** for real users with
`@tcgvault.com` emails. Architect-verified at brief time: pre-launch,
the ONLY `@tcgvault.com` accounts are admin + alice + bob (3 fixtures
created by `create-test-users.js`). Brief 2's migration handles all
three. If the operator opened the app to friends-and-family pre-this-
convoy and any signed up with `@tcgvault.com` emails, the same
migration covers them too (it sweeps every row matching
`email LIKE '%@tcgvault.com'`).
Two NEW follow-ups surfaced by architect inventory:
- **`convert-reset-db-to-esm`** (P2 hygiene) `scripts/reset-db.js` line
142 uses CommonJS `require('bcryptjs')` inside an ESM file. Same bug
pattern that hit `setup-neon-db.js` pre-`drop-public-setup` Brief 2.
Brief 2 of `pick-a-name` does NOT fix this in scope (it touches the
email literal and console-log copy only); a separate convoy converts
the require import. May fold into `purge-weak-creds-from-helpers`.
- **`update-seed-visual-baselines-on-linux-ordering`** the queued
`seed-visual-baselines-on-linux` convoy MUST run AFTER `pick-a-name`
so the first committed Linux baseline captures the Deck Hearth brand
strings (not TCG Vault). Architect recommends the operator (or the
conductor of that future convoy) add a `depends_on: pick-a-name`
ordering note to the `seed-visual-baselines-on-linux` queued entry
in `.convoys/ship-readiness.md`.
### As-shipped
Shipped 2026-05-24 as squash commit `9abbab6` (PR #21, architect-commit
`50ce9ab`, Brief 1 commit `ac8c998`, Brief 2 commit `1c18d21`). **This is
the first post-P0 convoy** `add-rate-limiting` (squash `708ef45`, PR
#20, 2026-05-24) closed the LAST P0 ship-blocker, flipping
`.convoys/ship-readiness.md`'s § Status summary to "8 of 8 RESOLVED";
this convoy opens the post-P0 P1 quality-bar phase by closing the
brand-consistency P1 (#12 in the same file) that AGENTS.md line 5 has
flagged since project setup. Two briefs in the convoy: B1
(display + comment sweep) and B2 (infrastructure + email migration)
shipped without scope expansions and with all five D-decisions ratified
verbatim at gate-1 plus Risk 4 PRESERVE applied to the negative
regression-lock test.
#### Decisions ratified at gate-1
All five canonical-string choices required operator gate-1 ratification
(naming choices are not architect-self-ratifiable per the convoy file's
§ Architect-proposed decisions header). Operator ratified all five
verbatim:
- **D1 Display string:** **"Deck Hearth"** (two words, internal cap,
space-separated). Avoids re-sweeping the seven user-facing surfaces
that already render the two-word form (`components/Layout.js`,
`components/AnimatedFireLogo.js`, `pages/index.js`, `pages/login.js`
welcome copy, `pages/signup.js`, `components/ShareModal.js`,
`styles/globals.css`).
- **D2 Kebab string:** **`deck-hearth`**. Matches the existing
`deck-hearth-logo-container` CSS class + Node-ecosystem convention for
`package.json` `name` fields. Used in `package.json` rename + the
three `User-Agent` HTTP product tokens in `pages/api/cards/import-*.js`
+ `scripts/import-lorcana.js` (`Deck-Hearth/1.0`, hyphenated lowercase
per HTTP UA convention).
- **D3 Camel/lower for IDs:** **`deckhearth`** (single token, no
separators). Used in the five Redis key prefixes
(`deckhearth:auth/search/upload/generate/import`) full word, NOT the
abbreviated `dh` (the ~800KB Redis-memory savings at peak load is
negligible against an Upstash plan that bills by request count, and
the self-documenting prefix is materially easier to debug in Redis
console output).
- **D4 Admin email:** **`admin@deckhearth.com`** (placeholder TLD
pending the queued `point-domain-at-deckhearth` convoy). Sweep
applied to all three `@tcgvault.com` accounts uniformly via
`REPLACE(email, '@tcgvault.com', '@deckhearth.com')`: admin + alice
+ bob (the test users created by `scripts/create-test-users.js`).
- **D5 Redis prefix:** **full `deckhearth`**. Pattern:
`deckhearth:auth`, `deckhearth:search`, `deckhearth:upload`,
`deckhearth:generate`, `deckhearth:import`. The five-prefixes-must-be-
distinct invariant from `add-rate-limiting` Brief 1 is preserved.
**Risk 4 PRESERVE applied:** the architect's recommendation
(`.convoys/pick-a-name.md` § Risk list § 4) was to preserve the
historical `admin@tcgvault.com` literal in
`test/lib/permission-middleware.test.js` line 87's negative regression
assertion as the EXACT pre-`fix-auth-bypass` synthetic-admin shape this
test locks against. The implementer kept the literal and added a
7-line comment block above the `.not.toEqual({...})` call documenting
why future doc-writers / refactorers must NOT update the literal to the
new `@deckhearth.com` email doing so would weaken the regression-lock
to a shape that never actually existed. **This matches the project's
established pattern** of treating historical-shape locks as audit-trail
artifacts rather than self-consistency markers (Gotchas #2, #3, #4, #5,
#8 in `AGENTS.md` all note "Entry kept (not renumbered) to preserve
cross-references" for the same reason). The post-merge state has
exactly **1** `@tcgvault.com` literal outside of `.convoys/**` and
`*.md` files the preserved historical regression-lock.
#### Brief structure (file-disjoint; serial-in-practice)
The architect designed B1 (7 files: `AGENTS.md` line 5, two
`.cursor/rules/*.mdc` description / branding paragraphs, `scripts/README.md`
title, three `User-Agent` strings) and B2 (10 modified + 1 new
migration script: `lib/rate-limit.js` 5 prefix renames, `package.json`
`name` rename, `package-lock.json` regen, `README.md` + `TESTING_GUIDE.md`
title + email + operator caveat, three seed/reset/test-user scripts'
admin/alice/bob email rename, `pages/login.js` quick-login fixtures,
`test/lib/permission-middleware.test.js` Risk 4 PRESERVE comment block,
new `scripts/migrations/2026-05-24-rename-admin-email.js`) so that the
two `files:` sets are DISJOINT and the conductor could dispatch them in
parallel via `/multitask`. **In practice the two briefs landed
serially** (B1 commit `ac8c998` first, B2 commit `1c18d21` second on
the same branch) for the lockfile-regen + migration-script-author
caution recommended by Brief 2 § Risk 3 (lockfile STOP-on-churn
verification is easier to audit if no other diff is in flight). The
parallel-via-multitask path stayed open as the architect's intended
fallback if either brief had hit a blocker.
#### As-shipped diff
Per `git show --stat 9abbab6`:
- **B1 (`ac8c998`): 7+/7-** across 7 files (the 7 `files:` listed in
`.convoys/pick-a-name/brief-1-display-and-comment-sweep.md`). Pure
string-literal edits; no new code, no new imports, zero ESLint
surface added.
- **B2 (`1c18d21`): 133+/30-** across 10 modified files + 1 new
migration script. The +/- imbalance is dominated by the new
~85-line migration script (`scripts/migrations/2026-05-24-rename-
admin-email.js`) + the new operator-caveat blockquote in `README.md`
(~10 lines added) + the 7-line Risk 4 PRESERVE comment block in
`test/lib/permission-middleware.test.js`. Source-code edits in
`lib/rate-limit.js` (5 prefix lines) + `pages/login.js` (2 quick-
login fixtures) + `scripts/setup-neon-db.js` / `reset-db.js` /
`create-test-users.js` (admin/alice/bob email INSERTs + console
logs) are pure string-literal swaps.
- **Lockfile regen:** the `package-lock.json` diff was confirmed
before B2 commit to touch ONLY the two `name` lines (top-level +
`packages[""]` entry); the brief's STOP-on-churn rule (Risk 3) was
exercised by the implementer and confirmed clean no opportunistic
dependency-metadata refresh, no integrity-hash churn, no `version`
bumps. `npm ci` succeeded against the regenerated lockfile.
#### As-shipped CI metrics
Post-merge run 26387909625 + subsequent runs on `main`:
- **`Playwright smoke` PASS in 1m4s, 3/3 tests** against the rebranded
Vercel preview. Same three checks (`home redirects or renders without
5xx` / `sign-in page renders` / `public health endpoint responds`
✓) all green. The new brand strings render correctly on `/` +
`/login`; the auth surface still works (smoke test 2 still asserts
the `<Link href="/login">Sign in</Link>` CTA wording, which is
brand-agnostic per architect-time verification of the smoke spec's
text matchers); no false 429s from the renamed Redis prefixes (the
post-rename `deckhearth:*` keys were brand-fresh in Upstash, but the
smoke spec's `/api/health` hit is anonymous and unrate-limited and
the home + sign-in routes don't touch any of the 6 newly-gated
rate-limit endpoints).
- **`Aggregate gate` PASS in 10s.** All upstream gate dependencies
green.
- **`Lint` PASS in 45s, 128 problems** (lint baseline preserved, no
regression from B1 or B2). Zero new lint problems; `|| true` wrapper
in `.github/workflows/ci.yml` was a no-op for this convoy.
- **`forbidden-endpoints` (from `fix-auth-bypass` Brief 3) PASS in
4s.** No new `pages/api/test-*.js` shapes reintroduced.
- **`forbidden-cors-headers` (from `cors-tighten`) PASS in 4s.** Pure
brand-rename diff; no `Access-Control-Allow-*` headers reintroduced.
- **`Unit tests (vitest)` PASS, 21/21 in 35s.** The Risk 4 PRESERVE
comment-block addition kept the negative regression-lock test green
byte-identically the assertion semantics are unchanged; only the
comment block above the `.not.toEqual({...})` call is new. Decision
6 (no new vitest specs in this convoy) honored per the
`add-rate-limiting` D6 + `cors-tighten` D4 precedent.
- **`Screenshot diff` PASS in 1m5s** (`continue-on-error: true`
swallow per `adopt-playwright-smoke` Decision 4 still active; the
rebrand introduced no visual regression because no baseline is
committed yet the queued `seed-visual-baselines-on-linux` convoy
generates the FIRST baseline and that baseline will capture the
Deck Hearth brand strings, per the architect's ordering note in
`.convoys/pick-a-name.md` § Test plan + the updated entry in
`.convoys/ship-readiness.md` § Queued convoys).
- **`Schema map up to date`** skipping (only runs on Prisma changes;
this convoy made an INSERT-semantics change but no DDL change).
- **Vercel deployment** completed cleanly. First production-tier
Vercel deploy serving the `deck-hearth` `package.json` `name`.
#### Cross-validation finding: smoke spec defends the brand-rename surface (organically)
This convoy is the **fourth in a row** where the same 3-test Playwright
smoke spec authored by `adopt-playwright-smoke` (PR #18, 2026-05-24)
defended the auth surface through a sweeping change without anyone
having to write a dedicated test. The lineage now reads:
- **PR #15** (`fix-layout-default-user`, `ca302a8`) introduced the
`<Link href="/login">Sign in</Link>` CTA that smoke test 2 asserts on.
- **PR #19** (`cors-tighten`, `da50d78`) removed wildcard CORS from
24 handlers; smoke test 2 still passed against the post-CORS preview.
- **PR #20** (`add-rate-limiting`, `708ef45`) wired 6 new route gates
(search/upload/generate/import) and refactored `lib/rate-limit.js`
end-to-end; smoke test 2 still passed (the new 60/min search ceiling
doesn't 429 the smoke spec because home + `/login` don't touch any
gated route).
- **PR #21** (`pick-a-name`, `9abbab6`) this convoy renamed the
product brand end-to-end across 17 files + 1 new migration script,
including all 5 Redis key prefixes; smoke test 2 still passes.
The smoke spec's text matchers (`'sign in'` regex, `/api/health`,
HTTP status < 500) are brand-agnostic by architect-time design but
**organically**, smoke is doing real work: it has now defended the
auth surface against four sweeping changes spanning the full P0 #5
+ P0 #6 + P0 #7 closures + the P1 #12 brand-consistency closure.
That's a "trust the smoke spec" data point worth preserving. Future
sweeping changes (single-auth-provider, single-sql-client, the
god-component-split convoys) should expect the smoke spec to keep
catching auth-surface regressions without dedicated coverage
unless and until a feature change deliberately rewords the
`<Link>Sign in</Link>` CTA, in which case the smoke spec needs a
matcher update in the same PR.
#### Implementer-vs-architect calibration note
The architect's B2 forecast paragraph (`.convoys/pick-a-name/brief-2-
infrastructure-and-email-migration.md` line 662, the post-B2
repo-wide grep cross-check) reads *"Expected: at most 1 match
`test/lib/permission-middleware.test.js` line 87's preserved historical
regression literal"*. Architect-verified the implementer's actual
post-B2 state showed exactly 1 non-`.convoys/`-non-`*.md` match. **But
the architect's authoritative § Full surface inventory § Convoy
artifacts + manifest list (line 344 of the convoy file)** had already
correctly enumerated `scripts/seed-collections-with-cards.js` and
`scripts/seed-collections-alice-bob.js` (3 hits each) as DO-NOT-TOUCH
historical seed jobs per `.cursor/rules/no-go-zones.mdc` § Append-only
/ historical, with the explicit reasoning: *"the email migration in B2
covers the in-DB rename, so these scripts' WHERE-clause literals would
simply match zero rows post-migration if re-run, which is a harmless
no-op."* So the forecast paragraph and the inventory are both correct
the forecast counted "in-scope source files" while the inventory
documented why the seed-collections matches stay. Worth noting for the
next architect: when forecasting post-brief grep-cleanliness, cite the
inventory's exact carve-out rationale to avoid a reader thinking the
two numbers contradict each other. Not a process gap both ends were
right; the cross-reference between them could be tighter.
#### Operator action required going forward
**Run `node scripts/migrations/2026-05-24-rename-admin-email.js`
against the prod Neon DB before any admin login attempt with the new
`admin@deckhearth.com` email.** The migration is ESM, idempotent,
UNIQUE-collision-safe re-running on an already-migrated DB prints
"Nothing to migrate." and exits 0; running on a DB where the seed
script already created a duplicate `admin@deckhearth.com` row fails
loud on the `users.email` UNIQUE constraint (which is the correct
fail-loud behavior manually delete one of the duplicate admin rows
before retrying). **Order: migration FIRST, then any subsequent
`npm run setup-db`** the migration sweeps the existing
`@tcgvault.com` row to `@deckhearth.com`; a subsequent `setup-db`
sees the row exists with the new email and no-ops via `ON CONFLICT
(email) DO NOTHING`.
Documented in three places for redundancy:
- `AGENTS.md` Gotcha #4 (extended in this doc-writer pass with the
email-rename + migration-script + ordering paragraph).
- `README.md` operator caveat blockquote (added by B2 alongside the
existing `admin123`-rotation caveat from `drop-public-setup`).
- The migration script's own header comment + stdout messages.
Two downstream convoys are queued in `.convoys/ship-readiness.md`
§ Queued convoys after this one:
- **`rename-repo-and-vercel-project`** (P2 polish) GitHub repo +
Vercel project rename to `deck-hearth`; auto-redirects make it
low-urgency. One-line update to local git remotes + Vercel link
+ the 8 architect-verified `tcg-vault` literal-repo references
(README cd / tree, AGENTS.md line 1, three workflow YAML comments,
`.agent-context-manifest.yml` source tags).
- **`point-domain-at-deckhearth`** (blocked on domain acquisition)
DNS pointer + Vercel domain claim + admin email TLD swap if the
operator buys a TLD other than `.com`.
#### What did NOT change
Audit trail of files explicitly NOT touched by this convoy, despite
sitting near the brand-rename surface:
- **All `.convoys/**` historical artifacts.** ~25 lines across the
drop-public-setup / fix-auth-bypass / cors-tighten / add-rate-limiting
/ fix-layout-default-user / bump-next-js / fix-vercel-deployment-
protection convoy files reference `tcgvault` / `tcg-vault` /
`TCG Vault` as the historical state at write-time per
`.cursor/rules/no-go-zones.mdc` § Append-only / historical, these
stay byte-identical. The doc-writer pass at THIS convoy's close
rewrites only `.convoys/pick-a-name.md` (this file) + Gotcha #4 +
Gotcha #12 of `AGENTS.md` + the launch-sequence step in
`.convoys/ship-readiness.md` + the lineage attribution in
`.cursor/rules/api-routes.mdc`. No retroactive historical rewrite.
- **`scripts/seed-collections-with-cards.js`,
`scripts/seed-collections-alice-bob.js`,
`scripts/add-*.js`, `scripts/fix-*.js`** historical seed /
migration jobs per the no-go-zones rule. The 6 hits (3 each in the
two seed-collections scripts) of `@tcgvault.com` literals in their
WHERE-clauses are now stale the migration sweeps the in-DB rows,
so any re-run of these scripts would match zero rows and no-op
harmlessly.
- **Repo-name references** `README.md` lines 30 + 105, `AGENTS.md`
line 1, `.github/workflows/ci.yml` lines 12 + 123,
`.github/workflows/visual-diff.yml` line 5,
`.agent-context-manifest.yml` source tags. All literally describe
the GitHub repo + Vercel project, both of which still ARE named
`tcg-vault` until the queued `rename-repo-and-vercel-project` convoy
ships. Per the convoy seed § *"Keep the repo `tcg-vault` for now"*.
- **`docs/agent-context/README.md` line 78** (`"we'll measure
tcg-vault's number after a few convoys"`) and
**`.cursor/skills/add-api-route/SKILL.md` line 7**
(`"…for the tcg-vault stack"`) both reference the literal repo
name, not the product brand. Stay byte-identical; they sweep
together with `rename-repo-and-vercel-project`.
- **`scripts/reset-db.js` line 142** (CJS-in-ESM bug) and **lines
143 + 156** (`admin123` weak password literals) out of scope
per `convert-reset-db-to-esm` (now queued, surfaced 2026-05-24
during this convoy's architect inventory) + `purge-weak-creds-
from-helpers`. B2 only renamed the email literals on lines 147 +
155; the CJS bug + weak-password literals stay byte-identical for
the dedicated follow-up convoys to fix together.
- **`tests/smoke/**`, `tests/visual/**`, `playwright.config.js`**
smoke spec is brand-agnostic (architect-verified at brief time);
visual baseline doesn't exist yet (per
`adopt-playwright-smoke` Decision 4). The queued
`seed-visual-baselines-on-linux` convoy generates the first
baseline AFTER `pick-a-name` lands so the captured strings are
Deck Hearth (now satisfied `9abbab6` is on `main`).
- **`.agent-context-manifest.yml` `source: "tcg-vault-local"` tags**
(6 entries) internal `sync-agent-context` skill source identifier
for drift tracking. Renaming could break the skill's behavior;
flagged for re-evaluation in `rename-repo-and-vercel-project` when
the actual repo name changes (Risk 5 of the convoy file).
- **`pages/api/cards/import-mtg.js`'s `User-Agent`** the file does
NOT set a custom UA (Scryfall is fine with Node's default per
Brief 1 Finding 2). Only the Lorcana + Pokémon TCG import handlers
+ the standalone `scripts/import-lorcana.js` CLI variant set
`Deck-Hearth/1.0`.
- **No new vitest or playwright specs** Decision 6 honored. The
Risk 4 PRESERVE comment-block addition is the only test-file edit;
assertion semantics are unchanged.