feat(rpc): type and gate alarm trigger conditions

Merged
FI
1d ago
+142-20

Summary

Addresses part of #958 — the "typed trigger conditions plus RPC validation" slice from the delivery plan, not the full issue. Evaluators, alarm_states, scheduling, and the dashboard picker/preview stay out of scope, pending the maintainer's answer to the issue's open architecture question ("where should the evaluator run?").

  • Replaced triggerConditions: z.record(z.string(), z.unknown()) with a discriminated union on triggerType, following the same pattern already used for destinations (z.discriminatedUnion("type", [...])) in this file:
    • uptime → { monitorIds: string[] } (defaults to []), matching what the code already expected informally (alarmMonitorIds in the dashboard and uptime-transition-alerts.ts already just check Array.isArray(...)).
    • traffic_spike / error_rate → typed per the shapes proposed in the issue, so the contract is documented, but see below — they're not creatable yet.
  • alarms.create and alarms.update now reject any trigger type other than uptime with a BAD_REQUEST explaining there's no evaluator for it yet, instead of silently accepting and persisting a dead alarm. update resolves the effective trigger type/conditions against the existing row when only one of the two fields is sent (e.g. the monitor-linking flow in monitor-sheet.tsx, which patches triggerConditions without triggerType).
  • Read paths (alarms.list, the alarms.create/update output schema) are unchanged and stay permissive (z.record), so any row written before this change — regardless of trigger type or conditions shape — stays listable. Only writes are tightened.
  • Added packages/rpc/src/routers/alarms.test.ts covering: valid/defaulted uptime conditions, malformed uptime conditions, and rejection of traffic_spike/error_rate/unknown types.

Verification

  • Checked the dashboard: alarm-sheet.tsx always hardcodes triggerType: "uptime" on create and has no UI for selecting traffic_spike/error_rate — there's nothing to remove, the tightened API doesn't break any existing UI flow.
  • Checked the create→link-monitor flow (alarm-sheet.tsx creates with triggerConditions: {}, then monitor-sheet.tsx later patches in monitorIds via alarms.update): still works because monitorIds defaults to [].
  • Ran bun run check-types, bun run lint, and packages/rpc's full bun run test (223 pass) locally against Postgres/Redis/ClickHouse. The pre-push hook additionally ran the full monorepo bun run test (1101+ pass across all packages) — all green.
  • Local dev DB was empty, so I couldn't inspect real stored alarm rows directly; reasoned through the code paths above instead (schema defaults handle the only row shape the app itself ever writes for uptime).

Out of scope (left for follow-up PRs, per the issue's delivery plan)

  • The actual traffic_spike/error_rate evaluators, alarm_states table, and BullMQ scheduling (step 2/3 in the issue).
  • The dashboard trigger-type picker and "would have fired" preview (step 4) — moot until an evaluator exists.

Disclosure: implemented with AI assistance (Claude Code); verified and tested by hand before submitting.


Summary by cubic

Types and gates alarm trigger conditions so alarms.create/update reject alarms nothing can evaluate. Previously any trigger type was accepted with untyped conditions, so traffic_spike and error_rate alarms saved but could never fire. Writes now validate triggerConditions against triggerType: uptime requires monitorIds: string[] (defaulting to []), and other types get a BAD_REQUEST. Webhook header blocking now reuses the shared FORBIDDEN_WEBHOOK_HEADERS export so alarms reject the headers delivery actually drops. Read paths stay permissive, so alarms written before this change remain listable. update resolves the effective trigger type and conditions against the stored row via a pure resolveTriggerUpdate helper, keeping single-field updates like the monitor-linking flow working. Adds packages/rpc/src/routers/alarms.test.ts covering defaulted and malformed uptime conditions, single-field resolution, and rejection of unsupported trigger types.