-- ============================================================================= -- Migration 025: mcp_calls + ai_call_budgets -- Operation Ollama-First v5.0 — Phase 1 (Phase 10 MCP / Phase 9 預算告警 預備) -- 日期: 2026-05-03 台北 -- ============================================================================= -- 說明: -- mcp_calls — Phase 10 引入 5 個 MCP server 後的遙測表,Schema 先到位。 -- 與 ai_calls 分表,因 MCP 沒有 token 概念、計費邏輯不同。 -- ai_call_budgets — Phase 9 預算告警表;種子資料即立即可用。 -- -- 設計決策: -- 1. mcp_calls.insight_id 不加 FK:避免 cascade(Phase 11 ai_insights 會頻繁 archive) -- 改用「軟連結」+ 應用層 join,保留可被 NULL 化的彈性。 -- 2. ai_call_budgets.provider NULL = 全供應商總額(UNIQUE constraint 用 (period, provider) -- 若 NULL 行為不一致需保護,由應用層強制單例) -- 註: PostgreSQL 預設 NULL != NULL,所以同 period 多筆 provider=NULL 會通過 UNIQUE, -- 需應用層自律或改用部分索引(見下方)。 -- -- 回滾腳本: -- DROP INDEX IF EXISTS idx_mcp_calls_called_at; -- DROP INDEX IF EXISTS idx_mcp_calls_caller_called_at; -- DROP INDEX IF EXISTS idx_mcp_calls_server_tool; -- DROP INDEX IF EXISTS idx_mcp_calls_status_called_at; -- DROP INDEX IF EXISTS uq_ai_call_budgets_period_null_provider; -- DROP TABLE IF EXISTS mcp_calls; -- DROP TABLE IF EXISTS ai_call_budgets; -- ============================================================================= -- ───────────────────────────────────────────────────────────────────────────── -- mcp_calls — MCP Server 呼叫遙測(Phase 10 預備) -- ───────────────────────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS mcp_calls ( id BIGSERIAL PRIMARY KEY, called_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 與 ai_calls.caller 同一張白名單,便於跨表 trace caller VARCHAR(64) NOT NULL, -- omnisearch / firecrawl / postgres / playwright / filesystem / git server VARCHAR(64) NOT NULL, -- search / scrape / query / read_file / git_log / ... tool VARCHAR(128) NOT NULL, input_args JSONB, output_size INTEGER, -- bytes,異常巨大可警示 duration_ms INTEGER, -- M1: NOT NULL 對齊 ai_calls status VARCHAR(16) NOT NULL, error TEXT, cost_usd NUMERIC(10,6) NOT NULL DEFAULT 0, cache_hit BOOLEAN NOT NULL DEFAULT FALSE, -- M6: 跨 ai_calls/mcp_calls 串鏈用(Phase 10 後 LLM→MCP→LLM 鏈不可斷) request_id VARCHAR(64), -- 軟連結:若 MCP 結果被 embed 寫入 ai_insights,記錄 insight_id 但不加 FK insight_id BIGINT, -- ─────── critic-A11 修補:白名單 + PII/膨脹護欄 ─────── -- M1: status 白名單 CONSTRAINT chk_mcp_calls_status CHECK ( status IN ('ok','error','timeout','rate_limited','cache_only') ), -- L3: duration 範圍 CONSTRAINT chk_mcp_calls_duration_range CHECK ( duration_ms IS NULL OR (duration_ms >= 0 AND duration_ms <= 600000) ), -- H2: input_args / error 大小護欄(postgres-mcp 可能含 SQL,含 PII 風險) CONSTRAINT chk_mcp_calls_args_size CHECK ( input_args IS NULL OR octet_length(input_args::text) <= 16384 ), CONSTRAINT chk_mcp_calls_error_size CHECK ( error IS NULL OR octet_length(error) <= 4096 ) ); CREATE INDEX IF NOT EXISTS idx_mcp_calls_called_at ON mcp_calls (called_at DESC); CREATE INDEX IF NOT EXISTS idx_mcp_calls_caller_called_at ON mcp_calls (caller, called_at DESC); CREATE INDEX IF NOT EXISTS idx_mcp_calls_server_tool ON mcp_calls (server, tool, called_at DESC); -- M5: 異常監控 partial 精確列舉 CREATE INDEX IF NOT EXISTS idx_mcp_calls_status_called_at ON mcp_calls (status, called_at DESC) WHERE status IN ('error','timeout','rate_limited'); -- M6: request_id 串鏈(部分索引,sparse 不全建) CREATE INDEX IF NOT EXISTS idx_mcp_calls_request_id ON mcp_calls (request_id) WHERE request_id IS NOT NULL; GRANT ALL PRIVILEGES ON mcp_calls TO momo; GRANT USAGE, SELECT ON SEQUENCE mcp_calls_id_seq TO momo; -- ───────────────────────────────────────────────────────────────────────────── -- ai_call_budgets — 預算與告警閾值(Phase 9 預算守門) -- ───────────────────────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS ai_call_budgets ( id SERIAL PRIMARY KEY, period VARCHAR(16) NOT NULL, -- daily / weekly / monthly provider VARCHAR(32), -- NULL = 全供應商總額 budget_usd NUMERIC(10,2) NOT NULL, alert_pct INTEGER NOT NULL DEFAULT 80, -- 達此百分比觸發 Telegram 告警 updated_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT chk_ai_budget_period CHECK (period IN ('daily', 'weekly', 'monthly')), CONSTRAINT chk_ai_budget_alert_pct CHECK (alert_pct BETWEEN 1 AND 100), CONSTRAINT chk_ai_budget_amount CHECK (budget_usd > 0) ); -- 部分唯一索引:分別處理 provider IS NULL 與 NOT NULL,避免 NULL != NULL 漏洞 CREATE UNIQUE INDEX IF NOT EXISTS uq_ai_call_budgets_period_provider ON ai_call_budgets (period, provider) WHERE provider IS NOT NULL; CREATE UNIQUE INDEX IF NOT EXISTS uq_ai_call_budgets_period_null_provider ON ai_call_budgets (period) WHERE provider IS NULL; GRANT ALL PRIVILEGES ON ai_call_budgets TO momo; GRANT USAGE, SELECT ON SEQUENCE ai_call_budgets_id_seq TO momo; -- ───────────────────────────────────────────────────────────────────────────── -- 種子資料(戰役 v5.0 規格 + critic-A11 H3 補 nim/nim_via_elephant/ollama) -- M2: ON CONFLICT 配 partial unique index 會炸;改用 WHERE NOT EXISTS 確保冪等 -- ───────────────────────────────────────────────────────────────────────────── -- 全供應商總額(period, provider=NULL) INSERT INTO ai_call_budgets (period, provider, budget_usd, alert_pct) SELECT 'daily', NULL, 1.00, 80 WHERE NOT EXISTS ( SELECT 1 FROM ai_call_budgets WHERE period='daily' AND provider IS NULL ); INSERT INTO ai_call_budgets (period, provider, budget_usd, alert_pct) SELECT 'weekly', NULL, 5.00, 80 WHERE NOT EXISTS ( SELECT 1 FROM ai_call_budgets WHERE period='weekly' AND provider IS NULL ); INSERT INTO ai_call_budgets (period, provider, budget_usd, alert_pct) SELECT 'monthly', NULL, 20.00, 80 WHERE NOT EXISTS ( SELECT 1 FROM ai_call_budgets WHERE period='monthly' AND provider IS NULL ); -- 個別供應商(含 H3 修補:補 nim / nim_via_elephant / ollama 雙線) INSERT INTO ai_call_budgets (period, provider, budget_usd, alert_pct) SELECT 'monthly', 'claude', 10.00, 80 WHERE NOT EXISTS ( SELECT 1 FROM ai_call_budgets WHERE period='monthly' AND provider='claude' ); INSERT INTO ai_call_budgets (period, provider, budget_usd, alert_pct) SELECT 'monthly', 'gemini', 8.00, 80 WHERE NOT EXISTS ( SELECT 1 FROM ai_call_budgets WHERE period='monthly' AND provider='gemini' ); -- H3: NIM 兩條獨立計費鏈(NemoTron 配額 + ElephantAlpha 49B),各設預算 INSERT INTO ai_call_budgets (period, provider, budget_usd, alert_pct) SELECT 'monthly', 'nim', 5.00, 80 WHERE NOT EXISTS ( SELECT 1 FROM ai_call_budgets WHERE period='monthly' AND provider='nim' ); INSERT INTO ai_call_budgets (period, provider, budget_usd, alert_pct) SELECT 'monthly', 'nim_via_elephant', 5.00, 80 WHERE NOT EXISTS ( SELECT 1 FROM ai_call_budgets WHERE period='monthly' AND provider='nim_via_elephant' ); -- H3: OpenRouter(PPT deepseek-v3.2) INSERT INTO ai_call_budgets (period, provider, budget_usd, alert_pct) SELECT 'monthly', 'openrouter', 3.00, 80 WHERE NOT EXISTS ( SELECT 1 FROM ai_call_budgets WHERE period='monthly' AND provider='openrouter' ); -- Ollama 雙線(免費,但設極低預算 + alert=100% 統一告警邏輯,異常激增可警示) INSERT INTO ai_call_budgets (period, provider, budget_usd, alert_pct) SELECT 'monthly', 'gcp_ollama', 0.01, 100 WHERE NOT EXISTS ( SELECT 1 FROM ai_call_budgets WHERE period='monthly' AND provider='gcp_ollama' ); INSERT INTO ai_call_budgets (period, provider, budget_usd, alert_pct) SELECT 'monthly', 'ollama_111', 0.01, 100 WHERE NOT EXISTS ( SELECT 1 FROM ai_call_budgets WHERE period='monthly' AND provider='ollama_111' ); DO $$ BEGIN RAISE NOTICE 'Migration 025 done: mcp_calls + ai_call_budgets + 10 seed budgets (Operation Ollama-First v5.0 P1, critic-A11 fixes B2/H1/H2/H3/M1/M2/M5/M6/L3 applied)'; END $$;