docs(website): §7.1 Q9 — core's own news.post emitter #189

Merged
whitlocktech merged 1 commits from docs/engagement-news-emitter-note into edge 2026-08-31 07:30:56 +00:00
Member

A note the plan was missing, written up as an open question rather than decided. (Follows #188, which merged while this was being written — hence a second PR rather than another commit on that branch.)

The gap

news.post is a declared trigger with no caller. config/coreTriggers.js says so in as many words ("these declare; nothing here emits yet") and Phase 6 migrated only the four team.* ones. A rule naming news.post can never fire, so on a real deployment the only in-app or email items the engine can produce today come from Teams. Phase 7 flagged it in passing; this writes it down.

Why it is not a one-liner

The substance is not the call — it is the three other things a news publish already fires, and which of them the engine has any business replacing. announceIfNewlyPublished fans one publish four ways and they are different in kind:

What fires Whose Kind The engine's?
the announce leg → announce_job_legs core, legs registered by modules — module-uo owns towncrier one-shot delivery to a channel of the deployment, with retry No
dispatchPostHook('onSaved') modules, via registerPostHook idempotent state mirroring — also runs on delete, refreshes on a silent edit No
pushDispatch.publish('news.post', …) core a per-person notification Yes — this becomes the emit
(missing) engagementEmit.emit('core','news.post',…) core rules → email / in-app / push Yes

registries.js already argues the first two apart ("a leg is a one-shot DELIVERY with retry and classification; a post hook maintains idempotent STATE"). Adding the engine makes a third distinction of the same kind, not a replacement for either.

What modules keep, stated so nobody widens it by accident

A module has two doors onto a news publish — the announce leg and the post hook — and both are untouched. What it does not get is the ability to fire news.post itself: the id's owner is core, ctx.events.emit binds the owner at the call and never reads it from the arguments, and §7.2's one namespace gives an id exactly one owner across both facets. A module wanting its own person-facing news notification declares its own trigger through registerEventTriggers.

Three things left open, deliberately

  • Continuity — the same shape as G22 and Phase 6's decision 3. If the emit replaces the raw tickle, push stops the moment it lands and stays stopped until an operator enables a rule, silently, because enabled defaults to 0. Seed a news.post rule (enabled, against the standing default? or disabled with a banner, as the Team rules got?), or keep the tickle beside the emit for one release?
  • The transition signal must be reused, not re-derived. enqueueIfNeeded returning a truthy job id is the single "newly published news" test, and the push call already piggybacks on it so an edit does not re-fire.
  • Which phase owns it. Recommendation: Phase 11, which already ships "the first real rule" and is where a declared trigger first gets a caller. A pointer and an extra acceptance line land in that phase here — its title understates the work.

  • AI-assisted: written with Claude Code; commits carry Co-Authored-By: Claude.
A note the plan was missing, written up as an open question rather than decided. *(Follows **#188**, which merged while this was being written — hence a second PR rather than another commit on that branch.)* ## The gap `news.post` is a **declared trigger with no caller.** `config/coreTriggers.js` says so in as many words ("these declare; nothing here emits yet") and Phase 6 migrated only the four `team.*` ones. A rule naming `news.post` can never fire, so on a real deployment the only in-app or email items the engine can produce today come from Teams. Phase 7 flagged it in passing; this writes it down. ## Why it is not a one-liner The substance is not the call — it is the **three other things a news publish already fires**, and which of them the engine has any business replacing. `announceIfNewlyPublished` fans one publish four ways and they are different in kind: | What fires | Whose | Kind | The engine's? | | --- | --- | --- | --- | | the announce leg → `announce_job_legs` | core, legs registered by modules — `module-uo` owns `towncrier` | one-shot **delivery to a channel of the deployment**, with retry | **No** | | `dispatchPostHook('onSaved')` | modules, via `registerPostHook` | idempotent **state mirroring** — also runs on delete, refreshes on a silent edit | **No** | | `pushDispatch.publish('news.post', …)` | core | a **per-person notification** | **Yes — this becomes the emit** | | *(missing)* `engagementEmit.emit('core','news.post',…)` | core | rules → email / in-app / push | **Yes** | `registries.js` already argues the first two apart ("a leg is a one-shot DELIVERY with retry and classification; a post hook maintains idempotent STATE"). Adding the engine makes a **third** distinction of the same kind, not a replacement for either. ## What modules keep, stated so nobody widens it by accident A module has two doors onto a news publish — the announce leg and the post hook — and **both are untouched.** What it does not get is the ability to fire `news.post` itself: the id's owner is core, `ctx.events.emit` binds the owner at the call and never reads it from the arguments, and §7.2's one namespace gives an id exactly one owner across both facets. A module wanting its own person-facing news notification declares its own trigger through `registerEventTriggers`. ## Three things left open, deliberately - **Continuity — the same shape as G22 and Phase 6's decision 3.** If the emit *replaces* the raw tickle, push stops the moment it lands and stays stopped until an operator enables a rule, silently, because `enabled` defaults to `0`. Seed a `news.post` rule (enabled, against the standing default? or disabled with a banner, as the Team rules got?), or keep the tickle beside the emit for one release? - **The transition signal must be reused, not re-derived.** `enqueueIfNeeded` returning a truthy job id is the single "newly published news" test, and the push call already piggybacks on it so an edit does not re-fire. - **Which phase owns it.** Recommendation: **Phase 11**, which already ships "the first real rule" and is where a declared trigger first gets a caller. A pointer and an extra acceptance line land in that phase here — its title understates the work. --- - [x] AI-assisted: written with Claude Code; commits carry `Co-Authored-By: Claude`.
wtclaude added 1 commit 2026-08-31 07:28:52 +00:00
`news.post` is a declared trigger with no caller: `coreTriggers.js` says so in
as many words, and Phase 6 migrated only the four `team.*` ones. A rule naming
it can never fire, so on a real deployment the only in-app or email items the
engine can produce today come from Teams. Phase 7 flagged it in passing; this
writes it down properly as an open question.

The substance is not the call — it is the three other things a news publish
already fires, and which of them the engine has any business replacing:

- the announce leg (`announce_job_legs`, `module-uo` owns `towncrier`) — a
  one-shot delivery to a channel of the deployment, with retry. NOT the
  engine's.
- a module's post hook (`registerPostHook`) — idempotent state mirroring that
  also runs on delete and refreshes on a silent edit. NOT the engine's.
- the raw `pushDispatch.publish('news.post', …)` — a per-person notification.
  THIS is the one that becomes an emit.

So modules keep both doors onto a news publish and neither changes. What a
module does not get is the ability to fire `news.post` itself — the id's owner
is core, `emit` binds the owner at the call, and §7.2's one namespace gives an
id exactly one owner across both facets. A module wanting its own person-facing
news notification declares its own trigger.

Three things to settle first, recorded rather than decided: continuity (the
emit replacing the tickle stops push silently until a rule is enabled — G22's
shape, and Phase 6 decision 3's), reusing the job-id transition signal rather
than re-deriving it, and which phase owns it. Recommended home: Phase 11, whose
title understates it — a pointer and an extra acceptance line land there too.

Code: RunicGateway/website#175

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 2fd5d065b7)
whitlocktech merged commit feeb2cac11 into edge 2026-08-31 07:30:56 +00:00
whitlocktech deleted branch docs/engagement-news-emitter-note 2026-08-31 07:30:57 +00:00
Sign in to join this conversation.
No description provided.