docs(website): the engagement engine as built (Phase 4a) #183

Merged
whitlocktech merged 1 commits from docs/engagement-engine into edge 2026-08-29 13:24:28 +00:00
Member

Companion to website#170. §6.0b's assignment for Phase 4 — ENGAGEMENT.md's as-built and BACKEND_DESIGN.md's table inventory — plus the two corrections building it forced on this document's own design sections.

ENGAGEMENT.md

  • Phase 4 is split 4a / 4b, with what each owes. 4a is the engine with no HTTP surface; 4b is Admin → Engagement → Rules and the §5.1a composition UI.
  • §7.1 Q2 and Q4 answered, so seven of the eight are settled and only Q8 (android CI) is open. Q2's answer is neither option the question offered — a compare-and-set claim into the sending state the ENUM already carried, which needs no open transaction and no version floor. Q4 gives the surface its own nav group.
  • The as-built: the gate order and why two of its placements are load-bearing (the ceiling re-checked at send time; the hourly ceiling before the cooldown); the segment rule §5.1a never stated (not is legal only inside an and, and contributes no ceiling of its own); dormancy three ways and why audience_segment_id has no foreign key; the conditions grammar's two fail-closed properties; and why emit does not await the engine.

Two of this document's own design sections were wrong, and they are corrected in place rather than left to contradict the phase below them.

  • §4.2a's UNIQUE KEY uq_engo_dedupe (dedupe_key) — a dedupe key names the event, and one event is one row per (rule, user, channel). A global unique index would admit the first recipient's row and silently ignore every other one. Now scoped, with the reasoning in the DDL comment.
  • §4.1's "the check is INSERT … ON DUPLICATE KEY UPDATE guarded on the interval, in one statement" — that statement reads its answer out of affectedRows, and the mariadb connector's default foundRows: true makes a no-op update report 1 rather than 0. Every cooldown passes. §4.1 now describes the two-statement form and says why the obvious one does not work here.

BACKEND_DESIGN.md

Five new tables in the schema inventory, each carrying the reasoning a reader would otherwise have to reconstruct from the DDL:

  • why subject_key is in the cooldown primary key (the four-decaying-houses case)
  • why the dedupe index is scoped, and what a global one would have cost
  • why engagement_sends.user_id is SET NULL rather than CASCADE, and why the address is a hash
  • why a rule points at a segment without a foreign key doing it — both of the database's options are wrong, and SET NULL is wrong in the dangerous direction

No route-table changes: Phase 4a adds no routes.


  • AI-assisted: written with Claude Code (Opus)

🤖 Generated with Claude Code

Companion to **website#170**. §6.0b's assignment for Phase 4 — `ENGAGEMENT.md`'s as-built and `BACKEND_DESIGN.md`'s table inventory — plus the two corrections building it forced on this document's own design sections. ## `ENGAGEMENT.md` - **Phase 4 is split 4a / 4b**, with what each owes. 4a is the engine with no HTTP surface; 4b is Admin → Engagement → Rules and the §5.1a composition UI. - **§7.1 Q2 and Q4 answered**, so seven of the eight are settled and only **Q8** (android CI) is open. Q2's answer is neither option the question offered — a compare-and-set claim into the `sending` state the ENUM already carried, which needs no open transaction and no version floor. Q4 gives the surface its own nav group. - **The as-built**: the gate order and why two of its placements are load-bearing (the ceiling re-checked at *send* time; the hourly ceiling before the cooldown); the segment rule §5.1a never stated (`not` is legal only inside an `and`, and contributes no ceiling of its own); dormancy three ways and why `audience_segment_id` has no foreign key; the conditions grammar's two fail-closed properties; and why `emit` does not await the engine. **Two of this document's own design sections were wrong, and they are corrected in place rather than left to contradict the phase below them.** - **§4.2a's `UNIQUE KEY uq_engo_dedupe (dedupe_key)`** — a dedupe key names the *event*, and one event is one row per (rule, user, channel). A global unique index would admit the first recipient's row and silently ignore every other one. Now scoped, with the reasoning in the DDL comment. - **§4.1's "the check is `INSERT … ON DUPLICATE KEY UPDATE` guarded on the interval, in one statement"** — that statement reads its answer out of `affectedRows`, and the mariadb connector's default `foundRows: true` makes a no-op update report 1 rather than 0. Every cooldown passes. §4.1 now describes the two-statement form and says why the obvious one does not work here. ## `BACKEND_DESIGN.md` Five new tables in the schema inventory, each carrying the reasoning a reader would otherwise have to reconstruct from the DDL: - why `subject_key` is in the cooldown primary key (the four-decaying-houses case) - why the dedupe index is scoped, and what a global one would have cost - why `engagement_sends.user_id` is `SET NULL` rather than `CASCADE`, and why the address is a hash - why a rule points at a segment **without** a foreign key doing it — both of the database's options are wrong, and `SET NULL` is wrong in the dangerous direction No route-table changes: Phase 4a adds no routes. --- - [x] AI-assisted: written with Claude Code (Opus) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
wtclaude added 1 commit 2026-08-29 13:08:56 +00:00
Companion to website#170. Section 6.0b's assignment for Phase 4 - ENGAGEMENT.md's
as-built and BACKEND_DESIGN.md's table inventory - plus the two corrections
building it forced on this document's own design sections.

ENGAGEMENT.md
  - Phase 4 is split 4a / 4b, with what each owes.
  - Section 7.1 Q2 and Q4 answered, so seven of eight are settled and only Q8
    (android CI) is open.
  - The as-built: the gate order and why two of its placements are load-bearing,
    the segment rule the design never stated (not is legal only inside an and,
    and contributes no ceiling), dormancy three ways and why audience_segment_id
    has no foreign key, the conditions grammar's two fail-closed properties, and
    why emit does not await the engine.
  - Section 4.1's cooldown statement and section 4.2a's dedupe index are
    corrected in place, so the design sections stop teaching the two defects.

BACKEND_DESIGN.md
  - Five new tables in the schema inventory, each with the reasoning a reader
    would otherwise have to reconstruct: why subject_key is in the primary key,
    why the dedupe index is scoped, why the send log survives an account
    deletion and is not a second address book, and why a rule points at a
    segment without a foreign key doing it.

No route table changes - Phase 4a adds no routes.

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech merged commit 5924276fe7 into edge 2026-08-29 13:24:28 +00:00
whitlocktech deleted branch docs/engagement-engine 2026-08-29 13:24:29 +00:00
Sign in to join this conversation.
No description provided.