- 刪除舊版 clawbot.py (已有新版 openclaw.py) - 更新 models/ai.py 類型定義 (ClawBotAnalysisRequest/Response) - 更新 api/v1/ai.py import 與註解 - 更新 Discord username - 更新所有註解與文檔 依據: feedback_openclaw_naming.md (統帥 2026-03-20 正式命名決議) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
413 lines
12 KiB
Python
413 lines
12 KiB
Python
"""
|
|
Database Models
|
|
===============
|
|
CTO-201: Approval & AuditLog persistence
|
|
|
|
Schema 設計原則:
|
|
- UUID 主鍵 (PostgreSQL 相容)
|
|
- JSON 欄位儲存複雜結構
|
|
- 完整時間戳記
|
|
- 索引優化查詢
|
|
"""
|
|
|
|
from datetime import UTC, datetime
|
|
from typing import Any
|
|
from uuid import uuid4
|
|
|
|
from sqlalchemy import (
|
|
JSON,
|
|
DateTime,
|
|
Index,
|
|
Integer,
|
|
String,
|
|
Text,
|
|
)
|
|
from sqlalchemy import (
|
|
Enum as SQLEnum,
|
|
)
|
|
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 IncidentStatus, Severity
|
|
|
|
# =============================================================================
|
|
# Helper Functions
|
|
# =============================================================================
|
|
|
|
def utc_now() -> datetime:
|
|
"""Get current UTC datetime"""
|
|
return datetime.now(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: OpenClaw 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"),
|
|
)
|