feat(shard): ingest world.ruleset and publish it at /site/rules
Protocol 3.0 §5 (docs/link/v3.md). The shard publishes its own ruleset —
expansion, which optional systems are on, skill/stat caps, account and house
limits, champion scroll rules, the save/restart schedule — and the site renders
it, so the rules page cannot drift from how the shard actually plays.
Server
- shard_ruleset: a singleton table (id = 1) holding the whole frame in
`payload`, with `rev` and `expansion` hoisted. Nothing is normalized out:
the frame is a flat description of config read as one page, and splitting it
into columns would mean a schema change every time the shard grows a block.
- shardIngest routes world.ruleset to setRuleset and deliberately does NOT
log it — the shard re-emits the whole ruleset on every sidecar connect, so
logging would append a duplicate row per reconnect, and server.hello already
marks each of those.
- uoLinkSocket backfills GET /ruleset explicitly rather than via snapshot(),
which asserts an array; this covers the order where the sidecar was already
up and holding the ruleset when we reconnected.
- GET /public/shard/ruleset behind requireFeature('ruleset') and projected,
per §3.6.1's rule that a shard read which doesn't project is a bug. `null`
means the shard has never published one — a real answer, distinct from a
published ruleset, and the page says so.
Client
- routes/public/Rules.jsx at /site/rules, live via world.ruleset (a frame is a
complete ruleset, not a delta, so the newest one wins outright). Caps are
rendered from tenths — 7000 is 700.0, and showing the raw number would
mislead. A systems key this build doesn't know still renders, humanised, so
a newer plugin can't go invisible against an older client.
- Nav entry gated on the `ruleset` feature, so it hides rather than 403s.
Verified end to end against the local MariaDB and a sidecar fed by a fake shard:
backfill snapshot, live SSE delivery of a changed ruleset, REST reflecting the
overwrite, an empty /feed (not logged), and the gate — 200 by default, 403 at
audience=staff (and dropped from /features so nav hides it), 404 when disabled.
Page rendered clean at all breakpoints checked, no console errors.
497 server tests pass; routes.manifest.json, routes.guards.json and the OpenAPI
spec regenerated.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -508,6 +508,35 @@ async function latestPresence() {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Shard ruleset (Protocol 3.0 world.ruleset) ─────────────────────────────
|
||||
//
|
||||
// The whole frame is stored in `payload` and served back whole. Nothing is
|
||||
// normalized out of it: it is a flat description of config read as one page, and
|
||||
// splitting it into columns would mean a schema change every time the shard grows
|
||||
// a new block. `rev` and `expansion` are hoisted only because they are cheap to
|
||||
// index/display, following shard_champs' payload-plus-hoisted-columns pattern.
|
||||
async function setRuleset(ev) {
|
||||
if (!ev) return
|
||||
await db.setRuleset({
|
||||
rev: ev.rev ?? null,
|
||||
expansion: ev.expansion ?? null,
|
||||
payload: JSON.stringify(ev),
|
||||
t: ev.t,
|
||||
})
|
||||
}
|
||||
|
||||
// The stored ruleset, or null when the shard has never published one (an old
|
||||
// plugin, or Bridge.RulesetEnabled=false). Null is a real answer here — the page
|
||||
// says "not published yet" rather than rendering an empty ruleset as if the shard
|
||||
// had no rules — so it is deliberately not smoothed into {}.
|
||||
async function getRuleset() {
|
||||
const r = await db.getRuleset()
|
||||
if (!r) return null
|
||||
const payload = typeof r.payload === 'string' ? safeJson(r.payload) : r.payload
|
||||
if (!payload) return null
|
||||
return { ...payload, updatedAt: r.updated_at }
|
||||
}
|
||||
|
||||
function safeJson(s) {
|
||||
try {
|
||||
return JSON.parse(s)
|
||||
@@ -557,4 +586,6 @@ module.exports = {
|
||||
replaceGovernors,
|
||||
setPresence,
|
||||
latestPresence,
|
||||
setRuleset,
|
||||
getRuleset,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user