docs(website): event triggers, audiences and the ceiling lattice (engagement Phase 2) #181

Merged
whitlocktech merged 1 commits from docs/engagement-trigger-registry into edge 2026-08-29 11:47:25 +00:00
Member

Documentation companion to website#168 (engagement Phase 2). Same review window.

MODULE_API.md

  • §1.1 gains a real 1.7.0registerEventTriggers, registerAudiences, ctx.events.emit, ctx.inbox.push. Additions only, so minor.
  • The stale-edge correction §0.5 asked for. 1.6.0's three in-place amendments justified themselves with "1.6.0 has only ever been on edge". That was true when written and is not now — 1.6.0 reached main with the Teams cutover — so those paragraphs are marked historical and every addition from here takes a version of its own.
  • §2.3ctx.events.emit with its six contract properties, and ctx.inbox.push documented as present and throwing until Phase 7, with the reason.
  • §2.4 — both register calls, their contract properties, and the one-namespace rule.
  • New §6.8 — the forward-compat note ENGAGEMENT.md §7.3 asked this document to hold: a rule, a template and an audience outlive the module that declared them, so trigger_id is a plain VARCHAR with no cascade (the same decision announce_job_legs took) and an unregistered id shows dormant rather than erroring or being auto-deleted. The failure that prevents is specific — an id that stops resolving must never silently become a send to a different set of people.

ENGAGEMENT.md

Phase 2 recorded as built, plus three things the document did not previously say:

  • §5.1a now DEFINES the ceiling lattice. The plan used "narrowest" and "tightest" throughout without ever saying what narrower meant, and the obvious reading is a security defect: under a flat total order a staff-ceilinged trigger permits an owner audience — a rule that mails cheat detection to the player it detected. It is containment, not size, and incomparable ceilings have no bound at all, so a composition is refused rather than guessed.
  • §7.2 is answered — ONE namespace, against the recommendation in its own text. Recorded with the two knock-on effects that only appeared once it was built (a relaxed id grammar, a shared legacy allowlist) and the risk that did not materialise: the push catalog is untouched, so the shipped Android client sees no change.
  • §7.1 Q6 is answered — declare kind: 'scheduled' now, build the evaluator after Phase 9. Registration accepts it and emit refuses to fire one, so kind means something from the moment it can be written.

Also: §4.3 kept true to what shipped (audience and ceiling are two fields not one; 'computed' is gone from the audience vocabulary; example is enforced rather than encouraged), and the status header + Phase 0 acceptance updated.

§6.0b's Phase 2 row is corrected. Its two "other repos" cells are cutover-window work, not this window — the integration kit pins a website main sha and runicgateway.com fetches its facts from main, so doing either now turns a green repo red for the whole edge period, and for the kit it would violate the explicit rule in its own pin ("written against what shipped, never what is in flight").

BACKEND_DESIGN.md

The two /admin/engagement/* routes, the router-tree entry, and engagement added to the adminOnly ops/config list. api-route-inventory.json regenerated from the live Express stack (8 lines, both new routes).


  • AI-assisted: written with Claude Code (Claude Opus 5). Commit carries Co-Authored-By: Claude <noreply@anthropic.com>.
Documentation companion to **website#168** (engagement Phase 2). Same review window. ## `MODULE_API.md` - **§1.1 gains a real 1.7.0** — `registerEventTriggers`, `registerAudiences`, `ctx.events.emit`, `ctx.inbox.push`. Additions only, so minor. - **The stale-`edge` correction §0.5 asked for.** 1.6.0's three in-place amendments justified themselves with *"1.6.0 has only ever been on `edge`"*. That was true when written and is not now — 1.6.0 reached `main` with the Teams cutover — so those paragraphs are marked **historical** and every addition from here takes a version of its own. - **§2.3** — `ctx.events.emit` with its six contract properties, and `ctx.inbox.push` documented as *present and throwing* until Phase 7, with the reason. - **§2.4** — both register calls, their contract properties, and the one-namespace rule. - **New §6.8** — the forward-compat note `ENGAGEMENT.md` §7.3 asked this document to hold: a rule, a template and an audience **outlive the module that declared them**, so `trigger_id` is a plain `VARCHAR` with no cascade (the same decision `announce_job_legs` took) and an unregistered id shows **dormant** rather than erroring or being auto-deleted. The failure that prevents is specific — an id that stops resolving must never silently become a send to a *different* set of people. ## `ENGAGEMENT.md` Phase 2 recorded as built, plus three things the document did not previously say: - **§5.1a now DEFINES the ceiling lattice.** The plan used "narrowest" and "tightest" throughout without ever saying what narrower meant, and the obvious reading is a security defect: under a flat total order a `staff`-ceilinged trigger permits an `owner` audience — a rule that mails cheat detection to the player it detected. It is containment, not size, and incomparable ceilings have no bound at all, so a composition is refused rather than guessed. - **§7.2 is answered — ONE namespace**, against the recommendation in its own text. Recorded with the two knock-on effects that only appeared once it was built (a relaxed id grammar, a shared legacy allowlist) and the risk that did **not** materialise: the push catalog is untouched, so the shipped Android client sees no change. - **§7.1 Q6 is answered** — declare `kind: 'scheduled'` now, build the evaluator after Phase 9. Registration accepts it and `emit` refuses to fire one, so `kind` means something from the moment it can be written. Also: **§4.3 kept true** to what shipped (`audience` and `ceiling` are two fields not one; `'computed'` is gone from the audience vocabulary; `example` is enforced rather than encouraged), and the status header + Phase 0 acceptance updated. **§6.0b's Phase 2 row is corrected.** Its two "other repos" cells are cutover-window work, not this window — the integration kit pins a website `main` sha and `runicgateway.com` *fetches* its facts from `main`, so doing either now turns a green repo red for the whole `edge` period, and for the kit it would violate the explicit rule in its own pin ("written against what shipped, never what is in flight"). ## `BACKEND_DESIGN.md` The two `/admin/engagement/*` routes, the router-tree entry, and `engagement` added to the `adminOnly` ops/config list. `api-route-inventory.json` regenerated from the live Express stack (8 lines, both new routes). --- - [x] **AI-assisted**: written with Claude Code (Claude Opus 5). Commit carries `Co-Authored-By: Claude <noreply@anthropic.com>`.
wtclaude added 1 commit 2026-08-29 11:42:02 +00:00
MODULE_API.md gets a real 1.7.0 — and §1.1's "1.6.0 has only ever been on
`edge`" paragraphs are now marked historical rather than current, which is the
correction §0.5 asked for: 1.6.0 reached `main` with the Teams cutover, so the
in-place-amendment rule those paragraphs invoke no longer applies and every
addition from here takes a version of its own.

§2.3 gains `ctx.events.emit` and `ctx.inbox.push` (present and throwing until
Phase 7, with the reason stated). §2.4 gains `registerEventTriggers` and
`registerAudiences`. A new §6.8 carries the forward-compat note ENGAGEMENT.md
§7.3 asked this document to hold: a rule, a template and an audience outlive
the module that declared them, so `trigger_id` is a plain VARCHAR with no
cascade and an unregistered id shows DORMANT rather than erroring or being
auto-deleted. The failure that prevents is specific — an id that stops
resolving must never silently become a send to a different set of people.

ENGAGEMENT.md records Phase 2 as built, and three things it did not previously
say:

§5.1a now DEFINES the ceiling lattice. The document named "narrowest" and
"tightest" throughout and never said what narrower meant, and the obvious
reading is a security defect: under a flat total order a `staff`-ceilinged
trigger permits an `owner` audience, i.e. a rule that mails cheat detection to
the player it detected. It is containment, not size, and incomparable ceilings
have no bound at all.

§7.2 is answered — ONE namespace, against the recommendation in its own text —
with the two knock-on effects that only appeared once it was built (a relaxed
id grammar, a shared legacy allowlist) and the risk that did not materialise
(the push catalog is untouched, so the shipped app sees no change).

§7.1 Q6 is answered: declare `kind: 'scheduled'` now, build the evaluator after
Phase 9.

§4.3 is kept true to what shipped: `audience` and `ceiling` are two fields
rather than one, `'computed'` is gone from the audience vocabulary, and
`example` is enforced rather than encouraged.

§6.0b's Phase 2 row is corrected. Its two "other repos" cells are cutover-window
work, not this window: the integration kit pins a website `main` sha and
runicgateway.com FETCHES its facts from `main`, so doing either now would turn a
green repo red for the whole edge period — for the kit, against the explicit
rule in its own pin ("written against what shipped, never what is in flight").

BACKEND_DESIGN.md gains the two admin routes, the router-tree entry and the
adminOnly line; api-route-inventory.json regenerated.

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech merged commit 6c19a0608a into edge 2026-08-29 11:47:25 +00:00
whitlocktech deleted branch docs/engagement-trigger-registry 2026-08-29 11:47:26 +00:00
Sign in to join this conversation.
No description provided.