# ADR-073: AIOps 飛輪完整盤點 — 問題根因 + 長期解決方案

**狀態**: Accepted — 等待統帥批准實作（細化步驟已完成）  
**日期**: 2026-04-12 (台北時間)  
**更新**: 2026-04-12 晚 — 加入架構轉型截圖定案 + 四階段細化實施步驟  
**作者**: Claude Sonnet 4.6 + 首席架構師  
**觸發**: 資料庫審計顯示飛輪從未真正運轉  
**完整規格**: `docs/superpowers/specs/2026-04-12-aiops-complete-flywheel-repair-design.md`（§十一 細化步驟、§十二 防偏差守則）

### 架構轉型核心（2026-04-12 截圖定案）

```
舊架構（中斷路徑）：
  告警 → LLM 瞎目（alertname=NULL）→ 目標不明 Unknown → KM 空白

新架構（飛輪 V2.2）：
  告警 → 分診工作站（decision_manager 入口）
         ├─ TYPE-1（資訊）→ 純文字通知，不進修復流程
         ├─ TYPE-4D（Drift）→ Drift 卡片，不進修復流程
         └─ TYPE-3（需修復）→ MCP 上下文 → LLM 推理 → 自動執行 → KM 向量化
```

**關鍵洞察**：分診工作站從 telegram_gateway 前移至 decision_manager 入口，是防止「HostBackupFailed 產生 kubectl scale deployment unknown」的架構根治。

---

## 一、飛輪設計圖（截圖定案版）

### 正常模式（自主運行）

```
監控觀察 → 接入去重 → 環境診斷 → 推理匹配 → 執行燒橇 → 學習固化
   ↑                                                           ↓
   └─────────────────── 飛輪閉環 ──────────────────────────────┘
```

**六個節點定義**：

| 節點 | 功能 | 對應程式碼 |
|------|------|-----------|
| 監控觀察 | Prometheus/Alertmanager 接收告警，Fingerprint 去重 | `webhooks.py` alertmanager_webhook |
| 接入去重 | Debounce 30 分鐘，相同 fingerprint 只建一個 Incident | `webhooks.py` fingerprint + debounce |
| 環境診斷 | MCP 收集真實環境狀態（SSH/K8s/Prometheus）| `decision_manager._collect_mcp_context()` |
| 推理匹配 | 查 KM Playbook → 有匹配直接執行；無匹配呼叫 LLM 推理 | `decision_manager._dual_engine_analyze()` |
| 執行燒橇 | 透過 MCP Provider 執行修復動作 | `decision_manager._auto_execute()` |
| 學習固化 | 成功→生成 Playbook；失敗→生成 Anti-Pattern；寫入 KM | `decision_manager._generate_playbook_draft_if_new()` |

### 人工介入模式（需人工路徑）

```
推理匹配
   ├→ TYPE-3（高風險審核）: AI 找到方案但動作風險高 → 人工批准 → 執行 → 學習固化
   └→ TYPE-4（根因確認）:  AI 無法判斷 → 人工手動處理 → 記錄手動步驟 → 生成新 Playbook
```

**人工介入只發生在兩種情況**：
1. `risk_level = critical` 或 DESTRUCTIVE_PATTERNS 命中 → TYPE-3 卡片
2. `confidence < 0.5` 或 MCP 全失敗 → TYPE-4 卡片，人工確認後轉 Playbook

---

## 二、資料庫實際數據（2026-04-12 統計）

### Incidents（132 筆）

| 欄位 | 數值 | 正常應有 |
|------|------|---------|
| severity P3 | 108（82%） | P3 應是少數 |
| severity P2 | 18（14%） | |
| severity P0 | 6（4%） | |
| status INVESTIGATING | 87（66%） | 應該接近 0 |
| status RESOLVED | 45（34%） | |
| alertname | **全部 NULL** | 應有 alertname |
| alert_category | **全部 NULL** | 應有分類 |
| notification_type | **全部 NULL** | 應記錄卡片類型 |
| vectorized | **全部 False** | 應 True（已向量化進 KM）|
| outcome | **全部 null** | 應記錄修復結果 |

### Approvals（380 筆）

| status | 數量 | 說明 |
|--------|------|------|
| EXECUTION_FAILED | 211（55%） | 超過一半執行失敗 |
| APPROVED | 132（35%） | 已核准，未執行或執行中 |
| REJECTED | 30（8%） | 人工拒絕 |
| EXPIRED | 5 | 超時未處理 |
| **EXECUTION_SUCCESS** | **2（0.5%）** | **實際成功修復只有 2 次** |

### Top Actions（失敗根因）

| action | 數量 | 問題 |
|--------|------|------|
| 未知操作 \|（空） | 93 | action 解析失敗，完全不知道要做什麼 |
| 重新啟動 postgres-primary-0 | 30 | 同一問題反覆重啟，未解決根因 |
| 重新啟動 postgresql-native | 25 | 同上 |
| 重新啟動 harbor-core-7d4b8c9f5-xk2m3 | 21 | Pod hash 寫死，每次不同 |
| OBSERVE | 16 | LLM 判斷無法修復，只觀察 |

### Playbooks = **0**
### Knowledge Entries（699 筆）
- INCIDENT_CASE: 690（有案例，**但全部未向量化**，無法 RAG 查詢）
- RUNBOOK: 7
- BEST_PRACTICE: 2

---

## 三、飛輪每個節點的實際狀態 vs 設計目標

### 節點 1：監控觀察 ✅ 部分正常
- Prometheus → Alertmanager → webhooks.py 接收 ✅
- **問題**：alertname 存入 signals JSONB，但 `signals->0->>'alertname'` 全為 NULL，代表 signals 結構不對

### 節點 2：接入去重 ⚠️ 部分異常
- Fingerprint 機制存在 ✅
- **問題**：debounce window 只有 5 分鐘，Prometheus 每 1 分鐘 fire 一次，5 分鐘後重新建 Incident，導致同一問題產生多個 Incident，反覆發 Telegram

### 節點 3：環境診斷 ❌ 未運作
- `_collect_mcp_context()` 已實作（commit c439277）
- **問題**：`8be87b0` 的修復程式碼 CD 失敗未部署，目前 Pod 是舊 image（`a86ecf3`），該 image 沒有 `_collect_mcp_context()`
- MCP Providers 雖然 enabled，但沒有在分析前被呼叫

### 節點 4：推理匹配 ❌ 嚴重問題
- LLM 分析有在跑（Nemotron NIM）✅
- **問題 A**：Playbooks = 0，`evaluate_auto_repair()` 在 Playbook 匹配步驟永遠失敗，**100% 走人工審核**
- **問題 B**：`signals` JSONB 裡 alertname = NULL，LLM 拿到的是空告警名稱
- **問題 C**：target_resource 解析失敗（DockerContainerUnhealthy → target = alertname or unknown）
- **問題 D**：LLM 習慣性回傳 `risk_level = high`，但 YAML 規則的 risk 沒有覆蓋 LLM 結果

### 節點 5：執行燒橇 ❌ 從未成功（0.5%）
- `_auto_execute()` 三層安全守衛存在 ✅
- **問題 A**：93 次 `未知操作|` → action 解析失敗，LLM 回傳的 action 包含 `|` 分隔符，解析邏輯出錯
- **問題 B**：`target = unknown` 或 `target = alertname` → 安全守衛攔截 → 發 `❌ 自動修復失敗`（而非 TYPE-4 卡片）
- **問題 C**：K8s deployment 名稱含 hash（`harbor-core-7d4b8c9f5-xk2m3`），K8s 驗證失敗
- **問題 D**：Docker/Host 層告警（DockerContainerUnhealthy / HostBackupFailed）走 K8s 路徑，根本路徑就錯

### 節點 6：學習固化 ❌ 完全失效
- `_generate_playbook_draft_if_new()` 已實作（commit 7eb49f9）✅
- **問題 A**：Playbooks 仍為 0，代表觸發條件從未成立，或有 exception 被靜默吞掉
- **問題 B**：690 筆 INCIDENT_CASE 全部 `vectorized = False`，RAG 查詢永遠找不到歷史案例
- **問題 C**：outcome / target_resource / alert_category 全為 NULL，KM 記錄的案例沒有分類，無法被有效查詢
- **問題 D**：重複發生的告警（postgres-primary-0 被重啟 30 次）沒有任何學習，每次重新走全流程

---

## 四、與設計定案的落差對照表

| 設計定案要求 | 實際狀況 | 落差等級 |
|------------|---------|---------|
| 告警 → 自動修復閉環 | Playbooks=0，100% 人工審核 | 🔴 P0 |
| 重複告警不重複觸發 | 5 分鐘 debounce，每次重建 | 🔴 P0 |
| 失敗 → 學習 → 建立規則 | 690 案例未向量化，Playbook 從未生成 | 🔴 P0 |
| KM 三段資料完整 | outcome/vectorized 全 NULL | 🔴 P0 |
| target 動態解析（Docker/Host）| target = unknown/alertname | 🔴 P0 |
| NO_ACTION 告警不顯示按鈕 | HostBackupFailed 仍有六個按鈕 | 🟠 P1 |
| 通知類型正確分類 | notification_type 全 NULL | 🟠 P1 |
| severity/risk_level 正確 | P3 佔 82%，risk 被 LLM 覆蓋 | 🟠 P1 |
| 人工修復後轉 Playbook | 無 handle_manual_fix_done 呼叫 | 🟠 P1 |
| 前端顯示飛輪狀態 | 截圖顯示 Playbook=0，飛輪靜止 | 🟡 P2 |

---

## 五、長期解決方案（按優先順序）

### P0-A：修復 CD + 部署新 image
**問題**：`8be87b0` 的三大修復未上線  
**解法**：確認 Harbor 有 `8be87b0` 的 web image；若沒有，重新觸發 build  
**驗收**：Pod image = 8be87b0，`_collect_mcp_context` 存在於 /app/src

### P0-B：修復 signals 結構 — alertname 存入正確欄位
**問題**：`signals->0->>'alertname'` = NULL  
**解法**：`webhooks.py` 在建 Incident 時，確認 signal 的 alertname 正確寫入 JSONB；或新增獨立欄位 `alertname VARCHAR(100)`  
**驗收**：新 Incident 的 alertname 不為 NULL

### P0-C：debounce window 延長到 30 分鐘
**問題**：5 分鐘 debounce → 同一問題每 5 分鐘重新建 Incident + 發 Telegram  
**解法**：`webhooks.py` fingerprint debounce window 從 5 分鐘 → 30 分鐘  
**驗收**：同一告警 30 分鐘內只有 1 個 INVESTIGATING Incident

### P0-D：冷啟動 Playbook 生成
**問題**：Playbooks = 0，自動修復永遠失敗  
**解法**：一次性腳本，讀取 approval_records 裡 EXECUTION_SUCCESS 的 2 筆 + APPROVED 的 top 10，用 LLM 生成初始 Playbook 草稿，寫入 DB 標記 APPROVED  
**驗收**：Playbooks >= 10，evaluate_auto_repair 至少有匹配

### P0-E：690 筆 INCIDENT_CASE 向量化
**問題**：vectorized = False，RAG 永遠找不到歷史案例  
**解法**：一次性腳本，批次向量化所有 `vectorized=False` 的 knowledge_entries  
**驗收**：vectorized = True，RAG 查詢返回相關案例

### P0-F：Docker/Host 層告警走 SSH MCP 路徑
**問題**：DockerContainerUnhealthy → K8s 路徑 → deployment 不存在 → 失敗  
**解法**：`_auto_execute()` 判斷 alertname prefix（Docker*/Host*）→ 走 SSH MCP `ssh_docker_restart`；不走 K8s 路徑  
**驗收**：DockerContainerUnhealthy 觸發 → SSH MCP 執行 → 成功

### P1-A：action 解析修復（93 筆「未知操作|」）
**問題**：LLM 回傳含 `|` 的 action，解析後為空  
**解法**：`_push_decision_to_telegram()` 的 action 佔位符替換邏輯，加 `|` 分隔符清理  
**驗收**：新的 approval_records action 不出現「未知操作|」

### P1-B：NO_ACTION → TYPE-1（無按鈕）
**問題**：HostBackupFailed 仍有六個按鈕  
**解法**：`classify_notification()` 加判斷：`suggested_action == "NO_ACTION"` → TYPE-1  
**驗收**：HostBackupFailed Telegram 卡片無操作按鈕

### P1-C：outcome / alert_category / notification_type 寫入
**問題**：三個欄位全 NULL，KM 無法分類  
**解法**：
- `_auto_execute()` 完成後寫 `incident.outcome`
- `webhooks.py` 建 Incident 時寫 `alert_category`（從 YAML 規則取）
- `_push_decision_to_telegram()` 發完寫 `notification_type`

### P1-D：risk_level YAML 優先覆蓋 LLM
**問題**：LLM 習慣性回傳 high，YAML 規則的 risk 沒有覆蓋  
**解法**：`_dual_engine_analyze()` 中，若 YAML 規則有 risk 值，優先用 YAML 的，不採用 LLM 的  
**驗收**：HostHighCpuLoad（YAML risk=medium）→ Telegram 顯示 MEDIUM

### P2-A：前端飛輪狀態即時顯示
**問題**：截圖顯示飛輪靜止，Playbook=0，成功率 0.5%，需人工介入  
**解法**：前端飛輪元件（已有）連接以下 API：
- `GET /api/v1/stats/flywheel` → 返回六節點狀態（活躍/靜止/錯誤）
- `GET /api/v1/stats/summary` → 返回 Playbook 數量、成功率、今日處理數
- WebSocket 推送：每次節點狀態變化即時更新  
**驗收**：飛輪動畫跟隨真實告警流轉動；Playbook 數量即時更新

### P2-B：前端顯示人工介入路徑
**問題**：截圖第二張「需人工介入」時，飛輪顯示 TYPE-4（根因確認）和 TYPE-3（高風險審核）路徑  
**解法**：飛輪元件增加「顯示人工干預路徑」toggle（已有），觸發時：
- 紅色虛線顯示從「推理匹配」到「人工處理中心」的路徑
- TYPE-3 / TYPE-4 標籤顯示在對應節點旁邊

---

## 六、實作優先順序與批准等待

> 以下所有變更等待首席架構師批准後才開始實作

### Sprint ADR-073-A（本週）：讓飛輪真的動起來

| # | 任務 | 風險等級 | 估計工時 |
|---|------|---------|---------|
| 1 | CD 修復 + 8be87b0 部署確認 | Tier 2 | 30 分鐘 |
| 2 | signals alertname NULL 修復 | Tier 2 | 1 小時 |
| 3 | debounce 5→30 分鐘 | Tier 2 | 30 分鐘 |
| 4 | 冷啟動 Playbook 生成腳本（一次性）| Tier 2 | 2 小時 |
| 5 | 690 筆 KM 向量化腳本（一次性）| Tier 1 | 1 小時 |

### Sprint ADR-073-B（下週）：修復路由 + 通知品質

| # | 任務 | 風險等級 | 估計工時 |
|---|------|---------|---------|
| 6 | Docker/Host 告警 → SSH MCP 路徑 | Tier 3 | 3 小時 |
| 7 | action 解析「未知操作|」修復 | Tier 2 | 1 小時 |
| 8 | NO_ACTION → TYPE-1 無按鈕 | Tier 2 | 1 小時 |
| 9 | outcome/alert_category/notification_type 寫入 | Tier 3 | 2 小時 |
| 10 | risk_level YAML 優先 | Tier 2 | 1 小時 |

### Sprint ADR-073-C（再下週）：前端飛輪即時化

| # | 任務 | 風險等級 | 估計工時 |
|---|------|---------|---------|
| 11 | `/api/v1/stats/flywheel` API | Tier 1 | 2 小時 |
| 12 | 前端飛輪元件連接真實 API | Tier 2 | 3 小時 |
| 13 | WebSocket 即時推送 | Tier 2 | 2 小時 |

---

### Sprint ADR-073-B 補充（整合 ADR-071 工作序，2026-04-12 更新）

| # | 任務 | 風險等級 | ADR-071 對應 |
|---|------|---------|-------------|
| B-DB | DB Migration: incidents +9 欄位 | Tier 1 | ADR-071-A |
| B1 | 檢傷分類站前移（decision_manager 入口）| Tier 3 | ADR-071-A0+B |
| B2 | TYPE-1 純資訊卡片 | Tier 2 | ADR-071-C |
| B3 | KMConversionService（RESOLVED→KM→向量化）| Tier 2 | ADR-071-G |
| B4 | TYPE-4 手動修復記錄 | Tier 2 | ADR-071-H |

### Sprint ADR-074（監控補全，依賴 ADR-073-A/B 完成後）

| # | 任務 | 告警名稱 | 嚴重度 |
|---|------|---------|-------|
| M1 | 飛輪健康度 Prometheus Exporter + 告警規則 | `FlywheelPlaybookZero` / `FlywheelExecutionSuccessLow` | P0 Critical |
| M2 | 主機間網路 Blackbox probe | `HostNetworkPartition` | P0 Critical |
| M3 | CoreDNS 解析失敗監控 | `CoreDNSResolutionFailed` | P0 Critical |
| M4 | Gitea CI/CD 管線失敗 webhook | `GiteaCIPipelineFailed` | P0 Critical |
| M5 | 備份還原週排程測試 | `BackupRestoreTestFailed` | P0 Critical |
| M6 | Docker 188 容器詳細健康 | `DockerContainerUnhealthyDetailed` | P1 Warning |
| M7 | Redis Streams 積壓 | `RedisStreamBacklogHigh` | P1 Warning |
| M8 | PostgreSQL 磁碟增長率 | `PostgreSQLDiskGrowthRate` | P1 Warning |
| M9 | Gemini API 錯誤率 | `GeminiAPIErrorRateHigh` | P1 Warning |

> **完整規格**: `docs/superpowers/specs/2026-04-12-aiops-complete-flywheel-repair-design.md`

---

## 七、驗收標準（飛輪真正運轉的指標）

完成後 7 天內，以下指標必須達到：

| 指標 | 當前 | 目標 |
|------|------|------|
| Playbooks 數量 | 0 | ≥ 20 |
| EXECUTION_SUCCESS 率 | 0.5% | ≥ 30% |
| 重複告警同 Incident 率 | < 10% | ≥ 70% |
| KM vectorized | 0% | ≥ 90% |
| alertname NULL | 100% | 0% |
| 飛輪節點有活動的告警 | 0 | ≥ 1 筆/小時 |
