# 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 補完) |