@caisson-sh/alerting
v0.3.3
Published
SOC2 CC7.2 multi-channel alerting pipeline: dedup, rate-cap with digest fallback, IANA-timezone quiet hours with a critical override, multi-channel delivery (email, webhook, Slack, Telegram), and a structured audit log.
Readme
@caisson-sh/alerting
A SOC2 CC7.2 multi-channel alerting pipeline — the concrete "anomaly detection and timely notification" control primitive. ADR-0135 (pipeline) · ADR-0151 (transport).
What it gives you
- Five-stage pipeline, pure where possible, injected
now: dedup (suppress a repeat while an incident sharing itsdedupeKeyis open) → rate-cap + digest (per-recipient cap, digest fallback once the recent count hits the policy) → IANA-timezone quiet hours (via stdlibIntl, no tz dependency; acriticalevent always overrides) → multi-channel delivery → structured audit log. - One
AlertChannelport, five drivers. Capture (in-memory, tests), Email (delegates to the injected@caisson-sh/emailEmailer— no second email path), Webhook (+ optional HMAC signing), Slack, Telegram. Every network driver is injected config +fetchWithTimeout+ a@caisson-sh/kerneltyped error on non-ok without the response body.deliverAllruns every channel and isolates a failing one — the rest still deliver. - A data-only
EventTypeRegistry(eventType -> { defaultSeverity, channels, ratePolicy }) — a small seed, not the full 12-type reference; extend per real usage. - Plain-Postgres audit logging, explicitly NOT hash-chained WORM — see
src/migrations/0001_alert_audit.sql.@caisson-sh/audit-wormowns tamper-evidence; this owns "what happened and why", one row per outcome.
Entry points
.— the full surface, including the five network delivery drivers (node-capable)../browser— the event contract, all three decision stages, the delivery port with its isolation wrapper and capture driver, the audit port with its in-memory driver, andprocessAlertitself: safe inside a client bundle. The network drivers are deliberately absent (a browser cannot hold a webhook signing secret, and the SSRF re-check resolves DNS). Every name on./browseris also on..
Use
import {
processAlert,
createCaptureChannel,
createEmailChannel,
createWebhookChannel,
createInMemoryAuditSink,
} from "@caisson-sh/alerting";
const result = await processAlert(event, {
openIncidents, // { dedupeKey }[] — from your incident store
recentCount, // this recipient's sends in the current rate-cap window
ratePolicy: { maxPerWindow: 5 },
recipientTz: "America/New_York",
quietPolicy: { startHour: 22, endHour: 7 },
now: new Date(),
channels: [createEmailChannel(emailer), createWebhookChannel({ url })],
auditSink: createInMemoryAuditSink(), // or a Postgres-backed AlertAuditSink
});
// result.outcome: "delivered" | "suppressed" | "held" | "digested"Drivers
| Channel | Factory | Transport |
| -------- | ------------------------------- | ------------------------------------------------------- |
| Capture | createCaptureChannel() | In-memory; test-only. |
| Email | createEmailChannel(emailer) | Delegates to an injected @caisson-sh/email Emailer. |
| Webhook | createWebhookChannel(config) | fetchWithTimeout POST; optional HMAC signature. |
| Slack | createSlackChannel(config) | fetchWithTimeout POST to an incoming-webhook URL. |
| Telegram | createTelegramChannel(config) | fetchWithTimeout POST to a bot-API sendMessage. |
Tests
bun test packages/alerting/src — dedup suppression, the rate-cap boundary, quiet-hours hold +
critical override, capture-channel delivery recording, deliverAll per-channel isolation, and
processAlert audit-row outcomes (delivered/suppressed/digested/held). No live network — capture
drivers only.
