Files
awoooi/apps/api/migrations/awooop_phase7_channel_hub_2026-05-04.sql
Your Name 8629ac709b
Some checks failed
run-migration / migrate (push) Failing after 59s
Code Review / ai-code-review (push) Successful in 1m8s
Type Sync Check / check-type-sync (push) Successful in 2m27s
feat(awooop): Phase 1-8 完整實作 — AwoooP Agent Platform 六平面架構
## 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>
2026-05-04 19:31:53 +08:00

132 lines
6.5 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- =============================================================================
-- AwoooP Phase 7: Channel Hub 雙表
-- ADR-106channel_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 softrun 可能晚於 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 PolicyWAITING_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_idTelegram: 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 PolicyWAITING_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;