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:
@@ -17,10 +17,12 @@
|
||||
# so let this workflow run on one PR first. The `PR Checks / *` glob matches
|
||||
# without needing the dropdown.
|
||||
#
|
||||
# Scope note: this gates PRs into `main` only. Feature work that lands on an
|
||||
# integration branch first (e.g. `edge`) is still caught on the branch's PR into
|
||||
# `main`. To gate that earlier hop too, add the branch to the `branches:` list
|
||||
# below — nothing else needs to change.
|
||||
# Scope note: `edge` is gated as well as `main`. Multi-phase work lands there
|
||||
# first, so gating only the `main` hop would run these checks for the first time
|
||||
# at the cutover — the one moment a red build is most expensive to discover. This
|
||||
# is the same call `RunicGateway/installer` made for the same reason, and it was
|
||||
# taken here after a nine-PR Android workstream landed on an ungated `edge` with
|
||||
# no CI at all. Adding a branch to the `branches:` list is the whole change.
|
||||
#
|
||||
# Runner: the same self-hosted `ubuntu-latest` runner release.yml uses. Rust is
|
||||
# not assumed to be preinstalled, so the toolchain step bootstraps it the same
|
||||
@@ -31,7 +33,7 @@ name: PR Checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
branches: [main, edge]
|
||||
|
||||
# A newer push to the same PR cancels the in-flight run.
|
||||
concurrency:
|
||||
|
||||
Reference in New Issue
Block a user