-- ============================================================================= -- AwoooP Phase 7: Channel Hub 雙表 -- ADR-106(channel_event family)+ Progressive Feedback Policy -- 2026-05-04 ogt + Claude Sonnet 4.6 -- ============================================================================= -- 兩張表: -- awooop_conversation_event — 入站事件鏡像(Telegram/LINE inbound) -- awooop_outbound_message — 出站訊息記錄(interim + final reply) -- ============================================================================= BEGIN; -- --------------------------------------------------------------------------- -- 1. awooop_conversation_event — 入站 Channel Event 鏡像 -- 目的:AwoooP 平台保留所有入站事件的不可變記錄,與 legacy 系統解耦 -- --------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS awooop_conversation_event ( event_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), project_id VARCHAR(64) NOT NULL REFERENCES awooop_projects(project_id) ON DELETE CASCADE, -- Channel 原始身份 channel_type VARCHAR(32) NOT NULL, -- 'telegram' | 'line' | 'slack' | 'api' provider_event_id VARCHAR(256) NOT NULL, -- Telegram: message_id, LINE: webhook event_id -- 統一身份(由 ProviderProxy 注入) platform_subject_id VARCHAR(128), channel_user_id VARCHAR(256), channel_chat_id VARCHAR(256), -- 關聯 run(若已建立) run_id UUID, -- FK soft(run 可能晚於 event 建立) -- 事件內容(只存摘要/hash,不存明文) content_type VARCHAR(32) NOT NULL DEFAULT 'text', -- 'text' | 'photo' | 'document' | 'command' content_hash VARCHAR(64), -- sha256(raw_content),明文不入庫 content_preview VARCHAR(256), -- 前 256 字元(無 PII/secret) attachment_sha256 VARCHAR(64), -- 附件 sha256 -- 去重(與 awooop_run_idempotency 對應) is_duplicate BOOLEAN NOT NULL DEFAULT FALSE, -- 時間 provider_ts TIMESTAMPTZ, -- provider 原始時間戳 received_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT chk_conv_event_channel_type CHECK (channel_type IN ('telegram','line','slack','api','internal')), CONSTRAINT chk_conv_event_content_type CHECK (content_type IN ('text','photo','document','command','callback_query')), CONSTRAINT uix_conv_event_dedup UNIQUE (project_id, channel_type, provider_event_id) ); CREATE INDEX IF NOT EXISTS idx_conv_event_run ON awooop_conversation_event (project_id, run_id, received_at DESC); CREATE INDEX IF NOT EXISTS idx_conv_event_subject ON awooop_conversation_event (project_id, platform_subject_id, received_at DESC); CREATE INDEX IF NOT EXISTS idx_conv_event_recent ON awooop_conversation_event (project_id, channel_type, received_at DESC); -- --------------------------------------------------------------------------- -- 2. awooop_outbound_message — 出站訊息記錄(interim + final reply) -- 目的:追蹤 AwoooP 發出的每一條訊息(shadow 不發、canary/active 發) -- Progressive Feedback Policy:WAITING_TOOL 超過 30s → 發 interim message -- --------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS awooop_outbound_message ( message_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), project_id VARCHAR(64) NOT NULL REFERENCES awooop_projects(project_id) ON DELETE CASCADE, run_id UUID NOT NULL, -- FK soft conversation_event_id UUID, -- 觸發訊息的入站 event -- 出站目的地 channel_type VARCHAR(32) NOT NULL, channel_chat_id VARCHAR(256) NOT NULL, -- 訊息分類 message_type VARCHAR(32) NOT NULL, -- 'interim' | 'final' | 'error' | 'approval_request' -- 內容(只存 hash,不存明文) content_hash VARCHAR(64), -- sha256(rendered_content) content_preview VARCHAR(256), -- 前 256 字元(無 PII/secret) -- provider 回報的 message_id(Telegram: message.message_id) provider_message_id VARCHAR(64), -- 狀態 send_status VARCHAR(16) NOT NULL DEFAULT 'pending', -- 'pending'|'sent'|'failed'|'shadow' send_error TEXT, -- 時間 queued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), sent_at TIMESTAMPTZ, -- Progressive Feedback Policy(WAITING_TOOL 超 30s 觸發 interim) triggered_by_state VARCHAR(32), -- 觸發本訊息的 run state('waiting_tool'等) waiting_since TIMESTAMPTZ, -- 開始等待的時間(計算 30s 超時用) CONSTRAINT chk_outbound_channel_type CHECK (channel_type IN ('telegram','line','slack','api','internal')), CONSTRAINT chk_outbound_message_type CHECK (message_type IN ('interim','final','error','approval_request')), CONSTRAINT chk_outbound_send_status CHECK (send_status IN ('pending','sent','failed','shadow')) ); CREATE INDEX IF NOT EXISTS idx_outbound_msg_run ON awooop_outbound_message (project_id, run_id, queued_at DESC); CREATE INDEX IF NOT EXISTS idx_outbound_msg_pending ON awooop_outbound_message (project_id, channel_type, queued_at) WHERE send_status = 'pending'; -- Progressive Feedback Policy 查詢:找等待超過 30s 的 runs CREATE INDEX IF NOT EXISTS idx_outbound_msg_waiting ON awooop_outbound_message (project_id, triggered_by_state, waiting_since) WHERE triggered_by_state = 'waiting_tool' AND send_status = 'pending'; -- ============================================================================= -- Row Level Security -- ============================================================================= ALTER TABLE awooop_conversation_event ENABLE ROW LEVEL SECURITY; ALTER TABLE awooop_outbound_message ENABLE ROW LEVEL SECURITY; ALTER TABLE awooop_conversation_event FORCE ROW LEVEL SECURITY; ALTER TABLE awooop_outbound_message FORCE ROW LEVEL SECURITY; CREATE POLICY conv_event_tenant_isolation ON awooop_conversation_event USING ( project_id = current_setting('app.project_id', TRUE) OR current_setting('app.project_id', TRUE) IS NULL ); CREATE POLICY outbound_msg_tenant_isolation ON awooop_outbound_message USING ( project_id = current_setting('app.project_id', TRUE) OR current_setting('app.project_id', TRUE) IS NULL ); COMMIT;