diff --git a/apps/api/src/api/v1/drift.py b/apps/api/src/api/v1/drift.py
index 5cc0e598..71b0d9f5 100644
--- a/apps/api/src/api/v1/drift.py
+++ b/apps/api/src/api/v1/drift.py
@@ -158,40 +158,13 @@ async def _analyze_and_notify(report: DriftReport) -> None:
_logger = _structlog.get_logger(__name__)
try:
interpreter = get_drift_interpreter()
- analyzer = get_drift_analyzer()
interpretation = await interpreter.analyze(report)
repo = get_drift_repository()
await repo.update_interpretation(report.report_id, interpretation)
- diff_summary = analyzer.format_diff_summary(report)
- intent_label = {
- "emergency_hotfix": "🚨 緊急 Hotfix",
- "human_error": "⚠️ 人為誤操作",
- "automated_change": "🤖 系統自動變更",
- "unknown": "❓ 意圖不明",
- }.get(interpretation.intent.value, "❓ 意圖不明")
-
- try:
- from src.services.telegram_gateway import get_telegram_gateway
- tg = get_telegram_gateway()
- await tg.send_text(
- f"🔍 Config Drift 偵測\n"
- f"Namespace: {report.namespace}\n"
- f"嚴重度: HIGH×{report.high_count} MEDIUM×{report.medium_count}\n\n"
- f"意圖分析: {intent_label}\n"
- f"{interpretation.explanation}\n"
- f"信心: {interpretation.confidence:.0%}\n\n"
- f"漂移詳情:\n{diff_summary}\n\n"
- f"Report ID: {report.report_id}\n"
- f"POST /api/v1/drift/reports/{report.report_id}/rollback — 覆蓋回 Git\n"
- f"(adopt 端點暫停開放,待 ADR-057 實作後啟用)"
- )
- except Exception as e:
- _logger.warning("drift_telegram_failed", error=str(e))
-
- # Phase 30 (2026-04-10 Claude Code ADR-067): AI 人話摘要推送
- # 在技術格式訊息之後,額外推送 qwen2.5:7b-instruct 生成的繁中摘要
+ # ADR-075: drift_narrator_service 負責發送 TYPE-4D 卡片(含按鈕)
+ # 舊的 send_text() 已移除,改由 narrate_and_notify() 統一處理
try:
from src.services.drift_narrator_service import get_drift_narrator_service
narrator = get_drift_narrator_service()
diff --git a/apps/api/src/api/v1/webhooks.py b/apps/api/src/api/v1/webhooks.py
index 06eb8e19..c77fc20f 100644
--- a/apps/api/src/api/v1/webhooks.py
+++ b/apps/api/src/api/v1/webhooks.py
@@ -976,6 +976,16 @@ async def receive_alert(
# 2026-04-08 ogt: 補傳 incident_id 以啟用詳情/重診/歷史按鈕
incident_id="", # /alerts 路徑尚無 incident,detail/reanalyze/history 按鈕不顯示
# /alerts 路徑沒有 notification_type(非 Alertmanager 路徑),不需 TYPE-4D routing
+ # ADR-075: alert_type → category 對應,啟用動態按鈕
+ alert_category={
+ "k8s_node_failure": "kubernetes",
+ "k8s_pod_crash": "kubernetes",
+ "db_connection_timeout": "database",
+ "high_cpu": "host_resource",
+ "high_memory": "host_resource",
+ "disk_full": "storage",
+ "ssl_expiry": "ssl_cert",
+ }.get(alert.alert_type, "general"),
)
return AlertResponse(
diff --git a/apps/api/src/services/drift_narrator_service.py b/apps/api/src/services/drift_narrator_service.py
index 65327703..de6d0ab8 100644
--- a/apps/api/src/services/drift_narrator_service.py
+++ b/apps/api/src/services/drift_narrator_service.py
@@ -17,7 +17,6 @@ Drift Narrator Service - Phase 30
from __future__ import annotations
-import html
from typing import TYPE_CHECKING
import httpx
@@ -224,25 +223,30 @@ class DriftNarratorService:
)
async def _send_telegram(self, report: "DriftReport", narrative: str) -> None:
- """推送 Telegram 人話摘要卡"""
+ """
+ 推送 TYPE-4D Config Drift 卡片(ADR-075)
+
+ 使用 send_drift_card() 取代舊 send_notification(),呈現結構化格式與操作按鈕。
+ diff_summary = AI 研判(narrative) + 漂移詳情(前 8 筆)
+ approval_id / incident_id 均使用 report_id(無需建立 ApprovalRequest)
+ """
from src.services.telegram_gateway import get_telegram_gateway
- severity_icon = "🔴" if report.high_count > 0 else "🟡"
- msg = (
- f"{severity_icon} K8s 配置漂移\n"
- f"Namespace: {html.escape(report.namespace)}\n"
- f"HIGH: {report.high_count} | MEDIUM: {report.medium_count}\n"
- f"\n"
- f"🤖 AI 研判\n"
- f"{html.escape(narrative)}\n"
- f"\n"
- f"📋 Report: {html.escape(report.report_id)}\n"
- f"qwen2.5:7b-instruct | 免費本地推理"
+ diff_summary = (
+ f"🤖 AI 研判\n{narrative}\n\n"
+ f"漂移明細(HIGH: {report.high_count} | MEDIUM: {report.medium_count})\n"
+ f"{self._format_drift_summary(report)}"
)
try:
tg = get_telegram_gateway()
- await tg.send_notification(msg[:4096])
+ await tg.send_drift_card(
+ incident_id=report.report_id,
+ approval_id=report.report_id,
+ resource_name=report.namespace,
+ diff_summary=diff_summary[:500],
+ detected_at="",
+ )
except Exception as e:
logger.warning("drift_narrator_telegram_error", error=str(e))