docs(modules): settle Phase 5 — the Integration Kit, and the sidecar rule
Phase 4 closed; this is the plan for Phase 5, the Integration Kit, settled with the org lead on six decisions. MODULE_SYSTEM.md - §2.11.1 (new): Phase 5's shape — measured starting state, the six decisions, the four slices, the acceptance mechanism. - §2.11: layer 2 rewritten. The sidecar is the default and only path to a game, and it is a non-blocking dumb forwarder that persists before it forwards. A game that already ships a remote-control surface gets a thin sidecar, not none. - §2.10: a MODULE_API_VERSION bump now carries a pass over the kit. - Part 3: decisions 28–33. - The Phase 5 line in Part 2's phase list: STARTED 2026-08-12. MODULE_API.md - §2.7 gains one prohibition: a module does not open a connection to a game server from the website process — a game socket, RCON, a query port, an engine admin API — with the three reasons it exists (the website is the internet-facing process, the sidecar owns the durable copy, neither side can stall the other). Normative prose with no CI behind it, stated so that every second module does not re-decide it. - §1.1: the 1.4.0 entry. No member changed; minor deliberately. modules/rust-dryrun.md - §2 "Talking to the game" rewritten from "No sidecar" to a thin RCON sidecar, with a dated Correction note recording what the exercise originally concluded and why it was overruled. Corrected in view rather than silently rewritten. Pairs with website#… (the 1.4.0 bump, into edge); either order. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -26,13 +26,23 @@ here extends the contract first, in this file, before the module is written agai
|
||||
Core exports a single integer-major semver string from `server/src/modules/version.js`:
|
||||
|
||||
```js
|
||||
const MODULE_API_VERSION = '1.3.0'
|
||||
const MODULE_API_VERSION = '1.4.0'
|
||||
```
|
||||
|
||||
The client half carries the same number (`client/src/modules/version.js`) and a test asserts the two
|
||||
agree. Duplicated rather than fetched because the value has to be on `window.__rg` before the first
|
||||
module chunk evaluates, which is earlier than any network round trip could answer.
|
||||
|
||||
**1.4.0 — Phase 5, the sidecar rule.** §2.7 gained one prohibition: a module does not open a
|
||||
connection to a game server from the website process. It talks to a **sidecar**, which owns the
|
||||
durable copy of the game's state. No member was added, removed or changed — the surface is identical
|
||||
to 1.3.0 — and the bump exists because a module written against 1.3.0 could conform to every member
|
||||
and still be built the wrong way round. It is **minor rather than major** deliberately: nothing that
|
||||
satisfied 1.3.0's *surface* stops working, `module-uo`'s `coreApi: "^1.3.0"` still resolves, and
|
||||
module-uo already complies because the sidecar is where it came from. The reasoning a module author
|
||||
needs is [`../../Integration-kit`](https://gitea.whitlocktech.com/RunicGateway/Integration-kit)
|
||||
chapter 3; the rule itself is below, because the kit teaches and never re-specifies.
|
||||
|
||||
**1.3.0 — Phase 3 slice 3, the client half's move.** Three client additions, each because the
|
||||
extraction needed it: a nav item may carry an **`icon`** component (§3.3), core declares a third slot
|
||||
**`player.invite.accepted`** (§3.7), and `window.__rg.api` gained **`BASE`** — which §3.5 specified
|
||||
@@ -441,6 +451,33 @@ them leaves an operator with orphaned data and no supported way to remove it.
|
||||
- Read `process.env` for core configuration. Its own config is a `settings` key or its own table.
|
||||
- Call `process.exit`, install signal handlers, or start a listener.
|
||||
- Write outside `ctx.paths.moduleRoot` and the upload directory.
|
||||
- **Open a connection to a game server from the website process** — a game socket, an RCON channel,
|
||||
a query port, an engine's admin API. A module talks to a **sidecar**, and the sidecar talks to the
|
||||
game. Added in 1.4.0.
|
||||
|
||||
**Why the sidecar is not optional** (added 2026-08-12; the reasoning, and the worked example, are
|
||||
Integration Kit chapter 3 — [`MODULE_SYSTEM.md`](MODULE_SYSTEM.md) §2.11):
|
||||
|
||||
- **The website is the internet-facing process and the game is not.** A module dialling the game
|
||||
directly makes the public web app the thing the game trusts, and puts the game's address in the
|
||||
same process as every request from the internet. With a sidecar, the game dials **out** and opens
|
||||
no listening port at all, which is the invariant [`../link/PLAN.md`](../link/PLAN.md) exists to
|
||||
hold.
|
||||
- **The sidecar owns the durable copy.** It is a non-blocking dumb forwarder that **persists before
|
||||
it forwards** — `uo-link` writes every event and every board snapshot to SQLite (`store.rs`) and
|
||||
serves its REST reads from there. So a website that is down, restarting or mid-deploy loses
|
||||
nothing, and a page renders the last thing the game said instead of going blank. A module holding
|
||||
the connection itself has nowhere to put what arrives while the website is not running.
|
||||
- **Neither side can stall the other.** The game's plugin enqueues onto a bounded drop-oldest queue
|
||||
and a writer thread drains it; the live feed is best-effort and lossy on purpose (a lagging
|
||||
consumer drops frames) because durability is the store's job, not the socket's. A module that owns
|
||||
the socket inherits both problems inside an Express process, where the failure mode is a wedged
|
||||
request handler.
|
||||
|
||||
This one is **normative prose with no CI behind it** — an outbound socket is not statically
|
||||
detectable the way an internal `require` is (§5.1), so it is enforced in review. Stated as a rule
|
||||
anyway, because the alternative is that every second module re-decides it, and the first one to
|
||||
decide wrong finds out during an outage.
|
||||
|
||||
### 2.8 The OpenAPI fragment
|
||||
|
||||
|
||||
Reference in New Issue
Block a user