Files
Aegis/backend/alembic/versions/b041_operational_alerts.py
kitos d4b147da7c
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
feat(alerts): Phase 13 — Operational Alert Engine
AlertRule + AlertInstance models (b041alerts migration), 8 pre-seeded system
rules (high_risk x2, stale_technique, coverage_regression, low_coverage,
expiry_wave, new_technique, orphan_spike), evaluation engine with per-rule
cooldown, full alert lifecycle (acknowledge/resolve/dismiss), custom rule CRUD,
and summary endpoint. Rules seeded at app startup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 15:25:55 +02:00

83 lines
3.5 KiB
Python

"""Phase 13: Operational Alerts — alert_rules and alert_instances tables.
Revision ID: b041alerts
Revises: b040ent
Create Date: 2026-05-21
"""
from alembic import op
import sqlalchemy as sa
revision = "b041alerts"
down_revision = "b040ent"
branch_labels = None
depends_on = None
def upgrade() -> None:
conn = op.get_bind()
# ── alert_rules ───────────────────────────────────────────────────────────
conn.execute(sa.text("""
CREATE TABLE IF NOT EXISTS alert_rules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(300) NOT NULL,
description TEXT,
rule_type VARCHAR(50) NOT NULL,
severity VARCHAR(20) NOT NULL DEFAULT 'medium',
is_enabled BOOLEAN NOT NULL DEFAULT TRUE,
is_system BOOLEAN NOT NULL DEFAULT FALSE,
config JSONB NOT NULL DEFAULT '{}',
notify_in_app BOOLEAN NOT NULL DEFAULT TRUE,
notify_webhook BOOLEAN NOT NULL DEFAULT FALSE,
webhook_id UUID REFERENCES webhook_configs(id) ON DELETE SET NULL,
cooldown_hours INTEGER NOT NULL DEFAULT 24,
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now(),
last_fired_at TIMESTAMP WITHOUT TIME ZONE
)
"""))
conn.execute(sa.text(
"CREATE INDEX IF NOT EXISTS ix_alert_rules_type ON alert_rules (rule_type)"
))
conn.execute(sa.text(
"CREATE INDEX IF NOT EXISTS ix_alert_rules_enabled ON alert_rules (is_enabled)"
))
# ── alert_instances ───────────────────────────────────────────────────────
conn.execute(sa.text("""
CREATE TABLE IF NOT EXISTS alert_instances (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
rule_id UUID REFERENCES alert_rules(id) ON DELETE SET NULL,
rule_name VARCHAR(300) NOT NULL,
rule_type VARCHAR(50) NOT NULL,
severity VARCHAR(20) NOT NULL,
title VARCHAR(500) NOT NULL,
message TEXT NOT NULL,
details JSONB,
status VARCHAR(20) NOT NULL DEFAULT 'open',
acknowledged_by UUID REFERENCES users(id) ON DELETE SET NULL,
acknowledged_at TIMESTAMP WITHOUT TIME ZONE,
resolved_at TIMESTAMP WITHOUT TIME ZONE,
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now()
)
"""))
conn.execute(sa.text(
"CREATE INDEX IF NOT EXISTS ix_alert_instances_rule_id ON alert_instances (rule_id)"
))
conn.execute(sa.text(
"CREATE INDEX IF NOT EXISTS ix_alert_instances_status ON alert_instances (status)"
))
conn.execute(sa.text(
"CREATE INDEX IF NOT EXISTS ix_alert_instances_severity ON alert_instances (severity)"
))
conn.execute(sa.text(
"CREATE INDEX IF NOT EXISTS ix_alert_instances_created ON alert_instances (created_at)"
))
def downgrade() -> None:
conn = op.get_bind()
conn.execute(sa.text("DROP TABLE IF EXISTS alert_instances CASCADE"))
conn.execute(sa.text("DROP TABLE IF EXISTS alert_rules CASCADE"))