diff --git a/apps/api/src/main.py b/apps/api/src/main.py index f2cd5e56..d96d2094 100644 --- a/apps/api/src/main.py +++ b/apps/api/src/main.py @@ -230,6 +230,15 @@ async def lifespan(_app: FastAPI) -> AsyncGenerator[None, None]: register_all_providers() logger.info("mcp_providers_registered") + # ADR-081 Phase 1: MCPToolRegistry 初始化(PreDecisionInvestigator 感官工具) + # 2026-04-16 ogt + Claude Sonnet 4.6: 修復 sensors=0/0 根因 — init 從未在 startup 被呼叫 + try: + from src.services.mcp_tool_registry import init_mcp_tool_registry + await init_mcp_tool_registry() + logger.info("mcp_tool_registry_initialized") + except Exception as e: + logger.warning("mcp_tool_registry_init_failed", error=str(e)) + # Phase 6.5: Telegram 心跳監控(每 30 分鐘發送到 SRE 戰情室群組) # 2026-04-16 ogt + Claude Sonnet 4.6: 恢復 — 使用者確認必須繼續在 SRE 戰情室發送 # 上次停用原因(forwarded_to_separate_group)有誤,群組就是 SRE_GROUP_CHAT_ID diff --git a/apps/api/src/services/signal_producer.py b/apps/api/src/services/signal_producer.py index fa58631b..1a4e3ec4 100644 --- a/apps/api/src/services/signal_producer.py +++ b/apps/api/src/services/signal_producer.py @@ -12,6 +12,7 @@ Signal Producer Service - Phase 17 P0 Router 層違規修復 - Router -> Service -> Redis """ +import json from dataclasses import dataclass from typing import Any @@ -74,8 +75,8 @@ class SignalProducerService: "namespace": signal.namespace, "target": signal.target, "message": signal.message, - "labels": str(signal.labels or {}), - "annotations": str(signal.annotations or {}), + "labels": json.dumps(signal.labels or {}), + "annotations": json.dumps(signal.annotations or {}), "received_at": now_taipei().isoformat(), } diff --git a/packages/lewooogo-brain/src/lewooogo_brain/engines/incident_engine.py b/packages/lewooogo-brain/src/lewooogo_brain/engines/incident_engine.py index d5317f33..991d22d7 100644 --- a/packages/lewooogo-brain/src/lewooogo_brain/engines/incident_engine.py +++ b/packages/lewooogo-brain/src/lewooogo_brain/engines/incident_engine.py @@ -30,6 +30,22 @@ from lewooogo_brain.interfaces.incident_processor import ( ) +def _parse_dict_field(value: Any) -> dict: + """將 Redis 讀回的欄位(可能是 JSON 字串或 dict)轉為 dict。 + signal_producer 以 json.dumps() 寫入;舊資料可能是 str(dict) repr,一律 fallback 空 dict。 + 2026-04-16 ogt + Claude Sonnet 4.6: 修復 labels/annotations 永遠為 {} 的根因 + """ + if isinstance(value, dict): + return value + if isinstance(value, str) and value: + try: + result = json.loads(value) + return result if isinstance(result, dict) else {} + except (json.JSONDecodeError, ValueError): + return {} + return {} + + # ============================================================================= # Memory Provider Protocol (依賴注入用) # ============================================================================= @@ -237,8 +253,8 @@ class IncidentEngine(IIncidentProcessor): severity=severity, source=data.get("source", "unknown"), fired_at=datetime.now(timezone.utc), - labels=data.get("labels", {}) if isinstance(data.get("labels"), dict) else {}, - annotations=data.get("annotations", {}) if isinstance(data.get("annotations"), dict) else {}, + labels=_parse_dict_field(data.get("labels")), + annotations=_parse_dict_field(data.get("annotations")), ) def _compute_fingerprint(self, data: dict[str, Any]) -> str: