docs(link,installer): protocol 4 is live - correct every stale 3
Protocol 4 shipped on 2026-08-19 as sidecar v2.0.0 and overlay v1.0.0, paired in bundle 2026.08.19. Three documents still said 3. INTEGRATION.md contradicted itself: its event catalog fully documents the protocol-4 kinds, including a subsection headed "On Protocol 4", while its normative section 2 still declared X-UOLink-Version: 3, "protocol": 3 in /health and ws.hello, and a worked JS client sending "3". That is the contract a third-party integrator implements against, so it mattered most. Section 2 now states the current version and what shipped it, and gains a v4 paragraph alongside v2 and v3. The "(Protocol 3.0)" markers on individual features are left alone - those record which version introduced a kind and are correct. Two stale examples the audit had not spotted: the --print-config sample reported version 0.1.0, and the minimal browser client told readers to check ev.protocol === 1, a leftover from protocol 1. INSTALL.md is the one that actively misconfigured a real install. Line 413 is the "Protocol version" value an operator pastes into Admin -> Shard, and the document's own troubleshooting table says a stale number comes back as a 409 that "looks exactly like your shard going offline". Corrected along with the stale bundle, overlay and sidecar versions throughout, including Appendix A's copy-pasteable curl commands, which pointed at overlay v0.1.1 and sidecar v1.1.0. v4.md still said the cutover had not happened. It has. Its outstanding- work note said the five-rung shard visibility walk was "outstanding for the cutover", which is now self-contradictory - so it says plainly that the cutover shipped without it and no result is recorded anywhere. guild.roster carries actor objects for every member of every guild, the widest surface any protocol version has added to that check, so it is worth walking against the released pair. No code or contract changes. Every value verified against the Gitea API: link main sidecar/src/main.rs PROTOCOL_VERSION, servuo-plugins overlay.toml, and current.json on the installer bundles branch. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -116,7 +116,7 @@ time rather than hardcoding versions or blindly taking each repo's newest releas
|
||||
Consequences worth knowing:
|
||||
|
||||
- A sidecar patch release does **not** mean re-downloading the installer. The bundle is data.
|
||||
- `--bundle <tag>` (e.g. `--bundle 2026.08.04`) pins an exact past combination, so a reinstall six
|
||||
- `--bundle <tag>` (e.g. `--bundle 2026.08.19`) pins an exact past combination, so a reinstall six
|
||||
months from now reproduces today's install rather than tomorrow's.
|
||||
|
||||
---
|
||||
@@ -156,12 +156,12 @@ one file); `doctor`, `update` and `uninstall` are run from it later. Examples be
|
||||
### An illustrative run
|
||||
|
||||
```
|
||||
Runic Gateway installer — bundle 2026.08.04 (protocol 3)
|
||||
Runic Gateway installer — bundle 2026.08.19 (protocol 4)
|
||||
|
||||
ServUO /opt/ServUO (57.4)
|
||||
Shard process not running
|
||||
Overlay servuo-plugins v0.1.1 protocol 3
|
||||
Sidecar uo-link v1.1.0 protocol 3
|
||||
Overlay servuo-plugins v1.0.0 protocol 4
|
||||
Sidecar uo-link v2.0.0 protocol 4
|
||||
|
||||
✓ overlay tarball verified sha256 75dc6d6c…
|
||||
✓ sidecar binary verified sha256 27d491ef…
|
||||
@@ -410,7 +410,7 @@ One manual step remains — connect the website to this sidecar:
|
||||
|
||||
Base URL http://shard.example.com:8080
|
||||
WebSocket URL ws://shard.example.com:8080/ws
|
||||
Protocol version 3
|
||||
Protocol version 4
|
||||
Auth token 4f9c… (also in /etc/runicgateway/sidecar.toml)
|
||||
|
||||
Paste these into Admin → Shard on your Runic Gateway site:
|
||||
@@ -430,7 +430,7 @@ On your site, sign in as an administrator and open **Admin → Shard (uo-link)**
|
||||
| Base URL (REST) | the **Base URL** line |
|
||||
| WebSocket URL (feed) | the **WebSocket URL** line |
|
||||
| Auth token | the **Auth token** line |
|
||||
| Protocol | the **Protocol version** line (`3`) |
|
||||
| Protocol | the **Protocol version** line (`4`) |
|
||||
|
||||
Saving restarts the site's ingest client, so the change takes effect immediately. The token is
|
||||
AES-GCM encrypted at rest and **never returned to any client** — losing it means reading it back
|
||||
@@ -490,7 +490,7 @@ shedding events rather than stalling — which it is designed to do. `[bridge re
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:8080/health
|
||||
{"status":"ok","protocol":3,"plugin_connected":true,"database":"ok","uptime":"2m","last_event":"2026-08-04T18:22:10.412Z"}
|
||||
{"status":"ok","protocol":4,"plugin_connected":true,"database":"ok","uptime":"2m","last_event":"2026-08-19T18:22:10.412Z"}
|
||||
```
|
||||
|
||||
`plugin_connected: true` is the one that matters — it is the only value in this whole guide that
|
||||
@@ -509,18 +509,18 @@ The command that makes this supportable. Run it before asking anyone for help
|
||||
first thing a maintainer will want.
|
||||
|
||||
```
|
||||
✓ Install record /etc/runicgateway/install.json (bundle 2026.08.04, installer 1.0.0, …)
|
||||
✓ Install record /etc/runicgateway/install.json (bundle 2026.08.19, installer 0.1.0, …)
|
||||
✓ ServUO found /opt/ServUO (57.4)
|
||||
✓ Overlay in sync 24 files, all hashes match install.json
|
||||
⚠ Patch tier 1 applied — moderation-audit (region-match)
|
||||
✓ uo-link installed uo-link-sidecar 1.1.0 (protocol 3)
|
||||
✓ uo-link installed uo-link-sidecar 2.0.0 (protocol 4)
|
||||
config /etc/runicgateway/sidecar.toml database /var/lib/runicgateway/uo-link.db
|
||||
✓ Service runicgateway-link.service active, enabled as runicgateway
|
||||
✓ Sidecar reachable 127.0.0.1:8080 /health ok, up 6h, database ok
|
||||
✓ Protocol sidecar 3 = overlay manifest 3
|
||||
✓ Protocol sidecar 4 = overlay manifest 4
|
||||
✗ Shard connected no — the shard is running (pid 8123) but has not dialed in
|
||||
✓ Bundle 2026.08.04 — up to date
|
||||
✓ Backups 2026-08-04T09:12:44Z — 3 file(s) replaced by update to bundle 2026.08.04
|
||||
✓ Bundle 2026.08.19 — up to date
|
||||
✓ Backups 2026-08-19T09:12:44Z — 3 file(s) replaced by update to bundle 2026.08.19
|
||||
3 kept in /etc/runicgateway/backups
|
||||
```
|
||||
|
||||
@@ -654,11 +654,11 @@ Use those versions together; that pairing is the only thing CI has verified.
|
||||
### A2. Deploy the plugin overlay
|
||||
|
||||
```bash
|
||||
curl -LO https://gitea.whitlocktech.com/RunicGateway/servuo-plugins/releases/download/v0.1.1/runicgateway-overlay-0.1.1.tar.gz
|
||||
curl -LO https://gitea.whitlocktech.com/RunicGateway/servuo-plugins/releases/download/v0.1.1/SHA256SUMS
|
||||
curl -LO https://gitea.whitlocktech.com/RunicGateway/servuo-plugins/releases/download/v1.0.0/runicgateway-overlay-1.0.0.tar.gz
|
||||
curl -LO https://gitea.whitlocktech.com/RunicGateway/servuo-plugins/releases/download/v1.0.0/SHA256SUMS
|
||||
sha256sum -c SHA256SUMS --ignore-missing # must say: OK
|
||||
|
||||
tar xzf runicgateway-overlay-0.1.1.tar.gz # → runicgateway-overlay/
|
||||
tar xzf runicgateway-overlay-1.0.0.tar.gz # → runicgateway-overlay/
|
||||
cd runicgateway-overlay
|
||||
cat manifest.json # version, commit, protocol, per-file hashes
|
||||
|
||||
@@ -702,8 +702,8 @@ says `aarch64`); releases from v1.2.0 carry both. Take the version from the bund
|
||||
A1 rather than the one written here.
|
||||
|
||||
```bash
|
||||
curl -LO https://gitea.whitlocktech.com/RunicGateway/link/releases/download/v1.1.0/uo-link-sidecar-linux-x86_64
|
||||
curl -LO https://gitea.whitlocktech.com/RunicGateway/link/releases/download/v1.1.0/SHA256SUMS
|
||||
curl -LO https://gitea.whitlocktech.com/RunicGateway/link/releases/download/v2.0.0/uo-link-sidecar-linux-x86_64
|
||||
curl -LO https://gitea.whitlocktech.com/RunicGateway/link/releases/download/v2.0.0/SHA256SUMS
|
||||
sha256sum -c SHA256SUMS --ignore-missing
|
||||
|
||||
sudo install -m 0755 uo-link-sidecar-linux-x86_64 /usr/bin/runicgateway-link
|
||||
@@ -722,8 +722,8 @@ sudo UOLINK_DB_PATH=/var/lib/runicgateway/uo-link.db \
|
||||
```json
|
||||
{
|
||||
"component": "uo-link-sidecar",
|
||||
"version": "1.1.0",
|
||||
"protocol": 3,
|
||||
"version": "2.0.0",
|
||||
"protocol": 4,
|
||||
"config_path": "/etc/runicgateway/sidecar.toml",
|
||||
"config_created": true,
|
||||
"token_generated": true,
|
||||
@@ -859,7 +859,7 @@ path = "uo-link.db" # relative paths resolve against this file's directo
|
||||
|
||||
| Sidecar command | Output |
|
||||
|---|---|
|
||||
| `uo-link-sidecar --version` | `uo-link-sidecar 1.1.0 (protocol 3)` |
|
||||
| `uo-link-sidecar --version` | `uo-link-sidecar 2.0.0 (protocol 4)` |
|
||||
| `uo-link-sidecar --print-config [--config PATH]` | The JSON in [A3](#a3-install-the-sidecar). Provisions on first run. **Contains the token.** |
|
||||
| `uo-link-sidecar --help` | Usage. An unrecognized argument exits `2` rather than starting a sidecar you did not ask for. |
|
||||
|
||||
|
||||
@@ -33,11 +33,11 @@ $ uo-link-sidecar --print-config --config /etc/runicgateway/sidecar.toml
|
||||
"component": "uo-link-sidecar",
|
||||
"config_created": false,
|
||||
"config_path": "/etc/runicgateway/sidecar.toml",
|
||||
"protocol": 3,
|
||||
"protocol": 4,
|
||||
"shard": { "bind": "127.0.0.1:7788" },
|
||||
"store": { "path": "/var/lib/runicgateway/uo-link.db" },
|
||||
"token_generated": false,
|
||||
"version": "0.1.0",
|
||||
"version": "2.0.0",
|
||||
"web": {
|
||||
"auth_required": true,
|
||||
"auth_token": "c0f04ace…",
|
||||
@@ -55,12 +55,14 @@ That is the same set of values Admin → Shard asks for — base URL and WS URL
|
||||
|
||||
The wire protocol is versioned so a mismatch is caught immediately instead of failing weirdly.
|
||||
|
||||
- Every response carries an **`X-UOLink-Version: 3`** header.
|
||||
- `GET /health` and the WebSocket `ws.hello` frame include `"protocol": 3`.
|
||||
- **Optionally**, send `X-UOLink-Version: 3` on your requests. If it disagrees with the sidecar, the request is rejected **409 Conflict**:
|
||||
The current version is **4**, shipped in sidecar **v2.0.0** and overlay **v1.0.0**.
|
||||
|
||||
- Every response carries an **`X-UOLink-Version: 4`** header.
|
||||
- `GET /health` and the WebSocket `ws.hello` frame include `"protocol": 4`.
|
||||
- **Optionally**, send `X-UOLink-Version: 4` on your requests. If it disagrees with the sidecar, the request is rejected **409 Conflict**:
|
||||
|
||||
```json
|
||||
{ "error": "protocol version mismatch", "sidecar_protocol": 3, "client_protocol": "2" }
|
||||
{ "error": "protocol version mismatch", "sidecar_protocol": 4, "client_protocol": "3" }
|
||||
```
|
||||
|
||||
Pin the version you built against and compare it to the header (or `/health.protocol`) at startup.
|
||||
@@ -74,12 +76,18 @@ ignores unknown kinds keeps working against the live feed, but the three new end
|
||||
sidecar. There is deliberately **no feature-negotiation array** — v3 implies all three kinds, so the
|
||||
version number alone tells you what is available.
|
||||
|
||||
**Upgrading a v2 integration.** The bump is an operator-visible hard break in one direction only: a
|
||||
client still declaring `2` gets a 409 on every protected route and, on the WebSocket, a closed
|
||||
connection on the `ws.hello` mismatch. So update the pinned version at the same time you deploy the
|
||||
v3 sidecar. Nothing that existed in v2 changed shape, so that is the whole migration — the website
|
||||
does it with a one-shot boot migration of its `uo_link_config.protocol` row ([`v3.md`](v3.md) §4.1);
|
||||
a third-party client changes the constant it sends.
|
||||
**v4 (Protocol 4.0)** adds `guild.roster` and `guild.leave`, and grows `GET /guilds` a `roster` key
|
||||
([`v4.md`](v4.md)). Before it, a guild's membership was a *count*; now the members themselves are on
|
||||
the wire. Additive in the same shape as the previous two bumps — nothing that existed in v3 changed,
|
||||
so a v3 consumer that ignores the new kinds and the new key keeps working against a v4 sidecar, once
|
||||
it declares `4`.
|
||||
|
||||
**Upgrading a pinned client.** Every bump is an operator-visible hard break in one direction only: a
|
||||
client still declaring the old number gets a 409 on every protected route and, on the WebSocket, a
|
||||
closed connection on the `ws.hello` mismatch. So update the pinned version at the same time you
|
||||
deploy the new sidecar. Nothing existing has ever changed shape across a bump, so that is the whole
|
||||
migration — the website does it with a one-shot boot migration of its `uo_link_config.protocol` row
|
||||
([`v3.md`](v3.md) §4.1); a third-party client changes the constant it sends.
|
||||
|
||||
---
|
||||
|
||||
@@ -92,7 +100,7 @@ GET /health (no auth)
|
||||
```json
|
||||
{
|
||||
"status": "ok", // "ok" when plugin connected AND db reachable, else "degraded"
|
||||
"protocol": 3,
|
||||
"protocol": 4,
|
||||
"plugin_connected": true, // is the shard link up right now?
|
||||
"database": "ok", // "ok" | "error"
|
||||
"uptime": "3d 12h",
|
||||
@@ -115,7 +123,7 @@ A push-only stream of game events as they happen. You do **not** send commands o
|
||||
**On connect**, the first frame is:
|
||||
|
||||
```json
|
||||
{ "kind": "ws.hello", "protocol": 3 }
|
||||
{ "kind": "ws.hello", "protocol": 4 }
|
||||
```
|
||||
|
||||
**Then** a continuous stream of event frames, each with at least `t` (epoch ms) and `kind`. Route on `kind`.
|
||||
@@ -133,7 +141,7 @@ const ws = new WebSocket(`ws://127.0.0.1:8080/ws?token=${TOKEN}`);
|
||||
ws.onmessage = (m) => {
|
||||
const ev = JSON.parse(m.data);
|
||||
switch (ev.kind) {
|
||||
case "ws.hello": /* check ev.protocol === 1 */ break;
|
||||
case "ws.hello": /* check ev.protocol === 4 */ break;
|
||||
case "mob.login": onLogin(ev); break;
|
||||
case "vendor.sale": onSale(ev); break;
|
||||
case "house.decay": onIdoc(ev); break;
|
||||
@@ -1012,7 +1020,7 @@ sidecar defines no audiences. Deciding who may see what is the consuming site's
|
||||
A typical character page:
|
||||
|
||||
```js
|
||||
const H = { "Authorization": `Bearer ${TOKEN}`, "X-UOLink-Version": "3" };
|
||||
const H = { "Authorization": `Bearer ${TOKEN}`, "X-UOLink-Version": "4" };
|
||||
|
||||
// 1. render the roster
|
||||
const roster = await fetch(`${BASE}/roster/${account}`, { headers: H }).then(r => r.json());
|
||||
|
||||
11
link/v4.md
11
link/v4.md
@@ -1,7 +1,7 @@
|
||||
# Protocol 4 — Guild membership on the wire
|
||||
|
||||
**Status:** Feature-complete on `edge`. All work lands on an `edge` branch in each repo; `edge` → `main` is the v4 cutover.
|
||||
**Date:** 2026-08-17
|
||||
**Status:** **Shipped.** The `edge` → `main` cutover is done and protocol 4 is live — released as uo-link sidecar **v2.0.0** and plugin overlay **v1.0.0**, paired in bundle **2026.08.19**.
|
||||
**Date:** 2026-08-17; cutover 2026-08-19
|
||||
**Codebase:** ServUO 57.4, `<servuo>`, net48 / x64, Expansion **EJ**.
|
||||
**Companion to** [`PLAN.md`](PLAN.md) (1.0 read/event plane), [`PROTOCOL_2.md`](PROTOCOL_2.md) (2.0 provisioning + world-state streams), [`v3.md`](v3.md) (3.0 shard content + the visibility framework), [`INTEGRATION.md`](INTEGRATION.md) (website API).
|
||||
|
||||
@@ -265,5 +265,8 @@ evidence and neither is a clean boot. What was actually run:
|
||||
Step 2 is what caught the reassembly bug in §3.1: every unit test passed through it, because they all
|
||||
exercised a single-frame roster. The case does not arise until a guild exceeds the cap.
|
||||
|
||||
Still outstanding for the cutover: the five-rung shard visibility walk against a live shard, confirming
|
||||
`acct`/`webId` never reach a caller below their rung.
|
||||
**Still outstanding, and the cutover shipped without it:** the five-rung shard visibility walk against
|
||||
a live shard, confirming `acct`/`webId` never reach a caller below their rung. `guild.roster` carries
|
||||
actor objects for every member of every guild, so it is the widest surface any protocol version has
|
||||
added to that check — the walk is worth doing against the released pair even though v4 is now live.
|
||||
No result is recorded here or in [`../website/SHARD_VISIBILITY.md`](../website/SHARD_VISIBILITY.md).
|
||||
|
||||
Reference in New Issue
Block a user