feat(alerts): Phase 13 — Operational Alert Engine
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
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>
This commit is contained in:
124
backend/app/schemas/operational_alert_schema.py
Normal file
124
backend/app/schemas/operational_alert_schema.py
Normal file
@@ -0,0 +1,124 @@
|
||||
"""Phase 13: Operational Alerts — Pydantic schemas."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
from app.models.operational_alert import AlertRuleType, AlertSeverity, AlertStatus
|
||||
|
||||
VALID_SEVERITIES = {s.value for s in AlertSeverity}
|
||||
VALID_STATUSES = {s.value for s in AlertStatus}
|
||||
VALID_RULE_TYPES = {r.value for r in AlertRuleType}
|
||||
|
||||
|
||||
# ── AlertRule schemas ─────────────────────────────────────────────────────────
|
||||
|
||||
class AlertRuleCreate(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=300)
|
||||
description: Optional[str] = None
|
||||
rule_type: str
|
||||
severity: str = "medium"
|
||||
config: Dict[str, Any] = Field(default_factory=dict)
|
||||
notify_in_app: bool = True
|
||||
notify_webhook: bool = False
|
||||
webhook_id: Optional[UUID] = None
|
||||
cooldown_hours: int = Field(24, ge=0, le=8760)
|
||||
|
||||
@field_validator("rule_type")
|
||||
@classmethod
|
||||
def validate_rule_type(cls, v: str) -> str:
|
||||
if v not in VALID_RULE_TYPES:
|
||||
raise ValueError(f"Invalid rule_type. Valid: {VALID_RULE_TYPES}")
|
||||
return v
|
||||
|
||||
@field_validator("severity")
|
||||
@classmethod
|
||||
def validate_severity(cls, v: str) -> str:
|
||||
if v not in VALID_SEVERITIES:
|
||||
raise ValueError(f"Invalid severity. Valid: {VALID_SEVERITIES}")
|
||||
return v
|
||||
|
||||
|
||||
class AlertRuleUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=300)
|
||||
description: Optional[str] = None
|
||||
severity: Optional[str] = None
|
||||
is_enabled: Optional[bool] = None
|
||||
config: Optional[Dict[str, Any]] = None
|
||||
notify_in_app: Optional[bool] = None
|
||||
notify_webhook: Optional[bool] = None
|
||||
webhook_id: Optional[UUID] = None
|
||||
cooldown_hours: Optional[int] = Field(None, ge=0, le=8760)
|
||||
|
||||
@field_validator("severity")
|
||||
@classmethod
|
||||
def validate_severity(cls, v: Optional[str]) -> Optional[str]:
|
||||
if v is not None and v not in VALID_SEVERITIES:
|
||||
raise ValueError(f"Invalid severity. Valid: {VALID_SEVERITIES}")
|
||||
return v
|
||||
|
||||
|
||||
class AlertRuleOut(BaseModel):
|
||||
id: UUID
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
rule_type: str
|
||||
severity: str
|
||||
is_enabled: bool
|
||||
is_system: bool
|
||||
config: Dict[str, Any]
|
||||
notify_in_app: bool
|
||||
notify_webhook: bool
|
||||
webhook_id: Optional[UUID] = None
|
||||
cooldown_hours: int
|
||||
created_by: Optional[UUID] = None
|
||||
created_at: Optional[datetime] = None
|
||||
last_fired_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# ── AlertInstance schemas ─────────────────────────────────────────────────────
|
||||
|
||||
class AlertInstanceOut(BaseModel):
|
||||
id: UUID
|
||||
rule_id: Optional[UUID] = None
|
||||
rule_name: str
|
||||
rule_type: str
|
||||
severity: str
|
||||
title: str
|
||||
message: str
|
||||
details: Optional[Dict[str, Any]] = None
|
||||
status: str
|
||||
acknowledged_by: Optional[UUID] = None
|
||||
acknowledged_at: Optional[datetime] = None
|
||||
resolved_at: Optional[datetime] = None
|
||||
created_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# ── Evaluation result ─────────────────────────────────────────────────────────
|
||||
|
||||
class EvaluationResult(BaseModel):
|
||||
rules_evaluated: int
|
||||
alerts_fired: int
|
||||
alerts: List[AlertInstanceOut] = Field(default_factory=list)
|
||||
duration_seconds: float
|
||||
|
||||
|
||||
# ── Summary ───────────────────────────────────────────────────────────────────
|
||||
|
||||
class AlertSummary(BaseModel):
|
||||
total_open: int
|
||||
total_acknowledged: int
|
||||
total_resolved: int
|
||||
by_severity: Dict[str, int]
|
||||
by_rule_type: Dict[str, int]
|
||||
recent_alerts: List[AlertInstanceOut] = Field(default_factory=list)
|
||||
Reference in New Issue
Block a user