feat(ws2): ADR-093 路由統一 — BIGINT + NotificationMatrix + feature flag

## 修復

### T2.1 BigInteger overflow 修復
- `db/models.py`: telegram_chat_id Integer → BigInteger
  (原 int32 無法容納群組 ID -1003711974679)

### T2.2 移除 CAST workaround
- `approval_db.py:739`: 移除 CAST(:telegram_chat_id AS BIGINT)
  ORM 已正確使用 BigInteger,workaround 可退役

### T2.3 Redis key 一致性修復
- `heartbeat_report_service.py:575`: telegram:polling_leader → telegram:polling:leader
  (telegram_gateway.py 使用冒號分隔,heartbeat 用底線是 bug)

## 新增

### T2.4 notification_matrix.py
- `services/notification_matrix.py`: ADR-093 路由矩陣
  - Destination(DM/GROUP/BOTH) + RoutingRule dataclass
  - NOTIFICATION_ROUTING dict(TYPE-1 ~ TYPE-8M 完整映射)
  - resolve_chat_ids(type, dm, group, *, tg_group_cutover=False) 灰階切流 API

### T2.5 telegram_gateway.py feature flag 保護
- line 43: 加 notification_matrix import
- line 1827-1834: TG_GROUP_CUTOVER=False 時維持舊行為
  TG_GROUP_CUTOVER=True 時解除 _interactive_types 黑名單,由矩陣控制

### T2.6 Migration SQL
- `migrations/adr093_notification_routing.sql`:
  - CREATE TABLE approval_records (telegram_chat_id BIGINT)
  - CREATE ROLE awoooi_migrator (IF NOT EXISTS)
  - 含舊環境 ALTER COLUMN int→bigint 保護

## 測試同步
- `tests/integration/setup_test_schema.sql`: telegram_chat_id BIGINT

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-04-25 01:45:16 +08:00
parent 054d0ae422
commit 6d5fd3c124
8 changed files with 176 additions and 8 deletions

View File

@@ -0,0 +1,78 @@
-- ADR-093: Notification Matrix Migration
-- =========================================
-- 1. 建立 approval_records 表BIGINT telegram_chat_id支援群組負數 ID
-- 2. 建立 awoooi_migrator 角色
-- 2026-04-25 ogt + Claude Sonnet 4.6
-- awoooi_migrator 角色ADR-090b 計畫的實作)
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'awoooi_migrator') THEN
CREATE ROLE awoooi_migrator LOGIN;
END IF;
END
$$;
GRANT CONNECT ON DATABASE awoooi_prod TO awoooi_migrator;
GRANT USAGE ON SCHEMA public TO awoooi_migrator;
GRANT CREATE ON SCHEMA public TO awoooi_migrator;
-- approval_records 主表(全新建立,直接用 BIGINT
-- 注意test schema setup_test_schema.sql 同步更新為 BIGINT
CREATE TABLE IF NOT EXISTS approval_records (
id VARCHAR(36) PRIMARY KEY,
action VARCHAR(500) NOT NULL,
description TEXT NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
risk_level VARCHAR(20) NOT NULL,
required_signatures INTEGER DEFAULT 1,
current_signatures INTEGER DEFAULT 0,
signatures JSON DEFAULT '[]',
blast_radius JSON DEFAULT '{}',
dry_run_checks JSON DEFAULT '[]',
requested_by VARCHAR,
rejection_reason TEXT,
extra_metadata JSON DEFAULT '{}',
fingerprint VARCHAR,
hit_count INTEGER DEFAULT 1,
last_seen_at TIMESTAMPTZ,
approval_level VARCHAR DEFAULT 'standard',
approval_votes JSONB,
required_votes INTEGER DEFAULT 1,
incident_id VARCHAR,
telegram_message_id INTEGER,
telegram_chat_id BIGINT, -- 支援群組負數 ID原 INTEGER 會 int32 overflow
matched_playbook_id VARCHAR(36),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ,
resolved_at TIMESTAMPTZ
);
-- 若表已存在(舊環境),執行欄位型別升級
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'approval_records'
AND column_name = 'telegram_chat_id'
AND data_type = 'integer'
) THEN
ALTER TABLE approval_records
ALTER COLUMN telegram_chat_id TYPE BIGINT;
RAISE NOTICE 'approval_records.telegram_chat_id upgraded INTEGER → BIGINT';
END IF;
END
$$;
-- 索引
CREATE INDEX IF NOT EXISTS idx_approval_records_status ON approval_records(status);
CREATE INDEX IF NOT EXISTS idx_approval_records_incident ON approval_records(incident_id);
CREATE INDEX IF NOT EXISTS idx_approval_records_fingerprint ON approval_records(fingerprint);
CREATE INDEX IF NOT EXISTS idx_approval_records_playbook ON approval_records(matched_playbook_id);
GRANT SELECT, INSERT, UPDATE, DELETE ON approval_records TO awoooi;
GRANT SELECT, INSERT, UPDATE ON approval_records TO awoooi_migrator;
COMMENT ON TABLE approval_records IS 'ADR-093 2026-04-25: telegram_chat_id 改 BIGINT 支援群組負數 ID';
COMMENT ON COLUMN approval_records.telegram_chat_id IS 'BIGINT: 支援 SRE 群組 ID (-1003711974679) 不 overflow';