16 KiB
phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, duration, completed, status
| phase | plan | subsystem | tags | requires | provides | affects | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | coverage | duration | completed | status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 09-botfights-platform-upgrade | 01 | api |
|
|
|
|
|
|
|
|
|
40min | 2026-07-31 | complete |
Phase 9 Plan 01: Arena-Proxy Reverse-Proxy Middleware Summary
Hono middleware that forwards every /api/* request (REST + SSE) to ARENA_UPSTREAM_URL via native fetch/stream-passthrough when set, proven end-to-end against a real second HTTP server — zero new dependencies.
Performance
- Duration: ~40 min
- Started: 2026-07-31T01:12:00Z (approx.)
- Completed: 2026-07-31T01:36:32Z
- Tasks: 2/2 completed
- Files modified: 5 (2 created, 3 modified)
Accomplishments
server/src/middleware/arena-proxy.ts— the BOT-03 reverse-proxy: no-ops tonext()whenARENA_UPSTREAM_URLis unset (today's behavior, byte-for-byte); bypasses/api/healthlocally even in proxy mode; forwards method/query/body/headers with a sharedHOP_BY_HOPdrop-list; strips stalecontent-encoding/content-lengthfrom the response; passes the upstreamReadableStreamstraight through (never buffers, which is what makes SSE work); forwards the caller's IP viax-forwarded-for/x-real-ipwhen a real socket address is available; times out non-stream requests at 30s (SSE exempt); returns a clean502 {error}JSON body when the arena is unreachable.- Mounted in
server/src/app.tsviaapp.use('/api/*', arenaProxy), positioned before everyapp.route('/api/...')registration. server/src/routes/fights.ts's SSE stream handler now setsX-Accel-Buffering: noso an nginx-fronted canonical arena (nginx-proxy-manager) doesn't buffer live fight events.docker-compose.ymldocumentsARENA_UPSTREAM_URLandTRUSTED_PROXY(commented, no active value — the canonical arena's own compose file is a later plan).- 9 end-to-end tests in
server/src/middleware/arena-proxy.test.ts, all driven against a REAL second Hono process started with@hono/node-server'sserve({ port: 0 })(not mocks): cross-node bot registration + readback, standalone fallthrough, health bypass, method/query/body forwarding, Host-header drop, content-encoding/length stripping, incremental SSE delivery (elapsed-time assertion), x-forwarded-for over a real loopback socket, and 502 on an unreachable arena.
Task Commits
Each task was committed atomically in /home/archipelago/Projects/botfight (pushed to origin main):
- Task 1: End-to-end REST forwarding (tracer) —
143ca80(feat) —arena-proxy.ts+ 6 REST-focused tests +app.tsmount. TDD: tests written first, confirmed failing (module didn't exist —Failed to load url ./arena-proxy.js), then implemented to green. - Task 2: SSE, client-IP forwarding, upstream-down —
0511b97(feat) — x-forwarded-for/x-real-ip, 30s timeout (SSE exempt), 502 handling,fights.ts'sX-Accel-Bufferingheader,docker-compose.ymldocs, + 3 more tests (SSE, XFF, 502). TDD: the 502 test was written first and confirmed failing (expected 502 to be 500) against the Task 1 implementation, then implemented to green. - Test hardening (post-hoc improvement) —
a95cada(test) — strengthened the x-forwarded-for test to drive the proxying app over a real loopback socket (@hono/node-server) instead of Hono's in-processapp.request()harness, which has no realsocket.remoteAddressto exercise. Not a plan task, but needed to make D6's coverage claim genuinely proven rather than a presence-or-absence tautology.
Plan metadata: this SUMMARY + STATE/ROADMAP updates, committed in archy via git push gitea-ai main.
Note: both feature tasks used a TDD red→green cycle; no separate refactor commit was needed.
Files Created/Modified
server/src/middleware/arena-proxy.ts— the reverse-proxy middleware (created)server/src/middleware/arena-proxy.test.ts— 9 end-to-end tests against a real upstream HTTP server (created)server/src/app.ts— mountsarenaProxyon/api/*before the route registrationsserver/src/routes/fights.ts—X-Accel-Buffering: noon the SSE stream handlerdocker-compose.yml— documentsARENA_UPSTREAM_URL/TRUSTED_PROXY(commented)
Decisions Made
- Plain HTTP is an explicitly supported upstream scheme, not just HTTPS. The plan's threat register (T-09-02) as originally written assumed
ARENA_UPSTREAM_URLis always anhttps://origin. Per the user's direct instruction during execution, this is superseded: the default VPS2 arena (http://146.59.87.168:9100) is plain HTTP and the proxy must accept bothhttp://andhttps://upstreams with no scheme assumption anywhere in the code — and there is none;buildTargetUrlandfetchare scheme-agnostic by construction. See "Deviations from Plan" below. - Timeout scoping is by path pattern, not by streaming-detection at runtime.
SSE_STREAM_PATH = /^\/api\/fights\/[^/]+\/stream$/is checked before the fetch, matching the plan's explicit path guidance rather than trying to detect a streaming response after the fact (which would be too late to skip an already-attachedAbortSignal).
Deviations from Plan
Auto-fixed Issues
1. [Rule 2 - missing critical functionality / threat-model correction] T-09-02 mitigation text superseded — HTTP is a supported upstream scheme, not just HTTPS
- Found during: pre-execution review of the plan's threat model (T-09-02) against the phase's CONTEXT.md "Arena-as-relay" decision and the user's direct instruction for this run.
- Issue: The plan's threat register states
ARENA_UPSTREAM_URL"is anhttps://origin." The actual architecture decision (CONTEXT.md, and the default arena addresshttp://146.59.87.168:9100used throughout 09-CONTEXT.md/09-RESEARCH.md/09-PATTERNS.md) is plain HTTP for the default arena, with TLS as an operator-chosen upgrade later — not a hardcoded assumption. - Fix: No code change was needed — the implementation was already scheme-agnostic (
new URL(path + search, upstream)andfetch()work identically forhttp://andhttps://; nothing inarena-proxy.tschecks or assumes a scheme). This is a documentation correction to the threat register, recorded here since the plan's committed threat model text is now stale. - Updated mitigation (superseding T-09-02 as written): "User-accepted plain HTTP for the default arena; TLS upgrade is env-only later (an operator can point
ARENA_UPSTREAM_URLat anhttps://origin with zero code change). Credentials crossing node → arena over plain HTTP are visible to any on-path observer between the node and VPS2 — accepted for the alpha/beta default arena per user decision; nodes on an untrusted network path to VPS2 should either use a VPN/Tailscale hop or wait for the TLS-upgraded arena." - Files affected: none (documentation-only; no source change required).
- Not blocking: per explicit instruction, this deviation is recorded and not treated as a blocker.
Total deviations: 1 (threat-model documentation correction, no code change; already scheme-agnostic by construction).
Impact on plan: None on delivered code — the implementation matches the corrected, user-stated architecture. Threat register text should be updated in a future pass over 09-01-PLAN.md or carried forward into 09-04/09-06's threat models.
Issues Encountered
- Fresh local dev environment needed setup before the pre-existing full server test suite (
pnpm vitest run --project server) could run at all:node_moduleswas missing (ranpnpm install);better-sqlite3's native binding wasn't built because pnpm's newer build-approval gate silently skipped postinstall scripts (pnpm approve-builds --all); the local dev SQLite DB had never been migrated. These are one-time local environment bootstrap steps, not code changes, and none were committed (thepnpm-lock.yaml/pnpm-workspace.yamlchurn from a locally-different pnpm version was explicitly reverted withgit checkout --before committing anything, to avoid polluting the shared repo with unrelated lockfile noise). - Pre-existing schema drift, unrelated to this plan, blocks ~13-20 tests in the full server suite:
server/src/db/migrate.ts's hand-writtenCREATE TABLE IF NOT EXISTSDDL is stale versusserver/src/db/schema.ts(missing at least thesats_woncolumn added for payments features), causingSqliteError: no such column500s inauth.test.ts,auth-audit.test.ts,auth-edge.test.ts, andtournaments.test.tson any freshly-migrated local DB. Verified this is not caused by arena-proxy —ARENA_UPSTREAM_URLis unset in the test environment, soarenaProxyis a purenext()no-op, and the failures occur deep insidedb.select(...)calls that never touch the new middleware or its mount point. A small number of additional failures (answers.test.ts,lifecycle.test.ts,bot-auth.test.ts's constant-time-comparison test) are timing-budget assertions that flake under this machine's parallel-worker CPU contention — also pre-existing and unrelated. Full detail logged to.planning/phases/09-botfights-platform-upgrade/deferred-items.mdper the Scope Boundary rule (out-of-scope for this plan's files). This plan's own required commands are unaffected and green:pnpm vitest run server/src/middleware/arena-proxy.test.ts(9/9),pnpm vitest run server/src/middleware/arena-proxy.test.ts server/src/middleware/rate-limit.test.ts(17/17),pnpm exec tsc --noEmit -p server/tsconfig.json(clean), andeslinton all four touched files (clean).
User Setup Required
None — no external service configuration required. (VPS2 canonical-arena deployment and the manifest's ARENA_UPSTREAM_URL default are later plans, 09-04 and 09-06.)
Next Phase Readiness
arena-proxy.tsis ready to be exercised against the real canonical VPS2 arena once it's deployed (plan 09-04) — no further code change expected on the proxy side;docker-compose.yml's documentedARENA_UPSTREAM_URL/TRUSTED_PROXYvars are the contract the manifest work (09-06) should follow.- Deferred item worth carrying forward: the
migrate.ts/schema.tsdrift (seedeferred-items.md) will keep breaking fresh local dev environments and CI-from-scratch until a future plan replaces the hand-written migration script with realdrizzle-kit generate/migrateoutput. - The plan's threat register text for T-09-02 should be corrected in a future edit pass to match the "plain HTTP accepted for the default arena" decision recorded above, so it doesn't read as contradicting what was actually built.
Phase: 09-botfights-platform-upgrade Completed: 2026-07-31
Self-Check: PASSED
- FOUND:
/home/archipelago/Projects/botfight/server/src/middleware/arena-proxy.ts - FOUND:
/home/archipelago/Projects/botfight/server/src/middleware/arena-proxy.test.ts - FOUND:
/home/archipelago/Projects/archy/.planning/phases/09-botfights-platform-upgrade/09-01-SUMMARY.md - FOUND:
/home/archipelago/Projects/archy/.planning/phases/09-botfights-platform-upgrade/deferred-items.md - FOUND commit
143ca80(Task 1) inbotfightgit history - FOUND commit
0511b97(Task 2) inbotfightgit history - FOUND commit
a95cada(test hardening) inbotfightgit history origin/mainHEAD matches local HEAD (a95cada) — push confirmed landed