2 Commits

Author SHA1 Message Date
30e72adfcf docs(rust-link): protocol 2 — the read path, and phase 3 as built
The specification the other three repositories are held against, plus the phase
record.

`PROTOCOL.md` §8 is the new contract. Its centre is one field: every frame now
carries `type` — `event`, `snapshot`, `reply`, `control` — and the sidecar files
on that and nothing else. That is the dumb-forwarder property made structural
rather than intended: ten new event kinds are zero change in Rust-Link, and only
a version adding an indexed column touches it at all.

Also in §8: the fifteen-kind catalogue and what each frame carries; `wipeId`
derived by the plugin, which REVERSES §3.2's "deriving one is the website's job"
and says why; boards re-sent on connect and on a cadence; the aggregate rule (a
hook that can fire more than once a second per player is a counter, not an
event); the void rule that stops a read-path hook vetoing a death or a login; and
`GET /feed`, a cursor route separate from `/events` because one route with two
orderings serves the wrong one to every caller that forgets the parameter.

§8.5 is the part to read twice. The classification of a kind as public or staff
is NOT on the wire, deliberately: a boundary declared by the sender is one a
compromised or out-of-date game host can widen, so the module holds a
default-deny allowlist and this table is what its test holds it against.

§8.8 corrects a catalogue rather than a defect: PLAN.md §10 sources
`rust.login.denied` from `CanUserLogin`, and that hook fires on every attempt —
the only way to learn of a denial from it is to be the denier. A denial is the
absence of an approval, and protocol 2 emits both facts so phase 10 can pair them.

`PLAYER_WALK.md` is new, and it exists because half this catalogue cannot fire
without somebody holding a mouse. Ten steps, what each one should produce, and
what counts as a pass — written so the walk can be run without watching the
output live, and so the answer afterwards is readable as a transcript.

PLAN.md §16 is phase 3 as built: the four decisions, the two defects only a
server that BOOTED with the plugin could find (a wipe id that was null for every
real session, and a two-second main-thread stall on unload), what was proven and
how, and — stated plainly rather than implied — the three measurements still
queued on the org lead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-16 08:39:27 -05:00
bd83b34614 docs(modules): module-rust phase 1 as built — the transport, and three org-lead decisions
Adds docs/rust-link/, the canonical spec for the Rust bridge: PROTOCOL.md (the
game link and the website API) and INTEGRATION.md (standing it up by hand, and
which of the three components is wrong when it does not work). A new top-level
directory mirroring link/, which is decision D1 below — it keeps the uo/link
symmetry and keeps module docs separate from bridge docs.

Records phase 1 in PLAN.md as section 13. Both criteria met: a server.hello
produced by the live Rust rig travelled game → sidecar → module → the public
website API, killing the sidecar left the game untouched, and all five guards are
green on the module skeleton.

Three org-lead decisions this phase needed, none settled by section 2:

* D1 — the bridge docs live at docs/rust-link/.
* D2 — loopback is the ONLY trust boundary on the game link, no token, exactly
  as on the ServUO bridge. Argued the other way on the grounds that Rust servers
  are far more often on GSPs; overruled, and the consequence is now written down
  as the mistake rather than defended against.
* D3 — the plugin reads Oxide's own config file, so it lands inside the phase-7b
  config editor for free. The argument against — that editing Host/Port from the
  website could cut the link carrying the edit — becomes that phase's guard
  rather than a reason for a second config mechanism.

Section 11.3 is corrected in place: it read module.json's "extensions" array as
held against reality by the loader in the way "mounts" is. Only half true. The
loader checks that a named slot EXISTS and never that the module filled it —
checkDeclared covers "mounts" alone — and only ONE of R13's two slots can be
declared there at all, because site.footer.status is a CLIENT slot and naming it
fails the load outright.

Section 13.3 records five defects a running server found that no test could,
including a bootId that regenerated on every plugin load rather than every server
start — which would have asked core to reconcile its whole ledger on every
oxide.reload, for a world that never moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-15 19:55:35 -05:00