From f80b9f95c88e175c929ad7b02be550c3b8af437a Mon Sep 17 00:00:00 2001 From: wtclaude Date: Wed, 16 Sep 2026 08:52:48 -0500 Subject: [PATCH] docs(modules): the ingest walk, and the two-writer trap it walked into MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The module half exercised against the live rig rather than only its own tests: the working tree installed into a running core, pointed at the workstation sidecar with a real Rust server behind it. The fresh-install path logged `cursor started at the feed tail`, a console banid/unban arrived as two rust_events rows carrying the wipe id, and the wipe row was created by being mentioned rather than by any "a wipe started" call. Then the boundary on real rows: with both ban events in the table, the public events route answered {"events":[]} — and answered the same when asked for player.banned BY NAME. The trap is worth more than the proof. The state row read as online=1 with hostname=NULL, a shape no code path writes. Two cores shared one database: a second instance still held the phase 2 release in memory, spoke protocol 1, was refused 409, and rewrote the row every thirty seconds. Nothing was wrong with either. What made it look like a defect was two SELECTs in two round trips assuming the state did not move between them — the same mistake the Pterodactyl work recorded, and a single atomic read caught the coherent row alternating with the other writer's. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4 --- modules/rust/PLAN.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) 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