5.4.5 Watcher notification job — `work-item/transitioned` event + fan-out on comment/transition (actor excluded, mention-dedupe, idempotent)
Estimate: 30m · Depends on: 5.4.4, 5.1.6
The watcher email pipeline, on the 5.1.6 pattern.
New event: work-item/transitioned (workspaceId, workItemId, actorId, fromStatusKey, toStatusKey, revisionId) — emitted by workItemsService.updateStatus AFTER its transaction commits (today it writes only the revision row — rung-2 fact; the event NEVER fires on rollback, the 5.1.2 rule). Typed in JobEventDataMap.
Job (lib/jobs/definitions/watcherNotify.ts, the 1.6 defineJob harness): consumes work-item/comment.created (5.1.6's event — the SAME emit, a second consumer; no new emit path) and work-item/transitioned. Per event: page through the issue's watchers; exclude the actor (the verified never-self-notify default); exclude the comment's mentioned users (they get the 5.1.6 mention email — one email per person per event, the dedupe rule); re-validate view access at send time; render watcherCommentNotification / watcherTransitionNotification templates (pure, hand-written plain text, deep link — the emailTemplates contract; "<Author> commented on PROD-N" / "<Actor> moved PROD-N to <Status>") and send idempotently per (event × user) via the harness key. Failures → DLQ. Fan-out is PAGED (a 200-watcher issue never builds an unbounded batch).
Acceptance criteria
work-item/transitionedis typed + emitted post-commit byupdateStatus(rollback emits nothing); existing transition tests stay green.- The job mails every watcher EXCEPT the actor and (for comments) the mentioned users; targets failing the send-time view check are skipped; both templates are pure with unredacted deep links.
- Replay/retry double-sends nothing (idempotency per event × user,
@inngest/testcoverage); failures land in the DLQ; fan-out pages. - Coverage gate ≥90% on the job + templates + the emit seam.
Context refs
lib/jobs/definitions/+ 5.1.6 (mentionNotify— the pattern + the sharedcomment.createdevent + the mention-recipient list to dedupe against)workItemsService.updateStatus(the post-commit emit point; its revision write at lines ~768)lib/emailTemplates/contract +workspaceInvite.tsxexemplar- The verified notify rules (watchers on comment/transition; never the actor) in the Story 5.4 description; Story 5.7 (the in-app consumer of the same events)