- docker-compose.arena.yml: image tag 1.1.0 -> 1.2.0, refreshed the
TRUSTED_PROXY comment to reflect the live NPM+TLS front-end (no
longer "no DNS/TLS this phase" — that shipped mid-phase).
- Deployed on VPS2: docker compose pull + up -d, container recreated,
healthy, data volume untouched.
- Verified end-to-end through the public HTTPS URL: health, unified
prompt (ARENA_URL substituted, zero leftover template tokens), a
freshly registered test bot visible in GET /api/bots, bot auth via
the now-fixed GET /api/fights/poll, and data integrity (100 + 15
classic bots, unchanged from before the roll).
- docs/arena-deployment.md: recorded the second (post-poll-fix) image
digest and the fix.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
server/src/routes/fights.ts registered the dynamic GET /:id route before
the static GET /poll route. Hono resolves same-shape single-segment
routes in registration order, so any GET /api/fights/poll request was
matched as a fight-id lookup for id="poll" and always returned
404 {"error":"Fight not found."} instead of the poll handler's
{"pending":false}/{"pending":true,...} response.
This meant the polling protocol — one of the two bot integration modes
BOT-02's unified prompt documents — never actually worked. Found while
verifying bot auth against the freshly-rolled 1.2.0 arena (plan 09-05
Task 2 acceptance criterion), reproduced independently on a throwaway
container with a fresh DB to confirm it wasn't an artifact of the
arena's seeded data.
Fix: move the /poll and /poll/respond route registrations above /:id.
No other GET route in this router collides in shape with /:id (verified
by listing every registered path/method pair).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Fixed a pre-existing pnpm overrides config drift (package.json's dead
pnpm.overrides key vs pnpm-workspace.yaml's overrides, never fully
migrated after commit bcb323e) that was blocking the docker build's
`pnpm install --frozen-lockfile` step under a current pnpm version.
Zero dependency specifier changes in the regenerated lockfile.
- Built and pushed 146.59.87.168:3000/lfg2025/botfights:1.2.0 from
botfight main @ 2a343ac (arena-proxy + nostr-only auth + unified
prompt, all three wave-1 plans confirmed present before build).
- Documented the build/push recipe, the gotcha, and the smoke test in
docs/arena-deployment.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Client: useNostr.ts's auto-restore now calls GET /api/auth/me (a plain
authFetch, no body) instead of POSTing {pubkey} to /api/auth/login —
identity is derived server-side from the JWT alone, never claimed by
the client.
Server: POST /login is reduced to a pure, documented-deprecated read.
Removed the creator auto-create branch and the creator auto-upgrade
db.update block — an unauthenticated request can no longer mutate the
database via this endpoint. The identical creator auto-create/upgrade
logic already exists, correctly gated behind NIP-98 verification, in
POST /nostr/session, so a creator signing in with a real signer still
gets the same row created/upgraded. Added a handler doc comment plus a
new auth.test.ts case asserting an unregistered creator pubkey now
returns exists:false and leaves the bots table row count unchanged.
e2e/helpers/auth.ts: doc comments updated to describe loginWithPubkey
as a read-only test lookup helper, not a login; request/signature
unchanged so existing e2e specs keep working.
Verification: auth.test.ts + auth-edge.test.ts + auth-audit.test.ts +
auth-me.test.ts = 56/56 pass. Full server suite (bypassing pnpm's
install-gate via ./node_modules/.bin/vitest, since this environment's
pnpm needs an interactive build-approval step unrelated to this task)
= 810/817 pass, remaining 7 are pre-existing timing/perf flakes under
CPU load (lifecycle/speed-meta/tier-balance/bot-auth constant-time),
none touching auth. tsc (server) and vue-tsc (frontend) both exit 0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- JoinBoutPage.vue: setupDocPath()/setupDocName() collapse to /docs/BOTFIGHTS.md
regardless of connection mode; keep YOUR_BOT_ID/YOUR_BOT_SECRET substitution
and add a third substitution for {{ARENA_URL}} -> window.location.origin
- BotProfilePage.vue: same collapse; the webhook/polling guide-type selector
is gone (there's only one guide now) -- replaced with a single 'LOAD SETUP
GUIDE' button, same credential + {{ARENA_URL}} substitution
- DocsPage.vue: new 'GIVE THIS TO YOUR AI' panel at the top of the page --
copy-to-clipboard for the full /api/docs/prompt response, plus the literal
URL shown so a user can hand an agent the link instead of the text.
Existing API reference / webhook tester tabs unchanged below it
- e2e/signup-bot.spec.ts: two new tests -- docs page shows the copy
affordance, and GET /api/docs/prompt (via page.request) returns 200 with
the registration endpoint and no leftover {{ARENA_URL}} token
- New docsRouter.get('/prompt') resolves the shipped container path
(server/public/docs/BOTFIGHTS.md) then falls back to the dev-checkout
path (frontend/public/docs/BOTFIGHTS.md), matching app.ts's publicDir
derivation pattern
- Substitutes {{ARENA_URL}} with PUBLIC_ARENA_URL when set, otherwise the
request's own origin, so a cloud agent that curls the prompt gets
working examples pointed back at the arena it fetched from
- Responds as text/markdown so an agent can pipe the response straight
into its context
- 5 new Vitest cases: 200+content-type, no leftover {{ARENA_URL}} token,
PUBLIC_ARENA_URL precedence, origin fallback, YOUR_BOT_ID placeholder
preserved for the in-app substitution flow
Deviation (Rule 1 — auto-fix bug), out-of-scope-but-cheap per plan 09-02's
explicit allowance. server/src/db/migrate.ts (the standalone `pnpm migrate`
CLI script) had drifted from server/src/db/schema.ts: it was missing 7
tables (payments, wallet_connections, bets, tournaments, tournament_entries,
analytics, tournament_matches) and several bots/fights columns (sats_won,
sats_wagered, has_wallet, zaps_received, bot_type, mode, pot_sats,
payout_status, current_season). server/src/db/startup.ts's runMigrations()
(the one actually called from index.ts at server boot) already had the
correct, up-to-date DDL — migrate.ts was the stale duplicate. Brought it
back in sync, column-for-column and table-for-table, against schema.ts.
Route-level tests (auth.test.ts, auth-audit.test.ts, auth-edge.test.ts,
tournaments.test.ts) hit the real db/index.ts singleton against the
on-disk, gitignored server/data/botfights.db, which only startup.ts or
this migrate.ts script populate — vitest itself never runs a migration.
Running `pnpm --filter server migrate` against a fresh DB with the fixed
script now creates all tables/columns; full server suite went from
15 failed / 789 passed to 6 failed / 798 passed, with the remaining 6
all pre-existing timing/perf flakes unrelated to auth (answers.test.ts,
lifecycle.test.ts x2, bot-auth.test.ts, docs.test.ts x2 — CPU-contention
sensitive, matches deferred-items.md's documented flake class).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a JWT-gated, read-only session-restore route. Mirrors POST /login's
projection and 200 body shape exactly so normalizeBotData on the client
is unchanged. extractPubkeyFromAuth (already imported later in the file
for /regenerate-secret) covers missing/malformed/forged/expired/
blacklisted tokens via verifyJwt; deduped the now-redundant import at
the /nostr/session section. All 7 auth-me.test.ts cases pass; tsc clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Covers missing / malformed / forged / blacklisted / unregistered / valid
JWT cases for the JWT-only identity route that replaces the bare-pubkey
auto-restore path. Route does not exist yet — 6/7 fail as expected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Merge BOTFIGHTS.md + BOTFIGHTS-EASY/POLLING/WEBHOOK.md + BOT_SETUP.md into
a single canonical prompt at frontend/public/docs/BOTFIGHTS.md
- Add the previously-undocumented registration step (POST /api/bots,
anonymous, poll vs webhook mode, rate limits, 409/422 behavior)
- Replace the stale botfights.io fallback host with the {{ARENA_URL}} token
(substituted server-side/client-side in later tasks)
- Document the exact HMAC-SHA256 webhook signature derivation, matching
orchestrator.ts (the old bot.js example's verifySignature() was wrong —
it hashed BOT_SECRET directly instead of via the secretHash+signingKey
two-step the server actually uses)
- Document the trash_talk (webhook, snake_case) vs trashTalk (poll,
camelCase) field-naming split, verified against the real zod schemas
- Add the endpoint reference table, troubleshooting table, and arena-as-relay
framing (any node can host an arena; default is the Foundation's)
- Replace root BOTFIGHTS.md with a 4-line stub pointing at the canonical copy
- Delete the four superseded docs (BOTFIGHTS-EASY/POLLING/WEBHOOK.md, BOT_SETUP.md)
The prior test drove the proxying app via Hono's in-process app.request()
harness, which has no real Node socket — so it could only assert the header
was non-empty-or-absent, not that the real client IP round-trips. Spin the
proxying app up with @hono/node-server (real loopback socket) and assert the
upstream actually receives 127.0.0.1/::1, exercising the same
remoteAddress lookup arenaProxy uses in production.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Forward client IP via x-forwarded-for/x-real-ip so the canonical arena's
per-IP rate limiting isn't collapsed to one bucket per node.
- 30s AbortSignal.timeout on non-stream requests; SSE fight streams
(/api/fights/:id/stream) are exempt (long-lived by design).
- On upstream fetch failure, log and return 502 {error} instead of a
buffered hang or a 500 stack trace.
- fights.ts: set X-Accel-Buffering: no on the SSE stream response so an
nginx-fronted arena (nginx-proxy-manager) doesn't buffer live fight events.
- docker-compose.yml: document ARENA_UPSTREAM_URL / TRUSTED_PROXY (commented,
no active value set here — the canonical arena gets its own compose file
in a later plan).
TDD: added the SSE/XFF/502 tests, confirmed the 502 test failed against the
prior implementation, then implemented to green (9/9 arena-proxy tests,
17/17 combined with rate-limit.test.ts).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hono middleware that forwards /api/* to ARENA_UPSTREAM_URL when set, with
standalone mode (env unset) and /api/health untouched. Verified end-to-end
against a real second HTTP server: register a bot upstream, read it back
through the proxy, method/query/JSON body forwarded unchanged, inbound Host
header dropped, response content-encoding/content-length stripped.
TDD: 6 tests written first and confirmed failing (module didn't exist),
then implemented to green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds docker-compose.arena.yml (registry image only, no build, no payment
env, no TRUSTED_PROXY since no reverse proxy fronts it) and
docs/arena-deployment.md (full runbook: host paths, port, secret handling,
seed decision, NPM/TLS upgrade path, teardown) for the one canonical public
arena at http://146.59.87.168:9100 (user decision 2026-07-30: no DNS/TLS
this phase, plain HTTP on the raw port; TLS is a later env-only upgrade).
Deployed + verified on VPS2: healthy on-host and off-host, seeded with a
full read-only copy of archi-dev-box's botfights.db (115 bots/102,440
fights; source untouched, mtime verified unchanged).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>