## 修復 ### 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>
124 lines
4.7 KiB
SQL
124 lines
4.7 KiB
SQL
-- Integration Test Schema Setup
|
|
-- ================================
|
|
-- 為 CI 環境的臨時 PostgreSQL 建立測試所需的 schema
|
|
-- 使用: psql $TEST_DATABASE_URL -f setup_test_schema.sql
|
|
-- 2026-04-10 Claude Sonnet 4.6 Asia/Taipei
|
|
|
|
CREATE EXTENSION IF NOT EXISTS vector;
|
|
|
|
DO $$ BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'incidentstatus') THEN
|
|
CREATE TYPE incidentstatus AS ENUM ('INVESTIGATING','MITIGATING','RESOLVED','CLOSED','ESCALATED');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'severity') THEN
|
|
CREATE TYPE severity AS ENUM ('P0','P1','P2','P3');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'approvalstatus') THEN
|
|
CREATE TYPE approvalstatus AS ENUM ('PENDING','APPROVED','REJECTED','EXPIRED','EXECUTION_SUCCESS','EXECUTION_FAILED');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'risklevel') THEN
|
|
CREATE TYPE risklevel AS ENUM ('LOW','MEDIUM','HIGH','CRITICAL');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'entrysource') THEN
|
|
CREATE TYPE entrysource AS ENUM ('AI_EXTRACTED','HUMAN');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'entrystatus') THEN
|
|
CREATE TYPE entrystatus AS ENUM ('DRAFT','REVIEW','APPROVED','ARCHIVED','published');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'entrytype') THEN
|
|
CREATE TYPE entrytype AS ENUM ('INCIDENT_CASE','RUNBOOK','BEST_PRACTICE','POSTMORTEM','auto_runbook','anti_pattern');
|
|
END IF;
|
|
END $$;
|
|
|
|
CREATE TABLE IF NOT EXISTS incidents (
|
|
incident_id VARCHAR(30) PRIMARY KEY,
|
|
status incidentstatus NOT NULL DEFAULT 'INVESTIGATING',
|
|
severity severity NOT NULL DEFAULT 'P2',
|
|
signals JSON DEFAULT '[]',
|
|
affected_services JSON DEFAULT '[]',
|
|
decision_chain JSON DEFAULT '[]',
|
|
proposal_ids JSON DEFAULT '[]',
|
|
outcome JSON DEFAULT '{}',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
resolved_at TIMESTAMPTZ,
|
|
closed_at TIMESTAMPTZ,
|
|
ttl_days INTEGER DEFAULT 30,
|
|
vectorized BOOLEAN DEFAULT false
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS approval_records (
|
|
id VARCHAR(36) PRIMARY KEY,
|
|
action VARCHAR(500) NOT NULL,
|
|
description TEXT NOT NULL,
|
|
status approvalstatus NOT NULL DEFAULT 'PENDING',
|
|
risk_level risklevel 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, -- ADR-093 2026-04-25: 支援群組負數 ID
|
|
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
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS knowledge_entries (
|
|
id VARCHAR(36) PRIMARY KEY,
|
|
title VARCHAR NOT NULL,
|
|
content TEXT,
|
|
entry_type entrytype NOT NULL,
|
|
category VARCHAR,
|
|
tags JSON DEFAULT '[]',
|
|
source entrysource NOT NULL DEFAULT 'HUMAN',
|
|
status entrystatus NOT NULL DEFAULT 'DRAFT',
|
|
related_incident_id VARCHAR,
|
|
related_playbook_id VARCHAR,
|
|
symptoms_hash VARCHAR,
|
|
view_count INTEGER DEFAULT 0,
|
|
created_by VARCHAR,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS rag_chunks (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
source TEXT NOT NULL,
|
|
source_id TEXT NOT NULL,
|
|
title TEXT,
|
|
chunk_text TEXT NOT NULL,
|
|
embedding vector(768),
|
|
metadata JSONB DEFAULT '{}',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- adr091: aider_events schema (2026-04-22 @ Asia/Taipei, 補入 integration test schema)
|
|
CREATE TABLE IF NOT EXISTS aider_events (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
session_id TEXT NOT NULL,
|
|
ts TIMESTAMPTZ NOT NULL,
|
|
type TEXT NOT NULL,
|
|
host TEXT DEFAULT 'ogt-mac',
|
|
payload JSONB NOT NULL,
|
|
incident_id TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS aider_events_session_idx ON aider_events(session_id);
|
|
CREATE INDEX IF NOT EXISTS aider_events_type_ts_idx ON aider_events(type, ts DESC);
|
|
CREATE INDEX IF NOT EXISTS aider_events_ts_idx ON aider_events(ts DESC);
|
|
CREATE INDEX IF NOT EXISTS aider_events_payload_gin ON aider_events USING GIN (payload);
|