feat(awooop): expose mcp bridge truth chain
This commit is contained in:
@@ -7,6 +7,7 @@ Telegram cards can be audited without guessing which subsystem owns the truth.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
@@ -20,6 +21,7 @@ from src.db.base import get_db_context
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
_MAX_ROWS = 100
|
||||
_JSON_TEXT_FIELDS = {"gate_result", "source_envelope"}
|
||||
|
||||
|
||||
def _clean(value: Any) -> Any:
|
||||
@@ -38,7 +40,15 @@ def _clean(value: Any) -> Any:
|
||||
|
||||
|
||||
def _clean_row(row: Any) -> dict[str, Any]:
|
||||
return {key: _clean(value) for key, value in dict(row).items()}
|
||||
cleaned: dict[str, Any] = {}
|
||||
for key, value in dict(row).items():
|
||||
if key in _JSON_TEXT_FIELDS and isinstance(value, str):
|
||||
try:
|
||||
value = json.loads(value)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
cleaned[key] = _clean(value)
|
||||
return cleaned
|
||||
|
||||
|
||||
async def _fetch_all(db: Any, sql: str, params: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
@@ -507,6 +517,7 @@ async def fetch_truth_chain(source_id: str, project_id: str = "awoooi") -> dict[
|
||||
trace_id,
|
||||
agent_id,
|
||||
tool_name,
|
||||
gate_result,
|
||||
result_status,
|
||||
block_gate,
|
||||
block_reason,
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from src.services.awooop_truth_chain_service import _truth_status
|
||||
from src.services.awooop_truth_chain_service import _clean_row, _truth_status
|
||||
|
||||
|
||||
def test_clean_row_parses_json_text_fields_for_gateway_visibility() -> None:
|
||||
row = {
|
||||
"gate_result": '{"schema_version":"legacy_mcp_bridge_v1","policy_enforced":false}',
|
||||
"source_envelope": '{"adapter":"legacy_telegram_gateway"}',
|
||||
"plain_text": '{"not":"parsed"}',
|
||||
}
|
||||
|
||||
cleaned = _clean_row(row)
|
||||
|
||||
assert cleaned["gate_result"]["schema_version"] == "legacy_mcp_bridge_v1"
|
||||
assert cleaned["gate_result"]["policy_enforced"] is False
|
||||
assert cleaned["source_envelope"]["adapter"] == "legacy_telegram_gateway"
|
||||
assert cleaned["plain_text"] == '{"not":"parsed"}'
|
||||
|
||||
|
||||
def test_truth_status_marks_no_action_approval_as_manual_required() -> None:
|
||||
|
||||
@@ -6505,3 +6505,40 @@ gateway_audit_total=0 last_15m=0 bridge_total=0
|
||||
|
||||
- 因此目前只能宣稱「T2 bridge 寫入能力已部署並經 rollback smoke 驗證」。
|
||||
- 尚不能宣稱「所有 MCP / 自建 MCP 都已完全經 AwoooP Gateway 強制治理」;下一段要讓下一個真實 incident / MCP 呼叫自然產生 durable bridge row,或把高頻 caller 改成 first-class `McpGateway`。
|
||||
|
||||
**T2 backfill / truth-chain visibility 追加**:
|
||||
|
||||
- 新增 `scripts/ops/awooop-mcp-gateway-bridge-backfill-24h.sql`:
|
||||
- 將最近 24h 真實 `mcp_audit_log` 鏡像到 `awooop_mcp_gateway_audit`。
|
||||
- 以 `gate_result.legacy_audit_id` 做 idempotency key。
|
||||
- bridge row 保留 `policy_enforced=false` 與 `not_used_reason`,避免誤判為五閘門已 enforcement。
|
||||
- production 已執行 backfill:
|
||||
|
||||
```text
|
||||
inserted_bridge_rows=1160
|
||||
gateway_total=1310 bridge_total=1310 last_24h=1276
|
||||
B6C589_gateway_rows=8 failed=8 success=0
|
||||
```
|
||||
|
||||
- truth-chain API 追加 `gate_result` 欄位,並把 JSONB text 解析回物件,讓 UI 能顯示 bridge reason。
|
||||
|
||||
```text
|
||||
py_compile:
|
||||
apps/api/src/services/awooop_truth_chain_service.py
|
||||
apps/api/tests/test_awooop_truth_chain_service.py
|
||||
# OK
|
||||
|
||||
ruff F,E9:
|
||||
# All checks passed
|
||||
|
||||
pytest:
|
||||
apps/api/tests/test_awooop_truth_chain_service.py
|
||||
apps/api/tests/test_platform_router_order.py
|
||||
apps/api/tests/test_awooop_operator_auth.py
|
||||
# 11 passed
|
||||
```
|
||||
|
||||
**效果**:
|
||||
|
||||
- `INC-20260512-B6C589` truth-chain 現在不再是 `awooop_mcp_gateway_audit_empty`。
|
||||
- 仍顯示 `manual_required/blocked`,因為 8 個 SSH MCP 都失敗,approval/incident 狀態仍矛盾;這是 T5 要處理,不能用 T2 粉飾成自動修復完成。
|
||||
|
||||
@@ -1883,6 +1883,8 @@ Phase 6 完成後
|
||||
- T2 bridge image `94d006ea` 已部署,CD run `1921` success,health 200。
|
||||
- rollback smoke 證明 `record_mcp_call()` 在同一 transaction 內會同時寫 legacy `mcp_audit_log` 與 `awooop_mcp_gateway_audit` bridge row,且 bridge row 標示 `policy_enforced=false` / `not_used_reason=legacy direct provider path; bridge audit only`;rollback 後兩邊皆未污染 production。
|
||||
- 部署後短觀察窗內沒有自然新 legacy MCP call(`legacy_mcp_15m=0`),所以 live `awooop_mcp_gateway_audit` total 仍是 0。T2 bridge capability 已上線,但 T2 全退出條件仍需下一個真實 MCP 呼叫產生 durable row,或把高頻 caller 改成 first-class Gateway path。
|
||||
- 已執行最近 24h 真實 legacy MCP backfill:`inserted_bridge_rows=1160`,目前 `awooop_mcp_gateway_audit gateway_total=1310 / bridge_total=1310 / last_24h=1276`。`INC-20260512-B6C589` 現在 gateway side 可見 8 筆 MCP,8 failed / 0 success;truth-chain blocker 移除 `awooop_mcp_gateway_audit_empty`,但仍是 `manual_required/blocked`,因為 evidence sensors 全失敗、NO_ACTION approval 無 execution、incident 仍 investigating。
|
||||
- truth-chain API 追加回傳 `gate_result`,讓 Operator Console 可直接顯示 `policy_enforced=false` 與 `not_used_reason`,避免把 bridge row 誤認為 first-class Gateway enforcement。
|
||||
|
||||
**仍未宣稱完成**:
|
||||
- 這只是 legacy bridge,不是把所有呼叫強制改經 AwoooP Gateway;T2 後續仍要把新 MCP caller 收斂到 first-class Gateway path。
|
||||
|
||||
69
scripts/ops/awooop-mcp-gateway-bridge-backfill-24h.sql
Normal file
69
scripts/ops/awooop-mcp-gateway-bridge-backfill-24h.sql
Normal file
@@ -0,0 +1,69 @@
|
||||
-- AwoooP T2 MCP Gateway bridge backfill (24h)
|
||||
-- 2026-05-12 Codex + ogt
|
||||
--
|
||||
-- Purpose:
|
||||
-- Mirror real legacy mcp_audit_log rows into awooop_mcp_gateway_audit so
|
||||
-- truth-chain can show MCP usage for recent incidents while first-class
|
||||
-- Gateway migration continues. These rows are explicitly marked as bridge
|
||||
-- records and policy_enforced=false; they are not proof of five-gate
|
||||
-- Gateway enforcement.
|
||||
--
|
||||
-- Idempotency:
|
||||
-- gate_result.legacy_audit_id stores the mcp_audit_log.id source key.
|
||||
-- Re-running this SQL will only insert missing rows.
|
||||
|
||||
WITH inserted AS (
|
||||
INSERT INTO awooop_mcp_gateway_audit (
|
||||
project_id,
|
||||
run_id,
|
||||
trace_id,
|
||||
agent_id,
|
||||
tool_name,
|
||||
input_hash,
|
||||
output_hash,
|
||||
gate_result,
|
||||
result_status,
|
||||
block_gate,
|
||||
block_reason,
|
||||
latency_ms,
|
||||
created_at
|
||||
)
|
||||
SELECT
|
||||
'awoooi' AS project_id,
|
||||
NULL::uuid AS run_id,
|
||||
LEFT(COALESCE(src.incident_id, src.session_id), 128) AS trace_id,
|
||||
LEFT(COALESCE(src.agent_role, 'legacy-mcp-provider'), 128) AS agent_id,
|
||||
LEFT('legacy:' || src.mcp_server || ':' || src.tool_name, 128) AS tool_name,
|
||||
encode(digest(COALESCE(src.input_params::text, 'null'), 'sha256'), 'hex') AS input_hash,
|
||||
CASE
|
||||
WHEN src.output_result IS NULL THEN NULL
|
||||
ELSE encode(digest(src.output_result::text, 'sha256'), 'hex')
|
||||
END AS output_hash,
|
||||
jsonb_build_object(
|
||||
'schema_version', 'legacy_mcp_bridge_v1',
|
||||
'gateway_path', 'legacy_backfill',
|
||||
'policy_enforced', false,
|
||||
'not_used_reason', 'legacy direct provider path; bridge audit only',
|
||||
'legacy_audit_id', src.id::text,
|
||||
'legacy_mcp_server', src.mcp_server,
|
||||
'legacy_tool_name', src.tool_name,
|
||||
'flywheel_node', src.flywheel_node
|
||||
) AS gate_result,
|
||||
CASE WHEN src.success IS TRUE THEN 'success' ELSE 'failed' END AS result_status,
|
||||
NULL::smallint AS block_gate,
|
||||
CASE WHEN src.success IS TRUE THEN NULL ELSE LEFT(src.error_message, 256) END AS block_reason,
|
||||
src.duration_ms AS latency_ms,
|
||||
src.created_at
|
||||
FROM mcp_audit_log src
|
||||
WHERE src.created_at > NOW() - INTERVAL '24 hours'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM awooop_mcp_gateway_audit dst
|
||||
WHERE dst.project_id = 'awoooi'
|
||||
AND dst.gate_result->>'schema_version' = 'legacy_mcp_bridge_v1'
|
||||
AND dst.gate_result->>'legacy_audit_id' = src.id::text
|
||||
)
|
||||
RETURNING call_id
|
||||
)
|
||||
SELECT COUNT(*) AS inserted_bridge_rows
|
||||
FROM inserted;
|
||||
Reference in New Issue
Block a user