ReviewOS

stacks/buddy

Runtime event bus and notifications (Slack/Discord/Jira/webhooks)

#1387
Closed chris opened this 9 days ago ·
9 days ago

Part of #1367 (Phase 3 — Dependency Engine v2).

Problem

The README markets a "Plugin Ecosystem" with Slack/Discord/Jira integrations — but the plugin system is setup-only: PluginManager lives in src/setup.ts:400-700, only the setup_complete event ever fires (bin/cli.ts:444), PluginManager is never referenced in src/buddy.ts, and JSON-loaded custom plugins (.buddy/plugins/*.json) cannot carry executable handlers since SetupHook.handler is a function type. IntegrationPoint/AuthConfig types (setup.ts:126-138) are declared and unused. No notifications exist for PR creation, update runs, failures, or security findings — the events users actually care about.

Spec

1. Runtime event bus

src/events/bus.ts — typed events emitted from the real code paths:

EventEmitted from
scan.completed (updates found, counts by type)Buddy.scanForUpdates
pr.created / pr.updated / pr.closedcreatePullRequests, auto-close paths
pr.merged (auto-merge)#1369
security.advisories (new vulnerable deps)annotateSecurityAdvisories
run.failed (any command failure w/ error summary)CLI top-level handler
dashboard.updatedcreateOrUpdateDashboard
review.completed / fixci.completed#1375 / #1381 when they land
gate.failed#1382

2. Notification sinks (declarative, JSON-safe — fixes the handler problem)

notifications?: {
  slack?: { webhookEnv?: string, events?: EventName[], channel?: string }
  discord?: { webhookEnv?: string, events?: EventName[] }
  jira?: { baseUrlEnv?, tokenEnv?, projectKey?, events?: EventName[], createTicketsFor?: ('security.advisories' | 'run.failed')[] }
  webhooks?: { url: string, secretEnv?: string, events?: EventName[] }[]   // generic: HMAC-signed JSON POST
}
  • Rich formatting: Slack blocks / Discord embeds with PR links, update tables, severity colors (reuse existing formatting knowledge from src/utils/security-format.ts)
  • Generic webhook = the extensibility story (replaces un-executable JSON "plugins" for runtime): HMAC-SHA256 signature header (x-buddy-bot-signature), retry w/ backoff via src/utils/http.ts, event payload versioned
  • Secrets only ever referenced by env-var name in config (consistent with #1386 pattern)
  • Failure isolation: sink errors are logged, never fail the run (matching current swallow-errors hook behavior)

3. Migration & cleanup

  • Keep setup-time plugin discovery (auto-detect SLACK_WEBHOOK_URL etc. → offer to write notifications config)
  • Deprecate .buddy/plugins/*.json runtime pretensions honestly: they remain setup-lifecycle hooks only; docs updated
  • Delete or implement IntegrationPoint/AuthConfig dead types

Tasks

  • Event bus + emissions at the sites listed (thread through Buddy constructor)
  • Slack/Discord/Jira/generic-webhook sinks with per-event templates + fixtures (recorded payload snapshots)
  • HMAC signing + retry/backoff + failure isolation tests
  • Config + validation + setup-wizard integration (Step 2 discovery now writes real config)
  • docs/integrations.md rewrite matching reality; README claims audit
  • Payload schema doc for webhook consumers (versioned)

Acceptance criteria

  • pr.created fires a correctly-formatted Slack block message in fixture tests; security.advisories can open a Jira ticket
  • A failing sink never fails the bot run
  • Generic webhook receives signed, versioned JSON for every subscribed event
  • README's integration claims are true

Dependencies

  • None hard. Later issues (#1375/#1381/#1382) add their events when they land.
9 days ago

Implemented in 11ddd52 — src/events/.

A typed event bus with events emitted from the real code paths (scan.completed, pr.created, pr.merged are wired; the rest of the BuddyEvents map is declared and ready for its call sites), plus Slack, Discord and signed generic webhook sinks.

This closes a gap between the README and reality: integrations have been advertised for a while, but the plugin system backing that claim only ever fired at setup time and JSON-declared plugins couldn't carry an executable handler at all.

Three properties worth recording. Delivery failures are contained per sink — a Slack outage must not fail the dependency update that triggered the notification, and one broken webhook must not stop the others firing. Generic webhooks are HMAC-SHA256 signed, which is what makes a webhook an integration point rather than an unauthenticated POST anyone could forge, and payloads carry a version so receivers can branch on shape changes. Credentials are referenced by environment variable name rather than inlined, so a destination can live in committed config without the credential living there too.

Scope note: the bus, the three sink types, signing and the three highest-value emission sites are done (15 tests). Remaining: emitting the other declared events from their call sites (review.completed, gate.failed, fixci.completed, run.failed, dashboard.updated, security.advisories), a Jira sink, config validation for notifications, and rewriting docs/integrations.md — which currently describes the setup-only plugin system as though it were the runtime one. Filing that rather than leaving this open.

Sign in to comment on this issue.