- Webhook ingestion: HMAC validation for Datadog/PagerDuty/OpsGenie with 5-min timestamp freshness - Payload normalizers: canonical alert schema with severity mapping per provider - Correlation engine: time-window grouping, late-alert attachment (2x window), FakeClock for testing - InMemoryWindowStore for unit tests - Tests: 12 HMAC validation cases, 5 normalizer cases, 7 correlation engine cases - PostgreSQL schema with RLS: tenants, incidents, alerts, webhook_secrets, notification_configs - Free tier enforcement columns (alert_count_month, reset_at) - Fly.io config, Dockerfile, Gitea Actions CI
91 lines
3.5 KiB
SQL
91 lines
3.5 KiB
SQL
-- dd0c/alert V1 schema — DynamoDB-style in PostgreSQL (Neon)
|
|
|
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
|
|
-- Tenants
|
|
CREATE TABLE tenants (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
name TEXT NOT NULL,
|
|
slug TEXT NOT NULL UNIQUE,
|
|
tier TEXT NOT NULL DEFAULT 'free' CHECK (tier IN ('free', 'pro')),
|
|
alert_count_month INT NOT NULL DEFAULT 0,
|
|
alert_count_reset_at TIMESTAMPTZ NOT NULL DEFAULT date_trunc('month', now()) + interval '1 month',
|
|
stripe_customer_id TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- Webhook secrets per provider per tenant
|
|
CREATE TABLE webhook_secrets (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
provider TEXT NOT NULL CHECK (provider IN ('datadog', 'pagerduty', 'opsgenie', 'grafana', 'custom')),
|
|
secret TEXT NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE(tenant_id, provider)
|
|
);
|
|
|
|
-- Incidents (correlation output)
|
|
CREATE TABLE incidents (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
incident_key TEXT NOT NULL,
|
|
fingerprint TEXT NOT NULL,
|
|
service TEXT,
|
|
title TEXT NOT NULL,
|
|
severity TEXT NOT NULL CHECK (severity IN ('critical', 'high', 'medium', 'low', 'info')),
|
|
status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'acknowledged', 'resolved', 'suppressed')),
|
|
alert_count INT NOT NULL DEFAULT 1,
|
|
first_alert_at TIMESTAMPTZ NOT NULL,
|
|
last_alert_at TIMESTAMPTZ NOT NULL,
|
|
resolved_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX idx_incidents_tenant ON incidents(tenant_id, status, created_at DESC);
|
|
CREATE INDEX idx_incidents_fingerprint ON incidents(tenant_id, fingerprint);
|
|
|
|
-- Alerts (raw, linked to incidents)
|
|
CREATE TABLE alerts (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
incident_id UUID REFERENCES incidents(id) ON DELETE SET NULL,
|
|
source_provider TEXT NOT NULL,
|
|
source_id TEXT NOT NULL,
|
|
fingerprint TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
severity TEXT NOT NULL,
|
|
status TEXT NOT NULL,
|
|
service TEXT,
|
|
environment TEXT,
|
|
tags JSONB NOT NULL DEFAULT '{}',
|
|
raw_payload JSONB NOT NULL,
|
|
received_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX idx_alerts_tenant ON alerts(tenant_id, received_at DESC);
|
|
CREATE INDEX idx_alerts_incident ON alerts(incident_id);
|
|
|
|
-- Notification configs
|
|
CREATE TABLE notification_configs (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
channel TEXT NOT NULL CHECK (channel IN ('slack', 'email', 'webhook')),
|
|
config JSONB NOT NULL DEFAULT '{}',
|
|
min_severity TEXT NOT NULL DEFAULT 'medium',
|
|
enabled BOOLEAN NOT NULL DEFAULT true,
|
|
UNIQUE(tenant_id, channel)
|
|
);
|
|
|
|
-- Free tier enforcement: 10K alerts/month, 7-day retention
|
|
-- Pro tier: unlimited alerts, 90-day retention
|
|
|
|
-- RLS
|
|
ALTER TABLE incidents ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE alerts ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE notification_configs ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_iso_incidents ON incidents
|
|
USING (tenant_id::text = current_setting('app.tenant_id', true));
|
|
CREATE POLICY tenant_iso_alerts ON alerts
|
|
USING (tenant_id::text = current_setting('app.tenant_id', true));
|
|
CREATE POLICY tenant_iso_notif ON notification_configs
|
|
USING (tenant_id::text = current_setting('app.tenant_id', true));
|