diff --git a/modules/rust/PLAN.md b/modules/rust/PLAN.md index 7a534fb..178f473 100644 --- a/modules/rust/PLAN.md +++ b/modules/rust/PLAN.md @@ -2337,6 +2337,9 @@ either a coincidence or a property of login paths.) | `rg.hooks` answers on both frameworks | Oxide and Carbon, identical output shape | ✅ | | 44 sidecar tests, 95 module server tests, 20 client tests, every guard | locally, and now in CI on both repos | ✅ | | The module's route manifest against a **real core** at the pinned ref | 10 routes, all documented, none of core's moved | ✅ | +| **The module ingests a live game** | the working tree installed into a running core, pointed at the workstation sidecar: `cursor started at the feed tail`, then a console `banid`/`unban` arrived as two `rust_events` rows with their wipe id, cursor advanced | ✅ | +| **The allowlist holds against real rows** | with both ban events in the table, `GET /public/rust/servers/:id/events` answered `{"events":[]}` — **and answered the same when asked for `player.banned` by name** | ✅ | +| A wipe row is created by being mentioned | `w-20260915T195817Z` appeared in `rust_wipes` from the first frame carrying it, with no "a wipe started" call anywhere | ✅ | **One finding about the rigs rather than the code:** the panel rigs cannot reach a sidecar running on the workstation, because Windows Firewall holds two program-scoped **Block** rules for @@ -2371,6 +2374,31 @@ boards and the classification are proven on both frameworks; the player half of built, reviewed against the hook documentation, and unmeasured.** That is written here rather than in a commit message because it is the kind of thing a later phase will want to know it inherited. +### 16.7b The ingest walk, and the trap it walked into + +The module half was exercised against the live rig rather than only against its own tests: the +working tree installed into a running core, pointed at the workstation sidecar with a real Rust +server behind it. It logged `cursor started at the feed tail {at: 1}` — the fresh-install path, +starting at the end rather than replaying — and a console `banid`/`unban` pair then arrived as two +`rust_events` rows carrying the wipe id, with the cursor advancing to 3 and `events_seen` at 2. + +Then the boundary, on real rows rather than fixtures: with both ban events sitting in the table, the +public events route answered `{"events":[]}`, **and answered the same when asked for +`player.banned` by name**. That is the difference between a filter and a refusal — the kinds are, as +far as a public caller is concerned, not there. + +**The trap, and it is the same one the Pterodactyl work recorded.** The server-state row read as +`online=1` with `hostname=NULL` and `protocol=NULL`, which is a shape no code path writes. The cause +was two cores sharing one database: a second instance left running from earlier in the day still +holds the **phase 2 release** in memory, speaks protocol 1, is refused `409` by a protocol 2 sidecar, +and writes the row back as unreachable every thirty seconds. Nothing was wrong with either of them. + +What made it *look* like a defect was the measurement: two `SELECT`s in two round trips, assuming +the state did not move between them. A single atomic read caught the coherent row a moment later — +`online: 1, protocol: 2, hostname: Test Server` — alternating with the other writer's. +**A differential diagnosis across two calls is only valid if nothing else holds the controls**, and +on a shared rig something usually does. + ### 16.8 Smaller things worth keeping - **`rg.hooks` collides with `RGProbe`**, the phase-0 rig plugin, which registered the same console