Commit graph

21 commits

Author SHA1 Message Date
varutasu
9abbab6c21
feat(brand): unify on Deck Hearth across in-repo strings + infra (P1 brand decision)
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-commit 50ce9ab, B1 ac8c998, B2 1c18d21.
2026-05-25 02:28:29 -05:00
Randall Stillwell
7832e03dec docs: post-convoy cleanup for add-rate-limiting — MILESTONE, last P0 closed
Reflects the merged add-rate-limiting convoy (PR #20, squash commit
708ef45) in repo documentation. **This is the milestone cleanup** —
add-rate-limiting closed P0 #6 (No rate limiting anywhere), the LAST
open P0 ship-blocker. `.convoys/ship-readiness.md`'s § Status summary
flips from "7 of 8 RESOLVED; 1 remains" to **"8 of 8 RESOLVED.
Launch-readiness P0 checklist is empty."** One brief in the convoy:
Brief 1 shipped as planned with no scope expansions and no implementer
deviations from the verbatim spec; all six architect decisions ratified
verbatim at gate 1 (D1 operator-ratified Option A; D2-D6
architect-self-ratified).

.convoys/add-rate-limiting.md:
  - frontmatter status: in-progress -> shipped (added shipped: 2026-05-24)
  - new ## As-shipped section. Opens with the milestone language
    pointing back at ship-readiness.md's flipped § Status summary.
    Decisions section captures all 6 ratifications (D1 operator-
    ratified Option A — Critical: WHY the atomic admin UI fix in
    pages/admin/card-import.js was Decision 1's hidden coupling
    requirement, since API gating alone would have broken every
    "Import Cards" click; D2 hybrid named-limiter shape with
    Map<className, Ratelimit> cache; D3 per-class table including
    the two D3 tuning-evidence raises — search 30 -> 60/min because
    ShareModal.handleSearch has no debounce so a 17-char email = 16
    requests in <5s, and generate kept at 5/hour because DiceBear is
    free not paid AI; D4 two-extractor shape with defensive THROW
    on null/empty userId; D5 uniform 429 message; D6 no new vitest
    or playwright specs deferred to fill-vitest-handler-coverage).
    As-shipped surface broken into 4 layers (1 lib refactor + 6 route
    gates + 1 atomic admin UI fix + 1 rule extension) mirroring the
    cors-tighten cleanup's pattern-split shape. Empirical CI metrics
    from post-merge run 26382185019 (Playwright smoke 59s 3/3 in
    3.8s, forbidden-cors-headers pass, vitest 21/21, lint 128 baseline,
    Screenshot diff continue-on-error swallow per Decision 4).
    Cross-validation finding: smoke test 2 still passes against the
    post-rate-limit preview — that's three convoys in a row (PR #15
    Layout default-user, PR #19 CORS-tighten, PR #20 rate-limiting)
    where the same 3-test smoke spec defended the auth surface
    through sweeping changes. Operator-action-required: none. What
    did NOT change audit trail.

.convoys/ship-readiness.md:
  - § Status summary at the top flipped from 7/8 to 8/8 RESOLVED.
    Header text updated to "Launch-readiness P0 checklist is empty."
    P0 #6 row in the table flips from PARTIAL to RESOLVED with the
    two-convoy lineage (fix-auth-bypass Brief 4 + add-rate-limiting).
    Trailing paragraph rewritten as a milestone note: security gate
    closed; remaining launch work is P1 quality bar + P2/P3 polish.
  - P0 #6 entry flipped from PARTIAL to RESOLVED with the full
    add-rate-limiting as-shipped block (8 sub-bullets covering the
    lib refactor shape, the per-class table, the defensive THROW,
    the three import routes' auth-gating, the atomic admin UI fix
    and WHY, the rule extension, the 6 decisions, and the diff
    breakdown). Brief 4's 2026-05-23 partial is preserved as the
    prior as-shipped layer to maintain the audit trail.
  - Launch sequence step 4 marked RESOLVED 2026-05-24 with the
    squash commit + smoke metrics inline.
  - Queued convoys: removed the add-rate-limiting entry (it shipped).
    Added a new delete-dead-lorcana-import entry (P3 polish; the
    Lorcana import route was gated defensively in PR #20 despite
    zero current frontend callers — pages/admin/card-import.js's
    <select> only offers mtg + pokemon — so if Lorcana stays
    permanently out of the admin UI, this is the cleanup PR).
    Added three "flagged but kept out of scope" follow-ups per the
    convoy's § What did NOT change: harden-multipart-parser (P2;
    5MB body still consumed before the 429 path on avatar.js),
    god-function-split / refactor-cards-search-sql (P2; 240-line
    7-branch SQL in cards/search.js), and withAdmin(handler) wrapper
    extraction (P3 DX; the three import routes are call sites #3-5
    in the codebase but uniform inline shape was preserved for
    convoy atomicity). Updated tighten-visual-diff-path-filter to
    note PR #20 also tripped the same false-positive.

AGENTS.md:
  - Gotcha #12 extended end-to-end. Was the single-class auth-only
    lib + the env-var contract; is now the 5-class reality with a
    full per-class table (helper / limit-window / key / routes),
    the defensive THROW pattern in extractUserIdentifier, the
    gate-ordering rule for per-user limiters, and the
    auth → admin-role → rate-limit ordering for the three import
    routes. Prominent milestone line opens the new content:
    "add-rate-limiting convoy (squash 708ef45, PR #20, 2026-05-24)
    closed P0 #6 — all 8 P0s now RESOLVED." Original env-var
    contract paragraph (KV_REST_API_URL / KV_REST_API_TOKEN,
    fail-closed-in-prod / warn-and-noop-in-dev) is preserved
    verbatim above the new content.
  - § 6 Testing: intentionally untouched (no test surface changed;
    vitest 21/21 and smoke 3/3 still apply).
  - § 7 Deployment: intentionally untouched (no deployment-shape
    changed; same KV_REST_API_* env vars from Brief 4).

.cursor/rules/api-routes.mdc:
  - The implementer extended § Rate limiting in PR #20 with the
    per-class table + verbatim call shape + gate-ordering rules +
    identifier-extraction + uniform 429 + fail-closed env-var
    contract + fail-open Upstash-outage behavior. Doc-writer pass
    verified completeness; added a one-sentence convoy-attribution
    line at the top of § Rate limiting citing the two-convoy
    lineage (fix-auth-bypass Brief 4 for the auth class +
    add-rate-limiting for the other four classes and 7 newly-gated
    routes), mirroring the post-cors-tighten § CORS attribution
    shape. No other touch-ups needed.

No changes to: package.json, package-lock.json, lib/rate-limit.js,
pages/**, components/**, scripts/**, test/**, tests/**,
.github/workflows/**, README.md, TESTING_GUIDE.md, playwright.config.js,
eslint.config.mjs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 23:09:07 -05:00
varutasu
708ef45a96
feat(security): rate-limit search/upload/import + gate import routes (P0 #6 - closes last P0)
Closes P0 #6 from PARTIAL to RESOLVED. 8/8 P0s now closed. Extends lib/rate-limit.js from single-class to 5 named limiters (auth/search/upload/generate/import). Atomically gates the 3 import routes (auth + admin-role check + rate limit) and fixes pages/admin/card-import.js's missing Bearer header in the same commit (architect's critical discovery: API gating alone would have broken the admin UI). Per Decision 1 Option A. 10 files +185/-23. Local: lint 128 baseline, vitest 21/21. CI: Playwright smoke 3/3 in 3.8s, forbidden-cors-headers pass, all gates green. PR #20 architect-commit 60b842e, implementer-commit 51a3a97. Brief 4's login.js + register.js byte-identical.
2026-05-24 22:59:59 -05:00
Randall Stillwell
f8b2cd85bc docs: post-convoy cleanup for cors-tighten
Reflects the merged cors-tighten convoy (PR #19, squash commit da50d78)
in repo documentation. Closes P0 #5 (Wildcard CORS on API surface) from
PARTIAL -> RESOLVED, leaving only P0 #6 (full add-rate-limiting) open
of the original P0 ship-blocker set. One brief in the convoy: Brief 1
shipped as planned with no scope expansions and no implementer deviations
from the verbatim spec.

.convoys/cors-tighten.md:
  - frontmatter status: in-progress -> shipped (added shipped: 2026-05-24)
  - new ## As-shipped section: all 5 architect-self-ratifiable decisions
    ratified verbatim (D1 Option B / D2 delete OPTIONS / D3 moot / D4
    no new tests / D5 add CI lock); Pattern split (16 Pattern A + 8
    Pattern B) per architect's 10-file audit + implementer's per-file
    diff review; diff size (25 files, +29/-261); empirical CI metrics
    from post-merge run 26378806555 (forbidden-cors-headers 4s PASS,
    Playwright smoke 56s 3/3 in 3.3s, Screenshot diff continue-on-error
    0 with the documented Decision-4 missing-baseline failure beneath);
    cross-validation that Playwright smoke continues to pass post-CORS
    removal (the auth + public surfaces don't depend on the wildcard
    header); implementer subagent-retry footnote (HEAD already at
    a843736 when retry woke up - transient retry, work is canonical);
    operator-action-required-going-forward: none; What did NOT change
    audit trail.

.convoys/ship-readiness.md:
  - new ## Status summary at the top (right after the code-graph line):
    P0 set is now 7/8 RESOLVED; only #6 (rate-limiting) remains. Table
    lists each P0 with its resolving convoy + squash commit for a quick
    scan of remaining work.
  - P0 #5 marked RESOLVED 2026-05-24. Added the cors-tighten as-shipped
    block (24 files swept, new CI job, 16/8 Pattern split, 5 decisions
    ratified, diff stat, post-merge CI metrics, transient retry
    footnote, operator-action: none). Brief 4's 2026-05-23 partial
    is preserved as the prior as-shipped layer above the cors-tighten
    layer to maintain the audit trail.
  - Queued convoys: removed the cors-tighten entry (no longer queued).
    Added a new tighten-visual-diff-path-filter entry (P3 polish) -
    Screenshot diff workflow triggered on API-only PR #19 because its
    paths: filter is pages/** which matches pages/api/** too. ~55s of
    CI waste per API-only PR; one-line YAML tweak; verify GitHub
    Actions' negated-glob semantics before merging.

.cursor/rules/api-routes.mdc:
  - new ## CORS section near the existing ## Dev/test endpoints
    (removed) section. Documents the no-CORS-by-default convention,
    the brief-4 + cors-tighten lineage, the new forbidden-cors-headers
    CI gate, and three forward-conventions (no setHeader for CORS,
    no OPTIONS preflight handlers, design a proper middleware layer
    if a future cross-origin caller is needed - not wildcards in
    individual handlers).

AGENTS.md intentionally untouched. Gotcha #5 (the public
setup-database.js endpoint) is already RESOLVED by fix-auth-bypass
Brief 3 and unrelated to this convoy. The new convention belongs in
.cursor/rules/api-routes.mdc (where API conventions live) rather than
AGENTS.md; the convoy file + the new CI gate are sufficient
documentation for the audit trail. Per convoy spec, no new gotcha
entry needed.

No changes to: package.json, package-lock.json, pages/api/**, lib/**,
components/**, scripts/**, test/**, tests/**, .github/workflows/**,
README.md, TESTING_GUIDE.md, playwright.config.js.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 20:49:30 -05:00
varutasu
da50d78406
fix(security): drop wildcard CORS + redundant OPTIONS from 24 API routes (P0 #5)
Closes P0 #5 from PARTIAL to RESOLVED. Sweeps the remaining 24 pages/api/** handlers that carried the identical scaffolded wildcard-CORS + OPTIONS preflight pattern (Brief 4 cleaned login + register; this finishes the job). Adds a blocking forbidden-cors-headers CI job modeled on forbidden-endpoints to lock the cleanup against future regression. 25 files changed (+29/-261). Local: lint 128 baseline, vitest 21/21, zero CORS matches, YAML valid. CI: Playwright smoke 3/3 in 3.3s against post-removal preview (login/verify flow still works), new forbidden-cors-headers job passes in 4s, all gates green. PR #19 architect-commit ec22b70, implementer-commit a843736.
2026-05-24 20:41:38 -05:00
Randall Stillwell
eefc390636 docs: post-convoy cleanup for adopt-playwright-smoke
Reflects the merged adopt-playwright-smoke convoy (PR #18, squash
commit 7b6f751) in repo documentation. Closes the test-infrastructure
side of P1 #10 step 2 (launch sequence step 10). One commit in the
convoy: Brief 1, with two small lint-baseline-preserving deviations
from the brief's verbatim shape that the implementer report flagged.

.convoys/adopt-playwright-smoke.md:
  - frontmatter status: in-progress -> shipped (added shipped:
    2026-05-24)
  - new ## As-shipped section: operator-ratified Decisions (D1 keep
    .ts, D4 defer baselines, D6 simple scripts); two implementer
    deviations (removed unused eslint-disable-no-console directive
    that would have regressed lint 128 -> 129; placed @playwright/test
    first in devDeps for alphabetical correctness); cross-validation
    that smoke test 2 ("sign-in page renders") locks in PR #15's
    logged-out CTA work in components/Layout.js; empirical metrics
    from post-merge run 26376162598 (59s workflow, 3/3 in 2.9s,
    0-leak); operator-action-required note pointing at the queued
    seed-visual-baselines-on-linux follow-up; What did NOT change
    audit trail.

.convoys/ship-readiness.md:
  - Queued convoys: new entry seed-visual-baselines-on-linux (Linux-
    Docker baseline generation per Decision 4 + Boot-the-brief
    Finding 7; Mac-generated baselines would silently overwrite Linux
    CI baselines because the custom snapshotPathTemplate has no
    {platform} token).
  - Queued convoys: new RESOLVED block for adopt-playwright-smoke
    (PR #18, 7b6f751) — as-shipped surface, implementer deviations,
    empirical metrics (59s workflow / 3/3 in 2.9s / 0 secret leaks),
    the PR #15 cross-validation finding, operator-action-required
    going forward (the seed-visual-baselines-on-linux follow-up),
    flagged-but-deferred items, and ownership trail (3 architect-
    self-ratified decisions + 3 operator-ratified).
  - Launch sequence step 10: marked RESOLVED 2026-05-24 with the
    commit + metrics inline.
  - P1 #10 No tests Fix sequence: step 2 marked RESOLVED with the
    convoy + metrics ref; step 3 (re-enable test: job in
    ci.yml) called out as the next remaining task; step 5 (wire
    preview-smoke.yml to the Vercel preview URL) marked RESOLVED
    across PR #17 + PR #18 since both contributed.

AGENTS.md:
  - Section 6 Testing: rewritten end-to-end. Was "E2E/smoke runner
    still pending"; is now "@playwright/test@^1.60.0 wired, two
    projects (smoke + visual), npm run test:smoke / test:visual /
    test:visual:update". Documents the local-run convention (boot
    next dev separately, then BASE_URL=... npm run test:smoke);
    the one-time npx playwright install --with-deps chromium step;
    the no-baselines-yet state + the Linux-Docker seed command +
    the cross-platform mismatch reason (no {platform} token in
    snapshotPathTemplate); the CI behavior split (vitest blocking,
    smoke on every PR with pipeline:skip-smoke escape hatch,
    Screenshot diff path-filtered with the first-red-on-missing-
    baseline state documented). Updates vitest coverage count
    16 -> 21 (the +5 Layout regression-lock tests from PR #15).
    Notes TESTING_GUIDE.md is being eclipsed and will be renamed
    to docs/MANUAL_QA.md in a future cleanup convoy.
  - Section 7 Deployment: rewrites the Vercel-bypass paragraph from
    a single "query param now / header reserved for future" bullet
    into a two-shape audit ((1) query param on the wait-action's
    path: input per PR #17; (2) HTTP header in playwright.config.js's
    use.extraHTTPHeaders per PR #18). Documents the Decision-2
    fail-loud-in-CI / warn-in-dev predicate and references Gotcha #12
    as the established precedent (lib/rate-limit.js). Picked Section
    7 over a new Gotcha because the bypass plumbing is operationally
    a deployment concern, not an app pitfall.

No changes to: package.json, package-lock.json, playwright.config.js,
eslint.config.mjs, lib/**, pages/**, components/**, scripts/**,
.github/workflows/**, .cursor/rules/**, README.md,
tests/visual/homepage.spec.ts (JSDoc is already neutral-tense, no
future-tense references to clean up).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 19:33:43 -05:00
varutasu
7b6f7519b2
feat(test): adopt @playwright/test + ship playwright.config.js + visual scaffold (P1 #10 step 2)
Closes P1 #10 step 2. Adds @playwright/test@^1.60.0, playwright.config.js (CI fail-loud / dev warn-and-continue for missing VERCEL_AUTOMATION_BYPASS_SECRET, two projects partitioned by testMatch, snapshotPathTemplate aligned with workflow upload path), tests/visual/homepage.spec.ts (1 test, no baseline committed per architect Boot-the-brief Finding 7), 3 npm scripts, and 3 .gitignore entries. Smoke tests now run end-to-end against Vercel preview with x-vercel-protection-bypass header: 3/3 passed in 2.9s, total workflow 59s. Zero secret leaks in log. PR #18 architect-commit 3ac527e, implementer-commit c72d006.
2026-05-24 19:25:18 -05:00
Randall Stillwell
c2a43a0ce2 docs: post-convoy cleanup for fix-vercel-deployment-protection-in-ci
Reflects the merged fix-vercel-deployment-protection-in-ci convoy
(PR #17, squash commit 9a3e077) in repo documentation. Closes the
CI-infra side-effect of P0 #7. Three commits in the convoy: Brief 1
(bypass plumbing per spec), plus two scope expansions discovered
during CI validation (shell-injection hardening, and a Decision-A
shape correction to drop the cookie-bypass param).

.convoys/ship-readiness.md:
  - Queued convoys: mark fix-vercel-deployment-protection-in-ci as
    RESOLVED 2026-05-24 with 9a3e077. Document the 3-commit reality
    (365e9f0 Brief 1 bypass plumbing, b6f8688 shell-injection
    hardening of the gate Decide step, 043a6ee dropping
    &x-vercel-set-bypass-cookie=true), the empirical metrics
    (wait-action: 10-min timeout -> 194ms; workflow runtime:
    10+ min -> 59s), the documented expected red on Playwright
    smoke (npx playwright test fails because playwright.config.js
    doesn't exist yet -- adopt-playwright-smoke owns that), and the
    operator-rotation caveat (R6).

AGENTS.md:
  - Section 7 Deployment: correct the noun "header" -> "query param
    on wait-action's path:" since that's what actually landed per
    Decision A. Also document the without-cookie form (the cookie
    variant 401s through axios's missing cookie jar) and the
    operator re-seed runbook for token rotation.
  - Section 7 Deployment: fold in a one-liner about the GitHub
    Actions ${{ }}-in-shell-is-injection-vector pattern, with the
    env: + quoted-shell fix shape. Picked Section 7 over a new
    Gotcha #13 because the existing Gotchas list is dominated by
    app-level pitfalls (auth, SQL clients, ESLint), and CI YAML
    hardening is naturally co-located with deployment.

.convoys/fix-vercel-deployment-protection-in-ci.md:
  - frontmatter status: in-progress -> shipped (added shipped:
    2026-05-24)
  - new ## As-shipped section: 3-commit reality, Decision-A shape
    deviation (we shipped without &x-vercel-set-bypass-cookie=true),
    empirical timings (194ms wait, 59s total), remaining-red
    attribution to adopt-playwright-smoke, and the
    operator-rotation caveat.

No changes to: package.json, lib/**, pages/**, components/**,
scripts/**, .github/workflows/**, .cursor/rules/**, README.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 16:32:45 -05:00
varutasu
9a3e077d40
fix(ci): plumb VERCEL_AUTOMATION_BYPASS_SECRET into preview-smoke + visual-diff (#17)
* architect: plan fix-vercel-deployment-protection-in-ci convoy (1 brief)

Flip convoy status queued -> in-progress and append the architect
output per the role-architect contract:

- Decisions (post-IA round) A/B/C/D with recommendations + rationale,
  routed back to operator for gate-1 ratification (A and D especially).
- Architecture section: file plan, risk list (R1-R8), test plan,
  decomposition (1 brief, justified), slice_dependencies block.
- brief-1: inject VERCEL_AUTOMATION_BYPASS_SECRET into both workflows
  via query-param-on-path (Decision A), tighten max_timeout 600 -> 120
  (Decision B), extend gate to skip fork PRs (Decision D), and forward
  the secret as an env var to the Playwright/visual step for forward-
  compat with adopt-playwright-smoke.

Boot-the-brief findings preempted in the brief:
- wait-for-vercel-preview@v1.3.2 source confirms only `targetUrl` (the
  bare deployment URL) is ever logged or emitted as outputs.url; the
  bypass query in `path:` is structurally invisible to logs and to
  downstream consumers of ${{ steps.vercel.outputs.url }}.
- The action exposes no custom-header input -- Option B is mechanically
  impossible for the wait step without forking the action.
- `path:` MUST begin with a leading `/` because the action parses it
  via `new URL(path, url)`.
- PR #16 run logs confirm the 401 timeout failure mode the convoy targets.
- Playwright config doesn't exist yet (adopt-playwright-smoke owns it);
  this brief only plumbs the env var.

No workflow YAML, no Playwright config, no AGENTS.md changes in this
commit -- those land in the implementer phase per the architect contract.

Awaiting human gate 1 (Decisions A + B + D ratification + brief approval)
before implementer dispatch.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ci): plumb VERCEL_AUTOMATION_BYPASS_SECRET into preview-smoke + visual-diff (Brief 1 of fix-vercel-deployment-protection-in-ci)

Closes the CI-infra half of P0 #7's follow-up. PR #16 (squash commit
7e97254) added scoped permissions to both workflows but exposed that
Vercel Deployment Protection 401s anonymous GitHub-runner requests,
causing both Playwright smoke and Screenshot diff to time out at 10
minutes on every PR. This brief plumbs the bypass secret end-to-end
so the wait-action's healthcheck reaches 200.

Per architect Decision A (.convoys/fix-vercel-deployment-protection-in-ci.md):
- preview-smoke.yml + visual-diff.yml: wait-for-vercel-preview's
  `path:` input now carries the bypass as a query parameter
  `?x-vercel-protection-bypass=${{ secrets.* }}&x-vercel-set-bypass-cookie=true`.
  The action only logs the bare targetUrl (verified in action.js:357,360,363)
  so the secret stays out of workflow logs.

Per Decision B:
- max_timeout: 600 -> 120. PR #16 evidence shows Vercel previews are up
  within seconds of job start; 120s gives ample headroom and surfaces
  misconfigurations in ~2 minutes instead of ~10.

Per Decision D (NEW -- surfaced by Boot-the-brief):
- gate: job's Decide step now checks github.event.pull_request.head.repo.fork
  FIRST. Forks lack repo secrets, so they would otherwise burn ~4 minutes
  per PR on a misleading 401. The fork-check emits `::notice::` and short-
  circuits before the existing skip-via-PR-body directive runs.

Forward-compat for adopt-playwright-smoke:
- Both workflows' Playwright/screenshot-capture step now exports
  VERCEL_AUTOMATION_BYPASS_SECRET as env. The actual Playwright config
  consumes it via extraHTTPHeaders in adopt-playwright-smoke's brief.
  BASE_URL stays as the bare \${{ steps.vercel.outputs.url }} (no query
  string) so it remains safe to echo.

Verification:
- YAML parses (js-yaml load on both files: preview-smoke jobs [gate, smoke];
  visual-diff jobs [gate, visual])
- actionlint not run (binary not installed locally); recommend installing
  for future PRs. Future adopt-actionlint convoy could add it to CI.
- No `set -x`, `echo`, `cat`, or `printf` of the secret or bypass URL
  in any modified step
- permissions: and concurrency: blocks unchanged (PR #16 contracts preserved)

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ci): route github.event.pull_request.body through env: to avoid shell injection

Pre-existing latent bug surfaced by PR #17's CI run. The Decide step's
inline `${{ github.event.pull_request.body }}` interpolation pastes
arbitrary PR-body text directly into a bash script. When the body
contains shell metacharacters (parens, pipes, backticks, redirections,
etc.) the resulting script either errors out at YAML-load time OR —
worse — executes attacker-controlled shell.

This bit PR #17 with a real syntax error ("unexpected token `('")
because the PR body contains parenthesized phrases like
"(was: 10-minute timeout)". Every Decide-step run in this repo has
been one badly-formatted PR body away from breaking the gate.

Fix: forward `github.event.pull_request.body` and
`github.event.pull_request.head.repo.fork` through the step's `env:`
block as `PR_BODY` and `PR_IS_FORK`, then quote them in shell
(`"$PR_BODY"`, `"$PR_IS_FORK"`). The env-var path leaves the values
as plain strings rather than syntactically embedded code, which is the
standard GitHub Actions hardening pattern (see GitHub's "Security
hardening for GitHub Actions" → "Using a third-party action").

Same change in both workflows; ~9 LOC each.

This fix is technically beyond Brief 1's scope (which targeted only
Vercel-bypass plumbing) but is added in this convoy because the bug
actively blocks Brief 1's success criterion from being validated on
PR #17. Documented in the convoy file's "Anything flagged but not
acted on" follow-up pass.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ci): drop x-vercel-set-bypass-cookie=true from wait-action path

The wait-action's healthcheck was still 401ing despite the bypass query
being correct. Root cause: with `x-vercel-set-bypass-cookie=true`, Vercel
returns 307 + Set-Cookie (`_vercel_jwt`), but axios in Node has no cookie
jar — it follows the redirect to the bare URL without the cookie, which
then 401s.

Local verification (run by operator):
  curl -sI "https://<preview>/?x-vercel-protection-bypass=<secret>" | head -1
  → HTTP/2 200   (works, no cookie needed)

  curl -sI "https://<preview>/?x-vercel-protection-bypass=<secret>&x-vercel-set-bypass-cookie=true" | head -1
  → HTTP/2 307   (the redirect-without-cookie path that breaks axios)

For a one-shot healthcheck, the per-request bypass query is enough.
The cookie variant stays reserved for the future Playwright config
(adopt-playwright-smoke) where a real browser cookie jar exists.

Added an inline comment in preview-smoke.yml explaining this so the
next agent doesn't accidentally re-add the cookie param.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 16:26:22 -05:00
Randall Stillwell
efca3e84c1 docs: mark P0 #8 (Next.js bump) RESOLVED in ship-readiness.md
Doc-drift fix discovered after the fix-layout-default-user cleanup
pass. The bump-next-js convoy shipped on 2026-05-23 as commit
e57ea17, and the commit body explicitly states "Closes P0
ship-blocker #8" — but no dedicated doc-writer pass ever ran for
that convoy, so .convoys/ship-readiness.md still had P0 #8 listed
as open even though Vercel has been deploying main + every PR
successfully since.

Patches only P0 #8 to RESOLVED with the same shape used by P0
#1, #2, #3, #4, #7:
  - Title: "— **RESOLVED 2026-05-23**"
  - Resolved by: bullet citing e57ea17
  - As-shipped: 7-point summary of the bump's actual deliverables
    (next 15.4.3->16.2.6, ESLint v9-fallback, typescript devDep,
    eslint.config.mjs, scripts.lint update, images.remotePatterns
    migration, build verification)
  - Side-effects deferred: bump-react, bump-eslint-10, bump-typescript-6,
    fix-lint-baseline, App Router migration
  - Doc drift note acknowledging this entry was added ~24h late

No code changes. No package.json / lib/** / pages/** / components/**
touched. AGENTS.md was already current (Gotchas #9, #10, #11 already
reflect Next 16 + ESLint v9 + Turbopack post-bump state).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 14:40:46 -05:00
Randall Stillwell
b7ddd0895e docs: post-convoy cleanup for fix-layout-default-user (+ queue CI-protection follow-up)
Reflects the merged fix-layout-default-user convoy (PR #15) and the
companion CI permissions fix (PR #16) in repo documentation. Also
queues the new fix-vercel-deployment-protection-in-ci convoy that
PR #16 exposed.

.convoys/ship-readiness.md:
  - P0 #7: mark RESOLVED 2026-05-24 with squash commit ca302a8.
    Document the as-shipped Layout default-null change, the 7-page
    sweep, the 5 new regression-lock vitest assertions, and the
    queued follow-ups (single-auth-provider, MobileNavigation
    cleanup) that stayed explicitly out of scope.
  - Queued convoys: add fix-vercel-deployment-protection-in-ci
    (P2, CI infra) — PR #16's permissions fix exposed that Vercel
    Deployment Protection 401s anonymous CI requests; needs a
    bypass-secret plumb to land cleanly. New section also captures
    other in-flight follow-ups (rotate-default-admin, cors-tighten,
    add-rate-limiting, purge-weak-creds-from-helpers,
    single-auth-provider, cleanup-mobile-nav-dead-props,
    bump-eslint-10) so the audit trail is centralized.

AGENTS.md:
  - § 4 Gotcha #8: mark RESOLVED with commit ref ca302a8. Mirror
    the convention used by the prior cleanup commits for #2, #3,
    #4, #5 (entry kept, not renumbered).
  - § 3 Key conventions: add a new "Layout user prop" bullet
    documenting the new default-null + logged-out-CTA contract so
    the convention is discoverable from the conventions list, not
    just the resolved-gotcha entry.

.convoys/fix-vercel-deployment-protection-in-ci.md (new):
  - Queued scaffold. Operator must seed
    VERCEL_AUTOMATION_BYPASS_SECRET as a repo secret before the
    implementer can run. Decisions to ratify (query param vs.
    header), known constraints, acceptance criteria, and
    out-of-scope all enumerated.

No changes to: package.json, lib/**, pages/**, components/**,
scripts/**, .github/**, README.md, .cursor/rules/**.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 14:37:39 -05:00
varutasu
ca302a89c1
fix(layout+pages): default user=null + page audit sweep (P0 #7) (#15)
* convoy: scope fix-layout-default-user (P0 #7 — Layout maintainer-email leak)

The last remaining P0 ship-blocker from .convoys/ship-readiness.md.
components/Layout.js line 562 defaults the user prop to a real email
address (me@randallstillwell.com); any page that renders Layout without
passing user explicitly impersonates the maintainer.

Scope: components/Layout.js + audit of 17 pages that import Layout
(grep-confirmed list in convoy file). Single PR likely. Auditor cohort
skipped (no design-system, IA, or browser-smoke surface).

Architect to address:
  - Q1: logged-out rendering branch design (navbar, mobile-nav,
        auth-only items treatment)
  - Q2: page audit triage into always-auth / public-or-auth /
        anonymous-allowed buckets
  - Q3: brief decomposition (single brief / 2 briefs in 1 PR / fan-out)
  - Q4: whether to add vitest coverage for the logged-out branch
        (recommend yes — small surface, high regression protection)

Hard out-of-scope: branding (pick-a-name), auth-provider collapse
(single-auth-provider), Layout god-component split (god-component-split).

depends_on: bump-next-js (shipped), fix-auth-bypass (shipped),
            drop-public-setup (shipped)
addresses: P0 #7 from .convoys/ship-readiness.md
parent: ship-readiness

Co-authored-by: Cursor <cursoragent@cursor.com>

* architect(fix-layout-default-user): plan + briefs 1-2 (Layout fix + page audit)

2 briefs, single PR. ~12 files net (down from the 18 in the original scope —
10 of the 17 Layout-importing pages already pass user explicitly).

Brief 1: components/Layout.js default user=null + Sign-in CTA branch in
  UserProfileDropdown when logged out. Adds first jsdom test in the repo
  at test/components/Layout.test.js (Decision D2) with 5 regression-lock
  assertions. devDeps: jsdom@^29, @testing-library/react@^16.

Brief 2: page audit sweep — 7 pages need code changes:
  - Pass user={user} to Layout: scanner.js, deck-builder.js (×4),
    deck/[id].js (×3), decks.js (×3)
  - Replace page-level useState({email: 'me@...'}) → useState(null) +
    null-guards: profile.js, settings.js
  - Replace hardcoded const user = {email: 'me@...'} with useAuth():
    card/[id].js

Discovered second anti-pattern: profile.js, settings.js, card/[id].js
seed page-level state with the maintainer email. Folded into Brief 2 since
success metric "no real email address remains in any component default-prop"
reads naturally to include page-level seed values.

Decisions:
  A1 — Sign-in CTA replaces avatar+email+dropdown when user===null;
       hides auth-only dropdown (Profile/Settings/Logout/Admin);
       keeps public + community nav visible
  B  — Per-page bucket assignment (10 already correct, 7 need fix);
       full per-page table with justification in convoy file
  C2 — Two briefs in one PR (Brief 1 = Layout + test; Brief 2 = page
       sweep depends on Brief 1). C1 buries the conceptual change under
       mechanical edits; C3 is over-orchestrated for this scope
  D2 — vitest lock-in; first jsdom test in repo; same negative-regression
       style as test/lib/permission-middleware.test.js (synthetic-admin
       shape). devDeps jsdom + @testing-library/react

Risks tracked R1-R8. Biggest: R2 (useState(null) null-deref in 3 leaky
pages — mitigated by audit-pass mandate + manual smoke).

MobileNavigation deliberately NOT folded in: its user prop is dead code
(never reads user.*); different bug class; cleanup queued separately to
avoid scope expansion.

Flagged-but-deferred:
  - 4 pages still import useAuth from lib/auth-context.js
    → single-auth-provider (queued P1 #9)
  - Layout headers still render "Deck Hearth" / "DH" branding
    → pick-a-name (queued P1 #12)
  - MobileNavigation dead user prop → cleanup-mobile-nav-dead-props
    or fold into god-component-split

addresses: P0 #7 from .convoys/ship-readiness.md (last P0 ship-blocker)
parent: ship-readiness
Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(layout): default user=null + Sign-in CTA when logged out (Brief 1 of fix-layout-default-user)

Closes the source-side half of P0 #7 from .convoys/ship-readiness.md.
The page-side sweep (Brief 2) follows in a separate commit.

components/Layout.js:
  - Default user prop is now null (was hardcoded to
    { email: 'me@randallstillwell.com', role: 'user' })
  - UserProfileDropdown renders a "Sign in" link to /login when
    user === null instead of the maintainer's email + auth-only menu
    items (Decision A1)
  - All user.* accesses guarded with optional chaining or null checks
  - useState hook stays above the new null-user early return to satisfy
    rules-of-hooks (boot-the-brief caught this on the first try;
    see AGENTS.md Gotcha #11.5)

test/components/Layout.test.js (new):
  - First jsdom test in the repo (Decision D2)
  - 5 regression-lock assertions: no maintainer email ever rendered
    (prop omitted, prop=null), Sign-in link exists with href=/login,
    supplied email renders when prop is set, no "Guest" placeholder
    (locks A1 copy choice)
  - Mocks next/link, next/router (prefetch, replace, events, query),
    and theme-context.useTheme for jsdom safety under Next 16

package.json + package-lock.json:
  - Add jsdom@^29 and @testing-library/react@^16 to devDependencies
  - @testing-library/dom@^10 added explicitly (peer auto-install
    skipped it under npm 11; brief anticipated this fallback)

vitest.config.js (deviation from brief — see PR description):
  - Add esbuild { loader: 'jsx', jsx: 'automatic' } so vitest can
    parse JSX in .js files. Required to import any React component
    written in the repo's Next.js pages-router .js convention
    (AGENTS.md Gotcha #9). The brief said "no change" to this file,
    but JSX-in-.js parsing is a hard prerequisite for the new test
    to import components/Layout.js — the alternatives (rename test
    to .test.jsx; rewrite test in React.createElement) either break
    the test glob or still hit the same Layout.js parse failure.
    Other tests are unaffected (they import non-JSX modules).

Smoke output: see PR description.

addresses: P0 #7 from .convoys/ship-readiness.md (last P0 ship-blocker)
Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(pages): pass user explicitly + null-guard leaky page seeds (Brief 2 of fix-layout-default-user)

Closes the page-side half of P0 #7 from .convoys/ship-readiness.md.
Brief 1 (commit ddf8fd2) handled the Layout-side fix.

Per the architect's per-page bucket table (Decision B in
.convoys/fix-layout-default-user.md), 7 pages needed code changes;
the other 10 of 17 Layout-importing pages already pass `user` correctly.

Pass user={user} to Layout (4 pages, 11 call sites):
  - pages/scanner.js (1 call)
  - pages/decks.js (3 calls)
  - pages/deck-builder.js (4 calls)
  - pages/deck/[id].js (3 calls)
  (All four still import useAuth from lib/auth-context.js — that's
   intentional and stays as-is until the single-auth-provider convoy
   collapses the three parallel auth surfaces.)

Replace leaky page-level seed values with useState(null) + null guards
(2 pages, R2 mitigation):
  - pages/profile.js: useState({email: 'me@...', role: 'user', ...})
                     → useState(null) + ?. on every sync user.* read
                     + early-return guards in getDisplayName/getInitials
                     + conditional render around the "Member since" block
                       so formatDate(undefined) never runs
  - pages/settings.js: same pattern (single user.email reader guarded)

Replace hardcoded const with useAuth from lib/use-auth.js (1 page):
  - pages/card/[id].js: const user = {email: 'me@...'}
                       → const { user } = useAuth() (called unconditionally
                       at the top of the component; rules-of-hooks safe)

Verification:
  - grep 'me@randallstillwell.com' pages/ → 0 hits
  - 21/21 vitest tests pass (16 pre-existing + 5 from Brief 1)
  - npm run lint matches baseline (128 problems pre, 128 post; verified
    via git stash before/after)
  - Manual static read-through of every diff; ReadLints clean on the 7
    files
  - Dev-server smoke: /cards anonymous returned HTTP 200 with 0
    'me@randallstillwell' matches before the user's shared dev server
    became unresponsive mid-session (same dev-server-shared-by-user
    constraint flagged in Brief 1); interactive logged-in smoke is
    parent/operator gated

Flagged-but-deferred (untouched per scope):
  - 4 pages still import useAuth from lib/auth-context.js
    → single-auth-provider (queued P1 #9)
  - components/MobileNavigation.js still receives dead user prop
    → cleanup-mobile-nav-dead-props (or fold into god-component-split)

addresses: P0 #7 from .convoys/ship-readiness.md (last P0 ship-blocker)
Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 14:31:37 -05:00
Randall Stillwell
eeb14eb0e0 docs: post-convoy cleanup for drop-public-setup
Reflects the merged drop-public-setup convoy (PR #13) in repo
documentation. Small focused pass; no behavior changes.

AGENTS.md:
  - § 1 auth bullet: replace "seed admin row still ships in
    setup-neon-db.js" claim with the new env-var-gated reality and
    the R1 operator-rotation caveat.
  - § 4 Gotcha #4: mark RESOLVED with commit refs (ff80753 + b63b509),
    document the as-shipped behavior, the Brief 2 CJS→ESM Node 22.x
    fix, and the R1 operator caveat. Entry kept (not renumbered) per
    the same convention used for resolved gotchas #2, #3, #5.
  - § 5 Running locally: add ADMIN_INITIAL_PASSWORD to the env-var
    template list with a note that setup-db exits 1 if it's unset.

.convoys/ship-readiness.md:
  - P0 #3: mark RESOLVED 2026-05-23 with commit refs, document the
    full as-shipped behavior including Brief 2's CJS→ESM bonus,
    the R1 operator caveat (Decision A — going-forward only),
    and the deferred sibling weak-cred references queued for
    purge-weak-creds-from-helpers.

.cursor/rules/no-go-zones.mdc:
  - Editing rules of thumb: clarify the schema-vs-operational
    distinction for scripts/setup-neon-db.js. drop-public-setup
    set the precedent that operational changes (env-var gating,
    pre-flight validation, module-system fixes) are allowed in
    place, while DDL changes still need a separate migration
    script. Documented so future agents don't have the same
    confusion the drop-public-setup architect did (see Decision B
    in .convoys/drop-public-setup.md).

No changes to: package.json, lib/**, pages/**, components/**,
scripts/**, .github/**, README.md (already updated in PR #13).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 17:06:35 -05:00
Randall Stillwell
792439b5eb architect(drop-public-setup): expand scope with brief 2 (CJS→ESM)
Mid-convoy discovery: brief 1's implementer confirmed scripts/setup-neon-db.js
does not actually run on Node 22.x because bump-next-js added "type": "module"
to package.json but the seed script still uses CJS require() calls.
Throws ReferenceError immediately on `npm run setup-db`.

Architect's original "Anything flagged but not acted on" #1 claim that "it
runs successfully today under Node 22" was incorrect for Node 22.14.0.

Decision D (ratified by user 2026-05-23): expand convoy to include brief 2
rather than queue a separate convert-setup-db-to-esm follow-up. Rationale:
brief 1's env-var gate is theatrical security on a script no operator can
execute; the CJS→ESM conversion is mechanical (~6 LOC, same file, no
functional changes); splitting into two convoys creates a regression window
where operators on Node 22.x cannot bootstrap a database.

Brief 2 scope: pure module-system conversion in scripts/setup-neon-db.js:
  - require('dotenv').config(...) → import dotenv + dotenv.config(...)
  - require('@neondatabase/serverless') → import { neon }
  - inline require('bcryptjs') hoisted to top-of-file import
  - no functional changes; same DDL, same env-var gate, same console.logs

Verification: smoke must now show npm run setup-db actually executes (no
ReferenceError); brief-1 env-var gate must still fire as documented;
all 16 vitest tests must still pass.

Updated:
  - .convoys/drop-public-setup.md Decomposition (brief 2 added, depends_on brief 1)
  - .convoys/drop-public-setup.md slice_dependencies YAML
  - .convoys/drop-public-setup.md § Decisions (added Decision D)
  - .convoys/drop-public-setup.md § Anything flagged but not acted on
    (item #1 marked resolved by brief 2)

addresses: P0 #3 from .convoys/ship-readiness.md + Node 22.x compat
parent: ship-readiness
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 17:02:44 -05:00
Randall Stillwell
4d32659545 architect(drop-public-setup): plan + brief 1 (env-var admin password)
Single-brief convoy. ~25 LOC net across 2 files (scripts/setup-neon-db.js,
README.md). No fan-out; single PR.

Decisions:
  A1 — going-forward only (matches JWT_SECRET pattern from
       fix-auth-bypass Brief 1). Existing weak-hash admin rows in
       deployed envs are NOT rotated; operators rotate manually
       via the app after merge. Queue rotate-default-admin follow-up
       if a real audit finds a deploy still on the weak hash.
  B  — operational change to setup-neon-db.js is allowed; no-go-zones
       rule prohibits SCHEMA edits, not env-var gating.
  C1 — no vitest coverage. Fail-loud path is validated by manual smoke
       (the brief mandates pasting fail-loud + happy-path output into
       the PR description).

Key risks tracked: R1 (existing weak hash), R2 (unhelpful error),
R3 (stdout password leak — delete the console.log lines, do NOT
interpolate the env-var), R4 (silent rotation if ON CONFLICT changed
to DO UPDATE), R5 (README env block omits the new var), R6 (3 sibling
files still have admin123 — out of scope per convoy spec).

Flagged-but-deferred:
  - CommonJS in ESM package (setup-neon-db.js) → convert-setup-db-to-esm
  - admin123 in reset-db.js, create-test-users.js, TESTING_GUIDE.md
    → purge-weak-creds-from-helpers (or fold into launch-polish)
  - admin@tcgvault.com hardcoded email → pick-a-name convoy

addresses: P0 #3 from .convoys/ship-readiness.md
parent: ship-readiness
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 17:02:44 -05:00
Randall Stillwell
466f859ec2 convoy: scope drop-public-setup (P0 #3 — admin credentials in seed)
Closes P0 #3 from .convoys/ship-readiness.md. Removes the hardcoded
admin@tcgvault.com / admin123 credentials from scripts/setup-neon-db.js
(make it env-var-driven) and from README.md.

Single PR; small surface (2 files). No UI, no API, no migration. Auditor
cohort skipped (no UX/a11y/design-system surface).

P0 #3's third file (pages/api/setup-database.js) was already deleted by
fix-auth-bypass Brief 3.

Architect to address:
  - Existing-admin rotation story (going-forward fix vs. forced reset)
  - Confirm scripts/setup-neon-db.js no-go-zones rule applies to schema
    changes only (this is operational)
  - Whether to add vitest coverage for the env-var-required path

depends_on: bump-next-js (shipped), fix-auth-bypass (shipped)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 17:02:44 -05:00
Randall Stillwell
c7ad0ffa29 docs: post-convoy cleanup for fix-auth-bypass
Closes out the fix-auth-bypass convoy (PRs #6–#11, merged through
1629afb) on the docs side. Code already on main; this PR is docs only.

Updates:

AGENTS.md
  - §1 auth bullet refreshed (auth-secret SoT, 24h TTL, no synthetic
    admin, login/register rate limit)
  - §3 conventions point at lib/auth-secret.js + lib/rate-limit.js
  - §4 gotchas #2/#3/#5 converted to "Resolved" notes in place
    (NOT renumbered, to preserve cross-references)
  - new #12 documents the KV_REST_API_* env-var convention
  - §5 setup list adds the rate-limit env vars
  - §6 testing rewritten for Vitest (16 unit tests, blocking CI gate)

.cursor/rules/auth-and-permissions.mdc
  - canonical-surface table gains lib/auth-secret.js + lib/rate-limit.js
  - token model now 24h (was 7d) with fail-loud explanation
  - server-side authorization patterns lead with null → 401 contract

.cursor/rules/api-routes.mdc
  - removes the "CRITICAL — known bug" callout (resolved by Brief 2)
  - adds a "Rate limiting" section with verbatim shape + env-var notes
  - "Dev/test endpoints" → "Removed" historical note so future agents
    searching for test-db understand why it's gone

.convoys/fix-auth-bypass.md (restored — was on convoy branch only)
  - frontmatter → status: shipped
  - new "Convoy outcome" section: briefs + commits + resolved gotchas,
    R1-R12 risk walk, env-var-rename deviation record, queued follow-up
    convoys, lessons learned

.convoys/fix-auth-bypass/brief-{1..5}-*.md (restored from convoy branch)
  - audit-trail completeness; convoy plan references them by name
  - brief 4 additionally updated: UPSTASH_REDIS_REST_* → KV_REST_API_*
    across init rules, smoke, pre-deploy checklist
  - brief 4 has a new "Post-merge addendum" explaining the rename

.convoys/ship-readiness.md
  - P0 #1, #2, #4 → RESOLVED with merge-commit citations
  - P0 #5 (CORS), #6 (rate limit) → PARTIAL with deferral pointers
    (cors-tighten and add-rate-limiting convoys)
  - each item gains an "As-shipped" line for self-containment

README.md
  - Next.js 15 → 16, TypeScript claim corrected to JS-with-devDep
  - auth + rate-limit + testing bullets updated
  - env-var template extended with KV_REST_API_*
  - deleted dev-endpoints note added to the API list
  - "Default Admin Account" section LEFT ALONE — drop-public-setup territory

Verified: build exit 0 (with JWT_SECRET set), 16/16 vitest tests pass,
lint baseline unchanged (128/81/47).

Convoy: fix-auth-bypass / role-doc-writer (closeout)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 12:22:50 -05:00
Randall Stillwell
7540b2c8d5 convoy(bump-next-js): plan + brief 1 (Decisions A-D)
Adds the architect's plan for bump-next-js (P0 ship-blocker #8) and the
single-brief decomposition that the implementer worked from.

- ## Architecture section: file plan, API surface (none), schema diff
  (none), test plan, risk list (R1-R16), Decomposition table, and
  slice_dependencies YAML.
- Brief 1: bump Next.js 15.4.3 -> 16.2.6, migrate images.domains ->
  images.remotePatterns, install ESLint flat config, replace removed
  'next lint' command with 'eslint .', add typescript devDep.
- Decisions log A-D, dated 2026-05-23, recording four gate-1 scope
  changes driven by Boot-the-brief findings and an empirical R15 firing:
  - A: expand scope to include ESLint v8 -> v9 + flat-config migration
  - B: pivot eslint pin v9.39.4 -> v10.4.0 (latest dist-tag)
  - C: add typescript@^5.9.3 devDep (peerDependenciesMeta.optional
    annotation only suppresses npm warning; runtime hard-requires it)
  - D: re-pin eslint v10.4.0 -> v9.39.4 (R15 fired empirically;
    @typescript-eslint/scope-manager@8.59.4 predates v10 GA, lacks
    new addGlobals API)
- Follow-up convoys queued: bump-eslint-10, bump-typescript-6 (both
  upstream-blocked on typescript-eslint shipping a v10-tested release).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 02:31:26 -05:00
Randall Stillwell
84aa381bd3 convoy: scope bump-next-js (P0 #8 — unblock Vercel deploys)
Conductor output for the highest-priority convoy in the launch
sequence. Closes P0 ship-blocker #8 from .convoys/ship-readiness.md.

Vercel is currently refusing to deploy any branch (including main)
due to a CVE in next@15.4.3 ("Vulnerable version of Next.js
detected"). Last successful main deploy: 2025-08-01. Until this
convoy lands, every downstream preview-smoke / visual-diff gate is
non-functional.

Classification: feature
Skip: ia, ux, flag
Next role: role-architect

Routing straight to architect (IA + UX skipped — no information
architecture or UX change). Architect reads the Next 15 → 16
migration guide and produces 1–3 briefs covering the bump itself,
any required code migrations (likely next.config.js
images.domains → images.remotePatterns), and Vercel preview
verification.

Audit cohort (post-PR draft, /multitask group):
  reviewer + design-system-auditor + a11y-auditor

Out of scope here (own convoys):
- React 18 → 19 bump        → bump-react (if/when desired)
- App Router migration      → out of horizon
- @playwright/test install  → adopt-playwright-smoke
- ESLint baseline cleanup   → fix-lint-baseline

Convoy file: .convoys/bump-next-js.md
Analytics: emitted via scripts/log-convoy-event.sh

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 02:31:26 -05:00
Randall Stillwell
9aaa599820 fix(bootstrap): make L3 CI green + record two new ship-blockers
The throwaway bootstrap PR exposed three pre-existing issues that
weren't visible before the pipeline was installed:

1. ESLint had no config (`.eslintrc.json` missing) even though the
   `lint` script and deps were both present. `next lint` was prompting
   interactively in CI. Added `.eslintrc.json` extending
   `next/core-web-vitals` (Next.js Strict).

2. Running lint surfaced ~100 pre-existing errors, including several
   real bugs (conditional React hook calls in components/pages).
   Marked the CI lint job `continue-on-error: true` with an explicit
   TODO so PRs aren't blocked while a follow-up convoy
   (fix-lint-baseline) cleans up the codebase. Lint output is still
   visible in PR logs.

3. Vercel is platform-blocking every deployment with "Vulnerable
   version of Next.js detected" — locked at 15.4.3, latest is 16.2.6.
   The last successful Vercel deploy on main was 2025-08-01. Until
   Next.js is bumped, every preview-smoke / visual-diff gate is
   non-functional. Added as P0 #8 with a new `bump-next-js` convoy at
   the front of the launch sequence.

Updated `.convoys/ship-readiness.md`:
- P0 #8: Vercel deploy blocked by Next.js CVE
- P1 #11.5: pre-existing lint baseline
- Launch sequence: prepend `bump-next-js` at step 0, add
  `fix-lint-baseline` at step 3.5

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 02:31:26 -05:00
Randall Stillwell
1944b1ed48 bootstrap: agent pipeline v0.5.0 + ship-readiness review
Installs the three-layer agent-pipeline scaffold (https://github.com/varutasu/agent-pipeline @ v0.5.0):

L1 — Context (curated brain)
- AGENTS.md: orientation, conventions, 8 explicit gotchas
- .cursor/rules/: no-go-zones, api-routes, auth-and-permissions,
  db-and-schema, ui-and-theming, schema-map
- .cursor/skills/: add-api-route, add-page recipes
- docs/agent-context/README.md: layer explainer
- docs/SCHEMA_MAP.md: hand-curated Neon Postgres reference
  (replaces Prisma schema map since stack is raw SQL)

L2 — Subagent roles (copied verbatim from upstream templates)
- 9 .cursor/agents/role-*.md files: Conductor, IA-Architect,
  UX-Reviewer, Architect, Implementer, Reviewer,
  Design-System-Auditor, A11y-Auditor, Doc-Writer

L3 — Pipeline scaffolding (Vercel variant)
- CI: lint + schema-map-drift only (no duplicate build —
  Vercel handles it). Test job commented out until vitest lands.
- preview-smoke + visual-diff via wait-for-vercel-preview
- pr-health-rollup sticky comment aggregator
- agent-context-drift weekly cron
- PULL_REQUEST_TEMPLATE, CODEOWNERS (auth/admin paths tagged)
- .convoys/ folder + seed ship-readiness.md review
- lib/flags/index.js (JS — converted from TS template)
- scripts/wt.sh (Cursor 3.2 deprecation stub),
  scripts/log-convoy-event.sh
- tests/smoke/app.smoke.spec.ts (Playwright skeleton)

Manifest
- .agent-context-manifest.yml: tracks 31 artifacts by sha256
  for future sync-agent-context drift detection

Review
- .convoys/ship-readiness.md: 16 findings (7 P0 ship-blockers,
  5 P1 quality-bar, 4 P2 refactor, P3 UX/IA/a11y/docs) with
  proposed 13-convoy launch sequence.

No production code changed in this commit. All findings in
the ship-readiness review will be addressed in follow-up convoys
starting with fix-auth-bypass.

Structural brain: user-code-review-graph MCP has indexed the
codebase (122 files, 628 nodes, 5602 edges, 11 communities,
84 flows). Per-developer; not committed.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 02:31:26 -05:00