fix(events): give a lease's ledger row a reconcile path (Phase 11b) #193
Reference in New Issue
Block a user
No description provided.
Delete Branch "feature/events-p11b-leases-participation"
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?
EVENTS_PLAN.mdPhase 11b. The plan said Phase 11 was four repos and this is the fifth, in the opposite direction from 11a's correction: 11a droppedinstallerbecause nothing there hardcodes a protocol version, and 11b addswebsitebecause a lease's ledger row turned out to have no reconcile path anywhere.No route, no schema change, no
MODULE_API_VERSIONbump.The hole, and why nothing failed to say so
cleanup.js'sreconcileModule()resolves a resource row to the action of the step that made it, then calls that action'sreconcile(). For a lease that action iscore.lease— a core action, on a path a module cannot register anything on, and one that declared noreconcile().So every
overriderow came backunansweredfor the life of the run. That is the correct posture for "I could not ask", and it was silently wrong here: nobody was being asked. A lease the shard had quietly dropped stayed in the ledger as live until teardown went hunting a baseline nobody was holding.It is not a hypothetical drop. A UO config lease is memory-only on the shard by design — nothing calls
Config.Save(), so a restart is a free restore — which means the resource genuinely vanishes on a restart with no message and no boot-stamp anyone could compare. That was invisible to core until now.inForce(), and why it is notread()with a comparisonTwo pieces, both small:
core.leasegains areconcile().registerEventLeasesgains an optionalinForce()— "does the game side still have any record of this hold?"The obvious implementation is
read()and compare againstpayload.applied, and it is wrong. A value that differs from what the run applied is DRIFT, and drift is teardown's verdict to deliver throughrestore()so the row landsdriftedwith the current value beside it — the one signal that tells an operator somebody moved their value rather than that it disappeared. A reconcile that inferred absence from a changed value would orphan the row first and destroy it. The two questions have different answers on purpose, and §F now says so.Optional, and the fallback is core's posture everywhere else: only an explicit
{ ok: true, held: false }takes a row out. A throw, a timeout, an unrecognised shape, and a lease that declares noinForce()at all leave the ledger exactly as it was. A module that cannot answer keeps core believing its own ledger, which is the pre-Phase-8 behaviour and a capability its deployment does without rather than a boot it fails.allEventLeases()strips the new callable like the other three: these objects leave the process, and the browser's whole relationship with a lease is naming one by id.Tests
npm test— 2003 pass, 0 fail (5 new; 89 skipped are the live-DB ones).core.leasestep with anoverriderow and a lease answeringheld: falsenow reports{ asked: 1, orphaned: 1 }where it previously reportedunansweredconfirmedheld: truewith a changedcurrent) beside the shrugs, because that is the case the wrong implementation would have got wronginForce()is optional, is refused when present but not callable, and is stripped from the catalogOne flake seen once and green on re-run:
eventRecurrence.test.js's bounded-expansion case is timing-sensitive under a loaded parallel run. Not touched by this change.Not in scope
This is only the reconcile path. The lease itself, its deadline, its compare-and-set restore and the participation ledger are the shard's and the module's —
servuo-plugins#22andModule-uo#30.Plugin: RunicGateway/servuo-plugins#22 · Sidecar: RunicGateway/link#37 · Module: RunicGateway/Module-uo#30 · Docs: RunicGateway/docs#220
🤖 Generated with Claude Code
A lease row had no reconcile path at all, and nothing failed to say so. `cleanup.js` resolves a resource to the action of the step that made it, and for a lease that action is `core.lease` -- a CORE action, on a path a module cannot register anything on. So every `override` row came back `unanswered` for the life of the run, and a lease the shard had quietly dropped (a config lease is memory-only there, so a restart reverts it by design) stayed in the ledger as live until teardown went hunting a baseline nobody was holding. `core.lease` gains a `reconcile()`, and `registerEventLeases` gains an optional `inForce()`: "does the game side still have any record of this hold?" Deliberately not `read()` plus a comparison. A value that differs from what the run applied is DRIFT, which teardown must deliver through `restore()` so the row lands `drifted` with the current value beside it; a reconcile that inferred absence from a changed value would orphan the row first and tell the operator the lease vanished rather than that somebody moved it. Only an explicit `{ ok: true, held: false }` takes a row out -- a throw, a timeout, an unrecognised shape and a lease with no `inForce()` all leave the ledger alone. MODULE_API_VERSION stays 1.10.0, amended in place. Co-Authored-By: Claude <noreply@anthropic.com>