docs(link): protocol 4 — guild membership on the wire

Adds v4.md as the spec of record for `guild.roster` and `guild.leave`, and
corrects the two older documents that Protocol 4 makes wrong.

PROTOCOL_2.md §10.1 already described this design — hold a member-serial set,
diff it each sweep, emit join/leave — and 2.0 then shipped only the half needing
no new state, folding membership into the board signature as a serial *sum*. The
section has read ever since as though the whole thing were built. It now says
which half shipped, and carries the correction that doing it produced: a sum is
not a safe stand-in for a set, because one member joining and another leaving
between two sweeps offset each other and the guild reads as unchanged.

INTEGRATION.md gains both kinds in the event catalogue, the `roster` key on
GET /guilds, and the three things an integrator gets wrong otherwise — that
`guild.leave`'s `who` is a bare serial rather than an actor object (the mobile has
already left, so there is nothing to attribute), that `acct` is genuinely optional
on a member, and that a guild with no `roster` key is not the same as one with an
empty roster.

v4.md documents what the phase found as well as what it built: the missing store
migration and the user_version decision, why the roster lives in its own column
rather than inside the guild.update snapshot, why a split roster is reassembled in
memory rather than appended to the column, and why guild.leave gets no board
projection at all. §6 records that the reassembly bug was invisible to every unit
test — they all exercised single-frame rosters — and only the live rig caught it.

TEAMS.md is amended where this phase disagreed with it: Phase 1 spans five repos,
not four, because installer/backup.rs justifies skipping the sidecar database on
reasoning the migration falsifies. The user_version decision is recorded there too,
since the design of record did not contemplate a migration mechanism at all.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-17 13:01:05 -05:00
parent 3bfa309a40
commit b369e728c2
4 changed files with 293 additions and 5 deletions

View File

@@ -1946,7 +1946,28 @@ Emit `guild.roster` for a **single** guild against the local ServUO tree
store, survives a sidecar restart, and comes back out of `GET /guilds`. Then build Phase 1. Days, not
weeks, and it retires the only unknown in the plan.
### Phase 1 — the roster on the wire (`servuo-plugins` + `link` + `module-uo` + `docs`)
### Phase 1 — the roster on the wire (`servuo-plugins` + `link` + `module-uo` + `installer` + `docs`)
> **Amended 2026-08-17, after Phase 0 and while building this.** Two corrections to what follows.
>
> **The sidecar had no schema-migration mechanism, and this phase is the first change that needs
> one.** `store.rs`'s `SCHEMA` is `CREATE TABLE IF NOT EXISTS`, which can add a table but cannot add a
> column to one that already exists — and every schema change up to Protocol 3.0 happened to add
> whole tables, so `ALTER TABLE` appears nowhere in `link`'s history and the gap was invisible until
> `guilds.members`. Settled by the org lead: **`PRAGMA user_version` stepped migrations**, each step
> transactional with the bump recording it; a failure aborts startup (already the behaviour, and safe
> because the shard dials *out*), while a database from a *newer* sidecar warns and continues so a
> binary rollback stays a recovery path. Not `sqlx::migrate!`, whose per-file checksums hard-fail
> startup if a released migration is ever edited.
>
> **`installer` joins the phase**, which is why the heading names five repos rather than four.
> `backup.rs` justifies not copying the sidecar database on two claims: that every table is
> `IF NOT EXISTS` (which the migration above falsifies) and that the sweeps repopulate everything
> (already false — `events` is never pruned and the website backfills from `GET /history` on every
> reconnect). The behaviour is unchanged and correct; only its stated reason needed fixing, and a
> wrong reason left in place is what lets someone extend it to a case it never covered.
>
> The spec for all of it is [`../link/v4.md`](../link/v4.md).
The prerequisite for everything. Nothing in Team core can be built against counts.
@@ -1954,7 +1975,8 @@ The prerequisite for everything. Nothing in Team core can be built against count
and `guild.leave`; `overlay.toml` protocol → 4. Sidecar: `members` on the `guilds` board,
`PROTOCOL_VERSION` → 4, `GET /guilds` projection. `module-uo`: ingest both kinds, a
`shard_guild_members` table, the kind→feature map entry and field projection for the new fields.
`docs/link/PROTOCOL_2.md` + `v3.md` + `INTEGRATION.md`.
A new `docs/link/v4.md` as the spec of record, plus `PROTOCOL_2.md` §10.1 (which sketched this design
in 2.0 and had it half-built) and `INTEGRATION.md`.
**Ships:** a richer public Guilds page (real rosters) on its own merit, with no Team code anywhere.
**Verify:** the five-rung shard visibility walk against a live ServUO + sidecar, confirming `acct`/`webId`