feat(events): the UO half of the Event System, and the core pin comes home (Phase 16b cutover, 3 of 6) #34
Reference in New Issue
Block a user
No description provided.
Delete Branch "chore/events-cutover-repin"
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?
The Event System cutover, step 3 of 6 (
EVENTS_PLAN.mdPhase 16b). This isedge's 13 commits plus theci/core-ref.jsonre-pin riding on them, which is why it is a branch rather thanedge→maindirectly: the pin has to name a websitemainsha carryingMODULE_API_VERSION1.10.0, and that sha only came into existence whenwebsite#199merged half an hour ago.What lands
uo.broadcast,uo.towncrier.post,uo.news.post, theuo.broadcastsbudget dimension and three atlas option sources.uo.broadcastretryable at all), the newchamp.boss.killedkind, lease deadlines and the participation ledger.uo.creature.spawn,uo.boss.spawn,uo.npc.place,uo.gate.open,uo.decor.place— over one command family on the wire.valueson a string lease and searchable option sources, which the 6,707-entry spawner dropdown forced.module.jsondeclaresversion0.6.0 andcoreApi^1.10.0. The protocol pin (DEFAULT_PROTOCOL) and both of its declarations indb/schema.sqlare at 7, with the one-shotuo_link_protocol_7_migratedmigration that carries an existing deployment's stored row from 5 to 7 — so an operator upgrading does not come up 409-ing against the protocol-7 sidecar, and one who has deliberately pinned an older sidecar stays pinned.The re-pin, and why nothing else moved with it
ci/core-ref.jsonpointed at a websiteedgesha for the length of this window (org lead, 2026-09-04). That was not laziness:api.registerEventActionsexists only from MODULE_API 1.10.0, so under the oldmainpin the frozen-manifest job'sregister()threw and this module did not load at all — the job would have been red by construction for eight phases while a real regression hid behind it. The cutover put 1.10.0 onmain, so the pin comes home to655fbf3f.routes.manifest.jsonneeded no regeneration. I ran the frozen-manifest job's own steps against that exact ref — core's manifest alone, this module installed intocore/modules/uo, core's manifest again, thenfrozenManifest.js --check:website's
mainandedgeare the identical tree (930422ff), which is why a measurement taken on the branch holds for the merge.This is also the move that turns
Integration-kitgreen again:checkCoreApiasserts equality against whatever core its own pin names, and the kit'smainhas been red since#10merged. Its re-pin is step 5.Verification
622server tests pass. Beyond that, every phase in here was walked on a real rig, and Phase 16a walked the whole of it — ServUO 57.4 (208k items, 42k mobiles) → a protocol-7 sidecar → core with this module installed from a release-shaped bundle. The atlas imports 309 decor types, 6,455 spawn points, 800 creatures, 558 landmarks, 387 regions; a four-phase run's teardown leaves the shard owning 0 of the 21 objects it spawned.Merging this cuts the module release that 16b's re-verify needs. The shard half of that walk is already standing from real released artefacts: the released installer resolved bundle 2026.09.10 (protocol 7), verified both component checksums, did a first install into a stock 57.4 tree (
add=30 change=1), the overlay compiled0 Warning(s) 0 Error(s), and the shard came up[Bridge] enabled=True … events=Trueand dialed the sidecar, which loggedserver.hellofor 208,568 items.🤖 Generated with Claude Code
https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
The whole-rig walk (ServUO + sidecar + website) against a real two-phase event. - **A WS reconnect would have orphaned every live resource.** The backfill replays the last several `server.hello` frames in order — this rig saw three, each with a different `bootId` — so every replayed frame reads as a restart, and the intermediate ones compare a resource stamped with the CURRENT boot against a boot that ended hours ago. The row is then `orphaned`: a live crier line core will never take down again, lost to nothing worse than the website reconnecting. Gated on `!fromBackfill`, the rule the engagement fan-out and the SSE broadcast beside it already state. The website-was-down case is not missed — core asks every module at its own boot. - **The shard explains its refusals and the run log dropped the explanation.** A 403 body reads `{"reason":"admin write plane disabled"}`; `legError` looks for `data.message`, finds nothing, and reports "sidecar responded 403". For a staff member clicking a button that is survivable. For an event that ran at four in the morning the run log is the only place anyone will learn why. - **The "not retried" clause explained the wrong thing on a permanent status.** A 403 will not succeed on any attempt, so telling an operator it was not retried "because a repeat would announce twice" points them at a policy decision instead of at the switch they have to flip. The clause is now added only where a retry was genuinely given up, and 403/404 join the statuses the keyed verbs treat as terminal. Co-Authored-By: Claude <noreply@anthropic.com>The UO half of protocol 6 part b. No route added, no schema change, no MODULE_API bump. `uo.playercaps.skillcap` is the one lease, and the catalog is short because ServUO made it short: of the 158 non-Bridge `Config.Get` call sites in `Scripts/`, roughly eight are read live. This one is read inside `CharacterCreation.cs`'s per-character path, so it is both live and observable -- which is what "proven" has to mean, since the failure an allowlist exists to prevent is a key that applies cleanly and changes nothing. Its `apply()` sends a DURATION rather than the deadline: an absolute time computed here and honoured there is measured against two clocks, and a shard running ten minutes fast would restore a ten-minute lease the instant it took it. Its `restore()` turns `lease.drifted` into `{ drifted: true, current }` rather than an error, because core records drift as a distinct successful outcome and an error would put the row on the retry ladder. Its `inForce()` asks whether the shard still HOLDS the lease, never whether the value still matches -- see the core PR. `uo.participation.open` / `.collect` count who took part and file them on the success envelope. `open` is the one resource in this module that must NOT reconcile by boot stamp: every other resource here lives in shard memory, so a changed bootId IS the proof it is gone, while the participation ledger is written into the world save precisely so it survives that restart. It asks instead. Co-Authored-By: Claude <noreply@anthropic.com>