feat(sidecar)!: guild rosters on the guild board, and a way to migrate the store
Protocol 4 gives the guild board a real member list instead of the member *count* that was all Protocol 2 could express. `guild.roster` carries the set; `guild.leave` is forwarded but deliberately not projected. The roster lives in its own `members` column rather than as a field folded into `json`. That column holds the verbatim `guild.update` line, so a roster write into it would clobber the snapshot — name, abbreviation, leader, online count — that `guild.update` owns. Two writers across two columns of one row means both stay plain upserts: neither reads the other's value first, so there is no read-modify-write and no ordering requirement between the two kinds. `GET /guilds` folds the roster back in as `roster` at read time. `guild.leave` gets no board arm on purpose. The shard re-emits `guild.roster` whenever the member set changes, so the board self-corrects within one sweep, and keeping the delta out of the projection is what keeps the sidecar a forwarder rather than a thing that maintains state. This is also the repo's first store migration, and the reason it needed one: `SCHEMA` is `CREATE TABLE IF NOT EXISTS`, which can add a table but cannot add a column to a table that already exists. Every schema change up to and including Protocol 3.0 happened to add whole tables, so `ALTER TABLE` appears nowhere in this repo's history and the gap was invisible. `guilds.members` is the first column added to an existing table, so without a mechanism the column would simply never reach an installed sidecar and every roster write would fail. The counter is SQLite's own `PRAGMA user_version` — an integer in the database header, so it costs no table and cannot drift from the file it describes. Each step runs in a transaction together with the bump recording it, so a step lands completely or not at all. A database written by a *newer* sidecar warns and continues rather than failing: every step is additive, so a newer schema has only columns an older reader ignores, and refusing to start would turn rolling the binary back — a recovery path — into a dead end. A migration failure aborts startup, which was already the behaviour and is the right one: a half-migrated store answers the website with confusing partial data, and the shard dials *out*, so a sidecar that refuses to start never stalls the game. store.rs had no tests before this. The six added here cover the upgrade path that matters (an existing pre-Protocol-4 database gains the column and lands at the current version), that a restart re-running the migration is a no-op, that a roster does not clobber the snapshot, that the two writers work in either order, and that a guild with no roster yet has no `roster` key at all — "not known" and "known to be empty" must not be conflated, or a website renders an empty roster as fact. Also gates PRs into `edge`, not just `main`. This workstream lands ten phases there, and gating only the `main` hop would run these checks for the first time at the cutover. The precedent and the reasoning are already in RunicGateway/installer's copy of this workflow. Refs: docs/website/TEAMS.md Part 12 Phase 1 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -46,7 +46,13 @@ use tracing_subscriber::EnvFilter;
|
||||
/// `vendor.listing.remove`, with the `GET /ruleset`, `/points` and `/market` reads that serve them
|
||||
/// from the store. Same shape as the v2 bump — the kinds are additive, the endpoints are not — and
|
||||
/// there is deliberately no feature-negotiation array: v3 implies all three kinds.
|
||||
pub const PROTOCOL_VERSION: u32 = 3;
|
||||
///
|
||||
/// v4 (Protocol 4): adds `guild.roster` and `guild.leave`, giving the guild board a real member list
|
||||
/// instead of the member *count* that was all v2 could express. Additive in the same way again — the
|
||||
/// kinds are new, `GET /guilds` grows a `roster` key, and nothing existing changed shape. This is the
|
||||
/// first bump that also needed a **store migration** (`guilds.members`), because it is the first to
|
||||
/// add a column to a table that already exists rather than a whole new table; see `store::migrate`.
|
||||
pub const PROTOCOL_VERSION: u32 = 4;
|
||||
|
||||
// Not `#[tokio::main]`: on Windows the SCM dispatcher takes over this thread and starts the runtime
|
||||
// itself, on its own thread, once the service actually begins. The runtime is built by whichever
|
||||
|
||||
Reference in New Issue
Block a user