-- governance_remediation_dispatch_2026-05-03.sql -- Wave 2 D: 治理事件修復派遣表 -- 2026-05-03 ogt + Claude Sonnet 4.6(亞太) -- -- 用途: -- 將 5 種治理事件(trust_drift / knowledge_degradation / llm_hallucination / -- execution_blast_radius / governance_slo_data_gap)接到修復執行器。 -- 每個事件同一時間最多 1 筆活躍 dispatch(partial unique index)。 -- 失敗重試採 INSERT 新 row(保留完整審計痕跡),舊 row 永久保留 failed。 -- -- 依賴(必須先存在): -- - ai_governance_events(governance_event_id FK) -- - playbooks(playbook_id FK) -- - incidents(incident_id FK) -- - approval_records(approval_id FK) -- -- 回滾路徑: -- DROP TABLE IF EXISTS governance_remediation_dispatch; -- DROP TYPE IF EXISTS governance_event_type; -- DROP TYPE IF EXISTS governance_dispatch_status; -- --------------------------------------------------------------------------- -- Step 1: 建立 ENUM 類型(create_type=False 的 ORM 需要 migration 預先建立) DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_type WHERE typname = 'governance_event_type' ) THEN CREATE TYPE governance_event_type AS ENUM ( 'trust_drift', 'knowledge_degradation', 'llm_hallucination', 'execution_blast_radius', 'governance_slo_data_gap' ); END IF; END $$; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_type WHERE typname = 'governance_dispatch_status' ) THEN CREATE TYPE governance_dispatch_status AS ENUM ( 'pending', 'dispatched', 'executing', 'succeeded', 'failed', 'skipped', 'cancelled' ); END IF; END $$; -- Step 2: 建立主表 CREATE TABLE IF NOT EXISTS governance_remediation_dispatch ( id VARCHAR(36) NOT NULL PRIMARY KEY, governance_event_id VARCHAR(36) NOT NULL REFERENCES ai_governance_events(id) ON DELETE RESTRICT, event_type governance_event_type NOT NULL, dispatch_status governance_dispatch_status NOT NULL DEFAULT 'pending', playbook_id VARCHAR(36) REFERENCES playbooks(playbook_id) ON DELETE SET NULL, incident_id VARCHAR(30) REFERENCES incidents(incident_id) ON DELETE SET NULL, approval_id VARCHAR(36) REFERENCES approval_records(id) ON DELETE SET NULL, decision_context JSONB NOT NULL DEFAULT '{}', executor_type VARCHAR(80) NOT NULL, attempt_count INTEGER NOT NULL DEFAULT 0, max_attempts INTEGER NOT NULL DEFAULT 3, last_error TEXT, dispatched_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), started_at TIMESTAMPTZ, completed_at TIMESTAMPTZ, created_by VARCHAR(100) DEFAULT 'governance_dispatcher', CONSTRAINT ck_grd_attempts CHECK (attempt_count >= 0 AND attempt_count <= max_attempts), CONSTRAINT ck_grd_max_attempts_positive CHECK (max_attempts > 0) ); COMMENT ON TABLE governance_remediation_dispatch IS 'Wave 2 D: 治理事件修復派遣記錄(失敗重試採 INSERT 新 row 審計策略)'; -- Step 3: 一般索引 CREATE INDEX IF NOT EXISTS ix_grd_status_dispatched ON governance_remediation_dispatch (dispatch_status, dispatched_at); CREATE INDEX IF NOT EXISTS ix_grd_event_status ON governance_remediation_dispatch (governance_event_id, dispatch_status); CREATE INDEX IF NOT EXISTS ix_grd_playbook_id ON governance_remediation_dispatch (playbook_id); CREATE INDEX IF NOT EXISTS ix_grd_event_type_status ON governance_remediation_dispatch (event_type, dispatch_status); CREATE INDEX IF NOT EXISTS ix_grd_governance_event_id ON governance_remediation_dispatch (governance_event_id); -- Step 4: Partial unique index(同 event_id 不可同時有 2 筆活躍 dispatch) -- 注意:ORM 層 __table_args__ 無法宣告 partial unique,此為唯一來源 CREATE UNIQUE INDEX IF NOT EXISTS ux_grd_one_active_per_event ON governance_remediation_dispatch (governance_event_id) WHERE dispatch_status IN ('pending', 'dispatched', 'executing'); -- Step 5: 權限授予(對齊 adr094 模式) GRANT SELECT, INSERT, UPDATE ON governance_remediation_dispatch TO awoooi; COMMENT ON INDEX ux_grd_one_active_per_event IS 'Partial unique: 同一治理事件同一時間最多 1 筆活躍 dispatch(pending/dispatched/executing)';