From 5d48e7f256f3edfbb9f6ecb897a152b17899b4be Mon Sep 17 00:00:00 2001 From: wtclaude Date: Tue, 8 Sep 2026 18:11:39 -0500 Subject: [PATCH] docs(events): what the integration kit's fifth chapter settled (Phase 15) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §F's Integration Kit paragraph gains what building it produced, and §H loses an envelope member that does not exist. **It is three chapters, not one.** The book taught a read-only data path end to end and never told anyone to build a command path, so a chapter 5 teaching a module to send an idempotency key would have addressed it to a sidecar with nowhere to put it. Chapters 3 and 4 each gain one section, both skippable. **Two defects, both found by running the template through core's real registry and real dispatcher rather than by writing prose:** * **An idempotency key belongs on a command, never on a question.** A read carrying one is answered by an at-most-once store with the FIRST read's reply, forever — the lease applied correctly and the module could no longer see it. * **§H named a `detail` member on an envelope and `classify()` has never read one.** The sentence §H was making is right and its example was wrong: a revert of something gone is `{ ok: true }`. Corrected in place, with the finding recorded in §F. That second one has a consequence outside this PR: **`module-uo` took §H at its word twice.** `uo.item.grant` answers `detail: { granted, missed, why }` and `uo.world.save` answers `detail: { started: true }`, and neither reaches a screen or the ledger. The grant is the one that matters — which recipients did not receive the item is reported nowhere else. Recorded here rather than fixed; the fix is a Module-uo change and is the org lead's call. Pairs with Integration-kit#10, which is red on `checkCoreApi` by design and merges in the P16 cutover with its pin move. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4 --- website/EVENTS.md | 42 +++++++++++++++++++++++++++++++++++++++++- website/EVENTS_PLAN.md | 10 ++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/website/EVENTS.md b/website/EVENTS.md index ae84234..85da4e9 100644 --- a/website/EVENTS.md +++ b/website/EVENTS.md @@ -1110,6 +1110,46 @@ a second module's author will get wrong are the envelope's failure default, the passthrough, recording a resource *before* confirming it, and under-declaring `cost`. All four are one paragraph each and all four are invisible until an outage. +> **Built in Phase 15** (`Integration-kit#10`), held unmerged until the cutover by the same mechanism +> Teams phase 11 was: `checkCoreApi` asserts EQUALITY between `template/module.json`'s `coreApi` and +> the `MODULE_API_VERSION` of the core `ci/core-ref.json` pins, so a template declaring `^1.10.0` +> against a `main` still on 1.9.0 is red on purpose from the day the branch opens. The pin move is +> the kit's leg of P16. +> +> **It is three chapters, not one.** The book as it stood taught a read-only data path end to end — +> a sidecar that listens and stores, a plugin that never blocks the game thread, a module that reads +> its own tables. Nothing in it told anyone to build a **command** path, so a chapter 5 teaching a +> module to send an idempotency key would have been addressing it to a sidecar with nowhere to put +> it. Chapter 3 gains §2a (request/reply correlation, the at-most-once store belonging where the +> state is, and a deadline sent as a duration rather than an absolute time) and chapter 4 gains *"A +> command that changes the world runs at most once"* (the key store persisted in the world save, the +> ownership registry, and the expiry this side arms and re-arms at load). Both open by saying they +> are skippable until you want chapter 5. +> +> **The template ships one of each of the four declarations**, and the phase's value came from +> running them through core's real registry and real `events/dispatch.js` classifier at `edge` rather +> than from the prose. That found two things: +> +> - **An idempotency key belongs on a command and never on a question.** A read that carries one is +> answered by an at-most-once store with the FIRST read's reply, forever — so a lease applied +> correctly, the game changed correctly, and the module could no longer see either: `read()` +> reported the pre-run baseline and `inForce()` said nothing was held. The narrower rule that falls +> out is worth stating with the others: a key is for a write whose repetition would be a second +> EFFECT, and a write that merely SETS a value to X is idempotent by its own nature. +> - **§H names a `detail` member on an envelope and core has never read one** — see the correction +> below. + +> **`detail` is not an envelope member.** The Rust wipes row in [§H](#h--rust--oxide-compatibility) +> says the revert contract must accept `{ ok: true, detail: 'resource no longer exists' }`, and +> `events/dispatch.js`'s `classify()` reads `ok`, `retry`, `error`, `await`, `holdFor`, `resources` +> and `participants` — nothing else. A reason under any other name is dropped in silence and the +> operator sees `" refused"`. The sentence §H was making is right and its example is +> wrong: a revert of something that no longer exists is a success, and it is spelled `{ ok: true }`. +> **`module-uo` took §H at its word twice** — `uo.item.grant` answers +> `detail: { granted, missed, why }` and `uo.world.save` answers `detail: { started: true }`, and +> neither reaches a screen or the ledger. The grant one is the one that matters: which recipients did +> not receive the item is reported nowhere else. + --- ## G — UO implementation plan: the gap list @@ -1183,7 +1223,7 @@ more than one. Nothing here contradicts it. | Participation | The hard part | Substantially easier — hooks carry attacker and victim. | | **Rewards** | An item into a backpack. `reversible: 'none'` — once given it is gone. | A kit, a permission group, currency via an economics plugin, a cosmetic. **Several of those are revocable**, so a Rust reward may be `reversible: 'override'` — a weekend VIP group is a lease with a deadline, not a gift. Core sees the difference as one enum value it never interprets. | | Several servers | One shard | `run.scope` is in the run's unique key, so one definition fans out to six servers without colliding with itself. Caps are per-run, so a fan-out to six servers is six separate budgets rather than one shared pool. | -| Wipes | Never | Monthly, and a wipe invalidates every ledgered resource for that server at once. The revert contract must accept `{ ok: true, detail: 'resource no longer exists' }` — "gone, and that is fine" is a successful revert. A wipe also resets leased values to their defaults, a second reason restore must be idempotent. | +| Wipes | Never | Monthly, and a wipe invalidates every ledgered resource for that server at once. The revert contract must accept "gone, and that is fine" as a successful revert, spelled `{ ok: true }` — **not** `{ ok: true, detail: ... }`, which this row said until Phase 15 and which `classify()` has never read (see [§F](#f--the-module-contract)). A wipe also resets leased values to their defaults, a second reason restore must be idempotent. | | Identity | In-game `[link` code | Steam — still `rust-dryrun` finding 1's open gap. Events neither closes it nor depends on it: `event_run_participants` carries a module-opaque `member_key`. | **The agnosticism is provable, not merely asserted.** Make `event_definitions.owner_module` nullable diff --git a/website/EVENTS_PLAN.md b/website/EVENTS_PLAN.md index 4954d95..a411377 100644 --- a/website/EVENTS_PLAN.md +++ b/website/EVENTS_PLAN.md @@ -1796,6 +1796,16 @@ confirming it, and under-declaring `cost`. **Like Teams Phase 11, this cannot merge until the cutover exists** — the kit is pinned to a `main` sha, and the contract it teaches is not on `main` until then. +> **Built** as `Integration-kit#10` + `docs#226`, red on `checkCoreApi` by design. **It is three +> chapters, not one, and the template gains code.** The book taught a read-only data path end to end +> and never told anyone to build a command path, so chapters 3 and 4 gain one section each (§2a; *"A +> command that changes the world runs at most once"*), both skippable until you want chapter 5. The +> template ships one budget, one option source, one lease and one ledgering action plus +> `server/sidecarClient.js` — named for the filename `noGameConnection.test.js` already anticipated, +> with a real timeout, a real key passthrough and a simulated transport in one replaceable function. +> Running those through core's REAL registry and dispatcher at `edge` is what found the two defects +> §F now records; the prose found neither. + --- ### Phase 16 — Acceptance walk and cutover