Chapter 6 · operations

Ops & Testing

One dev server runs the whole system locally; one GitHub push deploys it to the world. In between: a live ops console, a REST API foundation, an AI layer, and a test pyramid from pure engine units to full headless-browser walks.

Hub console (/hub)

live

The ops + corporate console: live match telemetry, bot health, and an activity feed — everything the demo runner needs on one screen.

Hub console with stats, matches table and activity feed
ops-hub.png · /hub live — stats, matches table (expandable player rows), activity feed

How it works. Polls /api/state every 3 seconds. That endpoint reads the bot match store, recomputes each player's raw/adjusted/progress/verified % server-side (same multipliers as the engine), merges bot heartbeats (fresh if seen < 45s) and the Qalarc Hub health, and returns one JSON. The feed is derived client-side by diffing successive polls — no server push needed. Degrades gracefully when anything is missing; the corporate tab (org leagues, employer-funded pots, k≥5 aggregate-only wellbeing dashboards) renders from the same state.

The match visible in the screenshot was played through the real CommandBus (the /debug simulator) minutes before capture.

lives: apps/hub/{index.html,hub.js,hub.css} · serve.ts apiState() · verified end-to-end by apps/figma-app/bots-sim.mjs (plays a match → asserts /api/state → asserts the hub renders it)

Debug console (/debug)

live
Debug bot simulator console mid-match
ops-debug.png · the bot simulator mid-match — see Bots for the transcript

What. Chat-shaped access to the real CommandBus plus the element gallery. The fastest way to demo the game end-to-end without a phone or a WhatsApp account — documented in depth in Bots.

lives: apps/debug/ · /api/sim in serve.ts (isolated scratch store)

Deploy pipeline

live
git push (main)
   └─▶ GitHub Actions: .github/workflows/deploy.yml
         ├─ bun setup
         ├─ bash scripts/build-deploy.sh        ← assembles deploy/public from the monorepo
         │     site → / · design → /design · web app → /app · hub → /hub · debug → /debug
         │     slack → /slack · connect → /connect · demo → /demo · system → /system
         │     figma-app → /figma-app · wiki → /wiki · functions/ → /api/{ai,state}
         ├─ wrangler pages deploy → Cloudflare Pages (project "rwf")
         └─ bun test packages/game-core packages/bot-core apps/api   ← 122 tests gate every push
~20 seconds later: live at rwf.qalarc.com

How it works. The deploy bundle is assembled by scripts/build-deploy.sh — pure copies, no per-app build except the web app bundle (Bun build, minified). Pages Functions (deploy/functions/api/) provide the AI proxy and state endpoint in production, mirroring the local serve.ts routes. The local dev story is one command — bun serve.ts on :4173 serves everything including this wiki at /wiki — so local and production surface the same paths.

Always-on hosting: the GMKtec minirig kit is prepared (scripts/hosting/ — install script, systemd units for the Slack bot) for bots that run without a dev machine; see blocker T3 in Status.

lives: scripts/build-deploy.sh · .github/workflows/deploy.yml · deploy/functions/api/ · scripts/hosting/ · docs/16_HOSTING.md

The REST API (apps/api)

built · unwired

What. A unified backend for crews, players, matches and seasons — the foundation that replaces the app's localStorage and the bots' file state with one source of truth.

How it works. Bun server on :4174 with CORS for the dev origin and production domain. Persistence is a single JSON file (.data/api-db.json) written atomically (tmp + rename) so a crash never corrupts the store. Crew records carry the 5-char join codes (no 0/O/1/I) that match the CREW-XXXX grammar the app and bots already display. The wiring step (app → API ← bots) is mechanical and tracked as blocker T5.

lives: apps/api/src/{main,routes,db}.ts
proven by: apps/api/test/api.test.ts — 24 tests, run in CI

AI layer

live · metered

What. GLM-5.3 behind a server-side proxy — powers the site's AI guide, the app narrator and the bots' taunts/digest one-liners.

How it works. Clients post to /api/ai; the key lives server-side only (local .env / Pages Function secret) and is never shipped to a client. The proxy injects a system prompt describing the whole product, keeps the last 12 messages, caps content length, rate-limits 60/min, preserves upstream error classes (429 ≠ 502), and every consumer has a canned fallback — if the AI is asleep the product still talks. Locally the same endpoint is served by serve.ts; in production by the Pages Function.

lives: serve.ts aiChat() · deploy/functions/api/ai.js · packages/bot-core/src/ai.ts (2s timeout + fallback)

The test suites

122 green

Unit → integration → headless-browser. Every push runs the suites; the E2E walks run on demand and produce the screenshots this wiki is built from.

SuiteTestsWhat it provesRun
game-core — core.test.ts29300-format math: scoring, handicaps, closure, comeback, pot, standingsbun test packages/game-core
game-core — season.test.ts153/2/1 + MVP, ladder ordering, forgiveness, relegation
game-core — gfamily.test.ts15second wave: rematch, photo finish, nemesis, digest inputs
bot-core — bus.test.ts27command grammar, aliases, cards, store behaviour, error cardsbun test packages/bot-core
bot-core — gfamily.test.ts12rematch/nemesis/digest commands over the bus
api — api.test.ts24REST routes, crew codes, atomic persistencebun test apps/api
figma-app — engine.test.js23the engine port matches the spec (incl. power-ups)bun test apps/figma-app/engine.test.js
figma-app — e2e.mjs~60 stepsfull product walk in headless Chromium; asserts DOM + state at every step, fails on any console error; writes the walk screenshotsbun apps/figma-app/e2e.mjs
figma-app — e2e-daily.mjs~40 stepsthe temporal loop with time travel: DZ1→2→3, day close, recap, day 2bun apps/figma-app/e2e-daily.mjs
figma-app — bots-sim.mjsplays a real match through the CommandBus then asserts /api/state + the hub render itbun apps/figma-app/bots-sim.mjs
wiki — walk.mjsthis wiki: every page loads with zero console errors, every referenced asset resolvesbun apps/wiki/test/walk.mjs

CI runs the three packaged suites (122 tests) on every deploy push — a red suite blocks nothing at Cloudflare but fails the workflow loudly. The E2E walks are the design contract: if a screen breaks, its screenshot can't be taken.

lives: the test dirs above · CI gate in .github/workflows/deploy.yml · measured live 29 Aug 2026: 122 pass · 0 fail · 395 expect() calls
← backAvatars