← All craftbooks

Notification Router

Comms and ops automationevalv1.0.0released 2026-06-05workflow: build-loop

Build an event-to-channel notification router: a small module that takes incoming events and routes each to the right destination (Slack channel, email, PagerDuty, webhook) based on type, severity, and quiet-hours rules — with dedupe, throttling, and a fallback channel. Scopes the routing matrix and policy FIRST (event taxonomy, channel map, severity→destination rules, dedupe key, rate limits, fallback), then a developer implements the router module to that matrix, then writes tests covering each routing branch. Scoping the matrix before coding is what guarantees every event type has a defined destination and that critical events can never be silently dropped. Use for notification routing, alert fan-out, event dispatch, webhook routing, and on-call/Slack notification systems.

Steps

Entry step: scope. Each step names the specialist role it wants; the full working prompt is expandable.

  1. Scope the routerplannerentry

    lock the routing matrix, dedupe, throttle, and fallback

    Show working prompt
    Define the routing contract before coding. (1) Enumerate the event taxonomy (the event types/shapes the router accepts) and the available destination channels. (2) Build the routing matrix: for each (event type × severity) pair, the destination channel(s) — make it total so no input is undefined. (3) Define the dedupe key (which fields identify a duplicate within a window) and the suppression window. (4) Set per-channel rate limits/throttling and a quiet-hours rule (and which severities override quiet hours). (5) Define a fallback channel for events that match no rule, so nothing is dropped silently, and the behavior on a destination send failure (retry/fallback). (6) Pick the language and the function signature, e.g. route(event) -> Destination[]. Write an acceptance-criteria checklist ('every (type,severity) maps to a destination', 'unmatched events hit the fallback, never dropped', 'duplicates within the window are suppressed', 'rate limits enforced', 'critical severity overrides quiet hours', 'pure routing logic is unit-testable without live channels') plus the matrix to notes/scope.md via write_task_note AND the produces path.
  2. Build the routerdeveloper

    implement the router module to the matrix

    Show working prompt
    Implement the router as a single module (e.g. router.ts/router.py) realizing the locked matrix. Separate PURE routing logic — route(event) returning the destination list — from any side-effecting send, so routing is testable in isolation. Implement: matrix lookup with the fallback for unmatched events, dedupe via the locked key + window (an in-memory recent-key set is fine), per-channel throttling, and the quiet-hours rule with the critical-severity override. Validate/normalize inbound events and reject malformed ones explicitly. Never let an event with no matching rule vanish — it goes to fallback. Add brief doc comments mapping code back to matrix rows. On a loop-back fix only the flagged behavior. Write the module path and the implemented signature to notes via write_task_note.
  3. Test the routerdeveloper

    cover every routing branch with assertions

    Show working prompt
    Write router.test.ts exercising the router's pure logic. Cover, with explicit assertions: (1) a representative event for each (type,severity) row resolves to the matrix's destination; (2) an unmatched event routes to the fallback and is NOT dropped; (3) a duplicate within the window is suppressed and the same key after the window is allowed; (4) throttling blocks the N+1th send to a channel in the window; (5) a critical event during quiet hours still routes, a non-critical one is held; (6) a malformed event is rejected. Use the test runner's describe/it/expect (or equivalent assert). Tests must run without live channels. On a loop-back add the missing cases. Write the test path and the branch-coverage list to notes via write_task_note.
  4. Evaluatereviewer

    Grade the deliverable against every acceptance criterion. All pass → finish; any fail → loop back and fix the gap.

    Show working prompt
    QA the router against the locked criteria. (1) Run the test suite (or trace it) — every test must pass; a failing/empty suite is FAIL. (2) Confirm tests exist for each matrix row, the fallback path, dedupe (hit and post-window allow), throttling, the quiet-hours + critical-override behavior, and malformed-event rejection. (3) Read router.ts: is routing pure and side-effect-free, is the matrix total, can an unmatched event ever be silently dropped (must be NO)? (4) Spot-check 2 matrix rows in code against notes/scope.md. Write PASS/FAIL per criterion with the gaps.
    
    Then route — this is the whole point of the loop:
    
    - **Every criterion PASSES →** call `advance_task_step({ ref, stepId: "evaluate", next: "finish" })`.
    - **Any criterion FAILS →** write the specific gaps to notes, then call `advance_task_step({ ref, stepId: "evaluate", next: "build" })` to loop back. The builder fixes exactly those gaps.
    
    Never route to `finish` while any criterion is unmet. The build phase's completion gate already blocked a grossly-incomplete deliverable; your job is the judgment an automated check cannot make (does it actually work, read well, look right). After ~3 unproductive loops, stop and report DONE_WITH_CONCERNS so the user can step in.
  5. Finishdeveloper

    All acceptance criteria met. Stamp a short summary and report DONE.

    Show working prompt
    Every acceptance criterion passed. Write a one-paragraph DONE summary to task notes via `write_task_note`: what was built, the deliverable path(s), and a one-line confirmation that each criterion is met. Then report DONE.

Triggers

Phrases that suggest this craftbook to a crew.

Source

View this craftbook on GitHub · MIT license