--- phase: 09-botfights-platform-upgrade plan: 07 type: execute wave: 4 depends_on: [09-06] files_modified: - .planning/phases/09-botfights-platform-upgrade/09-DEMO-CHECKLIST.md autonomous: false requirements: [BOT-01, BOT-02, BOT-03, BOT-04] must_haves: truths: - "archi-dev-box runs BotFights 1.2.0 installed through the normal signed-catalog path — not hand-placed — with its JWT secret injected from the orchestrator's secret store rather than a plaintext env value (D-04/BOT-04)" - "The BotFights UI on archi-dev-box shows fighters that live in the VPS2 arena, and a fight started there is visible from the arena (D-03/BOT-03)" - "A real nostr signer (NIP-07 browser extension) logs a human in on archi-dev-box, and the session survives a page reload without any bare-pubkey request (D-01/BOT-01)" - "A cloud-hosted AI agent builds a working bot from the single prompt URL alone, registers, and fights — with no other document (D-02/BOT-02)" - "The demo path is written down as a checklist so it can be re-run on demo day without re-deriving anything" artifacts: - path: .planning/phases/09-botfights-platform-upgrade/09-DEMO-CHECKLIST.md provides: "The exact demo-day run sheet: URLs, click path, expected results, and the fallback if the arena is unreachable" contains: "arena" key_links: - from: archi-dev-box's installed botfights container to: the canonical VPS2 arena via: "ARENA_UPSTREAM_URL delivered by the signed catalog's embedded manifest, verified in the running container's env" pattern: "ARENA_UPSTREAM_URL" --- Land the phase where it has to land: BotFights 1.2.0 installed on archi-dev-box through the real signed-catalog path, then the three things only a human can confirm — a real signer login, real cross-node fighter visibility, and a cloud bot built from the prompt alone. Decision IDs map to `09-CONTEXT.md` **Locked Decisions**: D-01 = BOT-01 signer login, D-02 = BOT-02 unified prompt, D-03 = BOT-03 shared public arena, D-04 = BOT-04 signed catalog. The hard deadline in `09-CONTEXT.md` (demo on 2026-07-31 from archi-dev-box with a cloud "openclaw" bot) is what this plan exists to satisfy. Purpose: every prior plan proved a component. This one proves the product, on the machine the demo runs from, through the path a real user takes. `09-RESEARCH.md` Pitfall 5 is explicit that the Playwright suite never drives a real `window.nostr`, so green tests are not evidence here — a human with an extension is. Output: a verified archi-dev-box install, three human-confirmed checks, and a demo run sheet. **Host: archi-dev-box (this machine).** `x250-dev` was offline during planning — if it is up, repeat Task 1 there to satisfy the standing dev-pair rule and record the result. @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/09-botfights-platform-upgrade/09-CONTEXT.md @.planning/phases/09-botfights-platform-upgrade/09-RESEARCH.md @.planning/phases/09-botfights-platform-upgrade/09-VALIDATION.md @.planning/phases/09-botfights-platform-upgrade/09-04-SUMMARY.md @.planning/phases/09-botfights-platform-upgrade/09-05-SUMMARY.md @.planning/phases/09-botfights-platform-upgrade/09-06-SUMMARY.md Task 1: Update archi-dev-box to 1.2.0 through the signed catalog and verify the real install The signed catalog carrying BotFights 1.2.0 is published and reachable at the vps2 raw URL (plan 09-06 Task 3 verified this), and the archipelago service on archi-dev-box can be running — it was inactive at planning time. Updating the installed app switches this node's BotFights to proxy mode; its local database (115 bots, 102,440 fights) is preserved on disk but no longer displayed. Reverting a single node means removing `ARENA_UPSTREAM_URL` from the container env; reverting the fleet means republishing the catalog (plan 09-06). .planning/phases/09-botfights-platform-upgrade/09-DEMO-CHECKLIST.md - `.planning/phases/09-botfights-platform-upgrade/09-06-SUMMARY.md` — the published catalog URL and the gate decision (federated by default, or held). - `.planning/phases/09-botfights-platform-upgrade/09-05-SUMMARY.md` — the verified arena URL and the 1.2.0 image digest to compare the node's pulled image against. - `core/archipelago/src/container/app_catalog.rs` lines 326-400 — `refresh_catalog` and how catalog URLs are derived from the configured update mirrors, plus the on-disk cache at `/app-catalog.json`; this is what must be refreshed before the node can see 1.2.0. - `core/archipelago/src/container/prod_orchestrator.rs` lines 1385-1470 — `load_manifests` and the catalog overlay: the catalog's embedded manifest wins over the on-disk one, which is why the update must come from the catalog and not from editing `/opt/archipelago/apps/botfights/`. - `CLAUDE.md` "Don't deploy via systemctl until Quadlet" and the deploy-to-dev-pair rule. - The current state to compare against: `podman inspect botfights` today shows a plaintext `JWT_SECRET` env value, `ARCHY_EMBEDDED=1` and `FIGHT_LOOP_ENABLED=true` from the pre-1.2.0 install — after the update the secret must come from the orchestrator's secret store instead. Bring archi-dev-box's node to 1.2.0 through the supported path: 1. Ensure the archipelago service is running (it was inactive at planning time). Start it and confirm it is healthy before touching apps. 2. Force a catalog refresh so the node picks up the newly published signed catalog, then confirm `/var/lib/archipelago/app-catalog.json` now shows `botfights` at 1.2.0 with a valid `signed_by`, and that the log line recording release-root signature verification appears. 3. Update the BotFights app through the orchestrator's normal app-update path (the same one the UI's per-app Update button drives). Do not hand-create the container and do not edit the on-disk manifest — a catalog-covered app ignores disk edits, and hand-created containers are exactly what this phase is moving away from. 4. Verify the resulting container: - image tag `1.2.0` and a digest matching the one recorded in `09-05-SUMMARY.md`; - `ARENA_UPSTREAM_URL` present and equal to the arena URL; - `JWT_SECRET` delivered as a podman secret reference rather than a plaintext env value, and `/var/lib/archipelago/secrets/botfights-jwt-secret` existing with mode 0600 and 64 hex characters of content (check the mode and length; do not print the value); - container healthy, `curl -fsS http://127.0.0.1:9100/api/health` ok; - `curl -fsS http://127.0.0.1:9100/api/bots` returns the arena's fighters (proxy mode live); - `curl -fsS http://127.0.0.1:9100/api/docs/prompt` returns the unified prompt. 5. Confirm the node's own database file is intact and untouched at `/var/lib/archipelago/botfights/botfights.db` (same size/mtime class as before — data is preserved, just not read). 6. If `x250-dev` is reachable, repeat steps 1-4 there and record the outcome; if it is still offline, record that the dev-pair rule was satisfied on one node only and why. Write `.planning/phases/09-botfights-platform-upgrade/09-DEMO-CHECKLIST.md`: the demo-day run sheet. It must contain the arena URL, the node UI URL, the prompt URL to hand the cloud agent, the exact click path for the signer login, the expected visible results at each step, and the fallback if the arena is unreachable on the day (a node can be switched back to standalone by removing one env var — state the command). podman inspect botfights --format '{{.Config.Image}}' | grep -q ':1.2.0' && podman inspect botfights --format '{{json .Config.Env}}' | grep -q 'ARENA_UPSTREAM_URL' && curl -fsS http://127.0.0.1:9100/api/health | grep -q '"status":"ok"' && curl -fsS http://127.0.0.1:9100/api/docs/prompt | grep -q '/api/bots' && test -f /var/lib/archipelago/botfights/botfights.db - `podman inspect botfights --format '{{.Config.Image}}'` ends in `:1.2.0` and the container reports healthy. - `podman inspect botfights --format '{{json .Config.Env}}'` contains `ARENA_UPSTREAM_URL` and does NOT contain a plaintext `JWT_SECRET=` assignment; `stat -c %a /var/lib/archipelago/secrets/botfights-jwt-secret` prints `600` and its content length is 64. - `curl -fsS http://127.0.0.1:9100/api/bots` returns the same fighter set as `curl -fsS /api/bots`. - `curl -fsS http://127.0.0.1:9100/api/docs/prompt` returns the unified prompt with the arena URL substituted. - `/var/lib/archipelago/app-catalog.json` shows `botfights` at 1.2.0 with the expected `signed_by`. - `/var/lib/archipelago/botfights/botfights.db` still exists with its pre-update row counts (verified read-only). - `09-DEMO-CHECKLIST.md` exists and names the arena URL, the prompt URL and the standalone-fallback command. The demo machine runs 1.2.0, installed the way every user installs it, proxying to the shared arena with a per-install secret. Task 2: Real nostr signer login and live arena fighters on archi-dev-box BotFights 1.2.0 is installed on archi-dev-box and is a thin client of the public arena. Two things need a human: a real nostr signer (no automated harness exists — there is no `window.nostr` mock anywhere in the Playwright suite), and a visual confirmation that the fighters shown here really are the arena's. Open the BotFights UI on archi-dev-box (the URL is in `09-DEMO-CHECKLIST.md`). **1. Signer login (D-01).** With a NIP-07 extension installed (nos2x or Alby), click Sign In and approve the signature request in the extension. Expect: you are signed in, your bot/profile appears, and the extension prompted you to sign an event — you were never asked for a private key. Then reload the page: you should still be signed in without a second signature prompt. Optional but valuable: repeat from an Android phone using Amber (NIP-55) against the same URL. **2. Shared arena (D-03).** The fighter list / leaderboard here should show the same fighters as the public arena URL open in a second tab. Pick any fighter visible only on the arena and confirm it is listed here too. Start a fight and confirm the rounds appear live (not all at once at the end). Report anything that looks wrong — a spinner that never resolves, an empty list, a login that silently fails, rounds that appear only when the fight finishes. Type "approved" or describe what you saw Task 3: Cloud openclaw bot registers and fights using only the unified prompt URL The unified AI bot-setup prompt is live at the public arena's `/api/docs/prompt` and contains everything an agent needs: registration, credentials, both the webhook and polling protocols, every endpoint and every response format. This is the D-02 acceptance test and the demo's centrepiece. Hand your cloud "openclaw" agent nothing but the prompt URL (in `09-DEMO-CHECKLIST.md`) — no other document, no extra instructions beyond "set yourself up as a BotFights bot using this". Expect the agent to be able to, from that alone: 1. register itself (`POST /api/bots`) and receive an id and secret, 2. authenticate and poll for challenges (or stand up a webhook, if it chooses that mode), 3. join the queue and fight, 4. have that fight appear in the BotFights UI on archi-dev-box. If the agent gets stuck, the exact question it could not answer from the prompt is the finding — please quote it. That tells us precisely what the prompt is still missing, which is more useful than "it didn't work". Type "approved" or paste the point where the agent got stuck ## Trust Boundaries | Boundary | Description | |----------|-------------| | signed catalog → this node's orchestrator | Decides the image and env this node runs | | browser + NIP-07 extension → node UI → arena | The human's private key stays in the signer throughout | | cloud AI agent → public arena | An anonymous internet client registering and fighting | ## STRIDE Threat Register | Threat ID | Category | Component | Severity | Disposition | Mitigation Plan | |-----------|----------|-----------|----------|-------------|-----------------| | T-09-34 | Tampering | a hand-placed container diverging from what the catalog describes | high | mitigate | The update goes through the orchestrator's catalog path only; the acceptance criteria assert the image digest matches the published one (Task 1) | | T-09-35 | Information disclosure | the JWT secret remaining a plaintext container env value after the update | high | mitigate | Asserted absent from the container env and present as a 0600 secret file (Task 1) | | T-09-36 | Denial of service | the node's app becoming unusable if the arena is down on demo day | medium | mitigate | `/api/health` stays local, the UI degrades to a 502 on data calls, and the checklist documents the one-env-var switch back to standalone | | T-09-37 | Spoofing | a signer flow that silently falls back to a locally generated key | high | mitigate | The human check explicitly requires the extension's signature prompt and that no private key was ever requested (Task 2) | | T-09-38 | Repudiation | claiming BOT-01/BOT-02 done on green automated tests alone | medium | mitigate | Both are gated behind blocking human checkpoints, per `09-RESEARCH.md` Pitfall 5 and the CLAUDE.md "test before claiming fixed" rule | - `podman inspect botfights` shows 1.2.0, the arena URL, and no plaintext secret. - The node's fighter list matches the arena's. - Human-confirmed: real NIP-07 login (and Amber if tested), live cross-node fighter visibility, cloud agent bootstrapped from the prompt alone. - Closes the `09-VALIDATION.md` "Manual-Only Verifications" list in full: real NIP-07 login, Amber login, cross-node fighter visibility, and the cloud openclaw bot using only the unified prompt. - Dev-pair rule: recorded for `x250-dev` (repeat Task 1 there if it is online, otherwise state why not). - archi-dev-box runs BotFights 1.2.0 from the signed catalog, proxying to the shared arena, with a per-install secret and its local data preserved. - A human has logged in with a real nostr signer and seen arena fighters on the node. - A cloud agent has built a working bot from the single prompt URL. - A demo run sheet exists for tomorrow. Create `.planning/phases/09-botfights-platform-upgrade/09-07-SUMMARY.md` when done, recording the human verdicts verbatim (including anything the cloud agent could not answer from the prompt — that is the highest-value finding this phase can produce). Commit the checklist and SUMMARY in archy, staged by explicit path, and `git push gitea-ai main`.