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

@@ -1217,8 +1217,27 @@ registers device endpoints (`/auth/me/devices`); nothing is pushed unless subscr
**content-free tickle**`{ stream, ref }`, no sensitive data — POSTed to each subscribed device's
self-hosted **ntfy** endpoint (`utils/pushDispatch`); the app wakes and pulls the real, ownership-
checked content over the authenticated API. Two producers fan out through the one publisher: the shard
ingest dispatcher (`utils/shardIngest`, beside the SSE broadcast) for shard-derived streams, and the
create/publish-post path for `news.post`. The catalog is assembled at boot by
ingest dispatcher (`utils/shardIngest`, beside the SSE broadcast) for shard-derived streams, and — from
**ENGAGEMENT.md Phase 11** — the **engagement engine** for `news.post`.
> **The news publish path changed in Phase 11 and it is an operator-visible change.** Publishing a
> news post used to call `pushDispatch.publish('news.post', …)` directly from
> `admin.controller.js`'s `announceIfNewlyPublished`; it now calls
> `utils/newsNotify.emitNewsPost`, which emits core's declared `news.post` trigger and lets the engine
> decide. Push therefore rides a **rule** like every other channel, and core seeds that rule
> `enabled = 0` beside the four Team ones — so **news push stops on upgrade** until an operator enables
> it in Admin → Engagement → Rules, where a banner says so. The `news.post` seed carries its own
> one-shot settings key (`engagement_news_rule_seeded`) rather than joining the Team group's, because
> the Team key is already stamped on exactly the deployments this affects.
>
> **The other two things a publish fires are untouched.** `announceJobs.enqueueIfNeeded` (a one-shot
> delivery to a channel of the deployment — the in-game town crier, Discord #news, with retry) and
> `registries.dispatchPostHook('onSaved')` (idempotent state mirroring, which also runs on delete) are
> different kinds of thing and still fire exactly as they did. The emit is gated on the same
> `enqueueIfNeeded` job id the push was gated on — the single "newly published news" transition
> signal — so an edit or a re-publish still does not re-fire.
The catalog is assembled at boot by
`modules/registries.js` from core's own streams (`config/coreStreams.js` — just `news.post`) plus
each installed module's. The seven shard streams and their event→stream mapping left with
`module-uo` in Phase 3 and are registered by it; their ids are grandfathered to that module