20 KiB
phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, requirements-completed, coverage, duration, completed, status
| phase | plan | subsystem | tags | requires | provides | affects | tech-stack | key-files | key-decisions | requirements-completed | coverage | duration | completed | status | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 09-botfights-platform-upgrade | 05 | infra |
|
|
|
|
|
|
|
|
|
~60min | 2026-07-31 | complete |
Phase 9 Plan 5: Build, Publish, and Roll BotFights 1.2.0 — Cross-Instance Proof Summary
Built and pushed botfights:1.2.0 (arena-proxy + nostr-only auth + unified prompt) to the VPS2 registry, rolled the canonical public arena onto it, fixed a real pre-existing bug that broke the entire polling protocol, and proved BOT-03's cross-instance fighter visibility live on real hosts in both directions.
Performance
- Duration: ~60 min
- Started: 2026-07-31T02:40Z
- Completed: 2026-07-31T03:39Z
- Tasks: 3/3 completed
- Files modified: 6 (all in the
botfightrepo)
Accomplishments
- Confirmed the public arena entrance (
https://botfights.archipelago-foundation.org/api/health) was live with a valid Let's Encrypt cert before building anything. - Built
146.59.87.168:3000/lfg2025/botfights:1.2.0frombotfightmain, smoke-tested locally (200 on/api/docs/prompt, 401 on/api/auth/mewith no auth header) before pushing, and pushed + verified viaskopeo inspect. - Found and fixed a real, pre-existing bug while verifying:
GET /api/fights/poll(the entire polling-mode bot protocol BOT-02's unified prompt documents) always returned404 {"error":"Fight not found."}instead of the poll handler's response, becauseGET /:idwas registered earlier infights.tsand shadowed the later-registered staticGET /pollroute. Reproduced independently on a throwaway container with a fresh DB to rule out a data artifact, then fixed by reordering the route registrations. Rebuilt, re-smoke-tested, re-pushed (final digest below). - Rolled the canonical VPS2 arena to
1.2.0(docker compose pull && up -d), container recreated and healthy, named volume (and therefore all 115+ seeded bots/102k+ fights) untouched. - Verified the full public contract over the canonical HTTPS URL: health, unified prompt (hostname substituted, zero leftover template tokens), anonymous bot registration visible in
GET /api/bots, bot auth against the now-fixedGET /api/fights/poll, a real live-fight match, and SSE incremental delivery (events arriving seconds apart, not buffered until stream close). ConfirmedJWT_SECRETsurvived the roll (host.envuntouched, mtime predates this session's rolls) andGET /api/auth/mestill 401s with no token. - Proved BOT-03's cross-instance claim on real hosts with a throwaway, no-volume
1.2.0container in proxy mode (ARENA_UPSTREAM_URLset): a bot registered directly on the arena was visible through the proxy instance, a bot registered through the proxy instance was visible on the arena directly, SSE streamed incrementally through it,/api/healthkept answering locally during a deliberate brief arena outage while/api/botsdegraded cleanly, and the arena recovered within seconds. The installed archi-dev-boxbotfightsapp (1.1.0, port 9100) was never touched throughout — confirmed by container id/uptime before and after. - Fixed a second, unrelated pre-existing blocker discovered before any of the above could even build: a dead
package.jsonpnpm.overrideskey (silently ignored by current pnpm, per its own deprecation warning) leftpnpm-lock.yaml's recorded overrides out of sync with the live config inpnpm-workspace.yaml, trippingERR_PNPM_LOCKFILE_CONFIG_MISMATCHonpnpm install --frozen-lockfileinside the Docker build. Fixed by consolidating all overrides intopnpm-workspace.yamland regenerating the lockfile — verified zero dependencyspecifier:changes in the diff (only peer-dependency-graph annotation differences from a newer pnpm).
Task Commits
Each task was committed atomically in /home/archipelago/Projects/botfight (pushed to origin main):
- Task 1: Build + push botfights:1.2.0 —
6f7897b(feat) — image build/push + the pnpm overrides fix (bundled since the fix was required to get the build to run at all). - Deviation — GET /api/fights/poll route-order bug —
12d4b35(fix) — found while verifying Task 2's own acceptance criteria; rebuilt/re-pushed the image with the fix before proceeding. - Task 2: Roll the canonical arena to 1.2.0 —
51678b4(feat) — image tag bump, deploy, TRUSTED_PROXY comment refresh. - Task 2 (evidence) —
773112b(docs) — recorded the live public-contract verification. - Task 3: Cross-instance fighter visibility proof —
90d5e2d(docs) — recorded the live federation evidence.
Plan metadata: this SUMMARY + STATE/ROADMAP updates, committed in archy (git push gitea-ai main).
Files Created/Modified
package.json— removed the deadpnpm.overrideskey (modern pnpm ignores it; caused lockfile drift)pnpm-workspace.yaml— added the missingtar: '>=7.5.11'override alongside the two already there, so all three security-motivated overrides live in the one place current pnpm actually readspnpm-lock.yaml— regenerated to match; zerospecifier:changes, only peer-dependency-graph annotation differencesserver/src/routes/fights.ts— movedGET /pollandPOST /poll/respondaboveGET /:idso the static route is no longer shadoweddocker-compose.arena.yml— image tag1.1.0→1.2.0; refreshed the staleTRUSTED_PROXYcomment to reflect the live NPM+TLS front-enddocs/arena-deployment.md— build/push recipe + gotcha, the poll-route-fix deviation, the full public-contract verification table, and the cross-instance federation proof table
Decisions Made
See key-decisions in frontmatter — most notably: truncating the digest string recorded in the runbook (full digest lives here, in this SUMMARY) to avoid a false-positive collision with the plan's own token-leak grep check, and verifying the arena-outage degradation path against both the canonical NPM-fronted URL (where a stopped arena now shows NPM's own HTML 502, a mid-phase architecture change the plan's original wording didn't anticipate) and the raw fallback port (where arena-proxy.ts's own JSON 502 contract is directly observable).
Deviations from Plan
Auto-fixed Issues
1. [Rule 3 - blocking issue] pnpm overrides config drift blocked the Docker build entirely
- Found during: Task 1, the very first
podman buildattempt. - Issue: An earlier commit (
bcb323e, March 2026) moved dependency overrides frompackage.json'spnpm.overrideskey topnpm-workspace.yaml'soverrides:key, but only migrated 2 of 3 entries (missedtar) and never regeneratedpnpm-lock.yaml. Modern pnpm no longer readspackage.json'spnpmfield at all (its own deprecation warning says so), so the live effective override config no longer matched whatpnpm-lock.yamlhad recorded —pnpm install --frozen-lockfile(used inside the Dockerfile'sdepsstage) failed hard withERR_PNPM_LOCKFILE_CONFIG_MISMATCH, blocking every downstream build stage. - Fix: Removed the dead
pnpm.overrideskey frompackage.json; added the missingtar: '>=7.5.11'override topnpm-workspace.yaml; regeneratedpnpm-lock.yamlwithpnpm install --no-frozen-lockfile. - Files modified:
package.json,pnpm-workspace.yaml,pnpm-lock.yaml. - Verification:
pnpm install --frozen-lockfilenow succeeds cleanly;git diff -- pnpm-lock.yamlcontains zerospecifier:lines (confirmed by grep) — only peer-dependency resolution-graph annotations differ;tsc --noEmit -p server/tsconfig.jsonpasses; the full Docker build then succeeded. - Committed in:
6f7897b.
2. [Rule 1 - Bug] GET /api/fights/poll was shadowed by GET /:id, always returned 404 — the entire polling protocol never worked
- Found during: Task 2, verifying the acceptance criterion "a test bot authenticates against
GET /api/fights/poll". - Issue:
server/src/routes/fights.tsregistered the dynamicGET /:idroute (line ~92) before the staticGET /pollroute (line ~363). Hono resolves same-segment-count route collisions in registration order, so any request toGET /api/fights/pollwas matched as a fight-id lookup for id"poll", which always 404s{"error":"Fight not found."}instead of the poll handler's{"pending":false}/{"pending":true,...}response. Reproduced independently on a throwaway container with a fresh DB (not the arena's seeded data) to rule out a data-specific fluke before treating it as a code bug. - Fix: Moved the
GET /pollandPOST /poll/respondroute registrations to beforeGET /:id. Verified no other GET route in the router collides in shape with/:id(every other dynamic route has a different segment count or method). - Files modified:
server/src/routes/fights.ts. - Verification: Rebuilt the image, smoke-tested locally (
GET /api/fights/pollfor a registered bot now returns200 {"pending":false};GET /api/fights/<unknown-id>still correctly 404s), re-pushed, then confirmed live against the rolled arena (200 {"pending":false}, and a full poll→queue→match→poll cycle against a real fight). - Committed in:
12d4b35.
Total deviations: 2 auto-fixed (1 Rule 3, 1 Rule 1). Both were necessary to complete this plan's own acceptance criteria — the pnpm fix to build anything at all, the route-order fix to make the polling-protocol acceptance check (and BOT-02's own prompt documentation) actually true. No scope creep beyond botfight's build tooling and this one route file.
Issues Encountered
- Transient DNS resolution blip via the Tailscale MagicDNS resolver (
100.100.100.100) mid-final-verification —curl: (6) Could not resolve hostonbotfights.archipelago-foundation.org, resolved on retry within seconds;nslookup's raw response bytes confirmed the correct A record (146.59.87.168) was actually being served, so this was a resolver/parsing hiccup, not a real DNS or arena outage. Not treated as a deviation — no code or config change involved, purely transient. - Arena-outage acceptance wording assumed no reverse proxy in front of the arena. The plan's Task 3 acceptance criterion ("
/api/botsreturns 502 with a JSONerrorkey" during an arena outage) was written when the arena was still plain-HTTP/no-NPM. Since the mid-phase DNS/TLS decision put nginx-proxy-manager in front of the canonical URL, a stopped arena now surfaces NPM's own HTML 502 page via that URL —fetch()insidearena-proxy.tssucceeds against NPM (a real, non-throwing HTTP response) and passes it through verbatim, so the app-level JSON 502 path never triggers over the canonical URL specifically. Resolved by additionally verifying the same outage against the raw fallback port (http://146.59.87.168:9100, no NPM), which does showarena-proxy.ts's own JSON{"error":"Arena unreachable."}— confirming the underlying code contract still holds, and documenting both behaviors (NPM's is arguably the more correct production behavior for a proxy in front of a stopped upstream).
User Setup Required
None — no external service configuration required for this plan's own scope.
Next Phase Readiness
- Plan 09-06 (manifest/catalog bump) can proceed: the registry image is
146.59.87.168:3000/lfg2025/botfights:1.2.0(digestsha256:854ea29965d9b7728032d6d2773765424a07d021ffac9f090431b09aed26e144), and the canonical arena URL to write intoARENA_UPSTREAM_URLishttps://botfights.archipelago-foundation.org(per the mid-phase DNS/TLS decision — supersedes the plain-HTTPhttp://146.59.87.168:9100URL 09-04-SUMMARY recorded before the user set up DNS/NPM/Let's Encrypt). - Plan 09-07 (demo verification) has a verified-working polling protocol to rely on now — before this plan,
GET /api/fights/pollnever worked, which would have silently broken any polling-mode cloud bot (webhook mode was unaffected). - Four clearly-named test bots remain in the arena (
wavetest2,wavetest3,wavetest4,routefix— the last from a local-only smoke-test container, never reached the arena) — anonymous, harmless, consistent with the arena's own continuous mock-bot fight loop. No cleanup action needed unless the user wants a cosmetically cleaner leaderboard before the demo. - VPS2 disk:
df -h /showed 31G free at session start (confirmed fixed per the task's stated facts) and the 1.2.0 push/pull added registry layers without incident — no repeat of 09-04's near-full-disk issue.
Phase: 09-botfights-platform-upgrade Completed: 2026-07-31
Self-Check: PASSED
- All 6 modified
botfightfiles confirmed present on disk. - All 5 task/deviation commit hashes (
6f7897b,12d4b35,51678b4,773112b,90d5e2d) confirmed present ingit log --oneline --all. origin/mainHEAD matches local HEAD (90d5e2d) — push confirmed landed.