feat(shard): ingest world.ruleset and publish it at /site/rules #111
Reference in New Issue
Block a user
No description provided.
Delete Branch "feat/shard-ruleset"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Protocol 3.0 order 2 —
docs/link/v3.md§5. Website half of a four-repo change.The shard now 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, CHECK-constrained) holding the whole frame inpayload, withrevandexpansionhoisted for cheap display. Stored whole rather than normalized: it is a flat description of config read as one page, so splitting it into columns would mean a schema change every time the shard grows a block. Same payload-plus-hoisted-columns shapeshard_champsuses.shardIngestroutesworld.rulesettosetRulesetand deliberately does not log it. The shard re-emits the whole ruleset on every sidecar connect, so logging would append a duplicateshard_eventsrow per reconnect — andserver.helloalready marks each of those.uoLinkSocketbackfillsGET /rulesetexplicitly rather than throughsnapshot(), which asserts an array underkey. This covers the ordering where the sidecar was already up and holding the ruleset when we reconnected.GET /public/shard/rulesetbehindrequireFeature('ruleset')and projected — per §3.6.1's standing rule that a read path returning shard data which does not callprojectFeatureis a bug.nullmeans the shard has never published one; a real answer, distinct from a published ruleset.Client
routes/public/Rules.jsxat/site/rules, live viaworld.ruleset. A frame is a complete ruleset, not a delta, so the newest one wins outright rather than merging.7000is700.0total skill. Showing the raw number is actively misleading, not merely unhelpful.systemskey this build doesn't know still renders, humanised, so a newer plugin can't go silently invisible against an older client.rulesetfeature, so it hides rather than 403s.Verification
Full-stack against the local MariaDB, the Rust sidecar, and a fake shard:
snapshotted shard ruleset from /ruleset {"rev":"1a2b3c4d"}GET /public/shard/rulesetworld.ruleset rev=feed0042 skillCap=1200deliveredGET /shard/feed?kind=world.ruleset[]— confirming it is not loggedaudience=staff, anonymous caller403, and dropped from/shard/featuresso nav hides it404(existence not leaked)200/site/rulesrendered clean with no console errors; blocks whose system is off (auto-restart) correctly disappear rather than rendering empty.497 server tests pass. New
test/shardIngest.ruleset.test.js(routing, not-logged, backfill-no-broadcast, overwrite, write-failure resilience) and fourgetRulesetcases added totest/shardControllerPublic.test.js.routes.manifest.json,routes.guards.jsonand the OpenAPI spec regenerated and committed.Sibling PRs
BridgeRuleset.csemitterGET /ruleset+ the store projectionINTEGRATION.mdcatalog,BACKEND_DESIGN.mdtable + route,v3.mdprogress🤖 Generated with Claude Code
https://claude.ai/code/session_01U7CBg11prhLimL9iHSX1bP
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>