# ADR-013: AIOps 自動修復閉環架構

**狀態**: Accepted  
**日期**: 2026-04-19  
**提案者**: Antigravity

---

## 背景與問題

EwoooC 系統已有 L1 Hermes 告警派發，但告警只能「通知」，無法「自癒」。
當 `psycopg2.OperationalError: could not translate host name "momo-postgres"` 這類明確的基礎設施問題發生時，仍需人工 SSH 登入修復，缺乏自動化閉環。

---

## 決策

建立三層 AIOps 閉環架構：

```
Exception → Incident(DB) → PlayBook 匹配 → Auto-Heal 執行 → HealLog(DB) → KM 沉澱(ai_insights) → Telegram 通知
```

### 新增元件

| 元件 | 類型 | 說明 |
|------|------|------|
| `database/autoheal_models.py` | Model | Incident / Playbook / HealLog 三張表 |
| `migrations/013_autoheal.sql` | Migration | 建表 + 種子 PlayBook 植入 |
| `services/auto_heal_service.py` | Service | 核心引擎（分類、匹配、執行、沉澱） |
| `database/manager.py` | 修改 | 加入 `_init_autoheal_tables()` |
| `scheduler.py` | 修改 | 三個核心任務植入 `handle_exception` |
| `utils/ssh_helper.py` | Helper | 共用 CLI `ssh` 執行層，供 AutoHeal / AiderHeal 使用 |

### PlayBook 動作類型

| action_type | 說明 |
|---|---|
| `DOCKER_RESTART` | 透過 SSH 跳板 restart 指定容器 |
| `SSH_CMD` | 執行白名單內的任意 SSH 指令 |
| `ALERT_ONLY` | 僅發 Telegram 告警，人工介入 |
| `WAIT_RETRY` | 紀錄後等待排程重試 |

### 安全設計

- SSH 指令白名單：2026-04-29 起僅允許明確列入 allowlist 的 read-only 診斷指令；高副作用容器操作需走受控 PlayBook 與保護清單
- `momo-db` / `momo-postgres` 永遠是 protected resource，不得被自動 restart / stop / recreate
- DB / DNS 類離線 playbook 若無法安全修復，降級為 `ALERT_ONLY` 或 `WAIT_RETRY`
- 冷卻機制：同 PlayBook 在 `cooldown_min` 內不重複觸發
- 升級機制：達到 `max_retries` 後 incident.status = `escalated` 並通知人工

### KM 沉澱格式

每次修復後寫入 `ai_insights`：
- `insight_type = "auto_heal_playbook"`
- 包含事件、症狀、行動、結果、教訓五要素
- 自動排入 `embedding_retry_queue` 完成 RAG 向量化

---

## 取捨

2026-05-13 實作修訂：現行程式碼已標準化到 `utils/ssh_helper.py`，以 CLI `ssh` + `ProxyJump` 參數組裝執行，AutoHeal 與 AiderHeal 共用同一層；因此 `paramiko` 不再是 runtime 依賴。若未來要重新引入 Paramiko，必須同時補回 helper 實作、requirements 與回歸測試。

---

## 結果

- P1/P2 等級的 DB_UNREACHABLE / DNS_FAIL 類問題可在 30 秒內完成自動修復
- 所有修復知識自動沉澱至 RAG KM，提升未來 AI 的判斷品質
- 覆蓋任務：`run_auto_import_task` / `run_icaim_analysis_task` / `run_weekly_strategy_task`

---

## 部署後記（2026-04-19 實測）

### 踩坑修正（共 5 個 hotfix commit）

| 問題 | 原因 | 修正 |
|---|---|---|
| `UndefinedTable: playbooks` | 建表順序 `[Incident, Playbook, HealLog]` FK 衝突 | 改為 `[Playbook, Incident, HealLog]` |
| `DetachedInstanceError` HealLog/Incident | `commit()` expire_on_commit 後 lazy-load 失敗 | `session.refresh(obj); session.expunge(obj)` |
| `TypeError: NoneType:.0f` | fallback HealLog 缺 `duration_ms` | except 分支補 `duration_ms=duration_ms` |
| compose=True 雙重呼叫 bug | `_execute_playbook` 先呼叫 compose，馬上覆寫為 docker restart | 刪除 use_compose 分支 |
| `No authentication methods available` | paramiko 找不到 SSH key | key 複製至 `/app/config/autoheal_id_ed25519`（rw mount），不需重建容器 |

### SSH 認證鏈設定

```
momo-scheduler → id_ed25519 → 110 (wooo) → tunnel → 188 (ollama) → docker restart
```

`ollama@188` 的 `id_ed25519.pub` 已加入 `authorized_keys`（第 11 行）。

### 實測結果

```
result=success duration=3110ms  # DNS_FAIL → docker restart momo-db 成功
```

heal_log 在 restart momo-db 後因 DB 瞬斷無法寫入 DB（id=7~9 遺失）屬設計邊界，Telegram 通知仍正常推播。

### 2026-04-29 安全修訂

上述 `DNS_FAIL → docker restart momo-db` 屬早期實測紀錄，現在不再允許作為自動修復策略。最新規則：

- `momo-db` / `momo-postgres` 是資料層 protected resource。
- AutoHeal 寫 DB 失敗時不可讓自癒流程 crash，需保留 Telegram / log / fallback 記錄。
- `CODE_FIX`、resource action 與 ElephantAlpha orchestration 必須橋接 AutoHeal，不得直接執行危險副作用。
- raw `ai_insights` 寫入後必須 enqueue embedding，讓自癒知識進入 RAG。

### 新增 DB 表

- `migrations/014_telegram_users.sql` — EventRouter 推播對象（替代 .env 硬編碼）
  - 種子：`telegram_id=-1003940688311, is_admin=true`
