feat(protocol2): guild and town-governor world-state streams (Part B ph.1)

Adds the first Part B streams from docs/PROTOCOL_2.md: guild rosters and
town governors ("mayors"), both outbound diff-board sweeps mirroring the
existing champ board.

Overlay:
- BridgeSocial (new): guild sweep+diff over BaseGuild.List -> guild.update /
  guild.remove (full-state upsert; disband detected via Disbanded), plus a
  real-time guild.join from EventSink.JoinGuild. (EventSink.CreateGuild is only
  the load-time factory, so creation is derived sidecar-side from a first-seen
  id, as champs do.)
- BridgeGovernance (new): city sweep over CityLoyaltySystem.Cities -> city.update
  (governor / governor-elect / election phase), gated on CityLoyaltySystem.Enabled.
- BridgeJson.Actor: shared serial/name/acct/webId/player writer used by both.
- BridgeConfig: GuildSweepSeconds (60s), CitySweepSeconds (300s).
- BridgeBoot: both wired into [bridge reload|sweepnow|status.

Sidecar:
- store: guilds + governors board tables with upsert/delete/all.
- main: route guild.update/remove and city.update into the boards.
- web: GET /guilds, GET /governors served from the store (snapshot-companion
  rule, so a fresh page or a restarted sidecar hydrates without the shard).

Docs: INTEGRATION.md event catalog (guild.*, city.update) + board endpoints;
PROTOCOL_2.md Part B phase 1 marked built.

Verified: sidecar cargo check clean; overlay compiles in the full ServUO
Scripts tree (0 errors, 0 warnings). Live end-to-end run still pending.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 07:54:11 -05:00
parent 808f6ab68b
commit 2ed0b0bd00
11 changed files with 657 additions and 1 deletions

View File

@@ -136,6 +136,34 @@ async fn main() -> anyhow::Result<()> {
}
}
}
// Guild board (Protocol 2.0): guild.update folds in the latest roster (one row
// per guild id); guild.remove drops a disbanded guild.
"guild.update" => {
if let Some(id) = ev.value.get("id").and_then(|v| v.as_i64()) {
if let Err(e) = event_store
.upsert_guild(id, ev.value.get("name").and_then(|n| n.as_str()), &text, t)
.await
{
tracing::warn!(error = %e, "failed to upsert guild board");
}
}
}
"guild.remove" => {
if let Some(id) = ev.value.get("id").and_then(|v| v.as_i64()) {
if let Err(e) = event_store.delete_guild(id).await {
tracing::warn!(error = %e, "failed to remove guild board row");
}
}
}
// Governor board (Protocol 2.0): city.update folds in each city's latest
// governance state (one row per city).
"city.update" => {
if let Some(city) = ev.value.get("city").and_then(|c| c.as_str()) {
if let Err(e) = event_store.upsert_governor(city, &text, t).await {
tracing::warn!(error = %e, "failed to upsert governor board");
}
}
}
_ => {}
}
}