feat(sidecar)!: Protocol 3.0 cutover — X-UOLink-Version 2 → 3 #21
Reference in New Issue
Block a user
No description provided.
Delete Branch "edge"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What & why
Order 6 of the Protocol 3.0 plan (
v3.md§4) — theedge→maincutover, sidecar side. One of four PRs that merge together, and the one that makes the break visible: after this, every response carriesX-UOLink-Version: 3and any client still declaring2is refused 409.What
edgecarries overmain:world.ruleset(#17) — persisted as a singleton and served fromGET /ruleset.points.board(#18) — one row per point system, served fromGET /points.vendor.listing/vendor.listing.remove(#19) — the player-vendor market index, served fromGET /market./market, not/vendors, because axum would otherwise route it into the account-provisioning tree.PROTOCOL_VERSION2 → 3 (#20 — must merge intoedgebefore this PR).The store-backed shape is deliberate and unchanged from 2.0's world-state boards (
PROTOCOL_2.md§12.2): the shard publishes, the sidecar persists, and the reads answer from the last-known snapshot without the shard — so a shard outage degrades to stale data rather than to nothing. The sidecar stays a dumb forwarder; access control and the admin-toggleable visibility scope live entirely on the website.Breaking change
X-UOLink-Versiongoes2→3. Any integration pinning2gets a 409 on every protected route and a closed WebSocket on thews.hellomismatch. Nothing that existed in v2 changed shape, so upgrading is changing the constant you send — seeINTEGRATION.md§2. The website migrates its own pinned version on boot, so operators need no manual edit.How it was tested
cargo build --releaseandcargo clippy --all-targetsclean. Against the release binary:/healthreports"protocol": 3, responses carryx-uolink-version: 3, an authenticatedGET /rulesetdeclaring3returns 200 and the same call declaring2returns the 409 with{"sidecar_protocol":3,"client_protocol":"2"}. Each stream was verified end-to-end against the real shard when it landed onedge— most recently 27 vendors / 1,040 listings from ServUO through this sidecar into the site.Note: no v3 PR ever ran CI, since
edgePRs don't trigger it. This PR is the first CI run over the whole stack.Merge order
edge(the bump), website #117 →edge, docs #72 →edge.This PR's diff picks up #20 automatically once that merges.
Checklist
AI-assisted contributions (required)
Claude Code. I have reviewed and understandevery change, and take responsibility for it. AI-authored commits are
marked with a
Co-Authored-By/Assisted-Bytrailer.License
(GNU GPL v3.0 or later), and I have the right to contribute it.
Protocol 3.0 §5 (docs/link/v3.md). The shard publishes one world.ruleset frame per connect describing how it is configured; the sidecar folds it into a singleton row and serves it back. Store-backed rather than an RPC, for the same reason /guilds and /houses are (PROTOCOL_2.md §12.2): a rules page that goes blank while the shard restarts is worse than one that is briefly stale. `{"ruleset": null}` distinguishes "the shard has never published one" — an old plugin, or Bridge.RulesetEnabled=false — from a published ruleset, which the website renders differently. `rev` (the shard's FNV-1a of the body) is kept alongside the JSON so a reader can tell "same ruleset, re-sent on reconnect" from "the operator changed something" without diffing. PROTOCOL_VERSION stays 2. The 2→3 bump is a hard operator-visible cutover and happens exactly once, at the end of v3 (§4), not per phase. Smoke-tested against a fake shard on loopback: frame ingested, GET /ruleset returns it with plugin_connected=false (outage path), and the route sits behind the gate (409 on a version mismatch, 401 unauthenticated). cargo build + clippy clean. Co-Authored-By: Claude <noreply@anthropic.com>Protocol 3.0 §7 (docs/link/v3.md). The shard publishes ~25 points/loyalty leaderboards as one points.board frame per system; the sidecar folds each into a projection table and serves them back, so the site's leaderboards page renders during a shard outage. - points_boards(system PK, name, json, updated_t), keyed by the shard's own PointsType name. `name` is hoisted only for the ORDER BY. - main.rs gains a points.board arm keyed on `system`, alongside the existing champ/guild/governor/house/ruleset projections. There is deliberately no delete counterpart: the shard's set of point systems is fixed at startup, so it emits no points.remove — the same shape the governor board already has. - GET /points returns every board ordered by display name; GET /points/:system returns one, or 404 when the shard has never published that system. 404 and "a published board nobody has scored in yet" (200, empty top) are different answers, and the website renders them differently. Store-backed rather than an RPC for the same reason as the other boards, and it matters more here: these are standings accumulated over months, so blanking them during a shard restart reads as data loss rather than as staleness. PROTOCOL_VERSION stays at 2 — the bump to 3 is the one-time edge → main cutover in v3.md §4, not a per-phase change. cargo build and cargo clippy --all-targets are clean. Smoke-tested against a driver on the loopback link: two boards stored and served, a re-emitted system overwriting rather than accumulating, 404 for an unknown system, 401 unauthenticated. Also verified against the real ServUO shard, which fed five live boards through this path. Co-Authored-By: Claude <noreply@anthropic.com>Protocol 3.0 is feature-complete on `edge` -- world.ruleset, points.board and vendor.listing / vendor.listing.remove all landed there while the sidecar kept declaring 2, because a bump is an operator-visible hard break (409 on every protected route via web.rs::gate, and the website closes the WS on the ws.hello mismatch). Doing it per phase would have broken the site four times; this is the one time it happens. Nothing that existed in v2 changed shape, so the version constant and its doc comment are the whole change here. The README's worked example moves with it -- it still claimed "currently 1", two bumps stale. Verified against the release binary: /health reports "protocol": 3, every response carries `X-UOLink-Version: 3`, an authenticated request declaring 2 is refused 409 {"sidecar_protocol":3,"client_protocol":"2"}, and one declaring 3 gets 200 off /ruleset. cargo build --release + cargo clippy --all-targets clean. Co-Authored-By: Claude <noreply@anthropic.com>