Review: feat: custom even-money wager amounts ($1-$500)

review-841-2026-04-05-r2 Doc

review ready

Verdict: APPROVED

Re-review after refinement. All 6 findings from review-841-2026-04-05 have been addressed. Scope is solid, file targets verified, traceability complete.

Template Completeness

  • [x] Type — Feature
  • [x] Lineage — Phase 1 of #44 (custom wager amounts)
  • [x] Repo — forgejo_admin/twitch-2k-wager
  • [x] User Story — challenger wants custom wager amount instead of fixed tiers
  • [x] Context — clear motivation, even-money constraint stated, fixed presets remain
  • [x] File Targets — 7 files listed with specific change descriptions
  • [x] Acceptance Criteria — 7 testable criteria
  • [x] Test Expectations — 4 test cases with specific dollar values and expected behavior
  • [x] Constraints — 5 constraints including Stripe minimum, $500 max, which CHECK to preserve
  • [x] Checklist — standard 3 items
  • [x] Related — parent issue #44, story note, arch note all referenced

Traceability

  • [x] story:challenger-pay — "Secure Buy-In Payment" (story-twitch-2k-wager-challenger-pay exists in pal-e-docs)
  • [x] story note verified — found in project-twitch-2k-wager user-stories table (key: challenger-pay)
  • [x] arch:domain-twitch-2k-wager — "Domain Model: Twitch 2K Wager" (arch-domain-twitch-2k-wager exists in pal-e-docs)
  • [x] arch note verified — architecture note, active status, documents Game entity with buy_in_amount field
  • [x] Forgejo issue — forgejo_admin/twitch-2k-wager#64, open

File Targets

  • [x] src/routes/play/+page.svelte — verified: has tier pills (lines 59-70), needs "Custom" option added alongside existing presets
  • [x] src/lib/stripe.ts — verified: has TIERS lookup (line 31-36), VALID_TIERS (line 45), createCheckoutSession requires TierDollars type (line 56). All need widening for custom amounts.
  • [x] src/lib/schema.sql — verified: line 28 has CHECK (buy_in_amount IN (100, 1500, 3000, 4500)) to REMOVE. Line 30 has CHECK (pot_amount = buy_in_amount * 2) to PRESERVE. Ticket correctly distinguishes between the two.
  • [x] src/lib/types.ts — verified: line 24 has export type BuyInAmount = 100 | 1500 | 3000 | 4500; — needs widening to number with runtime validation
  • [x] src/lib/db.ts — verified: line 132 has buyInAmount: Game['buy_in_amount'] derived from BuyInAmount union type. Coupled to enum; needs updating when type widens.
  • [x] src/routes/api/checkout/+server.ts — verified: validates via VALID_TIERS.includes() (line 45), casts through TIERS[tierDollars].amount (line 72). Needs range validation ($1-$500) instead of tier lookup.
  • [x] src/routes/api/webhooks/stripe/+server.ts — verified: line 111 reads session.metadata?.tier for logging. handleCheckoutCompleted reads game_id from metadata (line 73) which is tier-agnostic. Ticket correctly uses "evaluate if" language for this target.

Repo Placement

OK. All 7 files are in forgejo_admin/twitch-2k-wager. Issue is filed on the same repo. Single-repo change, no cross-repo concerns.

Dependencies

  • [x] No blocking dependencies. Parent #44 is a tracking issue (Phase 1 + Phase 2), not a blocker.
  • [x] Board item #832 (winner payout, #60) — independent, touches arch:dataflow-twitch-2k-wager, no file overlap.
  • [x] Board item #842 (#65, asymmetric odds) — Phase 2 of #44, sequenced after this ticket. No conflict.
  • [x] Production DB migration — removing CHECK constraint requires ALTER TABLE on live DB. Standard practice for this project. Not a code dependency.

Acceptance Criteria

All 7 AC are agent-verifiable:
  • [x] AC 1 ("Custom" option on /play) — verifiable via DOM inspection or screenshot
  • [x] AC 2 (any $1-$500 creates valid session) — verifiable via checkout API POST with custom amount
  • [x] AC 3 (pot always 2x) — verifiable via DB query after checkout, pot_amount CHECK constraint preserved in schema
  • [x] AC 4 (fixed presets still work) — verifiable via existing tier checkout tests
  • [x] AC 5 (admin shows custom amounts) — admin tierLabel() already returns ${cents/100} for non-standard amounts; safe
  • [x] AC 6 (input validation) — verifiable via API: reject decimals, amounts below $1 or above $500
  • [x] AC 7 (webhook processes custom amounts) — verifiable via webhook test with custom amount metadata

Blast Radius

  • Admin page (src/routes/admin/+page.svelte): tierLabel() already has fallback for non-standard amounts (${cents/100}). tierClass() returns empty string — no color badge for custom. Acceptable, not a blocker.
  • Game detail page (src/routes/game/[id]/+page.svelte): getTierLabel() already returns 'Custom' for default case. Safe.
  • Landing page (src/routes/+page.svelte): has hardcoded tier cards (lines 97-185). Display-only, not a checkout entry point. Not affected.
  • Existing tests: 3 test suites reference fixed tiers (checkout.test.ts, stripe.test.ts, db.test.ts). Will need updating, implied by AC "Tests pass."
  • DB migration: removing CHECK constraint on live table requires ALTER TABLE game DROP CONSTRAINT. Schema.sql is truth for new deploys; ALTER needed for production.
  • Rollback: straightforward — re-add the CHECK constraint, revert type changes. Custom-amount games already in DB would violate the constraint, so rollback would need to handle or exclude them.

Decomposition Assessment

7 file targets in 1 repo. 7 AC. 4 test expectations. Borderline on the three-thing limit by raw count, but the changes form a single tightly-coupled dependency chain:
types.ts (widen type) → db.ts (accept new type) → schema.sql (drop CHECK) → stripe.ts (accept any amount) → checkout/+server.ts (range validation) → play/+page.svelte (Custom UI) + webhooks/stripe/+server.ts (evaluate metadata)
Splitting this chain would create integration risk — each piece is meaningless without the others. Estimated agent time: 3-5 minutes. No decomposition needed.

Recommendation

No action needed. Ticket is ready for dispatch.

Refinement Verification (6/6 addressed)

  • [x] Project page — project-twitch-2k-wager now exists in pal-e-docs
  • [x] Architecture notes — arch-domain-twitch-2k-wager exists in pal-e-docs
  • [x] Label mismatch — board label corrected to arch:domain-twitch-2k-wager
  • [x] Missing db.ts target — added to File Targets with createGame type coupling description
  • [x] CHECK constraint clarity — issue explicitly distinguishes which CHECK to remove vs preserve
  • [x] Webhook handler — added to File Targets with "evaluate if" language