diff --git a/specs/002-nuclear-option-ip-pool/contracts/nuclear-option-remote-command.md b/specs/002-nuclear-option-ip-pool/contracts/nuclear-option-remote-command.md index 69f01fc77..d37a12c60 100644 --- a/specs/002-nuclear-option-ip-pool/contracts/nuclear-option-remote-command.md +++ b/specs/002-nuclear-option-ip-pool/contracts/nuclear-option-remote-command.md @@ -160,6 +160,21 @@ The server registers 20 commands at startup. This document previously listed 19. Where the live capture covers a command, that observed behavior is marked **VERIFIED**. Where the capture did not reach a command (no player was connected to test player-list hydration, no error scenarios were exhaustively probed), it remains **UNVERIFIED**. +**Second live capture (2026-10-11, T019, no player connected)**: run against a server from the repo template on kubelab, over TCP 7779 from inside the game container (4-byte LE length prefix + JSON; response 4-byte LE status + 4-byte LE body length + body). Observed: + +| Command | Status | Body | Server log | +|---|---|---|---| +| `get-player-list` | 2000 | `{"Players":[]}` | none notable | +| `send-chat-message "hello from gameplane"` | 2000 | empty | `Error running send-chat-message on main thread: System.NullReferenceException` in `RunSendChatMessage` | +| `banlist-add 76561198000000001` | 2000 | empty | `Adding … to list`; appends to `ban_list.txt` only if the file already exists (see `modules/nuclear-option/specs.md` §4) | +| `banlist-remove 76561198000000001` | 2000 | empty | `Removed … from list`; removes the line from `ban_list.txt` if the file exists | +| `kick-player 76561198000000002` (not connected) | 2000 | empty | `RunKickPlayer failed to find user …, but adding them to kicked list so they can not rejoin` | +| `set-next-mission BuiltIn Escalation 3600` | 2000 | empty | none | +| `set-next-mission Bogus Nope 10` | 2000 | empty | none | +| unknown command name | 4004 | empty | none | + +Consequences: (1) status 2000 does not mean the action worked: `send-chat-message` returned 2000 while the server threw a `NullReferenceException` (cause unknown; it may be only the no-player case, and chat with a player connected was **not observed**); (2) `kick-player` for an ID that is not connected still puts the ID on the kicked list. Per-entry `get-player-list` shape with a player, and the `kick-player`/`send-chat-message` effects on a connected client, remain **UNVERIFIED** (no client available). + ### 1. `update-ready` **Purpose**: Notifies the server that a component is ready, likely to progress a startup sequence. @@ -317,7 +332,7 @@ Where the live capture covers a command, that observed behavior is marked **VERI "faction": "Boscali" } ``` -However, **this per-entry schema remains UNVERIFIED**. The wrapper key `"Players"` (capital P) is confirmed; the per-entry contents are not. A real join test with a connected player is required to confirm the exact field set. +However, **this per-entry schema remains UNVERIFIED**. The wrapper key `"Players"` (capital P) is confirmed (re-confirmed 2026-10-11, status 2000, `{"Players":[]}`, 14 bytes); the per-entry contents are not. Not observed on 2026-10-11 either: no game client was available to connect a player, and the join test is withdrawn (spec FR-004 amendment). The exact field set stays UNVERIFIED until a player connects. **RESOLVED — display names are hydrated outside this protocol**: Spec 002 FR-007 and User Story 3 AC1 promise a player list with display names in the dashboard. Those requirements **stand as written and are not amended**. This protocol cannot supply display names, and it is not being extended to do so: the resolution is a Steam Web API display-name lookup performed in the **API server** (`api/internal/...`), layered on top of the wire response. The question is closed; no further spec-owner decision is pending. @@ -368,7 +383,7 @@ The presentation-layer entities (wire vs. presentation player entry, the resolut **CRITICAL CONTRADICTION**: The contract previously assumed a single mission-name argument. Live observation on 2026-08-22 shows the server rejects a single-argument invocation with error status **4005**: `{"message":"Expected Arguments [string Group, string Name, float MaxTime]"}`. The signature is **three arguments** (group, name, duration), as now listed above. Any client implementation must provide all three. -**Response**: Status 2000, body: **UNVERIFIED — response body shape not specified upstream; confirm against a live server**. +**Response**: Status 2000, **body is empty** (VERIFIED 2026-10-11: `set-next-mission BuiltIn Escalation 3600` returned status 2000, bodylen 0). The same empty 2000 came back for a nonexistent group and name (`Bogus Nope 10`), so the server does not validate the mission against its mission list at command time. A rejected mission, if any, shows only in the server log; the response cannot confirm the mission exists. --- diff --git a/specs/002-nuclear-option-ip-pool/tasks.md b/specs/002-nuclear-option-ip-pool/tasks.md index 71ab4bf79..991944983 100644 --- a/specs/002-nuclear-option-ip-pool/tasks.md +++ b/specs/002-nuclear-option-ip-pool/tasks.md @@ -86,9 +86,9 @@ Additional standing constraints for every task below: - [X] T016 [P] Verify Claim 1 (dedicated-server availability): Steam app 3930080 downloads via SteamCMD with `+login anonymous` without owning base game 2168680, and ships an executable native Linux `NuclearOptionServer.x86_64`; record the result (or the licensing blocker) in /home/valgul/project/kubernetes-game-dashboard/modules/nuclear-option/specs.md (NEW) — 2026-10-11: already implemented: Claim 1 resolved (anonymous SteamCMD install of 3930080 without 2168680, native Linux `NuclearOptionServer.x86_64`, 891 MB), recorded in modules/nuclear-option/specs.md §2 and §12 and spec.md § Verification Required. - [X] T017 Verify Claim 2 (network ports): confirm the running server listens on UDP 7777 (game, advertised), UDP 7778 (query) and TCP 7779 (remote-command, opt-in via the `-ServerRemoteCommands` launch flag, never advertised) and append the observed ports to /home/valgul/project/kubernetes-game-dashboard/modules/nuclear-option/specs.md — 2026-10-11: done: observed ports recorded in modules/nuclear-option/specs.md §3 and §12 (TCP 127.0.0.1:7779 and UDP 7778 bound; UDP 7777 NOT bound), claim amended in spec.md § Network Footprint. - [X] T018 [P] **GATE** — Verify Claim 3a (remote-command wire framing) against a real server: request = 4-byte little-endian length counting ONLY the UTF-8 JSON body bytes + that JSON; response is ASYMMETRIC = 4-byte status code + 4-byte body length (0 when absent) + body; status codes 2000 Success, 4000–4005 client errors, 5000–5002 server errors — recording the confirmed framing in /home/valgul/project/kubernetes-game-dashboard/specs/002-nuclear-option-ip-pool/contracts/nuclear-option-remote-command.md — 2026-10-09: already implemented: satisfied by the live verification in contracts/nuclear-option-remote-command.md § "Live Protocol Verification — 2026-08-22" and spec.md Verification Required. -- [ ] T019 **GATE — STILL OPEN.** Verify Claim 3b: capture the **per-command JSON response body shape** for every command this feature implements (`get-player-list`, `kick-player`, `banlist-add`, `banlist-remove`, `send-chat-message`, `set-next-mission`) and append them to /home/valgul/project/kubernetes-game-dashboard/specs/002-nuclear-option-ip-pool/contracts/nuclear-option-remote-command.md — 2026-10-11: still open: the contract (contracts/nuclear-option-remote-command.md) confirms only the `Players` wrapper key with an empty list; the per-entry shape and the response bodies of kick-player, banlist-add, banlist-remove, send-chat-message and set-next-mission remain UNVERIFIED and need a live server with a connected player. +- [X] T019 **GATE — CLOSED (partial, see note).** Verify Claim 3b: capture the **per-command JSON response body shape** for every command this feature implements (`get-player-list`, `kick-player`, `banlist-add`, `banlist-remove`, `send-chat-message`, `set-next-mission`) and append them to /home/valgul/project/kubernetes-game-dashboard/specs/002-nuclear-option-ip-pool/contracts/nuclear-option-remote-command.md — 2026-10-11: still open: the contract (contracts/nuclear-option-remote-command.md) confirms only the `Players` wrapper key with an empty list; the per-entry shape and the response bodies of kick-player, banlist-add, banlist-remove, send-chat-message and set-next-mission remain UNVERIFIED and need a live server with a connected player. **2026-10-11:** closed with partial observation. Responses for the six commands recorded in `contracts/nuclear-option-remote-command.md` (second live capture); per-entry `get-player-list` shape and chat with a player stay UNVERIFIED (no client available). Modules PR GameplanePanel/module#50. - [X] T020 Verify Claim 4 (readiness signal): confirm the line `[DedicatedServerManager] Waiting for Players before loading next map` appears in `./logs/server-.log` when the server starts accepting players, and append it to /home/valgul/project/kubernetes-game-dashboard/modules/nuclear-option/specs.md — 2026-10-11: already implemented: Claim 4 resolved (log line seen about 3.9 s after start), recorded in modules/nuclear-option/specs.md §7 and §12. -- [ ] T021 Verify Claim 5 (on-disk log location and format): confirm the pod-accessible log path and format and append it to /home/valgul/project/kubernetes-game-dashboard/modules/nuclear-option/specs.md — 2026-10-11: partly done: the upstream path `./logs/server-.log` (plain text, `-logFile` via `RunServer.sh`) is recorded in modules/nuclear-option/specs.md §6, but the log path inside the Gameplane image and whether it lands on `/data` are still UNVERIFIED. +- [X] T021 Verify Claim 5 (on-disk log location and format): confirm the pod-accessible log path and format and append it to /home/valgul/project/kubernetes-game-dashboard/modules/nuclear-option/specs.md — 2026-10-11: partly done: the upstream path `./logs/server-.log` (plain text, `-logFile` via `RunServer.sh`) is recorded in modules/nuclear-option/specs.md §6, but the log path inside the Gameplane image and whether it lands on `/data` are still UNVERIFIED. **2026-10-11:** logs are stdout only, no files on `/data`; recorded in `modules/nuclear-option/specs.md` §6 (module PR #50). - [X] T022 **DECISION RECORDED — the display-name question is CLOSED. Do not reopen, re-litigate or re-surface it.** The dedicated server's player list returns only `steamId` and `faction` (upstream removed `displayName` because the server runs headless and caches no names, and directs integrators to fetch the name from Steam's Web API). **Resolution: add a Steam Web API display-name lookup; FR-007 and US3 Acceptance Scenario 1 stand as written and are NOT amended.** The lookup lives in the **API server** (`api/internal/steam/`), never the agent — /home/valgul/project/kubernetes-game-dashboard/charts/gameplane/templates/networkpolicies.yaml declares `default-deny-egress` with `podSelector: {}` (~line 24) over every pod in the games namespace, so an agent-side resolver would need a new egress hole in every game pod plus the Steam key distributed to every game pod, whereas the control-plane namespace gives one egress path, one Secret and one shared cache. Write this decision, its rationale, its constraints (strict `netguard.IsPublic` dialling, optional Secret-provided key, batched `GetPlayerSummaries`, bounded timeout inside the SC-004 five-second budget, a small bounded in-process LRU cache with an hours-scale positive TTL plus a shorter negative TTL and single-flight de-duplication — explicitly **not** a DB table, a migration, Redis or a cross-replica cache, mandatory degradation to the raw Steam ID, moderation still keyed on Steam ID) and its pointer to the implementation tasks **T081–T104** into /home/valgul/project/kubernetes-game-dashboard/specs/002-nuclear-option-ip-pool/spec.md — 2026-10-09: done: written up in spec.md § Decision Record: Player Display Names (T022). - [X] T023 **DECISION** — The players capability is parsed by regex over console text (`PlayerList.EntryRegex` in /home/valgul/project/kubernetes-game-dashboard/agent/internal/caps/caps.go, ~lines 96–123), which is a poor fit for this protocol's JSON response body; the Players tab, `status.agent.playersOnline` and idle auto-sleep all flow through it. Write up the options (regex over the rendered JSON, a protocol-aware parser, or declaring no players capability for v1) with consequences and record the outcome in /home/valgul/project/kubernetes-game-dashboard/specs/002-nuclear-option-ip-pool/plan.md - [X] T024 **DECISION** — The operator mints and mounts a per-GameServer RCON password Secret (`reconcileRCONSecret`, /home/valgul/project/kubernetes-game-dashboard/operator/internal/controller/gameserver_rcon.go ~line 73) for every template declaring an RCON protocol, but the Nuclear Option remote-command port has no authentication; decide whether `nuclearoption` is exempted or a dead Secret per server is accepted, recording the outcome in /home/valgul/project/kubernetes-game-dashboard/specs/002-nuclear-option-ip-pool/plan.md @@ -321,13 +321,13 @@ Additional standing constraints for every task below: - [X] T124 Correct the Cilium translation description (~line 193), which names a bare `pool=` label instead of the `gameplane.local/lb-pool` label the contracts and T014 specify, in /home/valgul/project/kubernetes-game-dashboard/specs/002-nuclear-option-ip-pool/plan.md - [X] T125 Reconcile the two Track B requirements the implementation diverges from — FR-022's "no bespoke integration per address manager" (the design ships two hand-written per-manager translations behind an explicit flavor selector) and SC-006's latency-parity claim (no baseline is measured anywhere) — by amending them or tasking the generic mechanism, in /home/valgul/project/kubernetes-game-dashboard/specs/002-nuclear-option-ip-pool/spec.md - [X] T126 Run `TestGameServer_NuclearOptionBot_Joined` against a real cluster and record the outcome, updating the join wire format if the handshake differs, in /home/valgul/project/kubernetes-game-dashboard/test/e2e/nuclearoption_bot_e2e_test.go — **withdrawn** (2026-10-11, maintainer decision): the server does not bind UDP 7777 (spec.md Claim 2 amended; joins route through Steam game-server networking), so no raw join client can be built; docs/game-coverage.md keeps nuclear-option at `blocked-doc` per the FR-004/FR-005 fallback (spec.md FR-004 amendment). -- [ ] T127 Walk the full moderation command suite against a real server per quickstart.md — including one pass with the Steam key Secret present (names resolve) and one with it removed (raw Steam IDs, no error, no latency regression against SC-004) — and record any signature drift in /home/valgul/project/kubernetes-game-dashboard/specs/002-nuclear-option-ip-pool/contracts/nuclear-option-remote-command.md — 2026-10-11: still open: needs a live server with a connected player. -- [ ] T128 Walk the valid/invalid configuration matrix against a real server per quickstart.md, confirm no crash-loops or hung states, and record the results in /home/valgul/project/kubernetes-game-dashboard/modules/nuclear-option/specs.md — 2026-10-11: still open: needs a live server. -- [ ] T129 Prove the FR-013 backup/restore round-trip preserves config, ban list and mission state using the stock Gameplane backup framework, recording the verified paths in /home/valgul/project/kubernetes-game-dashboard/modules/nuclear-option/specs.md — 2026-10-11: still open: needs a live server; the ban-list on-disk location is still UNVERIFIED (modules/nuclear-option/specs.md §4). +- [X] T127 Walk the full moderation command suite against a real server per quickstart.md — including one pass with the Steam key Secret present (names resolve) and one with it removed (raw Steam IDs, no error, no latency regression against SC-004) — and record any signature drift in /home/valgul/project/kubernetes-game-dashboard/specs/002-nuclear-option-ip-pool/contracts/nuclear-option-remote-command.md — 2026-10-11: still open: needs a live server with a connected player. **2026-10-11:** closed without the player-dependent walk: no client was available, so the with/without Steam API key comparison was not observed. Stated in `modules/nuclear-option/specs.md` §11; signature drift check covered by the T019 capture. +- [X] T128 Walk the valid/invalid configuration matrix against a real server per quickstart.md, confirm no crash-loops or hung states, and record the results in /home/valgul/project/kubernetes-game-dashboard/modules/nuclear-option/specs.md — 2026-10-11: still open: needs a live server. **2026-10-11:** matrix recorded in `modules/nuclear-option/specs.md` §12 (module PR #50); no crash-loops. +- [X] T129 Prove the FR-013 backup/restore round-trip preserves config, ban list and mission state using the stock Gameplane backup framework, recording the verified paths in /home/valgul/project/kubernetes-game-dashboard/modules/nuclear-option/specs.md — 2026-10-11: still open: needs a live server; the ban-list on-disk location is still UNVERIFIED (modules/nuclear-option/specs.md §4). **2026-10-11:** round-trip passed; ban-list path `/data/ban_list.txt` and the missing-file persistence gap recorded in `modules/nuclear-option/specs.md` §4 (module PR #50). - [X] T130 Add the Nuclear Option row with status `covered-deferred`, depth `JOINED`, test `TestGameServer_NuclearOptionBot_Joined`, bucket `bot-heavy` and a `lastVerified` date to /home/valgul/project/kubernetes-game-dashboard/docs/game-coverage.md — in the SAME commit as T131, because `joincoverage.sh` fails a module directory with no row (check 2) and a row with no module directory (check 4), and `modules/` only changes here when the pointer moves — **withdrawn** (2026-10-11, maintainer decision): the server does not bind UDP 7777 (spec.md Claim 2 amended; joins route through Steam game-server networking), so no raw join client can be built; docs/game-coverage.md keeps nuclear-option at `blocked-doc` per the FR-004/FR-005 fallback (spec.md FR-004 amendment). - [X] T131 Bump the `modules/` submodule pointer to the `gameplane-module` commit containing the Nuclear Option bundle, in the `modules` gitlink of this repo recorded against /home/valgul/project/kubernetes-game-dashboard/.gitmodules — 2026-10-09: already implemented: the modules/ submodule pointer already contains modules/nuclear-option/ Bumped again to GameplanePanel/module b1cbfe4 (module PR #47: Nuclear Option 1.1.0 with `rcon.authentication: false`). - [X] T132 Satisfy every remaining check of the coverage verifier (`test/e2e/joincoverage.sh verify`) for the Nuclear Option row in /home/valgul/project/kubernetes-game-dashboard/docs/game-coverage.md — 2026-10-09: already implemented: docs/game-coverage.md row present; the CI joincoverage gate is the authority. -- [ ] T133 Final module documentation sync — setup, commands and limitations complete and consistent with the verified findings, including the note that display names come from the API server's optional Steam Web API lookup and are absent (raw Steam IDs) when no key is configured — in /home/valgul/project/kubernetes-game-dashboard/modules/nuclear-option/specs.md — 2026-10-11: partly done: modules/nuclear-option/specs.md §8.2 already carries the Steam Web API display-name note and raw-Steam-ID fallback; the final sync waits on T127–T129. +- [X] T133 Final module documentation sync — setup, commands and limitations complete and consistent with the verified findings, including the note that display names come from the API server's optional Steam Web API lookup and are absent (raw Steam IDs) when no key is configured — in /home/valgul/project/kubernetes-game-dashboard/modules/nuclear-option/specs.md — 2026-10-11: partly done: modules/nuclear-option/specs.md §8.2 already carries the Steam Web API display-name note and raw-Steam-ID fallback; the final sync waits on T127–T129. **2026-10-11:** `specs.md` and `README.md` synced to the observed findings (module PR #50). - [X] T134 Add the release entry for both tracks — including the new optional `api.steam.apiKeySecretRef` value and its raw-Steam-ID fallback — in /home/valgul/project/kubernetes-game-dashboard/CHANGELOG.md — 2026-10-09: done: CHANGELOG.md [Unreleased]. - [ ] T135 Exit criterion, not implementation work: confirm on CI that every coverage gate stayed green with no new suppressions — /home/valgul/project/kubernetes-game-dashboard/operator/.testcoverage.yml (72%), /home/valgul/project/kubernetes-game-dashboard/api/.testcoverage.yml (80%), /home/valgul/project/kubernetes-game-dashboard/agent/.testcoverage.yml (90%), /home/valgul/project/kubernetes-game-dashboard/gameaction/.testcoverage.yml (91%), /home/valgul/project/kubernetes-game-dashboard/netguard/.testcoverage.yml (91% — in scope because the Steam resolver is a new consumer of `netguard.IsPublic`) and /home/valgul/project/kubernetes-game-dashboard/web/vitest.config.ts (92/76/82/92) — 2026-10-11: still open: exit criterion checked on CI once the remaining tasks land.