""" Notification routing matrix — ADR-093 ====================================== 單一矩陣決定每種通知類型的發送目標,取代 telegram_gateway.py 內 24 處硬碼 chat_id。 設計原則: - 正式告警目的地一律 SRE_GROUP_CHAT_ID 優先 - OPENCLAW_TG_CHAT_ID 只在 SRE_GROUP_CHAT_ID 缺失時作 fail-soft fallback - 未知通知類型預設發群組 2026-04-25 ogt + Claude Sonnet 4.6 """ from __future__ import annotations from dataclasses import dataclass from enum import Enum class Destination(str, Enum): DM = "dm" # OPENCLAW_TG_CHAT_ID (僅缺群組設定時 fallback) GROUP = "group" # SRE_GROUP_CHAT_ID BOTH = "both" # legacy alias: 2026-04-30 起視為 group-first @dataclass(frozen=True) class RoutingRule: destination: Destination strip_buttons_for_group: bool = False # BOTH 時群組版是否去除 Callback Button # ADR-093 D1-D4 路由矩陣 # 2026-04-30 Codex: 所有告警類型群組優先,DM 只作缺群組設定 fallback。 NOTIFICATION_ROUTING: dict[str, RoutingRule] = { "TYPE-1": RoutingRule(Destination.GROUP), "TYPE-2": RoutingRule(Destination.GROUP), "TYPE-3": RoutingRule(Destination.GROUP), "TYPE-4": RoutingRule(Destination.GROUP), "TYPE-4D": RoutingRule(Destination.GROUP), "TYPE-5S": RoutingRule(Destination.GROUP), "TYPE-6B": RoutingRule(Destination.GROUP), "TYPE-7E": RoutingRule(Destination.GROUP), "TYPE-8M": RoutingRule(Destination.GROUP), } _DEFAULT_RULE = RoutingRule(Destination.GROUP) def get_routing_rule(notification_type: str) -> RoutingRule: """根據通知類型回傳路由規則。未知類型預設發群組。""" return NOTIFICATION_ROUTING.get(notification_type, _DEFAULT_RULE) def resolve_chat_ids( notification_type: str, dm_chat_id: str, group_chat_id: str, *, tg_group_cutover: bool = False, ) -> list[str]: """ 回傳此通知應發送的 chat_id 清單。 tg_group_cutover 僅保留為舊 caller 相容參數;正式策略永遠群組優先。 """ rule = get_routing_rule(notification_type) if rule.destination == Destination.DM and not group_chat_id: return [dm_chat_id] if dm_chat_id else [] return [group_chat_id or dm_chat_id] if (group_chat_id or dm_chat_id) else []