|
|
|
|
@@ -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 `"<action id> 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
|
|
|
|
|
|