feat: add all application source code
- apps/api: FastAPI backend with Dockerfile - apps/web: Next.js frontend with Dockerfile - apps/sensor: Signal collection agent - packages: shared packages Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
411
apps/api/src/db/models.py
Normal file
411
apps/api/src/db/models.py
Normal file
@@ -0,0 +1,411 @@
|
||||
"""
|
||||
Database Models
|
||||
===============
|
||||
CTO-201: Approval & AuditLog persistence
|
||||
|
||||
Schema 設計原則:
|
||||
- UUID 主鍵 (PostgreSQL 相容)
|
||||
- JSON 欄位儲存複雜結構
|
||||
- 完整時間戳記
|
||||
- 索引優化查詢
|
||||
"""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
Enum as SQLEnum,
|
||||
Index,
|
||||
Integer,
|
||||
String,
|
||||
Text,
|
||||
JSON,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from src.db.base import Base
|
||||
from src.models.approval import ApprovalStatus, RiskLevel
|
||||
from src.models.incident import Severity, IncidentStatus
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Helper Functions
|
||||
# =============================================================================
|
||||
|
||||
def utc_now() -> datetime:
|
||||
"""Get current UTC datetime"""
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
def generate_uuid() -> str:
|
||||
"""Generate UUID string"""
|
||||
return str(uuid4())
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# ApprovalRecord - 授權記錄持久化
|
||||
# =============================================================================
|
||||
|
||||
class ApprovalRecord(Base):
|
||||
"""
|
||||
授權記錄 - 對應 Pydantic ApprovalRequest
|
||||
|
||||
Note: 與 in-memory TrustEngine 的 ApprovalRequest 同步
|
||||
"""
|
||||
__tablename__ = "approval_records"
|
||||
|
||||
# Primary Key
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=generate_uuid,
|
||||
)
|
||||
|
||||
# Core Fields
|
||||
action: Mapped[str] = mapped_column(String(500), nullable=False)
|
||||
description: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
status: Mapped[str] = mapped_column(
|
||||
SQLEnum(ApprovalStatus),
|
||||
default=ApprovalStatus.PENDING,
|
||||
nullable=False,
|
||||
)
|
||||
risk_level: Mapped[str] = mapped_column(
|
||||
SQLEnum(RiskLevel),
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
# Signature Tracking
|
||||
required_signatures: Mapped[int] = mapped_column(Integer, default=1)
|
||||
current_signatures: Mapped[int] = mapped_column(Integer, default=0)
|
||||
signatures: Mapped[dict[str, Any]] = mapped_column(JSON, default=list)
|
||||
|
||||
# Blast Radius (JSON)
|
||||
blast_radius: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
|
||||
|
||||
# Dry-Run Checks (JSON)
|
||||
dry_run_checks: Mapped[list[dict[str, Any]]] = mapped_column(JSON, default=list)
|
||||
|
||||
# Metadata
|
||||
requested_by: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||
rejection_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
extra_metadata: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True)
|
||||
|
||||
# ==========================================================================
|
||||
# 戰略 B: 告警風暴收斂 (Alert Storm Convergence)
|
||||
# ==========================================================================
|
||||
# 告警指紋 - 根據 namespace + deployment + alert_name 產生的唯一 Hash
|
||||
fingerprint: Mapped[str | None] = mapped_column(
|
||||
String(64),
|
||||
nullable=True,
|
||||
index=True,
|
||||
comment="SHA256 hash of alert identity (namespace:deployment:alert_name)",
|
||||
)
|
||||
# 聚合次數 - 相同指紋告警的累計觸發次數
|
||||
hit_count: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
default=1,
|
||||
nullable=False,
|
||||
comment="Number of times this alert pattern was triggered",
|
||||
)
|
||||
# 最後觸發時間 - 同指紋告警最近一次出現的時間
|
||||
last_seen_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
default=utc_now,
|
||||
nullable=False,
|
||||
comment="Last time this alert pattern was seen",
|
||||
)
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
default=utc_now,
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
default=utc_now,
|
||||
onupdate=utc_now,
|
||||
)
|
||||
expires_at: Mapped[datetime | None] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=True,
|
||||
)
|
||||
resolved_at: Mapped[datetime | None] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=True,
|
||||
)
|
||||
|
||||
# Indexes
|
||||
__table_args__ = (
|
||||
Index("ix_approval_status", "status"),
|
||||
Index("ix_approval_risk_level", "risk_level"),
|
||||
Index("ix_approval_created_at", "created_at"),
|
||||
Index("ix_approval_requested_by", "requested_by"),
|
||||
Index("ix_approval_fingerprint", "fingerprint"), # 戰略 B: 指紋查詢優化
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# AuditLog - 稽核日誌
|
||||
# =============================================================================
|
||||
|
||||
class TimelineEvent(Base):
|
||||
"""
|
||||
時間軸事件 - Phase 4 Action Timeline
|
||||
|
||||
事件類型:
|
||||
- system: 系統告警接收
|
||||
- agent: ClawBot AI 分析
|
||||
- security: 權限阻擋
|
||||
- human: 人類授權
|
||||
- exec: 執行完成
|
||||
"""
|
||||
__tablename__ = "timeline_events"
|
||||
|
||||
# Primary Key
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=generate_uuid,
|
||||
)
|
||||
|
||||
# Event Type & Status
|
||||
event_type: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
comment="system, agent, security, human, exec",
|
||||
)
|
||||
status: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
default="info",
|
||||
comment="info, success, warning, error",
|
||||
)
|
||||
|
||||
# Content
|
||||
title: Mapped[str] = mapped_column(String(500), nullable=False)
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
|
||||
# Actor
|
||||
actor: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
actor_role: Mapped[str | None] = mapped_column(String(50), nullable=True)
|
||||
|
||||
# Context
|
||||
risk_level: Mapped[str | None] = mapped_column(String(20), nullable=True)
|
||||
approval_id: Mapped[str | None] = mapped_column(String(36), nullable=True, index=True)
|
||||
|
||||
# Timestamp
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
default=utc_now,
|
||||
)
|
||||
|
||||
# Indexes
|
||||
__table_args__ = (
|
||||
Index("ix_timeline_event_type", "event_type"),
|
||||
Index("ix_timeline_created_at", "created_at"),
|
||||
)
|
||||
|
||||
|
||||
class AuditLog(Base):
|
||||
"""
|
||||
稽核日誌 - 記錄所有執行結果
|
||||
|
||||
每次 K8s 操作完成後寫入一筆記錄
|
||||
"""
|
||||
__tablename__ = "audit_logs"
|
||||
|
||||
# Primary Key
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=generate_uuid,
|
||||
)
|
||||
|
||||
# Reference to Approval
|
||||
approval_id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
|
||||
# Operation Details
|
||||
operation_type: Mapped[str] = mapped_column(
|
||||
String(50),
|
||||
nullable=False,
|
||||
comment="e.g., RESTART_DEPLOYMENT, DELETE_POD",
|
||||
)
|
||||
target_resource: Mapped[str] = mapped_column(
|
||||
String(200),
|
||||
nullable=False,
|
||||
comment="e.g., deployment/api-backend, pod/nginx-xxx",
|
||||
)
|
||||
namespace: Mapped[str] = mapped_column(
|
||||
String(63),
|
||||
default="default",
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
# Execution Result
|
||||
success: Mapped[bool] = mapped_column(default=False, nullable=False)
|
||||
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
|
||||
# K8s Response (Raw)
|
||||
k8s_response: Mapped[dict[str, Any] | None] = mapped_column(
|
||||
JSON,
|
||||
nullable=True,
|
||||
comment="Raw Kubernetes API response",
|
||||
)
|
||||
|
||||
# Execution Context
|
||||
executed_by: Mapped[str] = mapped_column(
|
||||
String(100),
|
||||
nullable=False,
|
||||
comment="Who triggered the execution",
|
||||
)
|
||||
execution_duration_ms: Mapped[int | None] = mapped_column(
|
||||
Integer,
|
||||
nullable=True,
|
||||
comment="Execution time in milliseconds",
|
||||
)
|
||||
|
||||
# Dry-Run Result (pre-execution validation)
|
||||
dry_run_passed: Mapped[bool] = mapped_column(
|
||||
default=True,
|
||||
nullable=False,
|
||||
)
|
||||
dry_run_message: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
default=utc_now,
|
||||
)
|
||||
|
||||
# Indexes
|
||||
__table_args__ = (
|
||||
Index("ix_audit_approval_id", "approval_id"),
|
||||
Index("ix_audit_operation_type", "operation_type"),
|
||||
Index("ix_audit_success", "success"),
|
||||
Index("ix_audit_created_at", "created_at"),
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# IncidentRecord - Phase 6.2 Episodic Memory (PostgreSQL)
|
||||
# =============================================================================
|
||||
|
||||
class IncidentRecord(Base):
|
||||
"""
|
||||
事件記錄 - 對應 Pydantic Incident Schema v0.3
|
||||
|
||||
Phase 6.2: Episodic Memory (長期記憶)
|
||||
- 從 Working Memory (Redis) 遷移過來
|
||||
- 永久保留,供 RAG 檢索
|
||||
- 複雜結構使用 JSONB 欄位
|
||||
|
||||
三層記憶架構:
|
||||
- Working Memory (Redis): 7 天 TTL
|
||||
- Episodic Memory (PostgreSQL): 此表,永久保留
|
||||
- Semantic Memory (Vector DB): Phase 6.3+
|
||||
"""
|
||||
__tablename__ = "incidents"
|
||||
|
||||
# === 主鍵 ===
|
||||
incident_id: Mapped[str] = mapped_column(
|
||||
String(30),
|
||||
primary_key=True,
|
||||
comment="事件唯一識別碼 (如 INC-20260322-A1B2C3)",
|
||||
)
|
||||
|
||||
# === 狀態與嚴重度 ===
|
||||
status: Mapped[str] = mapped_column(
|
||||
SQLEnum(IncidentStatus),
|
||||
default=IncidentStatus.INVESTIGATING,
|
||||
nullable=False,
|
||||
comment="事件狀態 (investigating, mitigating, resolved, closed, escalated)",
|
||||
)
|
||||
severity: Mapped[str] = mapped_column(
|
||||
SQLEnum(Severity),
|
||||
nullable=False,
|
||||
comment="事件嚴重度 (P0, P1, P2, P3)",
|
||||
)
|
||||
|
||||
# === 感知層 (Signals) - JSONB ===
|
||||
signals: Mapped[list[dict[str, Any]]] = mapped_column(
|
||||
JSON,
|
||||
default=list,
|
||||
nullable=False,
|
||||
comment="關聯的告警信號列表 (JSONB)",
|
||||
)
|
||||
affected_services: Mapped[list[str]] = mapped_column(
|
||||
JSON,
|
||||
default=list,
|
||||
nullable=False,
|
||||
comment="受影響的服務列表",
|
||||
)
|
||||
|
||||
# === 認知層 (AI Decision Chain) - JSONB ===
|
||||
decision_chain: Mapped[dict[str, Any] | None] = mapped_column(
|
||||
JSON,
|
||||
nullable=True,
|
||||
comment="AI 決策鏈 (完整推論過程)",
|
||||
)
|
||||
|
||||
# === 決策層 (Proposals) ===
|
||||
proposal_ids: Mapped[list[str]] = mapped_column(
|
||||
JSON,
|
||||
default=list,
|
||||
nullable=False,
|
||||
comment="關聯的 ApprovalRequest ID 列表",
|
||||
)
|
||||
|
||||
# === 結果層 (Outcome) - JSONB ===
|
||||
outcome: Mapped[dict[str, Any] | None] = mapped_column(
|
||||
JSON,
|
||||
nullable=True,
|
||||
comment="事件結果與人類回饋",
|
||||
)
|
||||
|
||||
# === 時間軸 ===
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
default=utc_now,
|
||||
nullable=False,
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
default=utc_now,
|
||||
onupdate=utc_now,
|
||||
nullable=False,
|
||||
)
|
||||
resolved_at: Mapped[datetime | None] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=True,
|
||||
)
|
||||
closed_at: Mapped[datetime | None] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=True,
|
||||
)
|
||||
|
||||
# === 記憶管理 ===
|
||||
ttl_days: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
default=7,
|
||||
nullable=False,
|
||||
comment="Working Memory TTL (天)",
|
||||
)
|
||||
vectorized: Mapped[bool] = mapped_column(
|
||||
default=False,
|
||||
nullable=False,
|
||||
comment="是否已向量化到 Vector DB (Semantic Memory)",
|
||||
)
|
||||
|
||||
# === 索引 ===
|
||||
__table_args__ = (
|
||||
Index("ix_incident_status", "status"),
|
||||
Index("ix_incident_severity", "severity"),
|
||||
Index("ix_incident_created_at", "created_at"),
|
||||
Index("ix_incident_resolved_at", "resolved_at"),
|
||||
)
|
||||
Reference in New Issue
Block a user