docs(website): the email channel and the Teams migration as built (engagement Phase 6) #187

Merged
whitlocktech merged 1 commits from docs/engagement-email-channel into edge 2026-08-31 06:07:59 +00:00
Member

The documentation §6.0b assigns engagement Phase 6. Code: RunicGateway/website#174 · Site: RunicGateway/runicgateway.com#23

ENGAGEMENT.md

Phase 6 as built, the long one. What it records that a reader cannot recover from the diff:

  • The seven decisions the org lead settled before any code, each with the tree's contradiction that forced the question.
  • Why the audience had to come from the event. A rule names a plain ceiling or a saved segment composing module-declared audiences with constant params. Neither can express "the members of the Team this post was in" — the list differs per firing and is the answer to an access question core already knows how to ask. audiences.js said as much in its own comment. The three guards that keep it a narrowing input are spelled out, because they are what stop it reading as a hole in the ceiling lattice.
  • The one place decision 4 as phrased could not ship. "A suppression below the channel preference" reads naturally and would have silenced every existing Team-email subscriber on the migrating deploy — notification_channel_prefs holds a row only where a user expressed something, and nobody has ever expressed a stream-level opinion about a Team trigger. Replacement, not intersection, and the argument is written down so it is not "simplified" back.
  • scope_key is not subject_key, with the rename that would orphan every unsubscribe link in every mailbox.
  • The projection's rule and what it refuses to guess. §4.6.1 property 1 was never implemented; this is the implementation, and the table says what each fallback comes from and why a table of domain synonyms is wrong on the first module that names things differently.
  • The digest correcting §4.2a rather than only implementing §4.2b, plus the three defects the build found.

§4.2b gains the correction inline: a digest-mode recipient gets no outbox row, engagement_outbox gained scope_key, and the backfill's replay-safety is a property of the primary key rather than of a flag. §6.0b's Phase 6 row is ticked and now names the three docs it actually owed.

Two stale references fixed: notify.team-post's variable is postUrl, not threadUrl — it has to be the name team.forum.post declares or the mail links nowhere (seedVersion 2) — and §3.2's "off unless configured" rule now says where that gate moved.

TEAMS.md

§6.4 rewritten. The pipeline it described no longer exists as its own thing. It now says which of the three sinks moved and which did not (with the leg vs channel argument for the Discord bridge), where each of the four properties it always claimed lives now, that Team email is OFF until an operator turns a rule on, and what the generalized unsubscribe token does and no longer does. It links out to ENGAGEMENT.md for the mechanism rather than re-specifying it.

§6.3 marks last_digest_at as no longer read — the state moved to engagement_digest_state — while making clear the two preference columns are unchanged and still the authority.

BACKEND_DESIGN.md

engagement_digest_state in the table inventory, with why it is a digest's only state and deliberately not a digest queue. engagement_outbox.scope_key, and subject_key's note sharpened now that the two coexist. The route table gains the canonical /public/engagement/unsubscribe pair and keeps /public/teams/unsubscribe as its own row — permanently, because mail is not editable once sent.

AI disclosure

Written with Claude Code (Claude Opus 5). The commit carries the Co-Authored-By trailer.

The documentation §6.0b assigns engagement **Phase 6**. Code: RunicGateway/website#174 · Site: RunicGateway/runicgateway.com#23 ## `ENGAGEMENT.md` **Phase 6 as built**, the long one. What it records that a reader cannot recover from the diff: - **The seven decisions the org lead settled before any code**, each with the tree's contradiction that forced the question. - **Why the audience had to come from the event.** A rule names a plain ceiling or a saved segment composing module-declared audiences with *constant* params. Neither can express "the members of the Team this post was in" — the list differs per firing and is the answer to an access question core already knows how to ask. `audiences.js` said as much in its own comment. The three guards that keep it a *narrowing* input are spelled out, because they are what stop it reading as a hole in the ceiling lattice. - **The one place decision 4 as phrased could not ship.** "A suppression below the channel preference" reads naturally and would have silenced every existing Team-email subscriber on the migrating deploy — `notification_channel_prefs` holds a row only where a user expressed something, and nobody has ever expressed a stream-level opinion about a Team trigger. Replacement, not intersection, and the argument is written down so it is not "simplified" back. - **`scope_key` is not `subject_key`**, with the rename that would orphan every unsubscribe link in every mailbox. - **The projection's rule and what it refuses to guess.** §4.6.1 property 1 was never implemented; this is the implementation, and the table says what each fallback comes from and why a table of domain synonyms is wrong on the first module that names things differently. - **The digest correcting §4.2a rather than only implementing §4.2b**, plus the three defects the build found. **§4.2b** gains the correction inline: a digest-mode recipient gets no outbox row, `engagement_outbox` gained `scope_key`, and the backfill's replay-safety is a property of the primary key rather than of a flag. **§6.0b**'s Phase 6 row is ticked and now names the three docs it actually owed. Two stale references fixed: `notify.team-post`'s variable is `postUrl`, not `threadUrl` — it has to be the name `team.forum.post` **declares** or the mail links nowhere (`seedVersion` 2) — and §3.2's "off unless configured" rule now says where that gate moved. ## `TEAMS.md` **§6.4 rewritten.** The pipeline it described no longer exists as its own thing. It now says which of the three sinks moved and which did not (with the *leg* vs *channel* argument for the Discord bridge), where each of the four properties it always claimed lives now, that **Team email is OFF until an operator turns a rule on**, and what the generalized unsubscribe token does and no longer does. It links out to `ENGAGEMENT.md` for the mechanism rather than re-specifying it. **§6.3** marks `last_digest_at` as no longer read — the state moved to `engagement_digest_state` — while making clear the two *preference* columns are unchanged and still the authority. ## `BACKEND_DESIGN.md` `engagement_digest_state` in the table inventory, with why it is a digest's only state and deliberately not a digest queue. `engagement_outbox.scope_key`, and `subject_key`'s note sharpened now that the two coexist. The route table gains the canonical `/public/engagement/unsubscribe` pair and keeps `/public/teams/unsubscribe` as its own row — **permanently**, because mail is not editable once sent. ## AI disclosure Written with Claude Code (Claude Opus 5). The commit carries the `Co-Authored-By` trailer.
wtclaude added 1 commit 2026-08-31 06:03:54 +00:00
ENGAGEMENT.md gains the Phase 6 as-built: the seven decisions settled up front,
the audience problem that shaped the phase, why scope_key is not subject_key, the
one place decision 4 as phrased could not ship, the projection's rule and what it
refuses to guess, the digest correcting Phase 4a rather than only implementing
§4.2b, and the three defects the build found. §4.2b and §6.0b updated with it.

TEAMS.md §6.4 rewritten: the pipeline it described no longer exists as its own
thing. It now says which of the three sinks moved and which did not, where each
of the four properties it always claimed lives now, that Team email is OFF until
an operator turns a rule on, and what the generalized unsubscribe token does.
§6.3's `last_digest_at` is marked as no longer read.

BACKEND_DESIGN.md: engagement_digest_state, engagement_outbox.scope_key, and the
unsubscribe routes — the canonical /public/engagement pair plus the /public/teams
path kept permanently because mail is not editable once sent.

Code: RunicGateway/website#TBD

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech merged commit 73c3a467e7 into edge 2026-08-31 06:07:59 +00:00
whitlocktech deleted branch docs/engagement-email-channel 2026-08-31 06:08:01 +00:00
Sign in to join this conversation.
No description provided.