## Phase 1-3: Control Plane + Contract System - awooop_phase1_control_plane_2026-05-04.sql: 12 張核心表 + RLS - awooop_phase1_batch1_rls_2026-05-04.sql: 全部 FORCE RLS + GRANT - packages/awooop-contracts/: 六合約 JSON Schema + golden fixtures - src/models/awooop_contracts.py: Pydantic v2 contract models(extra=forbid) - src/repositories/contract_repository.py: contract lifecycle(draft→published→active) - src/services/contract_service.py: HMAC publish sig + Redis multi-sig activate - src/services/schema_validator.py: LLM output validator(retry×3, E-SCHEMA-001) ## Phase 2: Tenant Isolation - awooop_phase2_budget_ledger_2026-05-04.sql: budget_ledger + RLS - src/services/budget_service.py: Token Budget Hard Kill 三層防線 - src/core/context.py: PROJECT_ID ContextVar(31 background loop 自動繼承) - src/db/base.py + models.py: project_id 欄位 + RLS set_config 注入 - src/hermes/nl_gateway.py: project_id Redis key 前綴(Phase A 雙寫) - src/services/anomaly_counter.py: per-project 改造(Phase A fallback) ## Phase 4: Platform Shell in Shadow Mode - awooop_phase4_run_state_2026-05-04.sql: run_state + step_journal + idempotency - src/services/run_state_machine.py: 8-state FSM + SKIP LOCKED + stale reaper - src/services/platform_runtime.py: UUID v7 + W3C trace_id + shadow_execute - src/services/audit_sink.py: PII/secret redaction 9 patterns - src/api/v1/platform/runs.py: POST/GET /v1/platform/runs(Router→Service 架構) - src/workers/platform_worker.py: SKIP LOCKED worker + heartbeat + reaper loop - src/main.py: platform router + lifespan worker start/stop ## Phase 5: MCP Gateway 五閘門 - awooop_phase5_mcp_gateway_2026-05-04.sql: 4 表 + RLS - src/plugins/mcp/gateway.py: McpGateway(Gate 1~5, E-MCP-GATE-001~009) - src/plugins/mcp/redaction_middleware.py: 雙層 redaction + 16K 截斷 - src/plugins/mcp/registry.py: __provider name mangling(ADR-116) - src/plugins/mcp/credential_resolver.py: k8s secret ref 解析 - tests/test_mcp_credential_isolation.py: 10 個迴歸測試(secret leak 防再現) ## Phase 6-8: EwoooC + Channel Hub + Approval Token - awooop_phase6_ewoooc_onboarding_2026-05-04.sql: ewoooc tenant + 4 read-only MCP tools - awooop_phase7_channel_hub_2026-05-04.sql: conversation_event + outbound_message - src/services/provider_proxy.py: ProviderProxy + PlatformEnvelope(ADR-115) - src/services/channel_hub.py: Telegram inbound mirror + Progressive Feedback(30s) - src/services/awooop_approval_token.py: HS256 + jti NX replay 防護 + suggest mode Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
132 lines
6.5 KiB
PL/PgSQL
132 lines
6.5 KiB
PL/PgSQL
-- =============================================================================
|
||
-- 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;
|