Resolves the launch-blocking 'TCG Vault vs Deck Hearth' inconsistency called out in AGENTS.md line 5 since project setup. Operator gate-0 decision: Deck Hearth wins. Two briefs applied serially. B1 (mechanical): 7-file display + comment sweep. B2 (infrastructure): Redis prefix rename in lib/rate-limit.js (5 prefixes, accept one-time counter reset), package.json + lockfile regen (STOP-on-churn confirmed only name lines changed), admin/alice/bob email rename in seed scripts + login pre-fill + NEW idempotent migration script scripts/migrations/2026-05-24-rename-admin-email.js. Risk 4 PRESERVE applied: test/lib/permission-middleware.test.js retains admin@tcgvault.com literal with 7-line architect-authored why comment (documents pre-fix-auth-bypass bug shape; preserves historical truth per project's gotcha-documentation convention). All 5 D-decisions ratified at gate-1 (Deck Hearth / deck-hearth / deckhearth / admin@deckhearth.com / full deckhearth Redis prefix). Local: lint 128 baseline (B1 + B2), vitest 21/21 (B1 + B2). CI all green: Playwright smoke 3/3 against rebranded preview in 1m4s, forbidden-cors-headers pass, forbidden-endpoints pass, Screenshot diff pass, Vercel deployment complete. Cross-validation lineage: 4th convoy where the same 3-test smoke spec defends auth surface through sweeping change (after PR #15 Layout default-user, PR #19 CORS, PR #20 rate-limit, now this PR #21 brand rename). OPERATOR POST-MERGE ACTION REQUIRED: run 'node scripts/migrations/2026-05-24-rename-admin-email.js' against prod Neon DB before next admin login (ordering: migration FIRST, then any subsequent setup-db invocation). Migration is ESM, idempotent, UNIQUE-collision-safe. PR #21 architect-commit50ce9ab, B1ac8c998, B21c18d21.
33 KiB
pick-a-name (P1 — pre-launch brand consistency)
Status: in-progress
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
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-vaultfor now. GitHub rename is a separate concern (auto-redirects work, but Vercel-link /ghconfigs / local remotes / bookmarks all need touching). Track as a queued follow-up convoyrename-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.jsusestcgvault:auth/search/upload/generate/importas Redis key prefixes. These must rename todeckhearth:*(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.comrename toadmin@deckhearth.com(or similar — architect proposes the canonical email, operator ratifies). Includes the in-DB UPDATE for any existingadmin@tcgvault.comrow (write a one-offscripts/migrations/YYYY-MM-DD- rename-admin-email.jsper the no-go-zones rule for new migrations). package.jsonname field: rename todeck-hearthin 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.jsonin 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 adeckhearth.*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-assetsif they need new design.
Architect deliverables
- Full surface inventory — every occurrence of
TCG Vault,tcgvault,tcg-vault, plus every occurrence ofDeck Hearth/deck-hearth/deckhearth(the latter set tells you what's already correct and what shape collisions need handling, e.g., if Layout.js hasDeck Hearthbut AnimatedFireLogo.js hasDECKHEARTH— pick canonical casing). - 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.comor variant); the canonical Redis prefix (deckhearthordh). Mark each as D1-D5 (operator gate-1 for all five — they're naming choices, not architectural ones). - 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). - Boot-the-brief verification for each: confirm specs compile, lint passes, vitest passes, smoke can still run against a preview.
- 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-smokeDecision 4continue-on-errorswallow is still in effect, so visual-diff drift won't fail CI — but the queuedseed-visual-baselines-on-linuxconvoy 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.mdline 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 inpick-a-nameconvoy; 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 owndeckhearth.*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.comemails (besides admin), propose a migration. Architect to surface count via arg/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.jslines 623 + 716 →Deck Hearthcomponents/AnimatedFireLogo.jsline 13 →Deck Hearth Logopages/index.jslines 64, 96, 266, 299, 315 →Deck Hearthpages/login.jslines 76, 158 →Deck Hearthpages/signup.jsline 215 →Deck Hearthcomponents/ShareModal.jsline 116 →Deck Hearthstyles/globals.cssline 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 renderDeck 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.comand 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.jsagainst a staging DB. Expected: prints the count of@tcgvault.comrows 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
-
Operator forgets to run the migration script post-deploy. The admin email and Alice/Bob test-user emails would remain
@tcgvault.comin 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: thepick-a-namePR description MUST include a "Post-merge operator action" callout naming the migration script and thenode scripts/migrations/...command verbatim. (Same defense pattern asdrop-public-setup's "operator caveat" for the weak-hash rotation.) Doc-writer pass at convoy close adds this toAGENTS.mdGotcha #4's "Operator caveat" paragraph as a sibling caveat. -
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.
-
Lockfile regen surfaces unrelated dependency churn. Running
npm installafter apackage.jsonnamechange SHOULD only update thenamefield at the top ofpackage-lock.jsonplus thepackages.""entry inside it. But npm might opportunistically refresh staleintegrityhashes or pull in metadata updates. Mitigation: B2 acceptance criteria require the implementer to inspect thepackage-lock.jsondiff and confirm it touches onlyname-related entries; if any unrelateddependencies/versionchurn appears, the brief instructs the implementer to STOP and flag for a separatebump-deps-cleanupconvoy rather than landing accidental dependency updates inside the rebrand PR. -
The historical synthetic-admin negative regression assertion in
test/lib/permission-middleware.test.jsline 87. The literal emailadmin@tcgvault.comis 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-bypassshape"; or (b) update toadmin@deckhearth.comso 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. -
The
.agent-context-manifest.ymlsource: "tcg-vault-local"tag. Architect-verified at brief time that 6 lines reference this string as an internal source identifier for thesync-agent-contextskill's drift detection. Renaming todeck-hearth-localcould break the sync skill's behavior (it tracks "what shipped from this source"). Mitigation: leave as-is in this convoy; flag for re-evaluation inrename-repo-and-vercel-projectwhen the repo's literal name actually changes. -
Email is the unique constraint on
users— collision is impossible but admin-row recreation is. If the operator runssetup-neon-db.jsAFTER the migration runs, the seed'sINSERT … ON CONFLICT (email) DO NOTHINGwould NOT recreate the admin row (because the row exists with the NEW email post-migration). That's the correct behavior. If the operator runssetup-neon-db.jsBEFORE 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.comrow to@deckhearth.com, which would collide with the existing@deckhearth.comrow's UNIQUE constraint and fail loudly). The architect recommends the operator run the migration FIRST, then confirm viaSELECT email FROM users WHERE role = 'admin'returns exactly one row before runningsetup-neon-db.js. B2 acceptance criteria document this ordering. -
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
/multitasksince theirfiles: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)
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-verifiedtcg-vaultreferences that cite the literal repo (README cd/tree, AGENTS.md line 1, the three workflow YAML comments,.agent-context-manifest.ymlsource tags). Auto-redirects make this low-risk-low-urgency.point-domain-at-deckhearth— DNS / domain. Blocked on domain acquisition (operator pre-convoy: doesn't owndeckhearth.*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.icoexists 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.comemails. Architect-verified at brief time: pre-launch, the ONLY@tcgvault.comaccounts are admin + alice + bob (3 fixtures created bycreate-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.comemails, the same migration covers them too (it sweeps every row matchingemail LIKE '%@tcgvault.com').
Two NEW follow-ups surfaced by architect inventory:
convert-reset-db-to-esm(P2 hygiene) —scripts/reset-db.jsline 142 uses CommonJSrequire('bcryptjs')inside an ESM file. Same bug pattern that hitsetup-neon-db.jspre-drop-public-setupBrief 2. Brief 2 ofpick-a-namedoes NOT fix this in scope (it touches the email literal and console-log copy only); a separate convoy converts the require → import. May fold intopurge-weak-creds-from-helpers.update-seed-visual-baselines-on-linux-ordering— the queuedseed-visual-baselines-on-linuxconvoy MUST run AFTERpick-a-nameso 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 adepends_on: pick-a-nameordering note to theseed-visual-baselines-on-linuxqueued entry in.convoys/ship-readiness.md.
As-shipped
(To be appended post-merge by role-doc-writer.)