feat(store): persist world.ruleset and serve it from GET /ruleset
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>
This commit is contained in:
@@ -194,6 +194,21 @@ async fn main() -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Shard ruleset (Protocol 3.0): a singleton projection. The shard re-emits
|
||||
// world.ruleset on every connect, so this row is simply overwritten; `rev`
|
||||
// lets a reader tell a re-send from an actual config change.
|
||||
"world.ruleset" => {
|
||||
if let Err(e) = event_store
|
||||
.upsert_ruleset(
|
||||
ev.value.get("rev").and_then(|r| r.as_str()),
|
||||
&text,
|
||||
t,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::warn!(error = %e, "failed to upsert ruleset");
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user