docs(link): protocol 7 part b -- what an event borrows, and the one-shots
`link/v7.md` gains §11-§14: the two targeted lease planes, the two one-shots, the
routes, and what the build found in already-merged code. `EVENTS.md` §G's five
part-b rows are marked built, three of them carrying a correction. `MODULE_API.md`
records the three contract members 12b amends into 1.10.0. `EVENTS_PLAN.md` has
Phase 12b as built.
THE DESIGN POINT, WRITTEN DOWN
A borrowed value whose home is the world save does not come back on a restart the
way a config value does. 11b's fail-safe -- a lease that never reaches disk makes
a restart a free restore -- depends entirely on the leased value being
memory-only too, and for a spawner property or a seasonal status it is not: a
restart preserves the CHANGE and destroys only the timer that would have undone
it. So those two planes' holds are persisted and their deadlines re-armed, and
the config plane's still are not. The same argument, applied where its premise is
false.
FIVE CORRECTIONS TO EVENTS.md
- `Spawner.Amount` does not exist. The property is `MaxCount`, and
`MinDelay`/`MaxDelay` are TimeSpans, so the wire carries seconds.
- The seasonal toggle is not "small and safe". Safe, yes -- ServUO does it to
itself from a staff gump -- but `OnStatusChange()` generates or removes world
content for six of the eight permitted types.
- It is a THREE-value enum over nine named events, not a nine-value enum.
(Caught in 12a's survey; the row is corrected here now it is built.)
- `TreasuresOfTokuno` is excluded, because `IsActive()` reads its own `DropEra`
rather than `Status`. A lease on it applies cleanly, reads back, restores
cleanly and changes nothing -- §N10's "capability that lies", and the one
instance no runtime probe can catch.
- The grant row said failure aborts rather than retries. Protocol 6 changed that:
an idempotency key means a repeat is answered by the original reply, so a
retried grant cannot be one winner receiving two.
And the config-lease row is closed at one key. Counted on ServUO 57.4: 156
non-Bridge `Config.Get` call sites, 82 outside a field declaration, all but four
of those inside a `Configure()` or static constructor and cached at boot anyway.
The self-check ships regardless -- it exists for the operator whose OWN scripts
read config live.
MODULE_API 1.10.0, AMENDED IN PLACE
`target` on a lease declaration, `values` on a string lease, and `searchable` +
`{ q }` on option sources. Amended rather than bumped for the reason every phase
since P10 has: 1.10.0 has never reached `main`, so no deployment can tell the
difference, and the cutover is what publishes the whole of it.
The `target` entry records why this extends `core.lease` rather than giving the
module a lease verb of its own: §F settled that in Phase 8, and half its
objection no longer holds (the two-events-one-target refusal comes from the
ledger's unique index whichever verb reserves the row) while the other half --
`maxDurationMs` re-implemented per module -- still does.
§14 records the protocol-pin defect 11a and 12a both shipped, and why the test
that guards it passed anyway: it asserts the three declarations agree with each
other, which all three being equally stale satisfies.
CHECKS
`docs` has no CI. Every relative link in the new sections was resolved by hand;
the files are CRLF in the working tree and each diff is content-sized
(`--numstat` matches the real change), so nothing carries the `\r\r\n` full-file
rewrite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
This commit is contained in:
@@ -1127,23 +1127,28 @@ a capability exists.
|
||||
| Temporary decoration lockdown | ✅ **built (protocol 7)** | Permanent decoration prohibited in the program and should be prohibited here. Ledgered and reverted like anything else. Phase 12a places it from the shard's OWN `Data/Decoration/**/*.cfg` vocabulary, carrying the item id: `Static` alone accounts for 5031 placements under **1992 different graphics**, so a bare type name places the wrong thing. Containers are refused — teardown would delete what a player left inside. |
|
||||
| Named, hued creatures from an allowlist | ✅ **built (protocol 7)** | PEC's core capability, and its cap is the useful part: common creatures, custom name and hue, **capped at 30**. A bounded one-shot spawn with each serial ledgered — **not a spawner**, which PEC withholds precisely because it is unbounded over time. Phase 12a; the ledger is a PERSISTED shard-side registry, because a spawned creature survives the restart that proves a crier line gone. |
|
||||
| "Simple" boss variants | ✅ **built (protocol 7)** | An enhanced regular mob, capped at 2–4. The defensible form is an **event-owned creature template** — the event declares what it spawns, stats included, and never touches a creature it did not create. Phase 12a: an allowlisted type plus hits/damage/stat multipliers, each bounded. |
|
||||
| **Lease a live config value** — rates, toggles, caps | ✅ built (one key) | Protocol 6 part b: the registry, the deadline timer, compare-and-set restore and `lease.list`, proved end to end against one verified live-read key. **The allowlist is far shorter than this table assumed** — of the 158 non-Bridge `Config.Get` call sites in `Scripts/`, roughly *eight* are read live, so the split below is nearer 95/5 than half and half. Phase 12 adds the rest with the boot-time self-check. |
|
||||
| Lease a property on an existing object | 🔧 📡 | Practical, and an earlier revision was wrong to rule it out. The before-image lives in the website's database and survives a shard restart; a save just persists current state; a deleted target makes restore a no-op. The one real hazard — a GM editing the same property mid-event — is answered by compare-and-set restore and the `drifted` state. |
|
||||
| Grant an event item | 🔧 📡 | An ordinary action, not a special contract member. Admin-gated and capped like any other, and `reversible: 'none'` *for UO specifically* — an object in a backpack cannot be recalled. The constructible allowlist is the plugin's; every label and icon comes from `shard_clilocs` and `item_id`. Failure aborts rather than retries: a retried grant is one winner receiving two. |
|
||||
| Toggle a ServUO seasonal event | 🔧 📡 | Small and safe: `SeasonalEventSystem.GetEntry(type).Status`, already persisted across saves. **Corrected in Phase 12a's survey:** `EventStatus` is a **three**-value enum (`Inactive`, `Active`, `Seasonal`); it is `EventType` that has nine entries. So the verb is a three-state toggle over nine named events — a different form to author and to cap than one nine-way choice. Phase 12b. |
|
||||
| Trigger a world save | 🔧 📡 | Catalogued Tier B in `ADMIN_CONTROLS.md` §3.6, never built. `AutoSave.Save()`; emits the `world.save.*` events already streamed. Useful as a phase boundary. |
|
||||
| **Lease a live config value** — rates, toggles, caps | ✅ built (one key) | Protocol 6 part b: the registry, the deadline timer, compare-and-set restore and `lease.list`, proved end to end against one verified live-read key. **The allowlist is far shorter than this table assumed** — of the 158 non-Bridge `Config.Get` call sites in `Scripts/`, roughly *eight* are read live, so the split below is nearer 95/5 than half and half. **Phase 12b closed it at that one key and built the self-check anyway.** Counted on ServUO 57.4: of 156 non-Bridge `Config.Get` call sites, 82 sit outside a field declaration and all but four of *those* are inside a `Configure()` or static constructor, cached at boot exactly as the field initialisers are. The genuinely live, event-useful reads are this key and vendor bribe decay, which no event would lease. The self-check ships regardless — it exists for the operator whose OWN scripts read config live, and it is what keeps a capability that lies out of the catalog. |
|
||||
| Lease a property on an existing object | ✅ **built (protocol 7)** | Practical, and an earlier revision was wrong to rule it out. Phase 12b: an allowlist of (type, property) pairs — `MaxCount`, `MinDelay`, `MaxDelay`, `Running`, on both `Spawner` and `XmlSpawner`, which share all four names — addressed by a serial or by an `XmlSpawner.UniqueId`. **`Spawner.Amount` does not exist**; the plan named it and the property is `MaxCount`. The hold is PERSISTED, unlike a config lease's: a spawner is in the world save, so a restart preserves the change and destroys only the timer that would undo it. The one real hazard — a GM editing the same property mid-event — is answered by compare-and-set restore and the `drifted` state, and this is the first lease a GM can actually drift. |
|
||||
| Grant an event item | ✅ **built (protocol 7)** | An ordinary action, not a special contract member. Admin-gated, capped by `uo.rewards`, ledgered, and `reversible: 'none'` *for UO specifically* — an object in a backpack cannot be recalled. Phase 12b resolves the RECIPIENTS on the shard, from protocol 6's participation ledger, keyed by the same serials core stores as `member_key`: the website has the list too, but reaching it would have meant a core surface handing a module core's own participants. **This row said failure aborts rather than retries; protocol 6 changed that** — an idempotency key means a retried grant is answered by the original reply, so it is retryable like every other verb. |
|
||||
| Toggle a ServUO seasonal event | ✅ **built (protocol 7)** | **Corrected twice.** `EventStatus` is a **three**-value enum (`Inactive`, `Active`, `Seasonal`); it is `EventType` that has nine entries — so the verb is a three-state toggle over named events. And it is not "small and safe": the setter fires `OnStatusChange()`, which generates or removes world content for six of them. Safe (ServUO does it to itself from a staff gump), but not small. **Eight of the nine**: `TreasuresOfTokuno` is excluded because `IsActive()` reads its own `DropEra` rather than `Status`, so a lease on it would apply cleanly, read back, restore cleanly and do nothing — §N10's "capability that lies", and the one instance no runtime probe can catch. |
|
||||
| Trigger a world save | ✅ **built (protocol 7)** | Catalogued Tier B in `ADMIN_CONTROLS.md` §3.6, never built until Phase 12b. `AutoSave.Save()`; the `world.save.*` events it emits are already streamed, so the reply says only that the save STARTED. A save stops the world, so it is RATE-LIMITED rather than capped — `Bridge.EventsMinSaveIntervalSec`, counting ServUO's own autosave as the last one, refusing rather than queueing, and answering 429 because it is the one refusal on this plane that waiting fixes. |
|
||||
| Idempotent command application | ✅ **built (protocol 6)** | Phase 11a, and deliberately ahead of every verb below it rather than alongside one. A key is executed at most once; a repeat gets the original reply. Its immediate dividend is that `uo.broadcast` stopped being un-retryable — Phase 9 had to answer `retry: false` even to a 503 from a restarting shard, because a lost ack and a command that never applied were the same event. |
|
||||
| Loot-table changes | ⛔ | Per-creature-type and shard-wide, so it cannot be scoped to one run at all — the one thing on this list a lease genuinely cannot express. Attach loot to an event-owned creature template instead. |
|
||||
| Arbitrary `[set` / `[get` / `[add` | ⛔ | `ADMIN_CONTROLS.md` §8's exclusion of these should survive the reopening unchanged. Sharp, privilege-escalating, and expressible only as "trust the caller" — which is precisely a change with no baseline, no cap and no ledger entry. |
|
||||
|
||||
**What the reopening actually costs.** Nine new plugin verbs and **two** protocol bumps: 6
|
||||
for the idempotency key, the lease deadline and the participation ledger, and 7 for the world
|
||||
verbs themselves. (One bump was the estimate; the guarantee had to land before the verbs that
|
||||
depend on it, which is [`../link/v6.md`](../link/v6.md) §1's whole argument.) Bounded work, and every verb is
|
||||
cap-bounded, ledgered and attributable by construction. **None of it is `[add`.** The distinction that
|
||||
makes it defensible: an event may **own** what it creates and **borrow** what it changes — and a
|
||||
borrowed value carries a deadline the game itself enforces. What it may never do is change something
|
||||
with no baseline recorded, which is exactly what `[set` is.
|
||||
**What the reopening actually cost.** Two protocol bumps — 6 for the idempotency key, the lease
|
||||
deadline and the participation ledger, 7 for the world verbs and the two borrowed planes. (One bump
|
||||
was the estimate; the guarantee had to land before the verbs that depend on it, which is
|
||||
[`../link/v6.md`](../link/v6.md) §1's whole argument.) Bounded work, and every verb is cap-bounded,
|
||||
ledgered and attributable by construction. **None of it is `[add`.** The distinction that makes it
|
||||
defensible: an event may **own** what it creates and **borrow** what it changes — and a borrowed
|
||||
value carries a deadline the game itself enforces. What it may never do is change something with no
|
||||
baseline recorded, which is exactly what `[set` is.
|
||||
|
||||
**One thing the survey changed about "borrow".** A borrowed value whose home is the world save — an
|
||||
object property, a seasonal status — does not come back on a restart the way a config value does, so
|
||||
its HOLD is persisted on the shard and its deadline re-armed at load. The fail-safe still holds; it
|
||||
just has to be written down for the planes where a restart is not itself the restore.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1440,6 +1440,8 @@ anywhere in it.
|
||||
|
||||
#### Phase 12a — what an event OWNS
|
||||
|
||||
**Built and merged to `edge`** — `servuo-plugins#23`, `link#38`, `Module-uo#31`, `docs#221`.
|
||||
|
||||
Five verbs that put something in the world, ledger its serial, and delete it at teardown.
|
||||
|
||||
| Verb | Cap dimension | Author's action |
|
||||
@@ -1540,6 +1542,76 @@ caller in the whole tree.
|
||||
three-state toggle over nine named events, which is a different form to author and a different one
|
||||
to cap.
|
||||
|
||||
**Built** — `website#—`, `servuo-plugins#—`, `link#—`, `Module-uo#—`, `docs#—`, all onto `edge`.
|
||||
Spec of record: [`../link/v7.md`](../link/v7.md) §11–§14.
|
||||
|
||||
**It is FIVE repos, and `website` is the one 12a did not need** (org lead, 2026-09-07: *"you can do
|
||||
the 5 repos and no API bump since it is still on edge"*). A targeted lease is a shape `core.lease`
|
||||
did not have. Every lease before it named a single value, so the lease id WAS the target and none of
|
||||
the four callables took one; `Spawner.MaxCount` 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 the declaration gains a `target`, the callables gain it, and the ledger ref becomes
|
||||
`<lease id>#<target>` — which puts the two-events-one-target refusal at the granularity the world
|
||||
actually has, and leaves 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 already decided**
|
||||
(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 of
|
||||
that objection no longer holds — the target check comes free from the index whichever verb reserves
|
||||
the row — and the other half still does. **`MODULE_API_VERSION` stays 1.10.0, amended in place**, the
|
||||
shape every phase since P10 has used while this workstream sits on `edge`.
|
||||
|
||||
Two more contract members came with it, both forced by this phase rather than chosen: **`values` on a
|
||||
`string` lease** (the seasonal status is a three-value enum and nothing bounded `string`, so its only
|
||||
check was the game side's — a refusal arriving unattended, mid-run) and **searchable option sources**
|
||||
(see below).
|
||||
|
||||
**The spawner dropdown was the phase's one genuine blocker, and the answer was a core change** (org
|
||||
lead, 2026-09-07). `resolveOptionSource(id)` took no argument and every source answered a flat list
|
||||
bounded at 2,000. This tree has **6,707 spawn points**, so a flat list would have dropped two thirds
|
||||
of the world and said nothing about which two thirds — the exact 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. The atlas also keeps `<UniqueId>` again (parser
|
||||
version 4), because it is the only name for one particular spawner that exists off the shard — a
|
||||
serial is assigned when the world is built, so a lease addressable only by serial could have had no
|
||||
dropdown at all.
|
||||
|
||||
**Three plan assumptions the tree disproved.**
|
||||
|
||||
- **`Spawner.Amount` does not exist.** The property is `MaxCount`, and `MinDelay`/`MaxDelay` are
|
||||
`TimeSpan` rather than numbers, so the wire carries seconds. `Spawner` and `XmlSpawner` share all
|
||||
four names, which is why one catalog covers both — and why a catalog naming only one of them would
|
||||
have worked until the day it did not.
|
||||
- **A property lease's hold must be PERSISTED**, and the config lease's must not. 11b's fail-safe is
|
||||
*"a lease that never reaches disk means a restart is a free restore"*, which depends on the leased
|
||||
value being memory-only too. A spawner is in the world save and a seasonal status is in
|
||||
`Saves/Misc/SeasonalEvents.bin`, so a restart preserves the CHANGE and destroys only the timer that
|
||||
would undo it. The Bridge gains its **third** save file, written by the same `EventSink.WorldSave`
|
||||
as what it describes; a deadline that passed while the shard was down fires at once.
|
||||
- **The seasonal toggle is not "small and safe", and one of the nine lies.** `OnStatusChange()`
|
||||
generates or removes world content for six of them — safe, since ServUO does it to itself from a
|
||||
staff gump, but not small. And `TreasuresOfTokuno` is excluded: `IsActive()` reads its own
|
||||
`DropEra` rather than `Status`, so a lease on it applies cleanly, reads back, restores cleanly and
|
||||
does nothing at all. That is §N10's "capability that lies" in its purest form and the one instance
|
||||
no runtime probe can catch, so it is excluded by name at both ends.
|
||||
|
||||
**Who receives a grant is answered on the SHARD.** The website has the list in
|
||||
`event_run_participants`, but a module cannot read core's tables, so the alternative was a new core
|
||||
surface handing participants to a module's `perform()`. It is not needed: protocol 6 part b's
|
||||
participation ledger already holds them, keyed by the same character serials core stores as
|
||||
`member_key`. A run with no ledger open is a 404; a run whose ledger is open and empty is a 200 with
|
||||
`granted: 0`, because an event nobody attended still happened. And **the grant is retryable** — §G
|
||||
called it un-retryable before protocol 6 existed, and an idempotency key means a repeat is answered
|
||||
by the original reply.
|
||||
|
||||
**One defect in already-merged code, and it would have broken everything.** The website's protocol
|
||||
pin never left **5**: `uo_link_config.protocol` reaches the sidecar as `X-UOLink-Version` and an exact
|
||||
mismatch is a `409`, so on any real deployment every sidecar call would have been refused from Phase
|
||||
11a onward. It survived two phases because both live walks set the column by hand while standing the
|
||||
rig up. 12b carries all three declaration sites to 7. The test that guards them asserts they agree
|
||||
WITH EACH OTHER, which is a real check they once failed — but all three being equally stale passes
|
||||
it, and nothing in `module-uo` can anchor it to the wire.
|
||||
|
||||
**Ships:** the invasion.
|
||||
**Verify:** the whole rig, running a real multi-phase event with spawns, a lease, and a full teardown
|
||||
back to baseline — plus a deliberate mid-event GM edit of a leased property, confirming `drifted`
|
||||
|
||||
@@ -103,6 +103,12 @@ ctx.events.reconcile()
|
||||
api.registerEventOptionSources([{
|
||||
id: 'uo.options.creatures', label: 'Creatures',
|
||||
async resolve() { return [{ value: 'Orc', label: 'Orc', group: 'Humanoid' }] },
|
||||
}, {
|
||||
// A catalog bigger than a dropdown holds. Core passes `q` to EVERY source and
|
||||
// requires it of none, so a resolver that ignores it is unchanged; `searchable`
|
||||
// is what tells the authoring form to render a typeahead rather than a select.
|
||||
id: 'uo.options.spawners', label: 'Spawners', searchable: true,
|
||||
async resolve({ q } = {}) { return search(q).map((r) => ({ value: r.id, label: r.name })) },
|
||||
}])
|
||||
|
||||
// A value a run may borrow. The module ships the three callables; the VERB an
|
||||
@@ -115,6 +121,28 @@ api.registerEventLeases([{
|
||||
async apply(v, until) { return { ok: true } },
|
||||
async restore(baseline, { expected }) { return { ok: true } },
|
||||
async inForce() { return { ok: true, held: true } }, // optional
|
||||
}, {
|
||||
// A TARGETED lease: one capability over many things. Core adds the target to
|
||||
// the reservation ref (`<lease id>#<target>`) so two runs may hold the same key
|
||||
// on two different objects, and hands it to all four callables.
|
||||
id: 'uo.spawner.maxcount', label: 'Spawner: how many at once',
|
||||
type: 'int', min: 0, max: 100, maxDurationMs: 43200000,
|
||||
target: { label: 'Which spawner', source: 'uo.options.spawners' },
|
||||
async read({ target }) { return { ok: true, value: '3' } },
|
||||
async apply(v, until, { target }) { return { ok: true } },
|
||||
async restore(baseline, { expected, target }) { return { ok: true } },
|
||||
async inForce({ target }) { return { ok: true, held: true } },
|
||||
}, {
|
||||
// A string lease may close its value set. `min`/`max` bound the numeric types
|
||||
// and nothing bounded `string`, so without this the only check on the value is
|
||||
// the game side's -- a refusal arriving unattended, mid-run, rather than on the
|
||||
// authoring form.
|
||||
id: 'uo.seasonal.status', label: 'Seasonal event status',
|
||||
type: 'string', values: ['Inactive', 'Active', 'Seasonal'], maxDurationMs: 43200000,
|
||||
target: { label: 'Which seasonal event', source: 'uo.options.seasonal' },
|
||||
async read({ target }) { return { ok: true, value: 'Inactive' } },
|
||||
async apply(v, until, { target }) { return { ok: true } },
|
||||
async restore(baseline, { expected, target }) { return { ok: true } },
|
||||
}])
|
||||
```
|
||||
|
||||
@@ -133,6 +161,23 @@ field:
|
||||
- **`inForce()` is a fourth question, not a fourth spelling of `read()`.** Optional, and answering
|
||||
`{ ok: true, held: false }` is the only thing that takes a lease's ledger row out — everything
|
||||
else, including a throw and a lease that declares no `inForce()` at all, leaves the row alone.
|
||||
- **A lease that declares a `target` is a family of values, and core changes what it reserves.**
|
||||
Without one, the lease id *is* the target and the ledger reserves it alone — which is right for a
|
||||
config key and wrong for a property, because `Spawner.MaxCount` is one capability over thousands
|
||||
of spawners and reserving the id would let one run turning up one spawner refuse every other run
|
||||
every other spawner. With one, the ref is `<lease id>#<target>`, the two-events-one-target index
|
||||
bites at the granularity the world actually has, and the target reaches all four callables.
|
||||
**Core refuses a targeted lease with no target and an untargeted one with a target**, both
|
||||
`retry: false`: the second attempt has the same params. `target.source` names an option source
|
||||
for the authoring form, and is not resolved by core at registration — a source registered by a
|
||||
module that boots later must not make this one throw.
|
||||
- **`values` closes a `string` lease's set, and belongs to no other type.** `min`/`max` bound the
|
||||
numeric types; a set on an int lease would be a second bound beside them with no rule about
|
||||
which wins, so it is refused.
|
||||
- **A source is passed `{ q }` and may ignore it.** Additive: a resolver written before this
|
||||
existed behaves identically. Declare `searchable: true` when the term actually narrows the
|
||||
answer — the form reads that to decide between a typeahead and a select, and inferring it from a
|
||||
truncated list would read correctly right up until a small deployment's list happened to fit.
|
||||
Core needs it because a reconcile after an outage asks *"does the game side still have any record
|
||||
of this hold?"*, and none of the other three answers that: a value that DIFFERS from what the run
|
||||
applied is drift, which `restore()` reports so the row lands `drifted` with the current value
|
||||
@@ -315,6 +360,38 @@ Two members joined it in Phase 10, both on an envelope:
|
||||
envelope rather than in `events/` because "this particular firing is narrower than the kind
|
||||
usually is" is a fact any emitter can have.
|
||||
|
||||
Three more joined it in **Phase 12b**, all on declarations rather than envelopes, and all amended
|
||||
into 1.10.0 in place for the reason the two above were: 1.10.0 has never reached `main`, so there
|
||||
is no deployment that could tell the difference, and the events cutover is what publishes the
|
||||
whole of it. `module-uo`'s `coreApi` is unaffected; the integration kit is already red on purpose
|
||||
and stays so until the cutover re-pins `ci/core-ref.json`.
|
||||
|
||||
- **`target` on a lease declaration** (`EVENTS.md` §F, `link/v7.md` §11). A lease with one is a
|
||||
FAMILY of values rather than a single value, and core reserves `<lease id>#<target>` rather than
|
||||
the id — so two runs may hold the same key on two different objects while two runs holding one
|
||||
object still collide on the unique index. The target reaches `read`, `apply`, `restore` and
|
||||
`inForce`. Every lease before this named one value, so the id *was* the target and none of the
|
||||
four needed an argument; a property does not have that shape.
|
||||
|
||||
**The verb stays core's**, which is the whole reason this is an extension rather than a lease
|
||||
verb of the module's own. §F settled that in Phase 8: 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 of that objection no longer holds — the
|
||||
two-events-one-target refusal comes from the ledger's unique index whichever verb reserves the
|
||||
row — and the other half still does.
|
||||
- **`values` on a `string` lease.** The closed set an author may choose from, checked by
|
||||
`core.lease` at authoring time. `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.
|
||||
- **`searchable` on an option source, and `{ q }` passed to every `resolve()`.** A source whose
|
||||
catalog is larger than a dropdown can hold narrows its answer by the term; one that ignores the
|
||||
argument answers exactly as it did before this existed, which is what makes it additive. The
|
||||
first source that needed it is `module-uo`'s spawner target — 6,707 spawn points against the
|
||||
2,000-entry bound — and a truncated list is not an answer: it drops most of the world and says
|
||||
nothing about which part. `searchable` is declared rather than inferred, because inferring it
|
||||
from a truncated answer reads correctly right up until a small deployment's list happens to fit.
|
||||
|
||||
**1.7.0 — the engagement contract** (`website/ENGAGEMENT.md` Phase 2). Four additions, no removals
|
||||
and no changed signature, so minor; `module-uo`'s `coreApi: "^1.3.0"` still resolves.
|
||||
`api.registerEventTriggers([...])`, `api.registerAudiences([...])` and
|
||||
@@ -664,8 +741,8 @@ api.registerAudiences([{ id, label, params, ceiling, resolve }]) //
|
||||
api.registerEngagementSeeds({ templates, ruleGroups }) // 1.9.0
|
||||
api.registerEventActions([{ id, label, risk, reversible, cost, params, perform, revert, reconcile }]) // 1.10.0
|
||||
api.registerEventBudgets([{ id, label, unit }]) // 1.10.0
|
||||
api.registerEventLeases([{ id, label, type, min, max, maxDurationMs, read, apply, restore, inForce }]) // 1.10.0
|
||||
api.registerEventOptionSources([{ id, label, resolve }]) // 1.10.0
|
||||
api.registerEventLeases([{ id, label, type, min, max, values, target, maxDurationMs, read, apply, restore, inForce }]) // 1.10.0
|
||||
api.registerEventOptionSources([{ id, label, searchable, resolve }]) // 1.10.0
|
||||
api.onBoot(async (ctx) => {})
|
||||
api.onShutdown(async () => {})
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user