docs(09-06): BotFights 1.2.1 catalog signed and published — plan complete (BOT-04)

Signing ceremony ran successfully; catalog independently re-verified,
committed (a99522d0), and confirmed byte-identical to the content live at
the vps2 raw URL every node fetches. Updates the SUMMARY to reflect full
completion (status: complete, requirements-completed: [BOT-04]).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago 2026-07-31 04:50:20 -04:00
parent a99522d0d4
commit 463134960c

View File

@ -11,16 +11,16 @@ requires:
provides:
- "apps/botfights/manifest.yml at 1.2.1 with generated_secrets/secret_env for JWT_SECRET (hex32) and default-on ARENA_UPSTREAM_URL"
- "app-catalog/catalog.json and scripts/image-versions.sh bumped in lockstep (drift-checker clean)"
- "releases/app-catalog.json regenerated on disk with the 1.2.1 manifest embedded (NOT committed — unsigned)"
- "the release-signer binary built and staged at /tmp/archy-sign-bin/release/archipelago"
- "the per-node bot/fight impact briefing for the signing-ceremony gate"
affects: [09-07 (demo verification), the pending Task 2/3 signing+publish continuation]
- "releases/app-catalog.json regenerated, SIGNED by the release-root key, committed, pushed, and published live to the raw URL every node fetches"
- "the release-signer binary built and staged at /tmp/archy-sign-bin/release/archipelago (reusable for future ceremonies)"
- "the per-node bot/fight impact briefing used at the signing-ceremony gate"
affects: [09-07 (demo verification)]
tech-stack:
added: []
patterns:
- "generated_secrets (kind hex32) + secret_env, mirroring apps/netbird-server/manifest.yml and apps/barkd/manifest.yml — per-install secret materialised 0600, never a manifest literal"
- "catalog publish is two-remote: gitea-ai (source.archipelago-foundation.org) is the dev push target; nodes fetch releases/app-catalog.json from gitea-vps2/origin (146.59.87.168:3000) raw URLs specifically — pushing to gitea-ai alone does not publish to the fleet"
- "gitea-ai (source.archipelago-foundation.org) and gitea-vps2/origin (146.59.87.168:3000) resolve to the SAME physical Gitea backend (confirmed via DNS + byte-identical published content) — pushing gitea-ai main IS the publish, no separate vps2 push is required in practice. The gitea-vps2 remote alias's own HTTP Basic Auth token is independently stale (fails auth) and should be rotated or removed to stop it looking like a required-but-broken step."
key-files:
created: []
@ -28,14 +28,15 @@ key-files:
- apps/botfights/manifest.yml
- app-catalog/catalog.json
- scripts/image-versions.sh
# releases/app-catalog.json: regenerated on disk, deliberately left uncommitted (see key-decisions)
- releases/app-catalog.json
key-decisions:
- "Used the POST_PLANNING FACTS override version 1.2.1 (not the plan's original 1.2.0) throughout the manifest, catalog.json, image-versions.sh and the regenerated releases/app-catalog.json, since 09-05 shipped an additional prompt-hardening commit on top of 1.2.0 and the live arena is already on 1.2.1."
- "Left releases/app-catalog.json regenerated-on-disk but UNCOMMITTED. It was regenerated via scripts/generate-app-catalog.sh (66 apps embedded, botfights entry verified at 1.2.1 with the JWT secret/env and ARENA_UPSTREAM_URL intact) and diffed against the last-committed version to confirm the ONLY substantive change is the botfights entry (the rest of the diff is JSON key-reordering noise from a fresh json.dump, confirmed via a structural key-by-key comparison — zero other apps changed). It is not committed because it is unsigned (its previous signature was naturally stripped by the content change), and per CLAUDE.md/memory the release/manifest catalog must never be committed in an unsigned state mid-cycle. Nodes fetch the catalog from the gitea-vps2/origin raw URL (146.59.87.168:3000), a different host than the gitea-ai push target (source.archipelago-foundation.org) used for this commit, so pushing the other 3 files to gitea-ai main does not publish anything to the fleet either way — but out of caution the still-unsigned catalog itself was kept off git entirely until it can be committed already-signed in the Task 3 continuation."
- "Built the release signer binary (scripts/sign-catalog.sh's prerequisite) into /tmp/archy-sign-bin/release/archipelago ahead of the ceremony so the human's signing step is not blocked on a ~50-minute cold compile of the full archipelago workspace."
- "Split the work across two sessions at the human-only signing ceremony: Task 1 (manifest/catalog edits + regeneration + signer build) was committed and pushed WITHOUT releases/app-catalog.json (deliberately left uncommitted while unsigned, per CLAUDE.md/memory's rule against ever committing an unsigned release-manifest state). After the user ran the ceremony, this session independently re-verified the signature/signed_by/payload, then committed and pushed the signed file."
- "Discovered gitea-ai (source.archipelago-foundation.org, the dev push target) and gitea-vps2/origin (146.59.87.168:3000, the host nodes fetch releases/app-catalog.json raw from) are the SAME Gitea backend, not two separate mirrors needing separate pushes — confirmed by DNS resolution (source.archipelago-foundation.org -> 146.59.87.168) and by the raw catalog URL reflecting the gitea-ai push immediately, byte-for-byte identical to the local signed file (sha256 f66bd717...). A direct `git push gitea-vps2 main` was attempted as well per the plan's Task 3 instructions but failed with a stale HTTP Basic Auth token on that specific remote alias — reported rather than worked around, but non-blocking since the gitea-ai push already fully published the signed catalog."
- "Built the release signer binary (scripts/sign-catalog.sh's prerequisite) into /tmp/archy-sign-bin/release/archipelago ahead of the ceremony so the human's signing step was not blocked on a ~50-minute cold compile of the full archipelago workspace — the ceremony ran immediately once invoked."
requirements-completed: [] # BOT-04 not yet complete — signing/publish (Task 2/3) still pending human ceremony
requirements-completed: [BOT-04]
coverage:
- id: D1
@ -53,88 +54,96 @@ coverage:
status: pass
human_judgment: false
- id: D2
description: "releases/app-catalog.json regenerated with the 1.2.1 manifest embedded (secrets, secret_env, ARENA_UPSTREAM_URL all present), verified structurally against the previously-committed catalog to show zero other-app drift"
description: "releases/app-catalog.json regenerated with the 1.2.1 manifest embedded (secrets, secret_env, ARENA_UPSTREAM_URL all present), verified structurally against the previously-published catalog to show zero other-app drift"
requirement: BOT-04
verification:
- kind: other
ref: "python3 one-liner asserting botfights.version==1.2.1, image tag :1.2.1, generated_secrets/secret_env/ARENA_UPSTREAM_URL present -> 'catalog ok'"
ref: "python3 one-liner asserting botfights.version==1.2.1, image tag :1.2.1, generated_secrets/secret_env/ARENA_UPSTREAM_URL present -> 'catalog ok'; structural key-by-key diff against prior commit showed only the botfights entry changed (65 other apps byte-identical)"
status: pass
human_judgment: false
- id: D3
description: "The release-root signing ceremony and fleet publish — this is the blocking human gate this plan session stopped at. Not yet done."
description: "The release-root signing ceremony and fleet publish — the user ran scripts/sign-catalog.sh at their own terminal; the resulting catalog was independently re-verified and published"
requirement: BOT-04
verification: []
human_judgment: true
rationale: "Signing requires the human's 24-word release master mnemonic entered at their own terminal via scripts/sign-catalog.sh — this can never be automated or judged by the agent."
verification:
- kind: other
ref: "python3 assert signature present + signed_by == did:key:z6MkkidEnEpo6qHMCNSZoNKWtvQvxq3whnaME9wGgEFhq7ur + botfights.version==1.2.1 -> 'signed ok'"
status: pass
- kind: other
ref: "curl http://146.59.87.168:3000/lfg2025/archy/raw/branch/main/releases/app-catalog.json (off-host) -> byte-identical to local signed file, sha256 f66bd717f45cff0bf5dfe1eb609dbc7abcf3aa28cffbf4a652556711426bf48f on both sides"
status: pass
human_judgment: false
rationale: "Signing itself required the human's mnemonic and could not be automated; the RESULT is fully machine-verifiable and was verified, so this deliverable is proven, not merely asserted by the human."
duration: ~65min (mostly a ~50min cold `cargo build --release` of the signer binary, run in the background)
duration: ~4h across two sessions (dominated by a ~50min cold `cargo build --release` of the signer binary, run in the background while other verification proceeded; the ceremony + publish session itself was a few minutes)
completed: 2026-07-31
status: paused
status: complete
---
# Phase 9 Plan 6: BotFights 1.2.1 Manifest + Catalog Prep (Task 1 done, signing ceremony PENDING) Summary
# Phase 9 Plan 6: BotFights 1.2.1 Manifest + Signed Catalog Publish Summary
**Bumped `apps/botfights/manifest.yml` to 1.2.1 with a per-install generated `JWT_SECRET` and default-on shared-arena federation, regenerated the signed-catalog input, and built the release signer — stopped intentionally at the human-only signing ceremony (Task 2) per the execution override; signing/verification/publish (Tasks 2-3) remain for a follow-up session.**
**Bumped `apps/botfights/manifest.yml` to 1.2.1 with a per-install generated `JWT_SECRET` and default-on shared-arena federation, then got the regenerated catalog signed by the release-root key (human ceremony) and published live to the raw URL every node fetches — BOT-04 complete.**
## Performance
- **Duration:** ~65 min (dominated by a ~50-minute cold `cargo build --release -p archipelago` for the signer binary, backgrounded while other work proceeded)
- **Tasks:** 1/3 completed (Task 1 auto); Task 2 (human signing ceremony) reached and intentionally halted per the run's explicit override instruction; Task 3 not started (depends on Task 2)
- **Files modified:** 3 committed (`apps/botfights/manifest.yml`, `app-catalog/catalog.json`, `scripts/image-versions.sh`); 1 regenerated-but-uncommitted (`releases/app-catalog.json`)
- **Duration:** ~4h across two sessions (session 1: manifest/catalog edits, regeneration, signer build, verification, stopped at the human-only signing checkpoint; session 2, after the user ran the ceremony: independent re-verification, commit, push, off-host publish confirmation)
- **Tasks:** 3/3 completed
- **Files modified:** 4 (`apps/botfights/manifest.yml`, `app-catalog/catalog.json`, `scripts/image-versions.sh`, `releases/app-catalog.json`)
## Accomplishments
- `apps/botfights/manifest.yml` bumped to **1.2.1** (superseding the plan's originally-planned 1.2.0 per the session's POST_PLANNING FACTS — 09-05 shipped one additional prompt-hardening commit after 1.2.0 was built, and the live arena is already on 1.2.1). Image tag updated, `container.generated_secrets` adds `botfights-jwt-secret` (`kind: hex32`, matching `apps/barkd/manifest.yml`'s pattern — not `base64` like netbird, since `jwt.ts` uses the value directly as an HMAC key with no decode step), `secret_env` maps it to `JWT_SECRET`. `environment` gains `PORT=9100` and `ARENA_UPSTREAM_URL=https://botfights.archipelago-foundation.org` with an in-file comment documenting the per-node opt-out (delete the line to run standalone). Every other block (security/ports/volumes/health_check/interfaces/metadata) is untouched.
- `app-catalog/catalog.json`'s `botfights` entry and `scripts/image-versions.sh`'s `BOTFIGHTS_IMAGE` bumped to 1.2.1 in lockstep — `scripts/check-app-catalog-drift.py` reports `metadata_drift: 0`.
- Regenerated `releases/app-catalog.json` via `scripts/generate-app-catalog.sh` (66 apps, 56 embedded manifests). Verified programmatically: `botfights.version == "1.2.1"`, image tag `:1.2.1`, `generated_secrets` contains `botfights-jwt-secret`, `secret_env` contains `JWT_SECRET`, and `environment` contains `ARENA_UPSTREAM_URL`. Structurally diffed against the last-committed catalog and confirmed **zero other apps changed** — the large line-diff is pure JSON key-reordering noise from a fresh `json.dump`, not content drift.
- Built the release-signer prerequisite (`scripts/sign-catalog.sh` refuses to compile its own signer) into `/tmp/archy-sign-bin/release/archipelago` ahead of the ceremony, confirmed executable (`archipelago 1.7.118-alpha-dev`).
- Ran the plan's required verification: `cargo test -p archipelago catalog_overlay_accepts_all_real_image_manifests` (pass) and `cargo test -p archipelago-container manifest` (38 passed, including `parse_every_real_manifest`, confirming the new manifest parses and validates as a real overlay).
- Gathered the fleet-impact briefing the human gate needs: read the local BotFights SQLite DB on archi-dev-box directly (`/var/lib/archipelago/botfights/botfights.db`, read-only connection) — **115 bots, 102,440 fights**, matching the plan's stated pre-measurement exactly. Checked reachability of two other known nodes: x250-dev (100.72.136.6) timed out (unreachable this session); the .228 resilience node (100.64.204.114) is reachable but has **no** `botfights.db` (app not installed there). No other node currently holds local BotFights data that would be affected.
- Confirmed the live public arena is healthy and already on 1.2.1: `GET https://botfights.archipelago-foundation.org/api/health``{"status":"ok","name":"botfights"}`; `skopeo inspect` confirms `146.59.87.168:3000/lfg2025/botfights:1.2.1` exists in the registry.
- Regenerated `releases/app-catalog.json` via `scripts/generate-app-catalog.sh` (66 apps, 56 embedded manifests). Verified programmatically and structurally: `botfights.version == "1.2.1"`, image tag `:1.2.1`, `generated_secrets`/`secret_env`/`ARENA_UPSTREAM_URL` all present, and **zero other apps changed** vs the previously-published catalog.
- Built the release-signer prerequisite (`scripts/sign-catalog.sh` refuses to compile its own signer) into `/tmp/archy-sign-bin/release/archipelago` ahead of the ceremony, so the human's signing step ran immediately with no compile wait.
- Ran the plan's required verification: `cargo test -p archipelago catalog_overlay_accepts_all_real_image_manifests` (pass) and `cargo test -p archipelago-container manifest` (38 passed, including `parse_every_real_manifest`).
- Gathered the fleet-impact briefing for the human gate: read the local BotFights SQLite DB on archi-dev-box directly — **115 bots, 102,440 fights**, matching the plan's stated pre-measurement exactly. x250-dev was unreachable this session; the `.228` resilience node has no local `botfights.db` (app not installed there).
- **User ran the signing ceremony** (`scripts/sign-catalog.sh`) and confirmed success. Independently re-verified (not just trusting the script's own report): `signature` present, `signed_by` == `did:key:z6MkkidEnEpo6qHMCNSZoNKWtvQvxq3whnaME9wGgEFhq7ur`, `botfights` entry still 1.2.1 with the embedded manifest's secrets/env/arena URL intact, and a structural diff confirming signing altered nothing else in the payload.
- Committed the signed catalog (`a99522d0`) and pushed to `gitea-ai main`. **Discovered mid-publish that `gitea-ai` (`source.archipelago-foundation.org`) and `gitea-vps2`/`origin` (`146.59.87.168:3000`, the host nodes actually fetch the raw catalog from) are the same physical Gitea backend** — DNS resolves the domain to the same IP, and the raw URL reflected the push immediately. Confirmed via off-host `curl`: published bytes are byte-identical to the local signed file (`sha256:f66bd717f45cff0bf5dfe1eb609dbc7abcf3aa28cffbf4a652556711426bf48f` on both sides).
## Task Commits
1. **Task 1: Manifest 1.2.1 — generated JWT secret, default-on arena, catalogs in lockstep**`f281c7aa` (feat) — `apps/botfights/manifest.yml`, `app-catalog/catalog.json`, `scripts/image-versions.sh`. Pushed to `gitea-ai main`.
2. **Task 2: Sign the regenerated catalog (human ceremony)** — done by the user at their own terminal via `scripts/sign-catalog.sh`; no commit (script only mutates the working tree).
3. **Task 3: Verify signature, commit, publish**`a99522d0` (chore) — `releases/app-catalog.json` (signed). Pushed to `gitea-ai main`; confirmed same content live at the vps2 raw URL nodes fetch.
**releases/app-catalog.json is deliberately NOT committed** — see Deviations/Decisions below.
**Task 2 (checkpoint:human-action, blocking):** reached, not executed — the signing ceremony requires the human's 24-word release master mnemonic at their own terminal. This session never attempted it, never asked for it, and never scripted around it, per the run's explicit override instruction.
**Task 3:** not started — depends on Task 2 completing.
**Plan metadata:** this SUMMARY, committed separately in two docs commits (`921f450e` mid-plan, this rewrite folded into the same file) — `.planning/STATE.md`/`ROADMAP.md`/`REQUIREMENTS.md` were intentionally NOT touched per this run's explicit instruction; a follow-up should run the normal state-update step to mark BOT-04 complete in those files.
## Files Created/Modified
- `apps/botfights/manifest.yml` — version/image → 1.2.1, `generated_secrets`/`secret_env` for `JWT_SECRET`, `ARENA_UPSTREAM_URL` added to `environment`.
- `app-catalog/catalog.json` — legacy `botfights` entry version/dockerImage → 1.2.1.
- `scripts/image-versions.sh``BOTFIGHTS_IMAGE` tag → 1.2.1.
- `releases/app-catalog.json` — regenerated on disk (not committed; see below).
- `releases/app-catalog.json` — regenerated, signed by the release-root key, committed, published.
## Decisions Made
See `key-decisions` in frontmatter. Most notably: **`releases/app-catalog.json` was intentionally left uncommitted.** It is correctly regenerated and verified on disk, but it is unsigned (any prior signature was naturally invalidated by the content change), and CLAUDE.md/project memory are explicit that an unsigned/dead-URL release-manifest state must never be committed mid-cycle. Nodes actually fetch the catalog from the `gitea-vps2`/`origin` raw URL (`146.59.87.168:3000`) — a different host than the `gitea-ai` push target (`source.archipelago-foundation.org`) used for this session's commit — so pushing the other 3 files to `gitea-ai main` does not, by itself, publish anything to the fleet. Out of caution (unable to fully rule out an automatic mirror/replication between the two Gitea hosts from this session), the unsigned catalog itself was kept off git entirely rather than relying on that distinction. It will be committed already-signed in the Task 3 continuation, immediately after the ceremony succeeds.
See `key-decisions` in frontmatter. Most notable: the catalog was deliberately kept off git while unsigned across the checkpoint boundary (per CLAUDE.md/memory's rule against ever committing an unsigned release-manifest state), and committed only after independent re-verification of the signature once the human ceremony succeeded. Also notable: `gitea-ai` and `gitea-vps2`/`origin` turned out to be the same backend — the plan's Task 3 wording (written assuming two separate remotes needing separate pushes) is slightly stale; a direct `git push gitea-vps2 main` was still attempted per the plan's literal instructions but failed on a stale token specific to that remote alias, which is reported below as a non-blocking issue rather than worked around.
## Deviations from Plan
**1. [User-directed] Version 1.2.1 instead of the plan's 1.2.0.** The plan text was written against 1.2.0; the session's POST_PLANNING FACTS explicitly superseded this with 1.2.1 (an additional prompt-hardening commit `d2fc998` landed on top of 09-05's 1.2.0 build, and the live arena is already running 1.2.1). Applied everywhere the plan said 1.2.0 — manifest, both catalog files, image-versions.sh, and the regenerated `releases/app-catalog.json`.
**2. [User-directed] Stopped before Task 2's signing ceremony.** The plan's own Task 2 is a `checkpoint:human-action` gate; the run's explicit instruction additionally forbade attempting to sign or ask for the mnemonic. This session did all automatable prep (manifest/catalog edits, catalog regeneration + verification, signer binary build, impact briefing) and returns with the exact ceremony instructions for the user, rather than proceeding into Task 2's interactive flow.
**2. [User-directed] Execution split across the human checkpoint into two sessions.** The plan's own Task 2 is a `checkpoint:human-action` gate; this run's explicit instruction additionally forbade attempting to sign or ask for the mnemonic in the first session. That session did all automatable prep and returned exact ceremony instructions; this second session picked up after the user confirmed the ceremony succeeded, independently re-verified, committed, and published.
No Rule 1-3 auto-fixes were needed — the manifest/catalog edits matched the plan and the analog patterns (`netbird-server`, `barkd`) exactly, and both required cargo tests passed on the first run.
**3. [Rule 3 - blocking issue, reported not worked around] `git push gitea-vps2 main` failed with a stale HTTP Basic Auth token.** The plan's Task 3 read_first assumes a direct push to the vps2 remote is required to publish. In practice `gitea-ai` (already pushed in Task 1/3) and `gitea-vps2`/`origin` are the same backend (confirmed via DNS + byte-identical published content), so the publish was already complete before this push was attempted. The `gitea-vps2` remote alias's own credentials are independently stale — this is the same "known recurring issue with the vps2/local-origin remotes" flagged in project memory. Not fixed (out of scope — requires an admin token rotation), reported here instead of improvised around.
No Rule 1-2 auto-fixes were needed — the manifest/catalog edits matched the plan and the analog patterns (`netbird-server`, `barkd`) exactly, and both required cargo tests passed on the first run.
## Issues Encountered
- The signer binary build (`cargo build --release -p archipelago`, a fresh `CARGO_TARGET_DIR` with no incremental cache) took ~50 minutes on this heavily-loaded shared dev box (load average 24+ during the build, other agents/containers active on the same host) — far longer than a typical incremental build. Backgrounded and monitored to completion; no errors, `BUILD_EXIT=0`. The two required `cargo test` invocations (which share `core/target/debug`, unaffected by the release build's separate `CARGO_TARGET_DIR`) both completed successfully in parallel with the signer build.
- x250-dev (100.72.136.6) was unreachable this session (SSH connection timed out) — could not check whether it holds local BotFights data. Not blocking: the plan's own pre-measured reference count (115 bots/102,440 fights on archi-dev-box) was independently reproduced exactly, and the other checked node (.228) has no local install.
- The signer binary build (`cargo build --release -p archipelago`, a fresh `CARGO_TARGET_DIR` with no incremental cache) took ~50 minutes on this heavily-loaded shared dev box (load average 24+ during the build). Backgrounded and monitored to completion; no errors, `BUILD_EXIT=0`.
- x250-dev (100.72.136.6) was unreachable during the impact-briefing gathering — could not check whether it holds local BotFights data. Not blocking: the plan's own pre-measured reference count (115 bots/102,440 fights on archi-dev-box) was independently reproduced exactly, and the other checked node (`.228`) has no local install.
- `git push gitea-vps2 main` failed with `fatal: Authentication failed` — see Deviation #3. Non-blocking; the `gitea-ai` push already published the signed catalog to the same backend.
## User Setup Required
**The signing ceremony (Task 2) is the next required action — see the report returned alongside this summary for exact instructions.** In short: run `bash /home/archipelago/Projects/archy/scripts/sign-catalog.sh` at your own terminal, paste your 24-word release master mnemonic, press Enter then Ctrl-D. The signer binary is already built and staged, so this step should run immediately with no compile wait.
None remaining for this plan. **Recommended follow-up (not done here per this run's explicit instruction to leave STATE/ROADMAP/REQUIREMENTS untouched):** run the standard GSD state-update step to mark `BOT-04` complete in `.planning/REQUIREMENTS.md`, advance `.planning/STATE.md`, and update `.planning/ROADMAP.md`'s plan-progress row for 09-06. Separately: the `gitea-vps2` git remote's stored HTTP Basic Auth token is stale and should be rotated (or the redundant remote alias removed, since `gitea-ai` already reaches the same backend).
## Next Phase Readiness
- **Not ready to proceed to 09-07 (demo verification) for the manifest/catalog side of BOT-04** until the signing ceremony (Task 2) and publish (Task 3) complete. The live public arena itself (from 09-05) is already healthy at 1.2.1 and unaffected by this plan's remaining steps — only node-fleet catalog distribution is pending.
- Once the human runs the ceremony and replies "signed", a continuation session should: (1) independently re-verify the signature and `signed_by` DID and that the `botfights` payload wasn't altered by signing, (2) commit `releases/app-catalog.json` (staged by explicit path) with a message describing the BotFights 1.2.1 catalog publish, (3) push `gitea-ai main`, (4) ensure `main` also lands on `gitea-vps2` (the host nodes actually fetch from) and confirm via `curl` of the raw URL that the published bytes match the signed local file, exactly as Task 3 specifies.
- If the user instead says "hold federation": regenerate `releases/app-catalog.json` with `ARENA_UPSTREAM_URL` removed from `apps/botfights/manifest.yml` first, then re-run the ceremony — this reverts BOT-03's default-on behavior while keeping the JWT-secret crash-loop fix (BOT-04's other half) intact.
- **BOT-04 is complete.** Every node that refreshes its catalog now sees BotFights 1.2.1: the JWT_SECRET crash-loop fix and the default-on shared-arena federation are both live in the signed, published catalog, verified byte-for-byte from off-host.
- **09-07 (demo verification)** can proceed — the manifest/catalog side of the platform upgrade is done, on top of 09-05's already-verified live arena (health/prompt/registration/poll/SSE/cross-instance all proven working at 1.2.1).
- Four clearly-named test bots remain in the arena from 09-05 (`wavetest2/3/4`, `routefix`) — unaffected by this plan, no cleanup action taken here.
---
*Phase: 09-botfights-platform-upgrade*
@ -142,8 +151,8 @@ No Rule 1-3 auto-fixes were needed — the manifest/catalog edits matched the pl
## Self-Check: PASSED
- `apps/botfights/manifest.yml`, `app-catalog/catalog.json`, `scripts/image-versions.sh` confirmed present on disk with 1.2.1 content.
- Commit `f281c7aa` confirmed present in `git log --oneline` and confirmed pushed to `gitea-ai main` (remote ref updated `6904e789..f281c7aa`).
- `releases/app-catalog.json` confirmed present on disk, regenerated, `botfights` entry verified at 1.2.1 with secrets/env intact — confirmed intentionally absent from `git status` staged/committed state (still shows as modified-but-uncommitted).
- `/tmp/archy-sign-bin/release/archipelago` confirmed executable.
- Both required `cargo test` invocations confirmed passing from their captured output.
- `apps/botfights/manifest.yml`, `app-catalog/catalog.json`, `scripts/image-versions.sh`, `releases/app-catalog.json` confirmed present on disk with 1.2.1 signed content.
- Commits `f281c7aa` and `a99522d0` confirmed present in `git log --oneline` and confirmed pushed to `gitea-ai main`.
- `releases/app-catalog.json` confirmed signed (`signature` present, `signed_by` matches the expected release-root DID) and confirmed byte-identical to the content served live at `http://146.59.87.168:3000/lfg2025/archy/raw/branch/main/releases/app-catalog.json` (sha256 match).
- `git show HEAD` grepped for mnemonic/private-key patterns: 0 matches.
- `/tmp/archy-sign-bin/release/archipelago` confirmed executable and was the binary actually used for the ceremony.