# ADR-086: Telegram 通知卡片 UI 清洗規範

**狀態**: 已接受  
**日期**: 2026-04-17  
**作者**: ogt + Claude Sonnet 4.6  
**關聯**: ADR-071（通知類型定義）、ADR-075（Telegram 通知標準）

---

## 背景

Phase 2 多 Agent 協作（ADR-082）上線後，5-Agent debate 的輸出格式為：

```
診斷：{hypothesis}；方案：{action}；安全審查：{verdict}；質疑：{critic}
```

但 `decision_manager.py` 路由層在準備 Telegram 卡片資料時，多個分支直接截斷 `reasoning` 字串，導致完整 debate_summary 傾倒到 UI 欄位，使用者在 Telegram 看到：

- TYPE-1（純資訊）：`message=reasoning[:200]` → 顯示「診斷：...；方案：...；安全審查：...；質疑：...」
- TYPE-4D（Config Drift）：`diff_summary=description[:500]` → 顯示 LLM 輸出的 JSON 原文
- TYPE-3（人工審核）：`root_cause=_smt(reasoning, 500)` → 顯示完整 debate_summary 前 500 字
- TYPE-5S（資安）：`threat_behavior=reasoning[:150]` → 顯示 debate_summary 開頭

---

## 決策

### 1. `_parse_debate_summary()` 標準化清洗函數

在 `decision_manager.py` 函數體內定義（後續應提升為模組層級 — P1-1 技術債）：

```python
def _parse_debate_summary(reasoning: str) -> dict[str, str]:
    result = {"diagnosis": "", "plan": "", "review": "", "critic": ""}
    for part in reasoning.split("；"):
        part = part.strip()
        if part.startswith("診斷："):   result["diagnosis"] = part[3:]
        elif part.startswith("方案："): result["plan"] = part[3:]
        elif part.startswith("安全審查："): result["review"] = part[5:]
        elif part.startswith("質疑："): result["critic"] = part[3:]
    return result
```

### 2. 各 TYPE 分支修正規則

| TYPE | 欄位 | 修正前 | 修正後 |
|------|------|--------|--------|
| TYPE-1 | `message` | `reasoning[:200]` | `_parse_debate_summary(reasoning)["diagnosis"] + _smt(..., 200)` |
| TYPE-4D | `diff_summary` | `description[:500]` | JSON Catcher：`json.loads` → 格式化；失敗 → `description[:500]` |
| TYPE-3 | `root_cause` | `_smt(reasoning, 500)` | `_parse_debate_summary(reasoning)["diagnosis"] + _smt(..., 300)` |
| TYPE-5S | `threat_behavior` | `reasoning[:150]` | `_parse_debate_summary(reasoning)["diagnosis"] + _smt(..., 150)` |

### 3. TYPE-3 鍵盤按鈕重構

**原問題**：動態按鈕（K8s 操作類）可能蓋台 [批准][拒絕]，使審核按鈕不可見。

**新規則**（`telegram_gateway.py:_build_inline_keyboard()`）：
1. 第一行永遠是 `[✅ 批准][❌ 拒絕]`（無條件）
2. 第二行以後：K8s/DB/Host 類操作按鈕（每行最多 3 個）
3. 最後一行：`[📋 詳情][🔕 忽略]`

移除 `requires_human_approval` 參數——由有無 `_dynamic_buttons` 決定佈局，不再需要額外條件。

---

## 後果

- **TYPE-1**：純資訊通知只顯示 AI 診斷摘要，不顯示完整辯論過程
- **TYPE-3**：批准/拒絕永遠可見，不會被操作按鈕覆蓋
- **TYPE-4D**：Config Drift 卡片顯示結構化的「建議操作/說明/回滾方案」三段式
- **TYPE-5S**：資安告警的威脅行為欄位只顯示診斷結論

---

## 技術債

| 項目 | 優先 | 說明 |
|------|------|------|
| `_parse_debate_summary` 提升為模組層級 | P1 | 目前定義在函數體內，無法在同模組其他地方重用 |
| `import re as _re` 移出函數體 | P2 | 目前在函數體內 import，每次呼叫重建閉包 |

---

## 相關 Commits

| Commit | 說明 |
|--------|------|
| `6baa2e9` | TYPE-8M 三連修（第一波） |
| `418d735` | BUG-A TYPE-1 + BUG-B TYPE-4D（第二波） |
| `f421e65` | BUG-C TYPE-3 + 按鈕置頂（第三波） |
| `fb225c5` | P2-2 TYPE-5S secops 分支清洗（Code Review 補完） |
