From 7bbe9fa5eff95eb7887ef42bc2a665df351ffb1a Mon Sep 17 00:00:00 2001 From: wtclaude Date: Sat, 26 Sep 2026 01:22:53 -0500 Subject: [PATCH] docs(rust-link): a sidecar refuses a plugin that names another server (D155) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY --- rust-link/INSTALL.md | 3 ++- rust-link/INTEGRATION.md | 9 +++++---- rust-link/PROTOCOL.md | 10 +++++++--- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/rust-link/INSTALL.md b/rust-link/INSTALL.md index 2669dcf..dbb6ce5 100644 --- a/rust-link/INSTALL.md +++ b/rust-link/INSTALL.md @@ -113,7 +113,7 @@ When you create a server from it: | Variable | | |---|---| | **Modding Framework** (`FRAMEWORK`) | `oxide` or `carbon`. `vanilla` installs no bridge and the server boots exactly as egg 18's | -| **Runic Gateway: server id** (`RUSTLINK_SERVER_ID`) | The id on the website. Read **once**, when the plugin writes its first config; after that the file holds it and the site locks it, so changing the variable later changes nothing | +| **Runic Gateway: server id** (`RUSTLINK_SERVER_ID`) | The id on the website. Read **once**, when the plugin writes its first config; after that the file holds it and the site locks it, so changing the variable later changes nothing — the launcher hands the sidecar the id from the plugin's config and says so in the console | | **Runic Gateway: sidecar port** (`RUSTLINK_WEB_PORT`) | **One of this server's allocations.** The panel does not tell a server which ports it holds, so a port that is not allocated binds but is never reachable | | **Runic Gateway: sidecar token** (`RUSTLINK_WEB_TOKEN`) | Leave blank: the sidecar generates one. Anyone who can see this server's startup variables can read a token typed here | | **Runic Gateway: bundle** (`RUNICGATEWAY_BUNDLE`) | Blank takes the current bundle. A tag pins one | @@ -230,5 +230,6 @@ With the egg: reinstall to update (above); the console is the diagnosis. | The site says the sidecar is unreachable | The website cannot reach the URL: a loopback bind with the site elsewhere (see above), a firewall, or — on the egg — a sidecar port that is not one of the server's allocations | | The site answers `409` | The site's Rust module speaks another protocol than the bridge. Update whichever is behind (and see the note in *Before you begin*) | | `doctor`: *plugin connected — no, and the server is running* | The plugin did not compile or cannot reach its sidecar. The server console names a compile error; the plugin's `Port` must equal the sidecar's game port | +| The sidecar logs *refusing a plugin that names another server* | Two servers dial one sidecar: a plugin's `Port` is another server's game port. Correct that plugin's `Port` (in its config) and reload it. Nothing it sent was stored | | `doctor`: *not the file that was deployed* | The plugin was edited by hand. `update --game rust` puts the released one back | | The egg's console prints a new token after every boot | Only a sidecar older than the first Rust-Link release does this; reinstall the server | diff --git a/rust-link/INTEGRATION.md b/rust-link/INTEGRATION.md index 82c84fb..9511c6f 100644 --- a/rust-link/INTEGRATION.md +++ b/rust-link/INTEGRATION.md @@ -197,10 +197,11 @@ Each pair is fully independent: its own ports, its own `sidecar.toml`, its own d own token, its own row on the website. Set `[game].server_id` in each `sidecar.toml` to match that server's plugin config. It is a -**cross-check**, not a second source of truth — the plugin's announcement wins — and it exists to -catch exactly one mistake: two game servers pointed at one sidecar by a copied config, which is -silent in every other design and produces one server's history under another's name. When it fires -you get a warning naming both ids. +**cross-check**, not a second source of truth, and it exists to catch exactly one mistake: two game +servers dialling one sidecar, which is silent in every other design and produces one server's +history under another's name. When it fires, the sidecar refuses the plugin that names the other +server, before anything it sent is stored, and logs an `ERROR` naming both ids; that plugin retries +every few seconds until its `Port` is corrected. The installer and the egg set it for you. --- diff --git a/rust-link/PROTOCOL.md b/rust-link/PROTOCOL.md index 0c552c2..0c4a8ff 100644 --- a/rust-link/PROTOCOL.md +++ b/rust-link/PROTOCOL.md @@ -324,9 +324,13 @@ Two things about those are load-bearing: working directory. A service manager's working directory must not decide where the database lands — on Windows that can be `%SystemRoot%\System32`, or a silently redirected VirtualStore copy. - **`[game].server_id` is a cross-check, not a second source of truth.** The plugin announces its own - `serverId` and that is the authority; when both are set and they disagree, the sidecar logs the - disagreement loudly and keeps the plugin's. Two game servers pointed at one sidecar by a copied - config is the mistake this catches, and it is silent in every other design. + `serverId` and that is the authority for what a server is called. When `server_id` is set, the + sidecar **refuses a plugin that names another server**: it closes the connection on the first + frame that disagrees, before that frame is filed, and logs both ids at `ERROR` (D155). Until a + frame has named this server, no command is sent to the peer and `/health` does not report a + plugin connected. Blank, nothing is checked. Two game servers dialling one sidecar is the mistake + this catches. It was a warning that kept the plugin's id until phase 18, when a walk showed the + cost: one server's history filed under another's name, on the website (PLAN.md §34.5). `rust-link-sidecar --print-config` resolves the configuration exactly as a normal start would — writing the file and generating the token if they are missing — and prints it as JSON on stdout,