docs(engagement): MODULE_API 1.8.0, the UO trigger catalogue, the news path

The documentation half of Phase 11a (website#177, Module-uo#22). Three files,
each carrying one obligation from 6.0b's Phase 11a row.

**MODULE_API.md -> 1.8.0.** A seventh audience ceiling, `admin`, as a child of
`staff`. One addition, no removal, so minor -- every declaration valid under
1.7.0 is valid now and no stored value changes. 2.4's ceiling bullet now names
seven values rather than six, and says the thing a module author actually needs:
`admin` is the ONLY pair in the whole lattice with real containment, so
`permits('staff','admin')` holds and it is the only place `permits` is true
between two values below `authenticated`.

Also corrects two 2.3 rows that were stale rather than wrong: `ctx.inbox.push`
has not thrown since Phase 7, and both members now name the consumer that
actually calls them.

**modules/uo/API.md gains 5**, the full trigger catalogue -- which 6.0b asked for
in as many words ("the full trigger catalogue, its audiences and its ceilings,
not one entry"). The 24 triggers grouped by ceiling with the reason for each
ceiling; the four rows that deliberately do NOT ship with their recorded reasons;
the two that need a patch tier or a v5 overlay; the three things a rule cannot
express and are therefore done in the mapper; the three registered audiences; and
the ingest ORDERING, which is load-bearing rather than incidental.

**BACKEND_DESIGN.md -- the news publish path.** It described a direct
`pushDispatch.publish('news.post', ...)` from the create/publish-post path. That
call is gone: it is an emit through the engagement engine now, so news push rides
a rule, and core seeds that rule disabled. The note says so as an operator-visible
upgrade step, names the separate one-shot settings key and states what did NOT
change -- the announce legs and the post hooks still fire exactly as they did.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-31 20:34:31 -05:00
parent 7866bd7ab8
commit 561e5308e4
3 changed files with 152 additions and 9 deletions

View File

@@ -26,13 +26,31 @@ here extends the contract first, in this file, before the module is written agai
Core exports a single integer-major semver string from `server/src/modules/version.js`:
```js
const MODULE_API_VERSION = '1.7.0'
const MODULE_API_VERSION = '1.8.0'
```
The client half carries the same number (`client/src/modules/version.js`) and a test asserts the two
agree. Duplicated rather than fetched because the value has to be on `window.__rg` before the first
module chunk evaluates, which is earlier than any network round trip could answer.
**1.8.0 — a seventh audience ceiling: `admin`** (`website/ENGAGEMENT.md` Phase 11, decision 1). One
addition and no removal, so minor; every declaration valid under 1.7.0 is valid now and no stored
value changes. `admin` is a **child of `staff`**, so a module may declare `ceiling: 'admin'` on a
trigger or an audience and a `staff`-ceilinged trigger accepts an `admin` audience as a narrowing.
It exists because the narrowest role-shaped value the lattice had was `staff`, which means **admin,
editor AND moderator**. Phase 11's operator-facing triggers — a digest of what staff did in game, the
economy thresholds, the world-save counts — are admin-audience everywhere they are described, and
ceilinging them at `staff` would have let an operator save a rule that mails the staff audit digest to
every moderator in it.
**What a module author has to know beyond the new name.** `admin` is the **only pair in the whole
lattice with real containment** — every admin is staff, which is exactly what every other pair of
branches lacks — so it is the only place `permits` is true between two values below `authenticated`.
`permits('staff', 'admin')` holds; `permits('admin', 'staff')` does not, and neither direction holds
between `admin` and `owner`, `members` or `subscribers`. `permits`, `meet` and `meetAll` are otherwise
unchanged, and so is every rule about composition narrowing rather than widening.
**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([...])` and `api.registerAudiences([...])` (§2.4) ·
@@ -257,8 +275,8 @@ module-uo does not need is on the list.
| `ctx.teams.publish` | `(event) => Promise<void>` | `model/teams/teamSync` | the Team provider's module (1.6.0) |
| `ctx.teams.reconcile` | `({ reason }) => void`, returns at once | `model/teams/teamSync` | after a fresh account link (1.6.0) |
| `ctx.teams.activity.push` | `(items) => Promise<void>`, fire-and-forget | `model/teams/teamActivity` | the Team provider's module (1.6.0) |
| `ctx.events.emit` | `(triggerId, envelope) => void`, fire-and-forget | `utils/engagementEmit` | a module's own event mapper (1.7.0) |
| `ctx.inbox.push` | `(userId, item) => Promise<void>`**throws until Phase 7** | the in-app channel | not yet (1.7.0) |
| `ctx.events.emit` | `(triggerId, envelope) => void`, fire-and-forget | `utils/engagementEmit` | `module-uo`'s `utils/shardEngagement.js`, off the shard feed (1.7.0) |
| `ctx.inbox.push` | `(userId, item) => void`, fire-and-forget | the in-app channel (live since Phase 7) | `module-uo` reaches both through `server/core.js` (1.7.0) |
**`ctx.events.emit(triggerId, envelope)`** fires an event the module DECLARED with
`api.registerEventTriggers` (§2.4). It is the push half of the engagement seam
@@ -669,10 +687,12 @@ Six things about it are contract rather than implementation:
because under one namespace they are the same ids.
- **`ceiling` is required and has no default.** It is the audience ceiling (`ENGAGEMENT.md` §5.1a),
and there is no safe value to guess: `owner` would silently break a broadcast and `authenticated`
would silently widen a staff-only event. The six values are `everyone`, `authenticated`,
`subscribers`, `members`, `staff` and `owner`, ordered by **containment and not by size** — a
`staff` ceiling does NOT permit `owner`, because fewer people is not less exposure. A default
`audience` wider than, or incomparable with, the ceiling is refused at registration.
would silently widen a staff-only event. The seven values are `everyone`, `authenticated`,
`subscribers`, `members`, `staff`, **`admin`** (1.8.0, a child of `staff`) and `owner`, ordered by
**containment and not by size** — a `staff` ceiling does NOT permit `owner`, because fewer people is
not less exposure. `staff``admin` is the single true refinement in the tree and the only pair
below `authenticated` that `permits` accepts. A default `audience` wider than, or incomparable with,
the ceiling is refused at registration.
- **Every variable needs an `example`, and it is not decoration.** It is what makes previewing and
test-sending a template possible without a live game event, which is the reason template systems go
untested. A variable without one is refused.