Review: Checkout API: guest flow (gamertag + email)

review-813-2026-04-03-r4 Review

review ready

Verdict: READY

Fourth review pass (board item #813). All three recommendations from r3 have been addressed. Issue now has 11 source files + 6 test files = 17 total file targets, all verified in repo.

Previous Findings — Resolved

  • [x] [BODY] Add src/hooks.server.ts to File Targets — FIXED. Now listed under "Auth + routing" section with note: "relax PROTECTED_PREFIXES: /game/[id] must be accessible without auth (guest challengers have no session). /play also unprotected."
  • [x] [BODY] Add src/app.d.ts to File Targets — FIXED. Now listed under "Schema + types" section with note: "SessionData.twitchId becomes optional"
  • [x] [BODY] Clarify AC #8 re: hooks.server.ts — FIXED. AC #8 now reads: "Game status page accessible without auth (hooks.server.ts PROTECTED_PREFIXES relaxed for /game and /play)" — explicitly names the file and the mechanism.

Template Completeness

  • [x] Type — Feature
  • [x] Lineage — Required by #38 and #40. Modifies existing checkout endpoint + schema.
  • [x] Repo — forgejo_admin/twitch-2k-wager
  • [x] User Story — clear role/want/so-that for guest challenger
  • [x] Context — explains current twitch_id PK, full 6-step migration plan
  • [x] File Targets — 11 source files + 6 test files = 17 total, organized by section
  • [x] Acceptance Criteria — 11 testable criteria
  • [x] Test Expectations — 4 expectations with CI gate
  • [x] Constraints — backward compat, dedup keys, idempotent migration, no new deps
  • [x] Checklist — PR + tests + no unrelated changes
  • [x] Related — links #38, #40, story notes, arch note

Traceability

  • [x] story:challenger-pay label — Secure Buy-In Payment
  • [x] story note verified — found in project-twitch-2k-wager user-stories table (key: challenger-pay, note: story-twitch-2k-wager-challenger-pay, id 1158)
  • [x] arch:dataflow-twitch-2k-wager label — Data Flow: Twitch 2K Wager
  • [x] arch note verified — arch-dataflow-twitch-2k-wager note exists (id 1155, status: active)
  • [x] Forgejo issue — forgejo_admin/twitch-2k-wager#39, open

File Targets

Schema + types (3 files — all verified):
  • [x] src/lib/schema.sql — verified: challenger.twitch_id is TEXT PRIMARY KEY (line 7), game.challenger_id is TEXT REFERENCES challenger(twitch_id) (line 21). Migration plan in Context section is accurate.
  • [x] src/lib/types.ts — verified: Challenger interface has twitch_id: string (line 28), no numeric id field. Needs serial id + optional twitch_id + gamertag per issue.
  • [x] src/app.d.ts — verified: imports SessionData from $lib/auth (line 1), Locals.session typed as SessionData | null (line 6). Type coherence will flow correctly when SessionData.twitchId becomes optional.
Core DB + API (3 files — all verified):
  • [x] src/lib/db.ts — verified: getChallenger takes twitchId: string (line 51), upsertChallenger ON CONFLICT (twitch_id) (line 66), createGame takes challengerId: string (line 96). All need INT id migration.
  • [x] src/routes/api/checkout/+server.ts — verified: requires twitch_id in body (line 38), calls createGame(challenger.twitch_id) (line 59). Must accept gamertag+email without auth.
  • [x] src/lib/stripe.ts — verified: createCheckoutSession metadata uses challenger_twitch_id string key (line 84). Must switch to challenger_id INT + gamertag.
Auth + routing (2 files — all verified):
  • [x] src/hooks.server.ts — verified: PROTECTED_PREFIXES = ['/game'] (line 13), redirects unauthenticated to /auth/login (line 42). Must relax for /game and /play per AC #8.
  • [x] src/lib/auth.ts — verified: SessionData.twitchId is required string (line 181). Must become optional for guest flow.
Downstream consumers (3 files — all verified):
  • [x] src/routes/api/admin/result/+server.ts — verified: getChallenger(game.challenger_id) with TEXT type (line 101), updateConnectStatus uses challenger.twitch_id (line 169). Must use INT id.
  • [x] src/routes/game/[id]/+page.server.ts — verified: ownership check session.twitchId !== game.challenger_id (line 26). Must skip for guests (no session).
  • [x] src/routes/api/webhooks/stripe/+server.ts — verified: reads challenger_twitch_id from session metadata (line 112), queries challenger by twitch_id in handleAccountUpdated (line 143). Must use INT id from metadata.
Test files (6 — all verified):
  • [x] src/routes/api/checkout/__tests__/checkout.test.ts — exists
  • [x] src/routes/api/admin/result/__tests__/result.test.ts — exists
  • [x] src/routes/api/webhooks/stripe/__tests__/webhook.test.ts — exists
  • [x] src/routes/game/[id]/__tests__/page-server.test.ts — exists
  • [x] src/lib/__tests__/stripe.test.ts — exists
  • [x] src/lib/__tests__/db.test.ts — exists
Blast radius coverage: 16 files contain twitch_id references. 15 of 16 are covered by file targets. The 16th (src/routes/auth/callback/+server.ts) calls upsertChallenger with the same signature — backward compatible, correctly excluded.

Repo Placement

OK. All 17 file targets are in forgejo_admin/twitch-2k-wager. Single-repo change. No cross-repo work needed.

Dependencies

  • [x] #38 (/play guest checkout route) — depends on THIS ticket, in todo column. Not a blocker.
  • [x] #40 (admin queue view) — depends on THIS ticket, in todo column. Not a blocker.
  • [x] #11 (Stripe Checkout + webhook) — prerequisite, in validation column. Satisfied.
  • [x] #9 (Postgres schema + db.ts) — prerequisite, done. Satisfied.
  • [x] No unresolved upstream blockers.

Acceptance Criteria

11 AC are well-defined and testable by an agent:
  • AC 1-6: API + DB assertions. Testable via unit tests and curl.
  • AC 7: Admin result endpoint with INT challenger_id — testable via result.test.ts.
  • AC 8: "Game status page accessible without auth (hooks.server.ts PROTECTED_PREFIXES relaxed for /game and /play)" — now explicitly names the file and mechanism. Verifiable.
  • AC 9: Guest game status page skips ownership check — testable via page-server.test.ts.
  • AC 10: Webhook handler uses INT challenger_id — testable via webhook.test.ts.
  • AC 11: All test files updated and passing — testable via npm test.
4 test expectations are clear and CI-verifiable.

Blast Radius

  • twitch_id appears in 16 files across src/. All covered or backward-compatible.
  • Auth callback is the only untargeted file — uses upsertChallenger with preserved signature. Safe.
  • No other repos affected. #38 and #40 depend on this but are not yet implemented.

Decomposition Assessment

  • 11 source files + 6 test files = 17 files in one repo.
  • 11 AC + 4 test expectations = 15 verification points.
  • All changes driven by a single schema migration — tightly coupled, sequential dependency chain.
  • Estimated agent time: 5-8 minutes. Borderline but acceptable as single pass — changes are mechanical after schema/type updates.
  • No decomposition needed.

Recommendation

No action needed. All prior findings resolved. Ticket is ready for dispatch.