## 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>
201 lines
7.7 KiB
SQL
201 lines
7.7 KiB
SQL
-- AwoooP Phase 4: Platform Shell in Shadow Mode
|
||
-- Run State Machine 持久化表
|
||
-- 2026-05-04 ogt + Claude Sonnet 4.6(ADR-114/ADR-119)
|
||
--
|
||
-- 前置:Phase 1 control plane(awooop_projects)必須已執行
|
||
--
|
||
-- 三表:
|
||
-- awooop_run_state — Run FSM 主表(lease + heartbeat + SKIP LOCKED)
|
||
-- awooop_run_step_journal — SAGA step journal(tool call + 補償指令,ADR-119)
|
||
-- awooop_run_idempotency — 去重冪等表(ADR-114)
|
||
|
||
-- =========================================================
|
||
-- STEP 1: awooop_run_state
|
||
-- =========================================================
|
||
CREATE TABLE IF NOT EXISTS awooop_run_state (
|
||
run_id UUID PRIMARY KEY,
|
||
project_id VARCHAR(64) NOT NULL REFERENCES awooop_projects(project_id),
|
||
agent_id VARCHAR(128) NOT NULL,
|
||
|
||
-- FSM 狀態
|
||
state VARCHAR(32) NOT NULL DEFAULT 'pending'
|
||
CHECK (state IN (
|
||
'pending','running','waiting_tool',
|
||
'waiting_approval','completed','failed',
|
||
'cancelled','timeout'
|
||
)),
|
||
|
||
-- Worker lease(SKIP LOCKED 防 double-pickup)
|
||
lease_until TIMESTAMPTZ,
|
||
heartbeat_at TIMESTAMPTZ,
|
||
worker_id VARCHAR(128),
|
||
|
||
-- Retry 計數
|
||
attempt_count SMALLINT NOT NULL DEFAULT 0,
|
||
max_attempts SMALLINT NOT NULL DEFAULT 3,
|
||
|
||
-- Observability
|
||
trace_id VARCHAR(128),
|
||
|
||
-- Trigger 來源
|
||
trigger_type VARCHAR(32),
|
||
trigger_ref VARCHAR(256), -- channel_event_id / schedule_id / etc.
|
||
|
||
-- Shadow mode flag
|
||
is_shadow BOOLEAN NOT NULL DEFAULT TRUE,
|
||
|
||
-- Artifact integrity(ADR-112)
|
||
input_sha256 CHAR(64),
|
||
output_sha256 CHAR(64),
|
||
|
||
-- Budget
|
||
cost_usd NUMERIC(10, 4) NOT NULL DEFAULT 0.0000,
|
||
step_count SMALLINT NOT NULL DEFAULT 0,
|
||
|
||
-- 結果
|
||
error_code VARCHAR(64),
|
||
error_detail TEXT,
|
||
|
||
-- 時間戳記
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
started_at TIMESTAMPTZ,
|
||
completed_at TIMESTAMPTZ,
|
||
timeout_at TIMESTAMPTZ
|
||
);
|
||
|
||
COMMENT ON TABLE awooop_run_state IS
|
||
'ADR-114: Run FSM 主表,SKIP LOCKED worker lease';
|
||
COMMENT ON COLUMN awooop_run_state.is_shadow IS
|
||
'Phase 4 shadow mode:TRUE = 不產生 user response,不執行 destructive tool';
|
||
|
||
-- Index: worker 掃 PENDING(SKIP LOCKED 用)
|
||
CREATE INDEX IF NOT EXISTS idx_run_state_pending
|
||
ON awooop_run_state (project_id, created_at)
|
||
WHERE state = 'pending' AND lease_until IS NULL;
|
||
|
||
-- Index: stale run reaper(找 lease 過期的 running run)
|
||
CREATE INDEX IF NOT EXISTS idx_run_state_stale
|
||
ON awooop_run_state (lease_until)
|
||
WHERE state = 'running' AND lease_until IS NOT NULL;
|
||
|
||
-- Index: project timeline(dashboard 查詢)
|
||
CREATE INDEX IF NOT EXISTS idx_run_state_project_timeline
|
||
ON awooop_run_state (project_id, created_at DESC);
|
||
|
||
-- Index: trace_id(跨系統追蹤)
|
||
CREATE INDEX IF NOT EXISTS idx_run_state_trace_id
|
||
ON awooop_run_state (trace_id)
|
||
WHERE trace_id IS NOT NULL;
|
||
|
||
-- =========================================================
|
||
-- STEP 2: awooop_run_step_journal(SAGA step journal,ADR-119)
|
||
-- =========================================================
|
||
CREATE TABLE IF NOT EXISTS awooop_run_step_journal (
|
||
step_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
run_id UUID NOT NULL REFERENCES awooop_run_state(run_id) ON DELETE CASCADE,
|
||
project_id VARCHAR(64) NOT NULL,
|
||
|
||
-- Step 順序(每個 run 內遞增)
|
||
step_seq SMALLINT NOT NULL,
|
||
|
||
-- Tool call 資訊
|
||
tool_name VARCHAR(128) NOT NULL,
|
||
mcp_gateway_id VARCHAR(128),
|
||
|
||
-- Artifact integrity(ADR-112)
|
||
input_hash CHAR(64),
|
||
output_hash CHAR(64),
|
||
|
||
-- SAGA 補償指令(JSON)
|
||
compensation_json JSONB,
|
||
|
||
-- 執行結果
|
||
result_status VARCHAR(16) NOT NULL DEFAULT 'pending'
|
||
CHECK (result_status IN ('pending','success','failed','compensated')),
|
||
error_code VARCHAR(64),
|
||
|
||
-- Shadow 攔截記錄
|
||
was_blocked BOOLEAN NOT NULL DEFAULT FALSE,
|
||
block_reason VARCHAR(128),
|
||
|
||
-- 時間
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
completed_at TIMESTAMPTZ,
|
||
latency_ms INTEGER
|
||
);
|
||
|
||
COMMENT ON TABLE awooop_run_step_journal IS
|
||
'ADR-119 SAGA step journal:每個 tool call 獨立記錄 + 補償指令';
|
||
|
||
CREATE UNIQUE INDEX IF NOT EXISTS uix_run_step_seq
|
||
ON awooop_run_step_journal (run_id, step_seq);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_run_step_run_id
|
||
ON awooop_run_step_journal (run_id, step_seq);
|
||
|
||
-- =========================================================
|
||
-- STEP 3: awooop_run_idempotency(ADR-114 去重冪等)
|
||
-- =========================================================
|
||
CREATE TABLE IF NOT EXISTS awooop_run_idempotency (
|
||
idempotency_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
project_id VARCHAR(64) NOT NULL,
|
||
channel_type VARCHAR(32) NOT NULL,
|
||
provider_event_id VARCHAR(256) NOT NULL,
|
||
|
||
-- 映射到的 run
|
||
run_id UUID NOT NULL REFERENCES awooop_run_state(run_id),
|
||
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
COMMENT ON TABLE awooop_run_idempotency IS
|
||
'ADR-114: (project_id, channel_type, provider_event_id) → run_id 去重';
|
||
|
||
CREATE UNIQUE INDEX IF NOT EXISTS uix_run_idempotency_key
|
||
ON awooop_run_idempotency (project_id, channel_type, provider_event_id);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_run_idempotency_run_id
|
||
ON awooop_run_idempotency (run_id);
|
||
|
||
-- =========================================================
|
||
-- STEP 4: RLS(ADR-118 多租戶隔離)
|
||
-- =========================================================
|
||
ALTER TABLE awooop_run_state ENABLE ROW LEVEL SECURITY;
|
||
ALTER TABLE awooop_run_state FORCE ROW LEVEL SECURITY;
|
||
ALTER TABLE awooop_run_step_journal ENABLE ROW LEVEL SECURITY;
|
||
ALTER TABLE awooop_run_step_journal FORCE ROW LEVEL SECURITY;
|
||
ALTER TABLE awooop_run_idempotency ENABLE ROW LEVEL SECURITY;
|
||
ALTER TABLE awooop_run_idempotency FORCE ROW LEVEL SECURITY;
|
||
|
||
DROP POLICY IF EXISTS run_state_tenant_isolation ON awooop_run_state;
|
||
CREATE POLICY run_state_tenant_isolation ON awooop_run_state
|
||
FOR ALL TO awooop_app
|
||
USING (project_id = current_setting('app.project_id', TRUE))
|
||
WITH CHECK (project_id = current_setting('app.project_id', TRUE));
|
||
|
||
DROP POLICY IF EXISTS run_step_journal_tenant_isolation ON awooop_run_step_journal;
|
||
CREATE POLICY run_step_journal_tenant_isolation ON awooop_run_step_journal
|
||
FOR ALL TO awooop_app
|
||
USING (project_id = current_setting('app.project_id', TRUE))
|
||
WITH CHECK (project_id = current_setting('app.project_id', TRUE));
|
||
|
||
DROP POLICY IF EXISTS run_idempotency_tenant_isolation ON awooop_run_idempotency;
|
||
CREATE POLICY run_idempotency_tenant_isolation ON awooop_run_idempotency
|
||
FOR ALL TO awooop_app
|
||
USING (project_id = current_setting('app.project_id', TRUE))
|
||
WITH CHECK (project_id = current_setting('app.project_id', TRUE));
|
||
|
||
-- =========================================================
|
||
-- STEP 5: GRANT
|
||
-- =========================================================
|
||
GRANT SELECT, INSERT, UPDATE ON awooop_run_state TO awooop_app;
|
||
GRANT SELECT, INSERT, UPDATE ON awooop_run_step_journal TO awooop_app;
|
||
GRANT SELECT, INSERT ON awooop_run_idempotency TO awooop_app;
|
||
|
||
-- =========================================================
|
||
-- 驗收查詢
|
||
-- =========================================================
|
||
-- SELECT tablename, rowsecurity FROM pg_tables
|
||
-- WHERE tablename IN ('awooop_run_state','awooop_run_step_journal','awooop_run_idempotency');
|
||
-- 預期:所有 rowsecurity = true
|