Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
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>
83 lines
3.5 KiB
Python
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"))
|