feat(engine): module substrate wave 2 — events, outbox drain, webhook delivery, admin API (HT-69) - #79
Conversation
… delivery, admin API (HT-69) Implements specs/modules/substrate-v1.md §4/§5 on top of wave 1's schema and stores (HT-68): transactional event emission at every non-deleted state change (conversation.created/message_received in the ingestion append path, status_changed/tags_changed/assignee_changed on the corresponding ConversationStore writes, reply_sent on the delivery-status→'sent' transition), a cron-driven outbox drain that fans each event out to its matching active webhook endpoints via the existing queue, a signed HTTPS delivery handler with resolve-then-connect SSRF pinning and its own retry/dead-letter ceiling, and the webhooks admin API (CRUD + test-ping) mirroring agents.ts's conventions. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…edupe-key doc (HT-69 review) MAJOR: releaseThreadLease's 'sent' branch emitted conversation.reply_sent unconditionally, but delivery is not conversation-status-scoped — a thread claimed/leased before its conversation was soft-deleted could still be force-delivered afterward and fire the event, breaching spec §4's absolute "no event of any type fires for a soft-deleted conversation" exclusion. Gate the outbox append (not the delivery-status write, which must still proceed — mail already went out) on the conversation's live status, read in the SAME UPDATE...RETURNING statement via a correlated subquery, mirroring listAwaitingDrafts' existing status <> 'deleted' carve-out for drafts. MINOR: migration 023's doc comment said the outbox->queue hand-off is keyed by dedupe_key = event_id (singular); corrected to the implemented per-(event, endpoint) fan-out key event_id:endpointId, and dropped outbox-drain.ts's now- redundant "written before finalized" apology. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds transactional webhook event emission, an admin webhook API, secure signed delivery with SSRF protection, event-outbox draining into queue jobs, webhook health reporting, cron wiring, and deployment documentation. ChangesWebhook event and delivery platform
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/mail/ingest.ts`:
- Around line 714-721: Update the appendThreadInTx success branch to emit the
conversation.message_received outbox event only when appended.created is true.
Preserve the existing return behavior for all successful appends, including
replayed results with created false.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 8228189c-b2b9-4fc7-81e5-e45263eecb57
📒 Files selected for processing (26)
specs/deploy/gmail-inbound-runbook.mdsrc/api/agents.test.tssrc/api/index.test.tssrc/api/index.tssrc/api/router.tssrc/api/webhooks.test.tssrc/api/webhooks.tssrc/composition/app.test.tssrc/composition/app.tssrc/composition/health.test.tssrc/composition/health.tssrc/composition/root.tssrc/db/migrate.tssrc/mail/ingest.test.tssrc/mail/ingest.tssrc/store/conversations.test.tssrc/store/conversations.tssrc/webhooks/delivery.test.tssrc/webhooks/delivery.tssrc/webhooks/event-types.test.tssrc/webhooks/event-types.tssrc/webhooks/outbox-drain.test.tssrc/webhooks/outbox-drain.tssrc/webhooks/ssrf.test.tssrc/webhooks/ssrf.tsvercel.json
…CodeRabbit, HT-69) appendThreadInTx's AppendResult.ok:true does not guarantee a row was inserted — created:false is a replay (the get-or-insert found a pre-existing row). The ingestion append path fired conversation.message_received unconditionally on appended.ok, which would re-emit the event with a fresh eventId on a replay, defeating consumer eventId dedupe (spec §4). Verified empirically before fixing: at this call site `created` is always true today (inbound threads never carry an idempotencyKey — migration 003's CHECK forbids it — and InboundDeliveryStore's own ledger dedup already intercepts a genuine redelivery of the same raw message one layer up, never re-invoking this code at all), so this closes a latent type-level gap rather than an actively exploitable double-fire. Checked conversation.created's two call sites for the same exposure: createConversationInTx has no replay concept at all (always a fresh INSERT, no created flag), so no equivalent gate applies there. Added a regression test: redeliver the identical raw message (mailboxId+providerMessageId) and assert exactly one conversation.message_received (and one conversation.created) in the outbox. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Summary
Wave 2 of the module substrate (HT-69; spec §4/§5, merged in #76). Rebased onto main after #77 merged (GitHub closed the stacked #78 on base-branch deletion — this supersedes it; identical content, review trail there).
eventId:endpointId./api/v1/webhooks(+/test), secret shown once; health webhooks section; outbox-drain cron; runbook Part G.Review trail
Sonnet-authored → Opus adversarial review FIX-FIRST (1 MAJOR: soft-delete/delivery race; 1 MINOR) → both fixed (see #78 for the full review record). Producer/consumer signature interop verified byte-exact against the draft-assistant module's verifier by running both sides.
Gates at rebase: typecheck 0; full suite ran green pre-rebase (1230/1230) — CI re-verifies on this base.
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
/testendpoint to enqueue signed webhook delivery jobs.Documentation