Monitor adapter framework, Sentry and generic

Task 12270 · Queued · Coder · created · updated

## Requirement (verbatim) 2. "Monitor adapters (bring your own): new trigger source kinds `sentry`, `datadog`, `pagerduty`, `cloudwatch` (HTTP ingress with signature verification, beside triggers.py SOURCE_KINDS) normalised into an `incidents` table: fingerprint, first/last seen, count, severity, environment, stack frames, release/commit when sent. One row per fingerprint; recurrences update, never duplicate. Adapters count against the org's trigger quota." Settled: D4 adapters = Sentry, generic webhook shape, Datadog, PagerDuty, CloudWatch (this child: framework + `sentry` + the generic `incident` shape on `webhook`; a sibling adds the other three). D6: the quota clause is dropped — per-trigger caps only. Relaxed AC-11 (architect A-11, flagged for Finish): adapters file nothing, so the per-trigger hourly *filing* cap does not bite on them; adapter ingress gets exactly a webhook's ingress limits (body cap, 120/min per token, 600/min per IP); no org quota. Design: architecture artifact of Task 12249, §5.2, §5.3 (Sentry + webhook rows, `event_alert` vs `issue`, count max rule), §4 adapters/`Observation`, fingerprints, `metrics` shape (validate only). ## Scope (architecture C4) - Add `sentry` to `SOURCE_KINDS` and `HTTP_SOURCE_KINDS` (`triggers.py`), `task_source.trigger_source`/`SOURCE_LABELS`, `trigger_config.SHAPES` (+ `SentryConfig`: `environments[]`, `metrics?`), web `WHEN_CHOICES` and `triggers/vocabulary.ts`; generic `shape: incident` on `webhook`. - Adapter framework: `ingest_http` branches to `adapters.<kind>.verify` (failure = existing 401 refusal row); `trigger_secrets.takes_secret` widened to adapter kinds (sealed like GitHub's); `natural_dedup_key` learns vendor ids (Sentry `Request-ID`); recorder handler normalises via `adapters.<kind>.normalise` → `incidents.record` before the cap; adapter triggers never adopt an automation. - `sentry.py`: verify `Sentry-Hook-Signature` = hex HMAC-SHA256(raw body, client secret), constant time; normalise `issue` (absolute count, firstSeen/lastSeen, level) and `event_alert` (frames, release, increment) to fingerprint `sentry:{issue id}`; severity fatal→critical, error, warning, info/debug. Module docstring holds field map + vendor doc URL; fixtures are Sentry's documented samples (confirm scheme from Sentry docs, never memory). - `generic.py`: documented JSON (`fingerprint`, `title`, `severity` in A-6 words, `environment`, `count`, `release`, `commit`, `stack_frames`, `action: resolve`), existing API-key auth; document the shape in the reader docs or the module docstring. Credentials & reach: - Adapter deliveries (AC-7..11) — none — signed `curl` with the trigger's own Jaah-held secret — **verified** (needs nothing beyond the trigger) - UI checks on dev — `org/console/dev-login` (org Admin), `https://dev.jaah.ai` — credential **verified**; environment **deferred** for this node (operator R4: run from a tailnet node) - Sentry pulls / vendor "send test" — no `sentry` credential — **deferred** — operator R4: fixture tests with vendor sample payloads - AC-29 gates — worktree only — **verified** Out of scope: Do NOT add an org-level trigger quota or plan term (D6). Do NOT store a monitor credential or webhook secret anywhere but the existing trigger secret and credential stores. Do NOT build Datadog/PagerDuty/CloudWatch (sibling) or metric pulls (regression child). Do NOT file tasks. Epic out of scope (verbatim): "building an APM or log store; replacing the customer's monitor; auto-merging a revert without the project's policy; any scoring or ranking logic (the outcome ledger epic reads the states this epic writes)." Done means: - AC-7 (these kinds): "Source kinds ... are offered beside webhook/github, each with its ingress URL and signing secret shown the existing way. ... each one's vendor 'send test' or signed sample produces a `received` → processed delivery row." — create a `sentry` trigger in the console, signed `curl` of the documented sample → delivery `recorded`. - AC-8: "an unsigned or wrongly signed request to each adapter's ingress gets 401 and writes no incident. The delivery log shows the refusal the existing way." — tests for missing/wrong signature on sentry and wrong API key on generic. - AC-9: "a Sentry issue event produces exactly one incidents row carrying fingerprint, first seen, last seen, count, severity, environment, stack frames (file paths and functions) and release/commit when the payload has one. The generic webhook shape does the same from its documented JSON. ... Fields a vendor does not send show as 'not sent' (never blank and never a fake value)." — fixture tests asserting each field; NULL (not a placeholder) when absent. - AC-10: "send the payload twice, then send a resolve, then send it again. Incidents shows one row with count 3 and its state history." — use generic shape or `event_alert` (increment) per architecture §5.3; integration test. - AC-11 (relaxed, quoted above): existing trigger rate-limit tests extended to `sentry` and webhook-incident. - AC-26: with `production_qa` OFF the row is still written — test. - AC-29: `./dc check quick` green; changed-area tests green; merge lane full gate green.

Activity

  • Draft
  • Ready