docs(rust): the last fixes' docs (N1 N2 W20 W22 W23) and stage 4's core docs
Stage 5 of PLAN_LAST_FIXES (docs#366). - OPERATING.md: a RaidableBases section. Change its config with a restart, never a reload (N2); switching to the free edition rewrites a paid pack's profiles (N1). - PLAN_REDESIGNS 14.3.4: no manual retry for Dangerous Treasures (Q39, W20); 11.4: which hook rbevent setowner raises (W23); 9.1 status. - PLAYER_WALK: rows 2.2, 2.8 and 2.9, Before you start item 8 (BetterChat and the four title rules), Carbon's share trimmed to 2.11 (W22, W23). - PROTOCOL: 20.5 world.error already-out (W3), 20.12 the rollback's log is RaidableBases' own (W9) and setowner's two hooks (W23). - Core, from website#219: MODULE_SYSTEM 2.5 and MODULE_API 4.2 (one module at boot, W19), MODULE_API and EVENTS L (a resource's until read at reconcile, W1), EVENTS D/I/K and BACKEND_DESIGN (a draft's dry run carried by fingerprint, W5), EVENTS observability (W2's words). - PLAN_LAST_FIXES: status, Q37-Q40 answers, 7 updated, 8 what was built. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -420,7 +420,7 @@ trigger in 2026-10, [§E](#starting-a-run-from-a-trigger).) (The count said "nin
|
||||
|
||||
| Table | Holds | Why a table |
|
||||
| --- | --- | --- |
|
||||
| `event_definitions` | id, title, slug, summary, storyline body, image, `owner_module` **nullable**, `state` `ENUM('draft','ready','archived')`, `current_version_id`, `series_id`, `series_order`, `spec` (the working copy — see below), `concurrency_key`, `grace_seconds`, timezone, `listed` (Phase 14a), created/updated by. | The thing that is listed, searched, scheduled and audited. Three states, not five: an admin publishes their own work, so there is nobody to submit it to. **`listed` is announcement, not permission** — see below. |
|
||||
| `event_definitions` | id, title, slug, summary, storyline body, image, `owner_module` **nullable**, `state` `ENUM('draft','ready','archived')`, `current_version_id`, `series_id`, `series_order`, `spec` (the working copy — see below), `concurrency_key`, `grace_seconds`, timezone, `listed` (Phase 14a), `draft_verified_at`/`_by`/`_hash` (a dry run that passed on the draft and its spec's fingerprint, W5, [§K](#k--security-model)), created/updated by. | The thing that is listed, searched, scheduled and audited. Three states, not five: an admin publishes their own work, so there is nobody to submit it to. **`listed` is announcement, not permission** — see below. |
|
||||
| `event_series` | id, name, slug, description, ordering. Definitions optionally belong to one, at their own `series_order` within it. | **The arc.** "Royal Spy Mission → Risky Partner → Message From the Void" is continuity that exists nowhere in the tooling this replaces. One small table buys it. |
|
||||
| `event_versions` | `definition_id`, `version`, `spec` JSON — phases, steps, schedule, conditions, announcements — `published_at`, `published_by`, and (Phase 6) `verified_at` / `verified_by`. Immutable. | A run pins one. This is what makes a run reproducible and an audit answerable after an edit. The two `verified_*` columns are the exception that proves the immutability: they describe something that happened **to** the version afterwards — a review — rather than changing the plan reviewed, which is also why a pass recorded against one stays true for ever ([§K](#k--security-model)). |
|
||||
| `event_runs` | `definition_id`, `version_id`, `scope` (module-opaque), `status`, `health`, `current_phase`, `scheduled_for`, `timezone`, `started_at`, `ended_at`, `cleanup_status`, `claimed_by`, `claim_expires_at`, `started_by`, `origin` JSON (the firing that started it, when a trigger did). **`UNIQUE (definition_id, scope, scheduled_for)`** | The unique index — not the claim — is what makes "one run per occurrence per scope" true under two instances. `scope` is in the key so a worldwide event fans out to many servers without colliding with itself. |
|
||||
@@ -1812,7 +1812,7 @@ controller stamps it from the session.
|
||||
| Enablement defaulting off | Nothing that changes the world is invocable until an admin turned it on deliberately. |
|
||||
| The shard's own switches | `AdminWriteEnabled` is off by default in `Bridge.cfg`, lives on the shard host outside the website's reach, and `AdminAccessFloor` refuses any target at or above CoOwner. |
|
||||
| The ledger | Every world write is recorded with its run, step and actor, and every ledgered resource has a declared revert. Damage is attributable and, by construction, undoable. |
|
||||
| Dry run before anything unattended | **Built and enforced in Phase 6, for SCHEDULED starts only** (org lead, 2026-09-03). A version records who verified it and when; the scheduler refuses to materialise an occurrence of one that never passed, and says so once per definition per tick rather than silently. A human pressing *Start now* is not gated, because that human **is** the review the gate exists to require. A failing dry run records nothing, so it cannot unlock anything. **A module may also advise** (MODULE_API 1.16.0, Rust PLAN_REDESIGNS §11.4, D391): a step that would run, answered `{ ok: true, detail: { warnings } }` under `verify: true`, shows each line as a **warning** finding (code `advice`) that never fails the dry run, because it describes the world as it stands now (a raid base's spot with a player on it, a rock beside it), which may change before the run. |
|
||||
| Dry run before anything unattended | **Built and enforced in Phase 6, for SCHEDULED starts only** (org lead, 2026-09-03). A version records who verified it and when; the scheduler refuses to materialise an occurrence of one that never passed, and says so once per definition per tick rather than silently. A human pressing *Start now* is not gated, because that human **is** the review the gate exists to require. A failing dry run records nothing, so it cannot unlock anything. **A pass on a draft carries over to the version published from it** (2026-10-22, W5 in Rust [`PLAN_LAST_FIXES.md`](../modules/rust/PLAN_LAST_FIXES.md)): it is kept on the definition with the sha256 of the validated spec's canonical JSON, and publish stamps the new version verified, with that pass's reviewer and time, when the spec it freezes has the same fingerprint. An edit to the spec in between means nothing carries; a title edit is not in the spec and keeps it. Before, an author who dry-ran a draft and then published found v1 "not dry run", and its scheduled and trigger starts held. **A module may also advise** (MODULE_API 1.16.0, Rust PLAN_REDESIGNS §11.4, D391): a step that would run, answered `{ ok: true, detail: { warnings } }` under `verify: true`, shows each line as a **warning** finding (code `advice`) that never fails the dry run, because it describes the world as it stands now (a raid base's spot with a player on it, a rock beside it), which may change before the run. |
|
||||
|
||||
> **Stated plainly.** The module boundary is **not** a security boundary — MODULE_SYSTEM §2.2 says so
|
||||
> explicitly, and a module runs in the same Node process with full access. None of the above defends
|
||||
@@ -1829,7 +1829,7 @@ controller stamps it from the session.
|
||||
| **A step retries at all** | The run goes `degraded` on the FIRST retry, not on the eventual failure — an event whose announcements are landing on the second attempt is having trouble now, and now is when an operator wants to know. `health` is not `status`: the run is still genuinely running (§E). |
|
||||
| **Core restarts mid-run** | Nothing is held in memory. The next tick finds steps in `running` with expired leases, reclaims them *without resetting `attempts`*, and continues. A step whose ack was lost is re-dispatched with the *same* idempotency key. |
|
||||
| **Core is down when a run should start** | Within `grace_seconds` it starts late and the log says so. Past it the run is `missed` — a terminal state a human can see. An event that begins three hours after its announcement is worse than one that visibly did not. |
|
||||
| **Game server restarts mid-run** | `server.hello` arrives with a changed `bootId`, which module-uo already uses to tell a shard restart from a sidecar reconnect. The run goes `degraded`, world-write steps park, announce steps continue. On reconnect the runner asks each ledgered resource's module to **reconcile**; a resource the game no longer has becomes `orphaned`, never silently `reverted`. |
|
||||
| **Game server restarts mid-run** | `server.hello` arrives with a changed `bootId`, which module-uo already uses to tell a shard restart from a sidecar reconnect. The run goes `degraded`, world-write steps park, announce steps continue. On reconnect the runner asks each ledgered resource's module to **reconcile**; a resource the game no longer has becomes `orphaned`, never silently `reverted`, unless it is past the `until` it was reported with, when it is `expired` (W1, below). |
|
||||
| **The sidecar is unreachable** | Every client call returns `{ ok: false }` rather than throwing — `uoLinkClient` never throws, by contract. World-write steps retry on a flat backoff; the run degrades; the public page renders last-known state. |
|
||||
| **The module is disabled or fails to boot** | `installed_modules` records it and the dispatch guard 503s its routes. Its actions go dormant: a step naming one fails `terminal` with the module named, and the run degrades rather than claiming success. **Never a silent skip.** |
|
||||
| **An action succeeds but the ack is lost** | The step retries with the same idempotency key; the plugin recognises the repeat and re-answers the original result. *This is the only mechanism that makes it safe*, and as of protocol 6 (Phase 11a) it exists: proved on the rig with three broadcasts under two keys producing **two** rows in the shard's own audit trail. It has one bound worth knowing — the plugin remembers a key for an hour, capped at 4096, and says so loudly if the cap ever evicts a live one. |
|
||||
@@ -1962,6 +1962,15 @@ sweep no longer scans. A run still going when its last resource expires is settl
|
||||
ends: finished, `pending`, and no unresolved row is `complete` — the scan that joins on an unresolved row
|
||||
could not see it, and the step-2 walk's run 46 sat `pending` until it could.
|
||||
|
||||
**A reconcile can reach `expired` too** (2026-10-22, W1 in Rust
|
||||
[`PLAN_LAST_FIXES.md`](../modules/rust/PLAN_LAST_FIXES.md); no MODULE_API change). A resource whose
|
||||
answer carried `until` keeps it on its row. On a reconnect the module's reconcile could run before the
|
||||
game's backlog told it the zone had ended, so the run log read `resource.orphaned` five seconds before
|
||||
`resource.expired`. Now a reconcile that finds a row gone **past** its `until` files it `expired`, with the
|
||||
same `resource.expired` line and the same run settling as `expired()`. Gone before its `until`, or with no
|
||||
`until`, is still `orphaned`. A row past its `until` that the game still holds stays in force, because the
|
||||
game is the one that ends it. Module-Rust's zones send `until` (Module-Rust#56).
|
||||
|
||||
---
|
||||
|
||||
## Versioning, and editing a live event
|
||||
@@ -1996,7 +2005,7 @@ no URL moved.
|
||||
| `POST /admin/events/:id/publish` | admin | snapshot a version and go `ready` |
|
||||
| `DELETE /admin/events/:id` | admin | archive — never a hard delete while runs reference it |
|
||||
| `GET /admin/events/:id/versions` | staff | version history |
|
||||
| `POST /admin/events/:id/verify` | admin, editor | **dry run** (Phase 6) — dispatch every step with `verify: true`, report the findings and the whole-plan cost against the caps. Verifies the PUBLISHED version of a `ready` definition and the working spec of a draft; a pass against a version is recorded and is what unlocks scheduled starts (§K) |
|
||||
| `POST /admin/events/:id/verify` | admin, editor | **dry run** (Phase 6) — dispatch every step with `verify: true`, report the findings and the whole-plan cost against the caps. Verifies the PUBLISHED version of a `ready` definition and the working spec of a draft; a pass against a version is recorded and is what unlocks scheduled starts (§K). A pass on a draft is kept by fingerprint (`draftRecorded`), so publishing that spec unchanged makes a verified version (W5) |
|
||||
| `POST /admin/events/price` | admin, editor | **the live cap meter** (Phase 13) — price the spec IN THE BODY against the per-run caps. Dispatches nothing and records nothing, which is what separates it from the dry run and what makes it safe to call on a debounce while an author types; the spec is in the body because the plan being priced is unsaved between keystrokes. It therefore knows nothing a module knows — whether the landmark exists, whether the shard is up — and the screen says so under the numbers. A step it cannot price is reported in `unpriced` rather than counted as free; an **undeclared dimension is still counted**, because the action really will try to spend it and it is the enforcement rather than the amount that is missing. Over the caps is a `200`, for `verify`'s reason |
|
||||
| `POST /admin/events/:id/runs` | admin | start now; optional `rehearsal: true`, optional `scope` — and optional `params`, which **Phase 13 was the first thing to send**: the concurrency key is a `{placeholder}` template rendered from the run's own params, so a UI posting an empty body rendered the same key for every manual run and had the second refused as an overlap with the first |
|
||||
| `GET /admin/events/runs` | staff | run history across definitions |
|
||||
@@ -2084,7 +2093,10 @@ definition's state**, and the answer says which: a `ready` definition is checked
|
||||
PUBLISHED version, because that is the only thing that ever actually runs and it is that pass §K's
|
||||
gate is about; a draft is checked against the working spec the author is still holding. Only a pass
|
||||
against a version is recorded — a version is immutable, so a pass on one stays true, while a pass on
|
||||
a draft would be a claim about a spec that changes under the author's hands. **Findings come back
|
||||
a draft would be a claim about a spec that changes under the author's hands. **A pass on a draft is
|
||||
kept with the fingerprint of the spec it passed on** (W5, §K), and the answer says so with
|
||||
`draftRecorded`; `publish` answers `verified` when that fingerprint matches the spec it freezes, and
|
||||
writes `version.verified` on the occurrences it re-pins. **Findings come back
|
||||
with a 200**: the request succeeded, the plan has problems, and answering 4xx would make "this event
|
||||
asks for 45 and you allow 30" indistinguishable from "you sent a bad event id".
|
||||
|
||||
@@ -2147,8 +2159,13 @@ and there is a piece of reuse here better than it first looks. The condition gra
|
||||
`present` is *"is present"*. The same labels render the diagnosis. **Built in Phase 5**, on the
|
||||
server, from those labels — the caps line arrives with the budgets in Phase 6.
|
||||
|
||||
*(The console's words since 2026-10-22, W2 in Rust
|
||||
[`PLAN_LAST_FIXES.md`](../modules/rust/PLAN_LAST_FIXES.md): the box is headed **"Why this phase has not
|
||||
advanced"**, and the waiting phase reads **"Phase p3: steps done, waiting to advance"**. It said "has not
|
||||
started", but the phase is running and its steps are done; it is the advance that waits.)*
|
||||
|
||||
```
|
||||
Phase 3 — "The Boss" has not started.
|
||||
Phase p3: steps done, waiting to advance.
|
||||
waiting on: uo.champ.boss_up where region is "Yew"
|
||||
seen so far: 0 of 1
|
||||
since: 20:31:04 (28 min)
|
||||
|
||||
Reference in New Issue
Block a user