## 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>
199 lines
9.0 KiB
PL/PgSQL
199 lines
9.0 KiB
PL/PgSQL
-- =============================================================================
|
||
-- AwoooP Phase 5: MCP Gateway 四表
|
||
-- ADR-116(五閘門 enforcement)+ ADR-118(credential isolation)
|
||
-- 2026-05-04 ogt + Claude Sonnet 4.6
|
||
-- =============================================================================
|
||
-- 執行順序:
|
||
-- 1. awooop_mcp_tool_registry — Tool 白名單
|
||
-- 2. awooop_mcp_grants — Agent × Tool 授權記錄
|
||
-- 3. awooop_mcp_credential_refs — k8s Secret 參照(不儲存明文)
|
||
-- 4. awooop_mcp_gateway_audit — 每次 gateway call 稽核
|
||
-- =============================================================================
|
||
|
||
BEGIN;
|
||
|
||
-- ---------------------------------------------------------------------------
|
||
-- 1. awooop_mcp_tool_registry — Tool 白名單(Gate 3: Tool)
|
||
-- ---------------------------------------------------------------------------
|
||
CREATE TABLE IF NOT EXISTS awooop_mcp_tool_registry (
|
||
tool_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
project_id VARCHAR(64) NOT NULL
|
||
REFERENCES awooop_projects(project_id) ON DELETE CASCADE,
|
||
tool_name VARCHAR(128) NOT NULL,
|
||
tool_type VARCHAR(32) NOT NULL, -- 'builtin' | 'mcp_server' | 'custom'
|
||
description TEXT,
|
||
allowed_scopes JSONB NOT NULL DEFAULT '[]'::jsonb, -- ["read","write","admin"]
|
||
environment_tags JSONB NOT NULL DEFAULT '{}'::jsonb, -- {"env": "prod"} gate 4 用
|
||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
||
CONSTRAINT chk_tool_type
|
||
CHECK (tool_type IN ('builtin','mcp_server','custom')),
|
||
CONSTRAINT chk_allowed_scopes_array
|
||
CHECK (jsonb_typeof(allowed_scopes) = 'array'),
|
||
CONSTRAINT uix_tool_registry_project_name
|
||
UNIQUE (project_id, tool_name)
|
||
);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_mcp_tool_registry_project
|
||
ON awooop_mcp_tool_registry (project_id, is_active);
|
||
|
||
-- ---------------------------------------------------------------------------
|
||
-- 2. awooop_mcp_grants — Agent × Tool 授權(Gate 2: Agent + Gate 3: Tool)
|
||
-- ---------------------------------------------------------------------------
|
||
CREATE TABLE IF NOT EXISTS awooop_mcp_grants (
|
||
grant_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
project_id VARCHAR(64) NOT NULL
|
||
REFERENCES awooop_projects(project_id) ON DELETE CASCADE,
|
||
agent_id VARCHAR(128) NOT NULL, -- awooop_agents.agent_id
|
||
tool_id UUID NOT NULL
|
||
REFERENCES awooop_mcp_tool_registry(tool_id) ON DELETE CASCADE,
|
||
granted_by VARCHAR(128) NOT NULL, -- principal(human user / system)
|
||
granted_scopes JSONB NOT NULL DEFAULT '[]'::jsonb, -- subset of tool.allowed_scopes
|
||
expires_at TIMESTAMPTZ, -- NULL = 永不過期
|
||
is_revoked BOOLEAN NOT NULL DEFAULT FALSE,
|
||
revoked_at TIMESTAMPTZ,
|
||
revoked_by VARCHAR(128),
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
||
CONSTRAINT chk_grant_scopes_array
|
||
CHECK (jsonb_typeof(granted_scopes) = 'array'),
|
||
CONSTRAINT chk_revoke_consistency
|
||
CHECK (
|
||
(is_revoked = FALSE AND revoked_at IS NULL AND revoked_by IS NULL)
|
||
OR
|
||
(is_revoked = TRUE AND revoked_at IS NOT NULL)
|
||
),
|
||
CONSTRAINT uix_mcp_grant_agent_tool
|
||
UNIQUE (project_id, agent_id, tool_id)
|
||
);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_mcp_grants_lookup
|
||
ON awooop_mcp_grants (project_id, agent_id, tool_id)
|
||
WHERE is_revoked = FALSE;
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_mcp_grants_expiry
|
||
ON awooop_mcp_grants (expires_at)
|
||
WHERE is_revoked = FALSE AND expires_at IS NOT NULL;
|
||
|
||
-- ---------------------------------------------------------------------------
|
||
-- 3. awooop_mcp_credential_refs — k8s Secret 參照(ADR-118 credential isolation)
|
||
-- 只儲存 ref 路徑 + sha256 指紋;明文絕不入庫
|
||
-- ---------------------------------------------------------------------------
|
||
CREATE TABLE IF NOT EXISTS awooop_mcp_credential_refs (
|
||
ref_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
tool_id UUID NOT NULL
|
||
REFERENCES awooop_mcp_tool_registry(tool_id) ON DELETE CASCADE,
|
||
project_id VARCHAR(64) NOT NULL
|
||
REFERENCES awooop_projects(project_id) ON DELETE CASCADE,
|
||
-- k8s secret ref:格式 "namespace/secret-name#key"
|
||
k8s_secret_ref VARCHAR(256) NOT NULL,
|
||
-- sha256(actual_secret_value) — 用於 audit;不可還原原值
|
||
value_sha256 VARCHAR(64),
|
||
description TEXT,
|
||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
rotated_at TIMESTAMPTZ,
|
||
|
||
CONSTRAINT chk_k8s_ref_format
|
||
CHECK (k8s_secret_ref ~ '^[a-z0-9-]+/[a-z0-9-]+#[a-zA-Z0-9_-]+$'),
|
||
CONSTRAINT chk_value_sha256_hex
|
||
CHECK (value_sha256 IS NULL OR value_sha256 ~ '^[0-9a-f]{64}$'),
|
||
CONSTRAINT uix_credential_ref_tool
|
||
UNIQUE (tool_id, k8s_secret_ref)
|
||
);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_mcp_cred_refs_tool
|
||
ON awooop_mcp_credential_refs (tool_id)
|
||
WHERE is_active = TRUE;
|
||
|
||
-- ---------------------------------------------------------------------------
|
||
-- 4. awooop_mcp_gateway_audit — Gateway call 稽核日誌(ADR-116 P1-09)
|
||
-- 不儲存 raw input/output;只儲存 hash + 結果狀態
|
||
-- ---------------------------------------------------------------------------
|
||
CREATE TABLE IF NOT EXISTS awooop_mcp_gateway_audit (
|
||
call_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
project_id VARCHAR(64) NOT NULL,
|
||
run_id UUID, -- FK soft(run 可能不存在)
|
||
trace_id VARCHAR(128),
|
||
agent_id VARCHAR(128),
|
||
tool_id UUID NOT NULL
|
||
REFERENCES awooop_mcp_tool_registry(tool_id),
|
||
tool_name VARCHAR(128) NOT NULL,
|
||
credential_ref VARCHAR(256), -- k8s_secret_ref 路徑(不含 key value)
|
||
input_hash VARCHAR(64), -- sha256(canonical input JSON)
|
||
output_hash VARCHAR(64), -- sha256(canonical output JSON)
|
||
gate_result JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||
-- {"gate1_project": true, "gate2_agent": true, "gate3_tool": true,
|
||
-- "gate4_env": true, "gate5_approval": true}
|
||
result_status VARCHAR(16) NOT NULL, -- 'success' | 'blocked' | 'failed' | 'timeout'
|
||
block_gate SMALLINT, -- 哪個 gate 攔截(1-5,NULL=未攔截)
|
||
block_reason VARCHAR(256),
|
||
latency_ms INTEGER,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
||
CONSTRAINT chk_gateway_result_status
|
||
CHECK (result_status IN ('success','blocked','failed','timeout')),
|
||
CONSTRAINT chk_block_gate_range
|
||
CHECK (block_gate IS NULL OR (block_gate >= 1 AND block_gate <= 5)),
|
||
CONSTRAINT chk_input_hash_hex
|
||
CHECK (input_hash IS NULL OR input_hash ~ '^[0-9a-f]{64}$'),
|
||
CONSTRAINT chk_output_hash_hex
|
||
CHECK (output_hash IS NULL OR output_hash ~ '^[0-9a-f]{64}$')
|
||
);
|
||
|
||
-- 查詢熱路徑:by project + run
|
||
CREATE INDEX IF NOT EXISTS idx_mcp_audit_run
|
||
ON awooop_mcp_gateway_audit (project_id, run_id, created_at DESC);
|
||
|
||
-- 查詢熱路徑:blocked calls 分析
|
||
CREATE INDEX IF NOT EXISTS idx_mcp_audit_blocked
|
||
ON awooop_mcp_gateway_audit (project_id, block_gate, created_at DESC)
|
||
WHERE result_status = 'blocked';
|
||
|
||
-- 時序熱路徑(recent calls)
|
||
CREATE INDEX IF NOT EXISTS idx_mcp_audit_recent
|
||
ON awooop_mcp_gateway_audit (project_id, created_at DESC);
|
||
|
||
-- =============================================================================
|
||
-- Row Level Security
|
||
-- =============================================================================
|
||
|
||
ALTER TABLE awooop_mcp_tool_registry ENABLE ROW LEVEL SECURITY;
|
||
ALTER TABLE awooop_mcp_grants ENABLE ROW LEVEL SECURITY;
|
||
ALTER TABLE awooop_mcp_credential_refs ENABLE ROW LEVEL SECURITY;
|
||
ALTER TABLE awooop_mcp_gateway_audit ENABLE ROW LEVEL SECURITY;
|
||
|
||
ALTER TABLE awooop_mcp_tool_registry FORCE ROW LEVEL SECURITY;
|
||
ALTER TABLE awooop_mcp_grants FORCE ROW LEVEL SECURITY;
|
||
ALTER TABLE awooop_mcp_credential_refs FORCE ROW LEVEL SECURITY;
|
||
ALTER TABLE awooop_mcp_gateway_audit FORCE ROW LEVEL SECURITY;
|
||
|
||
-- awooop_app role:只能看自己 project 的資料
|
||
CREATE POLICY mcp_tool_registry_tenant_isolation ON awooop_mcp_tool_registry
|
||
USING (
|
||
project_id = current_setting('app.project_id', TRUE)
|
||
OR current_setting('app.project_id', TRUE) IS NULL
|
||
);
|
||
|
||
CREATE POLICY mcp_grants_tenant_isolation ON awooop_mcp_grants
|
||
USING (
|
||
project_id = current_setting('app.project_id', TRUE)
|
||
OR current_setting('app.project_id', TRUE) IS NULL
|
||
);
|
||
|
||
CREATE POLICY mcp_credential_refs_tenant_isolation ON awooop_mcp_credential_refs
|
||
USING (
|
||
project_id = current_setting('app.project_id', TRUE)
|
||
OR current_setting('app.project_id', TRUE) IS NULL
|
||
);
|
||
|
||
CREATE POLICY mcp_gateway_audit_tenant_isolation ON awooop_mcp_gateway_audit
|
||
USING (
|
||
project_id = current_setting('app.project_id', TRUE)
|
||
OR current_setting('app.project_id', TRUE) IS NULL
|
||
);
|
||
|
||
COMMIT;
|