feat(events): targeted leases, value sets and searchable sources (Phase 12b) #194

Merged
whitlocktech merged 1 commits from feature/events-p12b-borrowed-and-oneshots into edge 2026-09-07 16:22:46 +00:00
Member

Events Phase 12b, core half — and the half 12a did not need. One of five: website, servuo-plugins (#—), link (#—), Module-uo (#—), docs (docs#222). Spec of record: docs/link/v7.md §11.

Five repos and no MODULE_API_VERSION bump (org lead, 2026-09-07): 1.10.0 is amended in place, the shape every phase since P10 has used while this workstream sits on edge.

A targeted lease is a shape core.lease did not have

Every lease before this named a single value, so the lease id was the target and none of the four callables took one. Spawner.MaxCount is not that shape: it is one capability over thousands of spawners, and a reservation on the id alone would let one run turning up one spawner refuse every other run every other spawner.

So a lease may declare a target, the callables are handed it, and the ledger ref becomes <lease id>#<target> — which puts the two-events-one-target refusal at the granularity the world actually has while leaving it coming from the same unique index it always did.

Extending core rather than giving the module a lease verb of its own is what §F decided in Phase 8 ("the verb is core's"): a lease verb per module would re-implement maxDurationMs and the conflict check once per module, advisory everywhere and wrong in the first one that forgot. Half that objection no longer holds — the target check comes free from the index whichever verb reserves the row — and the other half still does.

Three readers of a lease ref, not one

cleanup.restoreLease and ledger.normalise both looked a lease up by the whole row.ref, and both were correct for exactly as long as a ref was a bare id. Left alone, a targeted row would have missed in both — cleanup reporting "no module registers the lease" and refusing to restore a world that really was changed, which is the worst failure this table has. All three now go through eventLeaseForRef.

values, and searchable sources

values closes a string lease's set. min/max bound the numeric types and nothing bounded string, so the only check on a string lease's value was the game side's — a refusal arriving unattended, mid-run, from a step nobody is watching.

Option sources become searchable, and the first one that needed it forced this phase's shape. resolveOptionSource(id) took no argument and every source answered a flat list bounded at 2,000; module-uo's spawner target is 6,707 spawn points, so a flat list would have dropped two thirds of the world and said nothing about which two thirds — the failure 12a named for decoration, arriving for real. resolve({ q }) is additive: every source is passed a term, none is required to read one, and searchable says which do, because inferring it from a truncated answer reads correctly right up until a small deployment's list happens to fit.

One incidental change in the regenerated spec

The committed swagger-output.json said the session cookie is rg_rig, which is neither the documented default nor what this repo's own server/.env sets — it was generated somewhere with that env var set. The regeneration corrects it to rg_token. Flagging it because it is a fix I did not go looking for.

routes.manifest.json is unchanged: this phase adds no route.

Checks

npm test: 2010 pass, 0 fail (89 DB-skipped), with modules/uo parked as the core suite requires. Six new tests cover the targeted-lease shape, both refusal directions, the value set and the search term.

Exercised end to end against the real local ServUO 57.4 world — see the servuo-plugins PR for the walk. Not driven by that rig: core's ref composition through a live run; it is covered by the unit tests above rather than by the walk.

🤖 Generated with Claude Code

https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4

Events Phase 12b, core half — **and the half 12a did not need**. One of five: `website`, `servuo-plugins` (#—), `link` (#—), `Module-uo` (#—), `docs` (docs#222). Spec of record: [`docs/link/v7.md`](https://gitea.whitlocktech.com/RunicGateway/docs/pulls/222) §11. **Five repos and no `MODULE_API_VERSION` bump** (org lead, 2026-09-07): 1.10.0 is amended **in place**, the shape every phase since P10 has used while this workstream sits on `edge`. ## A targeted lease is a shape `core.lease` did not have Every lease before this named a **single** value, so the lease id *was* the target and none of the four callables took one. `Spawner.MaxCount` is not that shape: it is one capability over thousands of spawners, and a reservation on the id alone would let one run turning up one spawner **refuse every other run every other spawner**. So a lease may declare a `target`, the callables are handed it, and the ledger ref becomes `<lease id>#<target>` — which puts the two-events-one-target refusal at the granularity the world actually has while leaving it coming from the same unique index it always did. **Extending core rather than giving the module a lease verb of its own is what §F decided in Phase 8** (*"the verb is core's"*): a lease verb per module would re-implement `maxDurationMs` and the conflict check once per module, advisory everywhere and wrong in the first one that forgot. Half that objection no longer holds — the target check comes free from the index whichever verb reserves the row — and the other half still does. ## Three readers of a lease ref, not one `cleanup.restoreLease` and `ledger.normalise` both looked a lease up by the whole `row.ref`, and both were correct for exactly as long as a ref was a bare id. Left alone, a targeted row would have missed in **both** — cleanup reporting *"no module registers the lease"* and refusing to restore a world that really was changed, which is the worst failure this table has. All three now go through `eventLeaseForRef`. ## `values`, and searchable sources **`values` closes a `string` lease's set.** `min`/`max` bound the numeric types and nothing bounded `string`, so the only check on a string lease's value was the game side's — a refusal arriving unattended, mid-run, from a step nobody is watching. **Option sources become searchable, and the first one that needed it forced this phase's shape.** `resolveOptionSource(id)` took no argument and every source answered a flat list bounded at 2,000; module-uo's spawner target is **6,707** spawn points, so a flat list would have dropped two thirds of the world and said nothing about which two thirds — the failure 12a named for decoration, arriving for real. `resolve({ q })` is additive: every source is passed a term, none is required to read one, and `searchable` says which do, because inferring it from a truncated answer reads correctly right up until a small deployment's list happens to fit. ## One incidental change in the regenerated spec The committed `swagger-output.json` said the session cookie is `rg_rig`, which is neither the documented default nor what this repo's own `server/.env` sets — it was generated somewhere with that env var set. The regeneration corrects it to `rg_token`. Flagging it because it is a fix I did not go looking for. `routes.manifest.json` is unchanged: this phase adds no route. ## Checks `npm test`: **2010 pass, 0 fail** (89 DB-skipped), with `modules/uo` parked as the core suite requires. Six new tests cover the targeted-lease shape, both refusal directions, the value set and the search term. Exercised end to end against the real local ServUO 57.4 world — see the `servuo-plugins` PR for the walk. **Not driven by that rig:** core's ref composition through a live run; it is covered by the unit tests above rather than by the walk. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
wtclaude added 1 commit 2026-09-07 13:10:11 +00:00
feat(events): targeted leases, value sets and searchable sources (Phase 12b)
Some checks failed
PR Checks / client-build (pull_request) Successful in 40s
PR Checks / server-tests (pull_request) Failing after 5m46s
PR Checks / bot-tests (pull_request) Successful in 8m28s
37f4623068
The core half of Phase 12b, and the half Phase 12a did not need. A targeted
lease is a shape `core.lease` did not have.

Every lease before this named a SINGLE value, so the lease id WAS the target and
none of the four callables took one. `Spawner.MaxCount` is not that shape: it is
one capability over thousands of spawners, and a reservation on the id alone
would let one run turning up one spawner refuse every other run every other
spawner. So a lease may declare a `target`, the callables are handed it, and the
ledger ref becomes `<lease id>#<target>` -- which puts the two-events-one-target
refusal at the granularity the world actually has while leaving it coming from
the same unique index it always did.

Extending core rather than giving the module a lease verb of its own is what §F
decided in Phase 8 ("the verb is core's"): a lease verb per module would
re-implement `maxDurationMs` and the conflict check once per module, advisory
everywhere and wrong in the first one that forgot. Half that objection no longer
holds -- the target check comes free from the index whichever verb reserves the
row -- and the other half still does.

Three readers of a lease ref, not one. `cleanup.restoreLease` and
`ledger.normalise` both looked a lease up by the whole `row.ref`, and both were
correct for exactly as long as a ref was a bare id. Left alone, a targeted row
would have missed in both -- cleanup reporting "no module registers the lease"
and refusing to restore a world that really was changed, which is the worst
failure this table has. All three now go through `eventLeaseForRef`.

`values` closes a `string` lease's set. `min`/`max` bound the numeric types and
nothing bounded `string`, so the only check on a string lease's value was the
game side's -- a refusal arriving unattended, mid-run, from a step nobody is
watching. Refused on any other type: a set beside `min`/`max` would be a second
bound with no rule about which wins.

Option sources become searchable, and the first one that needed it forced this
phase's shape. `resolveOptionSource(id)` took no argument and every source
answered a flat list bounded at 2,000; module-uo's spawner target is 6,707 spawn
points, so a flat list would have dropped two thirds of the world and said
nothing about which two thirds -- the failure 12a named for decoration, arriving
for real. `resolve({ q })` is additive: every source is passed a term, none is
required to read one, and a `searchable` flag says which do, because inferring it
from a truncated answer reads correctly right up until a small deployment's list
happens to fit.

`MODULE_API_VERSION` stays 1.10.0, amended IN PLACE (org lead, 2026-09-07) -- the
shape every phase since P10 has used while this workstream sits on `edge`.

The swagger regeneration carries one incidental change: the committed spec said
the session cookie is `rg_rig`, which is neither the documented default nor what
this repo's own `server/.env` sets. It was generated somewhere with that env var
set. The regeneration corrects it to `rg_token`.

2010 pass, 0 fail (89 DB-skipped), with `modules/uo` parked as the core suite
requires. Six new tests cover the targeted-lease shape, both refusal directions,
the value set, and the search term.

Refs: docs/link/v7.md §11, docs/website/MODULE_API.md, EVENTS_PLAN.md Phase 12b

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
whitlocktech merged commit db8e01e868 into edge 2026-09-07 16:22:46 +00:00
whitlocktech deleted branch feature/events-p12b-borrowed-and-oneshots 2026-09-07 16:22:47 +00:00
Sign in to join this conversation.
No description provided.