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
The exact demo-day run sheet: URLs, click path, expected results, and the fallback if the arena is unreachable
arena
from
to
via
pattern
archi-dev-box's installed botfights container
the canonical VPS2 arena
ARENA_UPSTREAM_URL delivered by the signed catalog's embedded manifest, verified in the running container's env
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.mdLocked 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.
@.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
<threat_model>
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
</threat_model>
- `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).
<success_criteria>
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.
</success_criteria>
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`.