Files
botfights/.claude/plans/breezy-questing-dove.md
T

153 lines
6.6 KiB
Markdown
Raw Normal View History

# Production Hardening v2 — Final Status
## Context
The original hardening plan (`greedy-skipping-lollipop.md`) was 95% complete. A full security audit confirmed the fight engine, scoring, auth, SSRF, rate limiting, and webhook systems were solid. This session addressed remaining production gaps AND added Creator ₿ animation/morph enhancements AND fixed a production ranked-fight bug.
---
## Phase 1: Security Headers & Error Hardening — DONE
### 1.1 Security headers middleware — DONE
**File:** `server/src/app.ts`
- Added `secureHeaders` from `hono/secure-headers` with CSP (production only), X-Frame-Options, X-Content-Type-Options, HSTS, Referrer-Policy
### 1.2 Request body size limit — DONE
**File:** `server/src/app.ts`
- Added `bodyLimit` from `hono/body-limit` — 256KB max for API requests
### 1.3 Hide error details in production — DONE
**File:** `server/src/app.ts`
- `onError` returns generic "Internal server error" in production, full message in dev
---
## Phase 2: Graceful Shutdown & Env Validation — DONE
### 2.1 Graceful shutdown handler — DONE
**File:** `server/src/index.ts`
- SIGTERM/SIGINT handlers wait 10s for in-flight fights, clear pending human challenges, then exit
### 2.2 Export helpers for shutdown — DONE
- `server/src/engine/orchestrator.ts``getActiveFighterCount()`
- `server/src/engine/human-responses.ts``clearAllPending()`
### 2.3 Stricter env validation — DONE
**File:** `server/src/index.ts`
- Warns if CORS_ORIGIN not set in production
---
## Phase 3: Payment Race Condition Fix — DONE
### 3.1 Atomic consumePaymentForQueue — DONE
**File:** `server/src/engine/payments.ts`
- Replaced check-then-set with atomic `UPDATE ... WHERE` (single SQL statement)
- Imported raw `sqlite` from db/index.js
### 3.2 releasePayment reverts DB state — DONE
**File:** `server/src/engine/payments.ts`
- Now also reverts `status` from `consumed` back to `confirmed` in DB
---
## Phase 4: Live Round-by-Round TUI — DONE
### 4.1 onRoundComplete callback — DONE
**File:** `server/src/engine/fight-loop.ts`
- New `onRoundComplete` option, subscribes to `fightEvents.onAll()` for `round_end` events
### 4.2 TUI wired to round updates — DONE
**File:** `server/src/fight-loop-cli.ts`
- `onRoundComplete` updates `state.currentFight` HP/round/challengeType and re-renders
---
## Phase 5: Creator ₿ Animation Enhancements — DONE
### 5.1 Persistent orbiting ₿ letters on entrance — DONE
**File:** `frontend/src/game/FightScene.ts` (Creator entrance)
- Replaced 6 plain circle dots with 10 ₿ text letters in 2 counter-rotating rings
- Varied sizes (7-14px), 5 colors, gentle rocking animation
- Visible for the entire fight
### 5.2 Upgraded omni-morph ₿ orbits — DONE
**File:** `frontend/src/game/FightScene.ts` (`applyCreatorOmniMorph`)
- Increased from 4 to 8 ₿ in dual rings (inner 5, outer counter-rotating 3)
- Varied sizes (10-18px), 3 colors, wobble animation
### 5.3 bitcoinRain uses ₿ text — DONE
**File:** `frontend/src/game/FightScene.ts` (`bitcoinRain`)
- Changed from circles to actual ₿ text with 3 colors, varied sizes (6-18px), spinning as they fall
### 5.4 Four new ₿ swarm choreographies — DONE
**File:** `frontend/src/game/FightScene.ts`
- `bitcoinSwarm` — 18-30 ₿ letters zigzag from Creator to defender
- `satoshiTornado` — 14-24 ₿ spiral in tightening vortex around defender, then explode
- `hodlWave` — wall of ₿ letters (4-6 rows x 7-10 cols) advances like a tidal wave
- `bitcoinBarrage` — rapid-fire stream of 20-35 spinning ₿ from the laptop
- All added to `CREATOR_MOVES` array and `choreographyMap`
---
## Phase 6: Creator Omni-Morph Visual Fix — DONE
### 6.1 Sprite swap on morph — DONE
**File:** `frontend/src/game/FightScene.ts` (`applyCreatorOmniMorph`)
- Was: only added overlays (aura, ₿, color tint) — sprite stayed as Creator
- Now: generates a full sprite sheet for the picked archetype via `generateSpriteSheet(seed, tier, colors, archetypeOverride)`
- Loads it as a Kaplay sprite and swaps with `fighter.use(k.sprite(morphKey))`
- Function changed from sync to `async`
- Unique sprite keys via incrementing counter prevent collisions
### 6.2 Sprite revert after attack — DONE
**File:** `frontend/src/game/FightScene.ts` (morph trigger in playRound)
- `morphRevert` now calls `attacker.use(k.sprite(originalSpriteKey))` to swap back to Creator
- Added `await` on `applyCreatorOmniMorph` call (now async)
---
## Phase 7: Production Ranked Fight Fix — DONE
### 7.1 Missing pubkey in ranked endpoints — DONE
**File:** `frontend/src/composables/useWallet.ts`
- `payEntryFee()` now sends `{ botId, pubkey: pubkey.value }` to `/api/payments/create-invoice`
**File:** `frontend/src/pages/JoinBoutPage.vue`
- `fightRanked()` now sends `{ paymentId, pubkey: pubkey.value }` to `/api/queue/join-ranked`
**Root cause:** Server requires `pubkey` in production for ownership verification, but frontend wasn't sending it. Worked in dev because dev mode skips pubkey checks.
---
## All Files Modified
| File | Phase | Changes |
|------|-------|---------|
| `server/src/app.ts` | 1 | +secureHeaders, +bodyLimit, error hiding |
| `server/src/index.ts` | 2 | +graceful shutdown, +env validation |
| `server/src/engine/orchestrator.ts` | 2 | +getActiveFighterCount() |
| `server/src/engine/human-responses.ts` | 2 | +clearAllPending() |
| `server/src/engine/payments.ts` | 3 | Atomic consume, releasePayment DB revert |
| `server/src/engine/fight-loop.ts` | 4 | +onRoundComplete via fightEvents |
| `server/src/fight-loop-cli.ts` | 4 | Wire onRoundComplete to TUI |
| `frontend/src/game/FightScene.ts` | 5,6 | ₿ animations, omni-morph sprite swap, 4 new choreographies |
| `frontend/src/composables/useWallet.ts` | 7 | +pubkey in create-invoice |
| `frontend/src/pages/JoinBoutPage.vue` | 7 | +pubkey in join-ranked |
| `frontend/src/game/sprites/archetypes/the_creator.ts` | — | No changes (reference only) |
---
## Verification
1. `pnpm build` — verify no type errors (both server and frontend pass)
2. Production server: `curl -I /api/health` — X-Frame-Options, X-Content-Type-Options, CSP headers present
3. Body limit: oversized POST returns 413
4. Error hiding: bad endpoint returns "Internal server error" (not stack trace)
5. Graceful shutdown: `kill -TERM` shows drain log
6. TUI: `pnpm fight-loop --max=3` shows live round-by-round HP updates
7. Payment race: concurrent ranked joins with same paymentId — only 1 succeeds
8. Ranked fights: no more "Missing pubkey" error in production
9. Creator fights: omni-morph visually transforms into random archetypes (sprite swaps)
10. Creator fights: ₿ letters orbit persistently, new swarm attacks fire regularly