feat(awooop): expose mcp bridge truth chain
All checks were successful
Code Review / ai-code-review (push) Successful in 13s
CD Pipeline / tests (push) Successful in 1m17s
CD Pipeline / build-and-deploy (push) Successful in 3m55s
CD Pipeline / post-deploy-checks (push) Successful in 1m45s

This commit is contained in:
Your Name
2026-05-13 03:21:31 +08:00
parent b81cb28615
commit b4d367eeb4
5 changed files with 136 additions and 2 deletions

View File

@@ -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,

View File

@@ -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:

View File

@@ -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 粉飾成自動修復完成。

View File

@@ -1883,6 +1883,8 @@ Phase 6 完成後
- T2 bridge image `94d006ea` 已部署CD run `1921` successhealth 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 筆 MCP8 failed / 0 successtruth-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 GatewayT2 後續仍要把新 MCP caller 收斂到 first-class Gateway path。

View 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;