Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled
2026-04-18 下午(台北時區)—— ogt + Claude Opus 4.7 (1M)
MoWoooWorkDown 假警報 RCA 暴露三重結構性失守:
- 110/188 主機 load 18/16 × 13 天 / cadvisor 288% / K3s 120/121 無監控
- Prometheus 僅 35 targets / 58 rules(覆蓋不到三成)
- HostHighCpuLoad 量錯維度(CPU idle vs load_avg)
統帥戰略指令:
- 全景資產 × 七大自動化 × 永久化 DB
- AI 四分工(OpenClaw × NemoTron × Hermes × Claude LLM)
- 所有自動化操作歷程必進 DB,不靠 MD(MD 會漂移)
本 commit 交付:
1. SQL migration (apps/api/migrations/adr090_asset_inventory_foundation.sql)
- 11 張表 + 33 indexes + 20 CHECK + 3 UNIQUE + 16 FK
- pgcrypto extension dependency
- 完整 idempotent(CREATE IF NOT EXISTS + single transaction)
- 已 apply 進 awoooi_prod(188 PG),驗證通過
2. ADR-090 (docs/adr/ADR-090-monitoring-blindspot-governance.md)
- 決策紀錄 + 7 引擎對映 + 4 替代方案否決
3. 主戰略文件 (docs/superpowers/specs/2026-04-18-blindspot-governance-capacity-l4.md)
- §0-§14: 背景 / 根因 / Schema DDL / 4 層防禦 / 7 Phase 實施 /
HARD_RULES / AI 分工矩陣 / 驗收指標 / 技術債 / 回滾 / 接手協議
4. MASTER §8 Living Changelog 追加 Phase 7 啟動條目
11 張表:
asset_inventory / asset_discovery_run / asset_coverage_snapshot /
asset_relationship / alert_rule_catalog / asset_change_event /
asset_compliance_snapshot / host_capacity_snapshot /
capacity_violation_event / automation_operation_log /
ai_collaboration_trace
首筆 bootstrap 記錄已 seed 進 asset_discovery_run
(run_id=6760c5bf-57e5-4a40-b82d-31b794464652)
相關 Memory (未 commit,存於 ~/.claude/...):
- project_blindspot_governance.md (跨 session 指針)
- feedback_monitor_self_monitoring.md (監控工具必須被監控)
- feedback_secrets_leak_incidents_2026-04-18.md (憑證外洩三防線)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
608 lines
28 KiB
SQL
608 lines
28 KiB
SQL
-- ADR-090: 監控盲區治理 + 資產盤點 × 7 項自動化覆蓋矩陣永久化 DB
|
||
-- 建立時間: 2026-04-18 下午 (台北時區)
|
||
-- 建立者: ogt + Claude Opus 4.7 (1M context)(亞太)
|
||
--
|
||
-- 上游:
|
||
-- - 主戰略: docs/superpowers/specs/2026-04-18-blindspot-governance-capacity-l4.md §5.2
|
||
-- - ADR: docs/adr/ADR-090-monitoring-blindspot-governance.md
|
||
-- - MEMORY: project_blindspot_governance.md
|
||
--
|
||
-- 設計說明:
|
||
-- 本檔建立 11 張表作為 AWOOOI L4 AIOps 的資產盤點 + 自動化覆蓋 + AI 協作稽核地基。
|
||
-- 目標: 把治理從 Markdown 搬進 PostgreSQL,讓 AI 四分工 (OpenClaw × NemoTron ×
|
||
-- Hermes × Claude LLM) 在結構化資料上做決策,且每次動作必留 trail。
|
||
--
|
||
-- 對應七大自動化引擎:
|
||
-- E1 自動監控 / E2 自動告警 / E3 自動建規則 / E4 自動匹配
|
||
-- E5 自動 Playbook / E6 自動修復 / E7 自動 KM
|
||
--
|
||
-- 執行順序:
|
||
-- Step 0: pgcrypto extension (gen_random_uuid 需要)
|
||
-- Step 1: asset_inventory — 全景資產主表
|
||
-- Step 2: asset_discovery_run — 每次盤點 header
|
||
-- Step 3: asset_coverage_snapshot — 資產 × 7 自動化覆蓋矩陣
|
||
-- Step 4: asset_relationship — 資產依賴圖 (爆炸半徑)
|
||
-- Step 5: alert_rule_catalog — 告警規則本身即資產
|
||
-- Step 6: asset_change_event — 資產變化追蹤
|
||
-- Step 7: asset_compliance_snapshot — SSL/CVE/secret/backup 合規
|
||
-- Step 8: host_capacity_snapshot — 主機容量快照 (NemoTron 每日 02:00 寫)
|
||
-- Step 9: capacity_violation_event — 配額違規
|
||
-- Step 10: automation_operation_log — 所有 AI 自動化動作稽核主表 🔴
|
||
-- Step 11: ai_collaboration_trace — 多 Agent 協作逐步 (辯證歷程)
|
||
-- Step 12: 驗收查詢 (comment-only)
|
||
--
|
||
-- Idempotent 鐵律:
|
||
-- - CREATE TABLE IF NOT EXISTS
|
||
-- - CREATE INDEX IF NOT EXISTS
|
||
-- - CHECK constraint 寫在 CREATE TABLE 內,依賴 IF NOT EXISTS 保護
|
||
-- - 本檔可重複執行安全 (rerun 不會破壞既有資料)
|
||
--
|
||
-- 回滾:
|
||
-- DROP TABLE IF EXISTS ai_collaboration_trace, automation_operation_log,
|
||
-- capacity_violation_event, host_capacity_snapshot, asset_compliance_snapshot,
|
||
-- asset_change_event, alert_rule_catalog, asset_relationship,
|
||
-- asset_coverage_snapshot, asset_discovery_run, asset_inventory CASCADE;
|
||
--
|
||
-- ============================================================================
|
||
-- Step 0: pgcrypto extension (gen_random_uuid)
|
||
-- ============================================================================
|
||
|
||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||
|
||
|
||
-- ============================================================================
|
||
-- Step 1: asset_inventory — 全景資產主表
|
||
-- 用途: 主機 / 容器 / K8s workload / DB / 網站 / API / 套件 / 日誌 / KM / 前端 /
|
||
-- 後端 / 容器 / Gitea / CI-CD 全部無例外
|
||
-- 主寫者: scanner (asset_discovery) + NemoTron (capacity 欄位)
|
||
-- ============================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS asset_inventory (
|
||
asset_id BIGSERIAL PRIMARY KEY,
|
||
asset_key TEXT NOT NULL UNIQUE,
|
||
asset_type TEXT NOT NULL,
|
||
parent_asset_id BIGINT REFERENCES asset_inventory(asset_id),
|
||
environment TEXT NOT NULL DEFAULT 'prod',
|
||
host TEXT,
|
||
namespace TEXT,
|
||
name TEXT NOT NULL,
|
||
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||
tags TEXT[] NOT NULL DEFAULT '{}',
|
||
owner_team TEXT,
|
||
criticality TEXT,
|
||
data_classification TEXT,
|
||
external BOOLEAN NOT NULL DEFAULT false,
|
||
lifecycle_state TEXT NOT NULL DEFAULT 'active',
|
||
source_repo TEXT,
|
||
source_commit_sha TEXT,
|
||
|
||
-- 容量欄位 (Layer 4 AI 巡檢用)
|
||
cpu_avg_7d NUMERIC(5,2),
|
||
mem_avg_7d NUMERIC(5,2),
|
||
capacity_headroom NUMERIC(5,2),
|
||
resource_limits JSONB,
|
||
resource_requests JSONB,
|
||
quota_violation_count INT NOT NULL DEFAULT 0,
|
||
sla_target JSONB,
|
||
cost_monthly_usd NUMERIC(10,2),
|
||
|
||
-- 生命週期時間戳
|
||
first_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
decommissioned_at TIMESTAMPTZ,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
||
CONSTRAINT asset_inventory_criticality_valid
|
||
CHECK (criticality IS NULL OR criticality IN ('P0','P1','P2','P3')),
|
||
CONSTRAINT asset_inventory_data_class_valid
|
||
CHECK (data_classification IS NULL OR data_classification IN
|
||
('public','internal','sensitive','secret')),
|
||
CONSTRAINT asset_inventory_lifecycle_valid
|
||
CHECK (lifecycle_state IN
|
||
('planned','provisioning','active','degraded','deprecated','decommissioned')),
|
||
CONSTRAINT asset_inventory_type_valid
|
||
CHECK (asset_type IN (
|
||
'host','container','k8s_workload','k8s_resource','database','table',
|
||
'website','api_endpoint','package','log_stream','km_entry',
|
||
'frontend','backend','ci_pipeline','gitea_repo','monitoring_target',
|
||
'secret','volume','network','certificate','scheduled_job',
|
||
'message_queue','cache','dashboard','ai_agent','llm_model',
|
||
'third_party_service','backup_target'
|
||
))
|
||
);
|
||
|
||
COMMENT ON TABLE asset_inventory IS
|
||
'ADR-090: 全景資產主表。每一個主機/容器/K8s workload/DB/網站/API/套件/...都有一筆,跨 run 沿用同 asset_id。';
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_asset_inventory_type_host
|
||
ON asset_inventory(asset_type, host);
|
||
CREATE INDEX IF NOT EXISTS idx_asset_inventory_env_lifecycle
|
||
ON asset_inventory(environment, lifecycle_state);
|
||
CREATE INDEX IF NOT EXISTS idx_asset_inventory_metadata_gin
|
||
ON asset_inventory USING GIN (metadata);
|
||
CREATE INDEX IF NOT EXISTS idx_asset_inventory_tags_gin
|
||
ON asset_inventory USING GIN (tags);
|
||
CREATE INDEX IF NOT EXISTS idx_asset_inventory_active_last_seen
|
||
ON asset_inventory(last_seen_at DESC)
|
||
WHERE lifecycle_state = 'active';
|
||
-- 註: partial index 只索引 active 資產,按最近出現時間排序
|
||
|
||
|
||
-- ============================================================================
|
||
-- Step 2: asset_discovery_run — 每次盤點 header
|
||
-- 用途: 記錄每次全景掃描的起止時間、掃描範圍、掃到什麼、新增/消失多少
|
||
-- 觸發: cron (每日) / ai (proactive_inspector) / human (手動) / incident
|
||
-- ============================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS asset_discovery_run (
|
||
run_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
triggered_by TEXT NOT NULL,
|
||
scope TEXT[] NOT NULL,
|
||
scan_depth TEXT NOT NULL DEFAULT 'shallow',
|
||
host_filter TEXT[],
|
||
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
ended_at TIMESTAMPTZ,
|
||
status TEXT NOT NULL,
|
||
total_assets INT,
|
||
new_assets INT NOT NULL DEFAULT 0,
|
||
modified_assets INT NOT NULL DEFAULT 0,
|
||
disappeared_assets INT NOT NULL DEFAULT 0,
|
||
tools_used JSONB,
|
||
duration_ms INT,
|
||
error TEXT,
|
||
summary JSONB,
|
||
|
||
CONSTRAINT asset_discovery_run_status_valid
|
||
CHECK (status IN ('running','success','partial','failed','aborted')),
|
||
CONSTRAINT asset_discovery_run_scan_depth_valid
|
||
CHECK (scan_depth IN ('shallow','deep','full'))
|
||
);
|
||
|
||
COMMENT ON TABLE asset_discovery_run IS
|
||
'ADR-090: 每次資產盤點的 header。run_id 作為下游 snapshot/event/change 的關聯主鍵。';
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_asset_discovery_run_started
|
||
ON asset_discovery_run(started_at DESC);
|
||
CREATE INDEX IF NOT EXISTS idx_asset_discovery_run_status
|
||
ON asset_discovery_run(status) WHERE status IN ('running','failed','partial');
|
||
|
||
|
||
-- ============================================================================
|
||
-- Step 3: asset_coverage_snapshot — 資產 × 7 項自動化 覆蓋矩陣
|
||
-- 用途: 每個資產在 7 個自動化維度上的覆蓋狀態 (green/yellow/red)
|
||
-- 鐵律: 每次 discovery_run 為每個 asset 寫 7 筆 (7 dimensions)
|
||
-- ============================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS asset_coverage_snapshot (
|
||
snapshot_id BIGSERIAL PRIMARY KEY,
|
||
run_id UUID NOT NULL REFERENCES asset_discovery_run(run_id) ON DELETE CASCADE,
|
||
asset_id BIGINT NOT NULL REFERENCES asset_inventory(asset_id),
|
||
dimension TEXT NOT NULL,
|
||
coverage_status TEXT NOT NULL,
|
||
evidence JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||
gap_reason TEXT,
|
||
recommended_action TEXT,
|
||
confidence NUMERIC(3,2),
|
||
detected_by TEXT NOT NULL,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
||
CONSTRAINT asset_coverage_snapshot_dimension_valid
|
||
CHECK (dimension IN (
|
||
'auto_monitoring','auto_alerting','auto_rule_creation',
|
||
'auto_rule_matching','auto_playbook','auto_remediation','auto_km_creation'
|
||
)),
|
||
CONSTRAINT asset_coverage_snapshot_status_valid
|
||
CHECK (coverage_status IN ('green','yellow','red','unknown')),
|
||
CONSTRAINT asset_coverage_snapshot_unique
|
||
UNIQUE (run_id, asset_id, dimension)
|
||
);
|
||
|
||
COMMENT ON TABLE asset_coverage_snapshot IS
|
||
'ADR-090: 計分卡。查 red COUNT 即覆蓋率 SLO。evidence 欄位串 playbook_id/km_entry_id/rule_name。';
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_asset_coverage_snapshot_asset_dim
|
||
ON asset_coverage_snapshot(asset_id, dimension);
|
||
CREATE INDEX IF NOT EXISTS idx_asset_coverage_snapshot_red_yellow
|
||
ON asset_coverage_snapshot(coverage_status)
|
||
WHERE coverage_status IN ('red','yellow');
|
||
CREATE INDEX IF NOT EXISTS idx_asset_coverage_snapshot_run
|
||
ON asset_coverage_snapshot(run_id);
|
||
|
||
|
||
-- ============================================================================
|
||
-- Step 4: asset_relationship — 資產依賴圖 (爆炸半徑必需)
|
||
-- 用途: 記錄資產之間的 depends_on / calls / stores_data_in / backs_up_to 關係
|
||
-- AI 用途: OpenClaw 計算 blast_radius 時查這張表
|
||
-- ============================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS asset_relationship (
|
||
relationship_id BIGSERIAL PRIMARY KEY,
|
||
from_asset_id BIGINT NOT NULL REFERENCES asset_inventory(asset_id),
|
||
to_asset_id BIGINT NOT NULL REFERENCES asset_inventory(asset_id),
|
||
relationship_type TEXT NOT NULL,
|
||
strength NUMERIC(3,2),
|
||
metadata JSONB,
|
||
first_detected_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
last_verified_at TIMESTAMPTZ,
|
||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||
|
||
CONSTRAINT asset_relationship_type_valid
|
||
CHECK (relationship_type IN (
|
||
'depends_on','calls','stores_data_in','backs_up_to',
|
||
'routes_to','authenticates_via','monitors','alerts_to','logs_to'
|
||
)),
|
||
CONSTRAINT asset_relationship_strength_valid
|
||
CHECK (strength IS NULL OR (strength >= 0 AND strength <= 1)),
|
||
CONSTRAINT asset_relationship_unique
|
||
UNIQUE (from_asset_id, to_asset_id, relationship_type),
|
||
CONSTRAINT asset_relationship_no_self_loop
|
||
CHECK (from_asset_id <> to_asset_id)
|
||
);
|
||
|
||
COMMENT ON TABLE asset_relationship IS
|
||
'ADR-090: 資產依賴圖。AI 計算爆炸半徑必讀。edge 而非 tree,支援多重關係。';
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_asset_relationship_from
|
||
ON asset_relationship(from_asset_id) WHERE is_active;
|
||
CREATE INDEX IF NOT EXISTS idx_asset_relationship_to
|
||
ON asset_relationship(to_asset_id) WHERE is_active;
|
||
CREATE INDEX IF NOT EXISTS idx_asset_relationship_type
|
||
ON asset_relationship(relationship_type);
|
||
|
||
|
||
-- ============================================================================
|
||
-- Step 5: alert_rule_catalog — 告警規則本身即資產
|
||
-- 用途: 把 alert_rules.yaml 升級為 DB-driven;記錄誰創的 / 何時 / 效能 / 生死
|
||
-- AI 用途: Hermes 做 noise_rate 分析 / 提建議 retire 低品質規則
|
||
-- ============================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS alert_rule_catalog (
|
||
rule_id BIGSERIAL PRIMARY KEY,
|
||
rule_name TEXT NOT NULL UNIQUE,
|
||
source TEXT NOT NULL,
|
||
expr TEXT NOT NULL,
|
||
duration_seconds INT,
|
||
severity TEXT,
|
||
labels JSONB,
|
||
annotations JSONB,
|
||
linked_asset_ids BIGINT[],
|
||
created_by_agent TEXT,
|
||
|
||
-- 規則品質追蹤
|
||
true_positive_count INT NOT NULL DEFAULT 0,
|
||
false_positive_count INT NOT NULL DEFAULT 0,
|
||
noise_rate NUMERIC(5,2),
|
||
last_fired_at TIMESTAMPTZ,
|
||
|
||
-- 信心與演化
|
||
confidence NUMERIC(3,2),
|
||
review_status TEXT,
|
||
superseded_by_rule_id BIGINT REFERENCES alert_rule_catalog(rule_id),
|
||
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
||
CONSTRAINT alert_rule_catalog_source_valid
|
||
CHECK (source IN ('yaml_hardcoded','ai_generated','human_written','playbook_derived')),
|
||
CONSTRAINT alert_rule_catalog_review_valid
|
||
CHECK (review_status IS NULL OR review_status IN
|
||
('draft','approved','deprecated','retired'))
|
||
);
|
||
|
||
COMMENT ON TABLE alert_rule_catalog IS
|
||
'ADR-090: 告警規則即一等資產。支援規則演化 (ai_generated) 與替代鏈 (superseded_by)。';
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_alert_rule_catalog_source
|
||
ON alert_rule_catalog(source);
|
||
CREATE INDEX IF NOT EXISTS idx_alert_rule_catalog_assets_gin
|
||
ON alert_rule_catalog USING GIN (linked_asset_ids);
|
||
CREATE INDEX IF NOT EXISTS idx_alert_rule_catalog_review
|
||
ON alert_rule_catalog(review_status) WHERE review_status IS NOT NULL;
|
||
|
||
|
||
-- ============================================================================
|
||
-- Step 6: asset_change_event — 資產變化追蹤 (diff between runs)
|
||
-- 用途: 兩次 discovery_run 之間的 delta。新增/消失/修改/覆蓋率變化
|
||
-- ============================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS asset_change_event (
|
||
event_id BIGSERIAL PRIMARY KEY,
|
||
run_id UUID NOT NULL REFERENCES asset_discovery_run(run_id),
|
||
asset_id BIGINT REFERENCES asset_inventory(asset_id),
|
||
change_type TEXT NOT NULL,
|
||
before_state JSONB,
|
||
after_state JSONB,
|
||
diff JSONB,
|
||
detected_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
ai_analysis TEXT,
|
||
|
||
CONSTRAINT asset_change_event_type_valid
|
||
CHECK (change_type IN (
|
||
'asset_added','asset_removed','asset_modified',
|
||
'coverage_improved','coverage_degraded',
|
||
'criticality_changed','owner_changed','lifecycle_changed'
|
||
))
|
||
);
|
||
|
||
COMMENT ON TABLE asset_change_event IS
|
||
'ADR-090: 資產變化追蹤。兩次掃描的 diff 明確落地,LLM 可加 ai_analysis 解讀。';
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_asset_change_event_run
|
||
ON asset_change_event(run_id);
|
||
CREATE INDEX IF NOT EXISTS idx_asset_change_event_asset_time
|
||
ON asset_change_event(asset_id, detected_at DESC);
|
||
|
||
|
||
-- ============================================================================
|
||
-- Step 7: asset_compliance_snapshot — 合規狀態 (SSL/CVE/secret/backup)
|
||
-- 用途: 與 coverage 不同軸的合規追蹤。SSL cert 到期 / CVE 掃描 / secret 輪替
|
||
-- ============================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS asset_compliance_snapshot (
|
||
snapshot_id BIGSERIAL PRIMARY KEY,
|
||
run_id UUID REFERENCES asset_discovery_run(run_id),
|
||
asset_id BIGINT NOT NULL REFERENCES asset_inventory(asset_id),
|
||
dimension TEXT NOT NULL,
|
||
status TEXT NOT NULL,
|
||
expires_at TIMESTAMPTZ,
|
||
detail JSONB,
|
||
remediation_deadline TIMESTAMPTZ,
|
||
detected_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
||
CONSTRAINT asset_compliance_snapshot_dimension_valid
|
||
CHECK (dimension IN (
|
||
'ssl_cert_valid','cve_scan','secret_rotated','backup_tested',
|
||
'audit_log_enabled','access_reviewed','encryption_at_rest'
|
||
)),
|
||
CONSTRAINT asset_compliance_snapshot_status_valid
|
||
CHECK (status IN ('compliant','warning','violation','unknown'))
|
||
);
|
||
|
||
COMMENT ON TABLE asset_compliance_snapshot IS
|
||
'ADR-090: 合規狀態快照。與 coverage 不同軸,SSL/CVE/secret/backup 專用。';
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_asset_compliance_snapshot_asset_dim
|
||
ON asset_compliance_snapshot(asset_id, dimension);
|
||
CREATE INDEX IF NOT EXISTS idx_asset_compliance_snapshot_expiring
|
||
ON asset_compliance_snapshot(expires_at)
|
||
WHERE expires_at IS NOT NULL;
|
||
CREATE INDEX IF NOT EXISTS idx_asset_compliance_snapshot_violations
|
||
ON asset_compliance_snapshot(status)
|
||
WHERE status IN ('warning','violation');
|
||
|
||
|
||
-- ============================================================================
|
||
-- Step 8: host_capacity_snapshot — 主機容量快照
|
||
-- 用途: NemoTron 每日 02:00 台北 自主容量巡檢寫入
|
||
-- Layer 4 核心表。hermes 做預測,openclaw 產建議,全寫這張
|
||
-- ============================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS host_capacity_snapshot (
|
||
snapshot_id BIGSERIAL PRIMARY KEY,
|
||
host TEXT NOT NULL,
|
||
captured_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
load1 NUMERIC(6,2),
|
||
load5 NUMERIC(6,2),
|
||
load15 NUMERIC(6,2),
|
||
cpu_used_pct NUMERIC(5,2),
|
||
cpu_iowait_pct NUMERIC(5,2),
|
||
mem_used_pct NUMERIC(5,2),
|
||
swap_used_pct NUMERIC(5,2),
|
||
disk_used_pct JSONB,
|
||
container_count INT,
|
||
k8s_pod_count INT,
|
||
top_cpu_offenders JSONB,
|
||
top_mem_offenders JSONB,
|
||
headroom_pct NUMERIC(5,2),
|
||
ai_verdict TEXT,
|
||
ai_reasoning TEXT,
|
||
recommended_actions JSONB,
|
||
written_by_agent TEXT NOT NULL,
|
||
|
||
CONSTRAINT host_capacity_snapshot_verdict_valid
|
||
CHECK (ai_verdict IS NULL OR ai_verdict IN ('safe','warning','critical','unknown'))
|
||
);
|
||
|
||
COMMENT ON TABLE host_capacity_snapshot IS
|
||
'ADR-090: NemoTron 每日主機容量巡檢結果。Layer 4 AI 自主治理核心表。';
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_host_capacity_snapshot_host_time
|
||
ON host_capacity_snapshot(host, captured_at DESC);
|
||
CREATE INDEX IF NOT EXISTS idx_host_capacity_snapshot_critical
|
||
ON host_capacity_snapshot(ai_verdict)
|
||
WHERE ai_verdict IN ('warning','critical');
|
||
|
||
|
||
-- ============================================================================
|
||
-- Step 9: capacity_violation_event — 配額違規事件
|
||
-- 用途: 記錄任何「缺 limit」「超 request」「主機飽和」的違規
|
||
-- ============================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS capacity_violation_event (
|
||
event_id BIGSERIAL PRIMARY KEY,
|
||
asset_id BIGINT REFERENCES asset_inventory(asset_id),
|
||
host TEXT,
|
||
violation_type TEXT NOT NULL,
|
||
threshold NUMERIC(10,2),
|
||
actual_value NUMERIC(10,2),
|
||
detected_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
auto_action TEXT,
|
||
auto_action_op_id UUID,
|
||
human_override TEXT,
|
||
resolved_at TIMESTAMPTZ,
|
||
|
||
CONSTRAINT capacity_violation_event_type_valid
|
||
CHECK (violation_type IN (
|
||
'no_limit_set','over_request','over_limit','host_saturation',
|
||
'over_sla_budget','unauthorized_new_deploy'
|
||
))
|
||
);
|
||
|
||
COMMENT ON TABLE capacity_violation_event IS
|
||
'ADR-090: 配額違規稽核。每次 AI 偵測到資產無 limit/主機飽和/未授權部署 都寫一筆。';
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_capacity_violation_event_asset_time
|
||
ON capacity_violation_event(asset_id, detected_at DESC);
|
||
CREATE INDEX IF NOT EXISTS idx_capacity_violation_event_unresolved
|
||
ON capacity_violation_event(detected_at DESC)
|
||
WHERE resolved_at IS NULL;
|
||
|
||
|
||
-- ============================================================================
|
||
-- Step 10: automation_operation_log — 所有 AI 自動化動作稽核主表 🔴
|
||
-- 鐵律: 每一個 AI 自動化動作都必須寫一筆。缺筆 = 治理失效
|
||
-- ============================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS automation_operation_log (
|
||
op_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
operation_type TEXT NOT NULL,
|
||
asset_id BIGINT REFERENCES asset_inventory(asset_id),
|
||
incident_id BIGINT,
|
||
run_id UUID REFERENCES asset_discovery_run(run_id),
|
||
actor TEXT NOT NULL,
|
||
input JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||
output JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||
dry_run_result JSONB,
|
||
status TEXT NOT NULL,
|
||
error TEXT,
|
||
duration_ms INT,
|
||
tokens_in INT,
|
||
tokens_out INT,
|
||
cost_usd NUMERIC(10,6),
|
||
budget_bucket TEXT,
|
||
parent_op_id UUID REFERENCES automation_operation_log(op_id),
|
||
retry_count INT NOT NULL DEFAULT 0,
|
||
retry_of_op_id UUID REFERENCES automation_operation_log(op_id),
|
||
stderr_feed_back TEXT,
|
||
tags TEXT[],
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
||
CONSTRAINT automation_operation_log_type_valid
|
||
CHECK (operation_type IN (
|
||
'monitor_configured','monitor_removed',
|
||
'alert_fired','alert_suppressed','alert_routed',
|
||
'rule_created','rule_updated','rule_matched','rule_rejected','rule_deprecated',
|
||
'playbook_generated','playbook_updated','playbook_executed',
|
||
'remediation_executed','remediation_verified','remediation_rolled_back',
|
||
'self_correction_attempted',
|
||
'km_created','km_updated','km_linked',
|
||
'asset_discovered','coverage_recalculated',
|
||
'capacity_recommendation','quota_enforced'
|
||
)),
|
||
CONSTRAINT automation_operation_log_status_valid
|
||
CHECK (status IN ('pending','success','failed','dry_run','rolled_back'))
|
||
);
|
||
|
||
COMMENT ON TABLE automation_operation_log IS
|
||
'ADR-090: 所有 AI 自動化動作稽核主表。retry_of_op_id + stderr_feed_back 支援引擎 4 閉環。';
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_automation_operation_log_type_time
|
||
ON automation_operation_log(operation_type, created_at DESC);
|
||
CREATE INDEX IF NOT EXISTS idx_automation_operation_log_asset_time
|
||
ON automation_operation_log(asset_id, created_at DESC);
|
||
CREATE INDEX IF NOT EXISTS idx_automation_operation_log_incident
|
||
ON automation_operation_log(incident_id)
|
||
WHERE incident_id IS NOT NULL;
|
||
CREATE INDEX IF NOT EXISTS idx_automation_operation_log_actor_time
|
||
ON automation_operation_log(actor, created_at DESC);
|
||
CREATE INDEX IF NOT EXISTS idx_automation_operation_log_retry
|
||
ON automation_operation_log(retry_of_op_id)
|
||
WHERE retry_of_op_id IS NOT NULL;
|
||
CREATE INDEX IF NOT EXISTS idx_automation_operation_log_tags_gin
|
||
ON automation_operation_log USING GIN (tags);
|
||
|
||
|
||
-- ============================================================================
|
||
-- Step 11: ai_collaboration_trace — 多 Agent 協作逐步 (LLM × OpenClaw × NemoTron × Hermes)
|
||
-- 用途: 每個 automation_operation_log 背後的 N 步 AI 決策過程
|
||
-- 最寶貴的語料: challenged_by + accepted 支援 RLHF fine-tune
|
||
-- ============================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS ai_collaboration_trace (
|
||
trace_id BIGSERIAL PRIMARY KEY,
|
||
op_id UUID NOT NULL REFERENCES automation_operation_log(op_id) ON DELETE CASCADE,
|
||
step_order INT NOT NULL,
|
||
agent TEXT NOT NULL,
|
||
model TEXT,
|
||
system_prompt_version TEXT,
|
||
prompt TEXT,
|
||
response JSONB,
|
||
confidence NUMERIC(3,2),
|
||
challenged_by TEXT[],
|
||
accepted BOOLEAN,
|
||
tokens_in INT,
|
||
tokens_out INT,
|
||
duration_ms INT,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
||
CONSTRAINT ai_collaboration_trace_unique_step
|
||
UNIQUE (op_id, step_order)
|
||
);
|
||
|
||
COMMENT ON TABLE ai_collaboration_trace IS
|
||
'ADR-090: AI 多 Agent 協作逐步紀錄。challenged_by + accepted = RLHF 訓練語料金礦。';
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_ai_collaboration_trace_op
|
||
ON ai_collaboration_trace(op_id, step_order);
|
||
CREATE INDEX IF NOT EXISTS idx_ai_collaboration_trace_agent_time
|
||
ON ai_collaboration_trace(agent, created_at DESC);
|
||
|
||
|
||
-- ============================================================================
|
||
-- Step 12: 驗收查詢 (執行後手動跑,驗證 11 張表都到位)
|
||
-- ============================================================================
|
||
|
||
-- SELECT table_name
|
||
-- FROM information_schema.tables
|
||
-- WHERE table_schema = 'public'
|
||
-- AND table_name IN (
|
||
-- 'asset_inventory',
|
||
-- 'asset_discovery_run',
|
||
-- 'asset_coverage_snapshot',
|
||
-- 'asset_relationship',
|
||
-- 'alert_rule_catalog',
|
||
-- 'asset_change_event',
|
||
-- 'asset_compliance_snapshot',
|
||
-- 'host_capacity_snapshot',
|
||
-- 'capacity_violation_event',
|
||
-- 'automation_operation_log',
|
||
-- 'ai_collaboration_trace'
|
||
-- )
|
||
-- ORDER BY table_name;
|
||
-- -- 預期: 11 筆
|
||
|
||
-- SELECT table_name, COUNT(*) AS column_count
|
||
-- FROM information_schema.columns
|
||
-- WHERE table_schema = 'public'
|
||
-- AND table_name LIKE 'asset_%' OR table_name IN
|
||
-- ('alert_rule_catalog','host_capacity_snapshot','capacity_violation_event',
|
||
-- 'automation_operation_log','ai_collaboration_trace')
|
||
-- GROUP BY table_name
|
||
-- ORDER BY table_name;
|
||
|
||
-- SELECT conname, conrelid::regclass AS table_name
|
||
-- FROM pg_constraint
|
||
-- WHERE conrelid IN (
|
||
-- 'asset_inventory'::regclass,
|
||
-- 'asset_discovery_run'::regclass,
|
||
-- 'asset_coverage_snapshot'::regclass,
|
||
-- 'asset_relationship'::regclass,
|
||
-- 'alert_rule_catalog'::regclass,
|
||
-- 'asset_change_event'::regclass,
|
||
-- 'asset_compliance_snapshot'::regclass,
|
||
-- 'host_capacity_snapshot'::regclass,
|
||
-- 'capacity_violation_event'::regclass,
|
||
-- 'automation_operation_log'::regclass,
|
||
-- 'ai_collaboration_trace'::regclass
|
||
-- ) AND contype = 'c' -- CHECK constraints only
|
||
-- ORDER BY table_name, conname;
|
||
|
||
-- ============================================================================
|
||
-- END OF MIGRATION adr090_asset_inventory_foundation.sql
|
||
-- 預計新增物件: 11 tables + 33 indexes + 20 CHECK constraints + 3 UNIQUE + 16 FK references
|
||
-- 依賴: pgcrypto extension (for gen_random_uuid)
|
||
-- 影響資料: 無 (純 DDL, 不動現有表)
|
||
-- 回滾: 見檔案頭部
|
||
-- ============================================================================
|