From 50edeaa9ea573504305eb6a4ce852f872afe7c02 Mon Sep 17 00:00:00 2001 From: OG T Date: Tue, 14 Apr 2026 20:22:03 +0800 Subject: [PATCH] =?UTF-8?q?docs(Phase=205):=20=E5=88=86=E9=A1=9E=E6=8C=89?= =?UTF-8?q?=E9=88=95=E5=AE=8C=E6=95=B4=E5=8C=96=20=E2=80=94=20=E5=AE=8C?= =?UTF-8?q?=E6=95=B4=E8=A7=A3=E6=B1=BA=E6=96=B9=E6=A1=88=E8=88=87=E5=AF=A6?= =?UTF-8?q?=E6=96=BD=E6=AD=A5=E9=A9=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 統帥要求「提出完整的解決方案和詳細的實施步驟」→ 本 plan 回覆。 內容涵蓋: - 28 按鈕完整 action → MCP tool 對應表(3 類:查/寫/secops) - 6 個 Sprint 工作分解(5.0 規格 → 5.1 dispatch → 5.2 查類 → 5.3 寫類 → 5.4 secops → 5.5 E2E) - 架構設計決策(callback_dispatcher registry pattern) - 依賴與風險矩陣 - 5 個 E2E 驗收案例 - Rollout 策略(查類先上線,觀察 24h 再上寫類) 估時: 3-5 天(總計 5.5 工作日) Co-Authored-By: Claude Haiku 4.5 --- ...-14-PHASE-5-category-buttons-completion.md | 248 ++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-14-PHASE-5-category-buttons-completion.md diff --git a/docs/superpowers/plans/2026-04-14-PHASE-5-category-buttons-completion.md b/docs/superpowers/plans/2026-04-14-PHASE-5-category-buttons-completion.md new file mode 100644 index 000000000..4ff9421de --- /dev/null +++ b/docs/superpowers/plans/2026-04-14-PHASE-5-category-buttons-completion.md @@ -0,0 +1,248 @@ +# Phase 5 實施計畫:Telegram 分類按鈕完整化 + +> **建立**: 2026-04-14 台北深夜 Claude Sonnet 4.6(首席架構師) +> **狀態**: 🟡 待統帥批准啟動 +> **相關**: ADR-079(下架 + 計畫), feedback_no_ghost_buttons.md(鐵律) +> **估時**: 3-5 天(6 個 Sprint) + +--- + +## 0. 戰略目標 + +把已下架的 28 個分類按鈕**完整實作為可用功能**,讓 Telegram 卡片從「資訊展示板」升級為「一鍵 SRE 動作平台」。 + +**成功條件**: +- 統帥在 Telegram 點任何分類按鈕 → 在原卡片下方 reply 執行結果(成功/失敗/log摘要) +- 寫類按鈕(有副作用)有 nonce + audit log +- 查類按鈕(無副作用)秒回 +- E2E 測試覆蓋 click → action → MCP → result reply 完整鏈 + +--- + +## 1. Action → MCP 對應表(完整規格) + +### 1.1 查類按鈕(無副作用,優先實作) + +| # | 分類 | Action | callback_data 格式 | MCP Tool | 參數 | 回覆 | +|---|------|--------|-------------------|---------|------|------| +| 1 | host_resource | check_process | `check_process:{incident_id}` | `ssh_get_top_processes` | host=from labels.instance | Top 10 by CPU | +| 2 | host_resource | check_log | `check_log:{incident_id}` | `ssh_get_nginx_error_log` / `ssh_get_container_logs` | service=from signals | 最後 50 行 | +| 3 | network | check_port | `check_port:{incident_id}` | `ssh_check_port` | host, port | Port 狀態 | +| 4 | devops_tool | check_log | `check_log:{incident_id}` | `ssh_get_container_logs` | container_name | 最後 50 行 | +| 5 | storage | check_minio_log | `check_minio_log:{incident_id}` | `ssh_get_container_logs` | container=minio | Log | +| 6 | external_site | check_health | `check_health:{incident_id}` | `prometheus_query` | `probe_success{instance=...}` | Probe 結果 | +| 7 | external_site | check_log | `check_log:{incident_id}` | `ssh_get_nginx_error_log` | host=188 | Log | +| 8 | business | open_signoz | `open_signoz:{incident_id}` | (URL 返回) | SignOz link | 返回 deeplink URL | +| 9 | flywheel_health | open_flywheel | `open_flywheel:{incident_id}` | (URL 返回) | Flywheel dashboard | 返回 URL | +| 10 | database | check_slow_query | `check_slow_query:{incident_id}` | `database_list_slow_queries` (需新增) | namespace | Slow query 列表 | + +**查類總計 10 個按鈕 | 估時 1 天** + +### 1.2 寫類按鈕(有副作用,需 nonce + audit) + +| # | 分類 | Action | callback_data(4-part nonce) | MCP Tool | 風險 | +|---|------|--------|-------------------------------|---------|-----| +| 11 | kubernetes | restart | `restart:{id}:{ts}:{rand}` | `kubectl_restart` | MEDIUM | +| 12 | kubernetes | scale_up | `scale_up:{id}:{ts}:{rand}` | `kubectl_scale` replicas+1 | MEDIUM | +| 13 | kubernetes | scale_down | `scale_down:{id}:{ts}:{rand}` | `kubectl_scale` replicas-1(禁 0) | MEDIUM | +| 14 | kubernetes | rollback | `rollback:{id}:{ts}:{rand}` | `kubectl_rollout_undo`(需新增) | HIGH | +| 15 | host_resource | restart_service | `restart_service:{id}:{ts}:{rand}` | `ssh_systemctl_restart` | HIGH | +| 16 | host_resource | clear_log | `clear_log:{id}:{ts}:{rand}` | `ssh_clear_docker_logs` | LOW | +| 17 | devops_tool | restart_service | `restart_service:{id}:{ts}:{rand}` | `ssh_docker_restart` | HIGH | +| 18 | storage | restart_minio | `restart_service:{id}:{ts}:{rand}` | `ssh_docker_restart` container=minio | HIGH | +| 19 | network | reload_nginx | `reload_nginx:{id}:{ts}:{rand}` | `ssh_reload_nginx` | LOW | +| 20 | ssl_cert | renew_cert | `renew_cert:{id}:{ts}:{rand}` | `ssh_renew_ssl` | MEDIUM | +| 21 | database | kill_slow_query | `kill_slow_query:{id}:{ts}:{rand}` | `kubectl_exec` psql terminate | HIGH | +| 22 | database | clear_conn_pool | `clear_conn_pool:{id}:{ts}:{rand}` | `kubectl_restart` (間接) | HIGH | +| 23 | business | pause_1h | `pause_1h:{id}:{ts}:{rand}` | Alertmanager silence API | LOW | +| 24 | flywheel_health | trigger_diagnose | `flywheel_diagnose:{id}:{ts}:{rand}` | 內部 API call | LOW | + +**寫類總計 14 個按鈕 | 估時 2 天** + +### 1.3 資安按鈕(最高風險,需 Multi-Sig) + +| # | 分類 | Action | callback_data | MCP Tool | 風險 | +|---|------|--------|---------------|---------|-----| +| 25 | secops | isolate | `secops_isolate:{id}:{ts}:{rand}` | `kubectl_exec` NetworkPolicy apply | CRITICAL | +| 26 | secops | block_ip | `secops_block_ip:{id}:{ts}:{rand}` | `ssh_systemctl_restart` iptables add | CRITICAL | +| 27 | secops | evict | `secops_evict:{id}:{ts}:{rand}` | `kubectl_delete` pod | CRITICAL | +| 28 | secops | authorize | `secops_authorize:{id}:{ts}:{rand}` | 內部授權 API | HIGH | + +**資安總計 4 個按鈕 | 估時 0.5 天** + +--- + +## 2. Sprint 工作分解 + +### Sprint 5.0:規格與設計(0.5 天) + +- [ ] 建立 `callback_action_spec.yaml` — 每 action 的規格(alert_category / MCP tool / 參數模板 / 風險 / 所需 label) +- [ ] 更新 `_security.parse_callback_data()` — 支援 action namespaced format +- [ ] 在 `telegram_gateway.py` 建 `_CATEGORY_ACTIONS` registry(action → spec) + +### Sprint 5.1:Dispatch 框架(0.5 天) + +- [ ] 新增 `apps/api/src/services/callback_dispatcher.py` + - `async def dispatch_action(action: str, incident_id: str, user: User) -> ActionResult` + - 從 registry 查 spec → 建 MCP call → 執行 → format reply +- [ ] 在 `telegram_gateway._handle_callback_query` 加 fallback 分支 → `callback_dispatcher.dispatch_action()` +- [ ] Reply 格式:`✅ {action_label}\n{mcp_output[:500]}` reply_to 原卡片 + +### Sprint 5.2:查類按鈕實作(1 天) + +按 1.1 表格實作 10 個查類按鈕。每按鈕: +- [ ] callback_data 用 2-part info 格式(無副作用無需 nonce) +- [ ] dispatcher 呼叫對應 MCP tool +- [ ] reply_to 原卡片顯示結果摘要 +- [ ] 單元測試:action spec 註冊 + dispatch 呼叫正確 MCP +- [ ] E2E 測試:發告警 → 點按鈕 → reply 正確 + +### Sprint 5.3:寫類按鈕實作(2 天) + +按 1.2 表格實作 14 個寫類按鈕。每按鈕: +- [ ] callback_data 用 4-part nonce 格式 +- [ ] nonce 驗證通過 +- [ ] dispatcher 執行前 log `audit_action_start` +- [ ] MCP 執行 +- [ ] 執行後 log `audit_action_complete` + reply 原卡片 +- [ ] 失敗 reply 「❌ 執行失敗: {error}」 +- [ ] rollback 動作需 Multi-Sig(見 Sprint 5.4) + +### Sprint 5.4:資安按鈕(Multi-Sig)(0.5 天) + +4 個 secops 按鈕都標為 CRITICAL: +- [ ] 啟用 Guardrail BLOCK 層(已有 `sprint5r_guardrail`) +- [ ] 2 人簽核才執行(current_signatures >= 2) +- [ ] audit log 需記錄兩個簽核者 +- [ ] 失敗通知發 SRE 群組 + 統帥 DM + +### Sprint 5.5:E2E 測試與上線(1 天) + +- [ ] 每按鈕至少 1 個 E2E 測試(click simulation → dispatcher → MCP mock OR real → reply) +- [ ] 端到端驗證:真實告警觸發 → 統帥 Telegram 點按鈕 → 看到 reply +- [ ] Rollout 策略:先上查類(Sprint 5.2)觀察 24h,確認零 bug 再上寫類(5.3) +- [ ] 更新 ADR-079 狀態為 Completed + +--- + +## 3. 架構設計決策 + +### 3.1 為何用 `callback_dispatcher` 而非每按鈕寫 elif + +**原因**: +- 28 個按鈕寫 28 個 elif → 900 行代碼,違反 leWOOOgo 積木化 +- Registry pattern + spec 分離 → 新增按鈕只需 yaml 一行 + +**檔案結構**: +``` +apps/api/src/services/ +├── callback_dispatcher.py # dispatcher 主邏輯 +├── callback_action_spec.yaml # action → MCP spec 註冊 +└── callback_action_registry.py # spec loader (lru_cache) +``` + +### 3.2 為何不直接重新啟用 `_CATEGORY_BUTTONS` + +**原因**: +- 當前下架狀態 = 零風險 baseline +- Phase 5 完成才重新啟用(按 category 逐批) +- 避免「啟用→爆炸→再下架」振盪 + +**步驟**: +1. Phase 5.2 完成 → 啟用查類按鈕(只 check_*) +2. 觀察 24h +3. Phase 5.3 完成 → 啟用寫類按鈕 +4. Phase 5.4 完成 → 啟用 secops 按鈕 + +### 3.3 reply_to 訊息策略 + +**所有 action reply 必須 `reply_to_message_id=原告警卡 msg_id`**: +- 用戶可以點 reply 看到對應的告警上下文 +- Redis `tg_msg:{incident_id}` 已有此資料(approval_execution 用同一套) + +--- + +## 4. 依賴與風險 + +### 4.1 必須先修的技術債 + +- [ ] `_security.parse_callback_data()` 目前只認 4-part nonce 和 2-part info,需擴充為「註冊表驗證」 +- [ ] Redis `tg_msg:{incident_id}` 寫入時機確認(approval_execution 寫,其他路徑也要寫) +- [ ] `callback_dispatcher` 要整合 Langfuse trace(每 action 可追蹤) + +### 4.2 新增 MCP 能力 + +- [ ] `kubectl_rollout_undo`(Sprint 5.3 rollback 按鈕需要) +- [ ] `database_list_slow_queries`(Sprint 5.2 database check 需要) +- [ ] Alertmanager `/api/v2/silences` integration(Sprint 5.3 pause_1h 需要) + +### 4.3 風險 + +| 風險 | 機率 | 影響 | 緩解 | +|------|-----|-----|-----| +| 寫類按鈕被誤點 | 中 | 非預期修復 | nonce 4-part + 寫明「確認執行」 UI | +| Multi-Sig 未達 2 人 | 中 | secops 動作卡住 | 超時 15 min 降級 TYPE-4 人工 | +| MCP 呼叫超時 | 中 | 用戶看不到 reply | 每 dispatcher call 包 30s timeout | +| 大量同時點按鈕 | 低 | Ollama/K8s 負載 | Redis lock `dispatch:{incident_id}:{action}` 60s | + +--- + +## 5. 驗收標準 + +### 5.1 單元測試覆蓋率 + +- [ ] callback_action_spec loader 100% +- [ ] callback_dispatcher 主路徑 + 錯誤路徑 > 80% +- [ ] 每按鈕 action → MCP mapping 正確性測試 + +### 5.2 E2E 驗收 + +- [ ] **E2E-1 (查類)**:發 HostHighCpuLoad 告警 → 點「🔍 查程序」→ 1 秒內 reply「Top 10 processes」 +- [ ] **E2E-2 (寫類)**:發 KubePodCrashLooping → 點「🔄 重啟」→ nonce 驗證 → 執行 kubectl rollout restart → reply「✅ 執行成功」 +- [ ] **E2E-3 (secops)**:發 UnauthorizedSSH → 點「🚫 隔離」→ 等第二簽核 → apply NetworkPolicy → reply「✅ 已隔離」 +- [ ] **E2E-4 (failure)**:寫類按鈕執行失敗 → reply「❌ 執行失敗: {error}」+ fallback TYPE-4 人工審核 +- [ ] **E2E-5 (nonce replay)**:舊 callback_data 重複使用 → parser reject → reply「⚠️ Callback 已過期」 + +### 5.3 效能指標 + +- [ ] 查類按鈕 P95 < 2s +- [ ] 寫類按鈕 P95 < 10s(含 MCP call + reply) +- [ ] 所有 dispatcher call 100% 記錄 audit log + +--- + +## 6. 文件更新 + +Phase 5 完成後需更新: +- [ ] `ADR-079` 狀態改為 `Completed` +- [ ] `ADR-075` 更新:分類按鈕從 `_CATEGORY_BUTTONS` dict 改引用 registry +- [ ] `LOGBOOK.md` 記錄 Sprint 5.0-5.5 完成 +- [ ] `docs/operations/ON-CALL-HANDBOOK.md` 新增「如何用 Telegram 按鈕自助排障」章節 +- [ ] `docs/reference/ALERT-TAXONOMY-CATALOG.md` 每分類加「可用按鈕」欄位 + +--- + +## 7. 起始點(等批准後第 1 步) + +**Kick-off 檢查**(0.5 小時): +```bash +# 1. 確認 MCP provider 全綠 +kubectl exec -n awoooi-prod deploy/awoooi-api -- python -c " +from src.plugins.mcp.registry import list_providers +for p in list_providers(): + print(f'{p.name}: {len(p.tools)} tools') +" +# 預期: k8s=10, ssh=15, prometheus=3, signoz=5, database=4, sentry=3 + +# 2. 確認 Redis tg_msg:* 有資料 +ssh 192.168.0.188 'redis-cli -p 6380 -n 10 KEYS "tg_msg:*" | head' + +# 3. 確認 Guardrail BLOCK 層啟用(Sprint 5.4 依賴) +grep "GUARDRAIL_MODE" apps/api/src/core/config.py +``` + +批准後第 1 個 commit 範圍:`callback_action_spec.yaml` + `callback_dispatcher.py` 骨架 + 單元測試。 + +--- + +*Awaiting 統帥批准 → 啟動 Sprint 5.0*