Files
awoooi/apps/api/migrations/awooop_phase4_run_state_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

201 lines
7.7 KiB
SQL
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 4: Platform Shell in Shadow Mode
-- Run State Machine 持久化表
-- 2026-05-04 ogt + Claude Sonnet 4.6ADR-114/ADR-119
--
-- 前置Phase 1 control planeawooop_projects必須已執行
--
-- 三表:
-- awooop_run_state — Run FSM 主表lease + heartbeat + SKIP LOCKED
-- awooop_run_step_journal — SAGA step journaltool 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 leaseSKIP 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 integrityADR-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 modeTRUE = 不產生 user response不執行 destructive tool';
-- Index: worker 掃 PENDINGSKIP 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 timelinedashboard 查詢)
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_journalSAGA step journalADR-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 integrityADR-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_idempotencyADR-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: RLSADR-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