docs(engagement): the two walk defects, fixed — and what digest is #204

Merged
whitlocktech merged 1 commits from docs/engagement-walk-fixes into main 2026-09-01 19:34:00 +00:00
3 changed files with 13 additions and 3 deletions
Showing only changes of commit b3dd2c7cbb - Show all commits

View File

@@ -508,7 +508,7 @@ is only harmless while the default is off. Existing subscriptions are carried ac
| audience_segment_id | INT NULL | a composed segment (§5.1a). **Deliberately no FK** — see below |
| max_sends_per_hour | INT NOT NULL DEFAULT 100 | the hard per-rule ceiling (§7.1 Q3), counted in `engagement_sends` and enforced before an outbox row is written |
| channels | JSON NOT NULL | `['email','inapp']` — a rule may span channels |
| template_keys | JSON NOT NULL | `{ email: 'idoc-warning' }`. Keys are shape-checked, not existence-checked: templates are Phase 5 |
| template_keys | JSON NOT NULL | `{ email: 'idoc-warning' }`. Keys are shape-checked, not existence-checked: templates are Phase 5. Every key is one of the rule's `channels` **plus one that is not a channel at all**: `digest` names the body the digest worker renders for a rule whose email channel an individual set to digest mode, so it belongs to a mode and can never appear in `channels`. The rules validator rejected it until website#181, which made every rule shipping one — core's own Team and news rules included — unsaveable from the Rules screen |
| conditions | JSON NULL | a small closed and/or/not grammar over the trigger's **declared** variables |
| cooldown_seconds | INT NOT NULL DEFAULT 0 | 0 = no cooldown |
| delay_seconds | INT NOT NULL DEFAULT 0 | the grace window (§4.2a) |

View File

@@ -3852,8 +3852,15 @@ is the Phase 11b defect class staying fixed.
key they never typed. The only way to save is to delete the digest body, which silently removes
digest support from that rule. The two validators have to agree about what `digest` is.
Neither is fixed here: this phase's PR is the documentation of a cutover, and a core change belongs
in its own. Both are recorded for the org lead to place.
**Both are fixed in website#181.** `settings.db` gained `claim(key, value)` — the same
`INSERT IGNORE` as `seedDefault`, reporting its own `affectedRows`, so exactly one caller can win a
key — and both seeders now claim before inserting rather than stamping after. The trade the code
already documented is unchanged, only its order: a process that dies mid-loop leaves the group
stamped and partly seeded, which both functions already called the better failure. The stubs are
`claim`-shaped now and each suite gained a test that runs two seeders under `Promise.all`; reverting
the fix makes the module one report every rule inserted twice, which is how the fix was checked. And
`engagementRules.model.js` now admits `digest` as the one key that is a slot rather than a channel,
so the two validators agree; anything else is still refused, with a test in each direction.
**Also still open:** nothing else. `.profile/README.md` (step 8) landed — the org lead called the
engagement system a headline capability — and Phase 12's second finding became **Phase 14**.

View File

@@ -856,6 +856,9 @@ are in §1.1 under **1.9.0**; four things are contract rather than implementatio
`notify.event` / `inapp.event` / `notify.digest` and author nothing — §4.6.1 property 1. Ship a
bespoke body when the message has something to say that the structural projection cannot; a trigger
whose message is "this happened, here is the link" should not have one.
- **`digest` is a slot, not a channel.** It names the body the digest worker renders for a rule whose
email channel an individual set to digest mode, so it is legal in `template_keys` and never appears
in `channels`. Every other key must be one of the rule's channels.
- **It is not a send path.** Every value on the object is data. Core still decides who is told.
**`onBoot(fn)` / `onShutdown(fn)`** — §2.5.