318 lines
12 KiB
Python
318 lines
12 KiB
Python
"""市場情報 MCP runtime smoke 收據審核 preview。
|
||
|
||
操作員可貼上 /api/market_intel/mcp_readiness?execute=true&timeout=3
|
||
的實際 JSON 結果;本模組只審核收據,不打 health、不開 DB、不連外。
|
||
"""
|
||
|
||
EXPECTED_EXTERNAL_SERVERS = ("postgres", "omnisearch", "firecrawl", "filesystem")
|
||
PROHIBITED_RECEIPT_FLAGS = (
|
||
"database_write_executed",
|
||
"database_commit_executed",
|
||
"scheduler_attached",
|
||
"writes_executed",
|
||
"would_write_database",
|
||
)
|
||
|
||
|
||
def _truthy(value):
|
||
if isinstance(value, bool):
|
||
return value
|
||
if isinstance(value, (int, float)):
|
||
return bool(value)
|
||
if isinstance(value, str):
|
||
return value.strip().lower() in {
|
||
"1",
|
||
"true",
|
||
"yes",
|
||
"y",
|
||
"ok",
|
||
"pass",
|
||
"passed",
|
||
"healthy",
|
||
"ready",
|
||
}
|
||
return False
|
||
|
||
|
||
def _mapping(value):
|
||
return value if isinstance(value, dict) else {}
|
||
|
||
|
||
def _server_statuses(receipt):
|
||
statuses = receipt.get("server_statuses") or []
|
||
if not isinstance(statuses, list):
|
||
return []
|
||
normalized = []
|
||
for item in statuses:
|
||
if not isinstance(item, dict):
|
||
continue
|
||
server = str(item.get("server") or item.get("name") or "").strip().lower()
|
||
if not server:
|
||
continue
|
||
healthy = (
|
||
_truthy(item.get("healthy"))
|
||
if "healthy" in item
|
||
else str(item.get("status") or "").strip().lower() in {"healthy", "ok"}
|
||
)
|
||
normalized.append(
|
||
{
|
||
"server": server,
|
||
"health_checked": _truthy(item.get("health_checked"))
|
||
or _truthy(item.get("checked")),
|
||
"healthy": healthy,
|
||
"status": item.get("status") or "unknown",
|
||
"base_url": item.get("base_url"),
|
||
"error_message": item.get("error_message"),
|
||
}
|
||
)
|
||
return normalized
|
||
|
||
|
||
def _sample_receipt_template():
|
||
return {
|
||
"mode": "mcp_readiness_read_only",
|
||
"execute_requested": True,
|
||
"router_enabled": True,
|
||
"external_mcp_complete": True,
|
||
"internal_mcp_complete": True,
|
||
"market_intel_mcp_integrated": True,
|
||
"server_statuses": [
|
||
{
|
||
"server": "postgres",
|
||
"health_checked": True,
|
||
"healthy": True,
|
||
"status": "healthy",
|
||
},
|
||
{
|
||
"server": "omnisearch",
|
||
"health_checked": True,
|
||
"healthy": True,
|
||
"status": "healthy",
|
||
},
|
||
{
|
||
"server": "firecrawl",
|
||
"health_checked": True,
|
||
"healthy": True,
|
||
"status": "healthy",
|
||
},
|
||
{
|
||
"server": "filesystem",
|
||
"health_checked": True,
|
||
"healthy": True,
|
||
"status": "healthy",
|
||
},
|
||
],
|
||
"mcp_tool_contract": {
|
||
"contract_ready": True,
|
||
"tool_count": 3,
|
||
"checks": {
|
||
"market_intel_caller_registered": True,
|
||
"all_router_tools_whitelisted": True,
|
||
"all_tools_read_only": True,
|
||
"no_database_write_allowed": True,
|
||
"no_scheduler_attach_allowed": True,
|
||
},
|
||
"database_write_executed": False,
|
||
"scheduler_attached": False,
|
||
},
|
||
"telemetry": {
|
||
"mode": "mcp_telemetry_read_only",
|
||
"table_exists": True,
|
||
"read_only_query_executed": True,
|
||
"database_session_created": False,
|
||
"database_write_executed": False,
|
||
"database_commit_executed": False,
|
||
},
|
||
"database_session_created": False,
|
||
"database_write_executed": False,
|
||
"database_commit_executed": False,
|
||
"scheduler_attached": False,
|
||
"writes_executed": False,
|
||
"would_write_database": False,
|
||
}
|
||
|
||
|
||
def _tool_contract_read_only(contract):
|
||
checks = _mapping(contract.get("checks"))
|
||
return bool(
|
||
_truthy(contract.get("contract_ready"))
|
||
and _truthy(checks.get("market_intel_caller_registered"))
|
||
and _truthy(checks.get("all_router_tools_whitelisted"))
|
||
and _truthy(checks.get("all_tools_read_only"))
|
||
and _truthy(checks.get("no_database_write_allowed"))
|
||
and _truthy(checks.get("no_scheduler_attach_allowed"))
|
||
and not _truthy(contract.get("database_write_executed"))
|
||
and not _truthy(contract.get("scheduler_attached"))
|
||
)
|
||
|
||
|
||
def _telemetry_read_only(telemetry):
|
||
return bool(
|
||
telemetry.get("mode") == "mcp_telemetry_read_only"
|
||
and _truthy(telemetry.get("table_exists"))
|
||
and _truthy(telemetry.get("read_only_query_executed"))
|
||
and not _truthy(telemetry.get("database_session_created"))
|
||
and not _truthy(telemetry.get("database_write_executed"))
|
||
and not _truthy(telemetry.get("database_commit_executed"))
|
||
)
|
||
|
||
|
||
def build_mcp_runtime_smoke_receipt_preview(*, receipt=None, phase=None):
|
||
"""審核 runtime smoke 收據;不執行任何 health、DB 或 network 動作。"""
|
||
receipt = receipt or {}
|
||
payload_received = bool(receipt)
|
||
statuses = _server_statuses(receipt)
|
||
status_map = {item["server"]: item for item in statuses}
|
||
expected_present = all(server in status_map for server in EXPECTED_EXTERNAL_SERVERS)
|
||
all_checked = bool(statuses) and all(
|
||
status_map.get(server, {}).get("health_checked")
|
||
for server in EXPECTED_EXTERNAL_SERVERS
|
||
)
|
||
all_healthy = bool(statuses) and all(
|
||
status_map.get(server, {}).get("healthy")
|
||
for server in EXPECTED_EXTERNAL_SERVERS
|
||
)
|
||
telemetry = _mapping(receipt.get("telemetry"))
|
||
contract = _mapping(receipt.get("mcp_tool_contract"))
|
||
prohibited_flags = [
|
||
key for key in PROHIBITED_RECEIPT_FLAGS
|
||
if _truthy(receipt.get(key))
|
||
]
|
||
blocked_from_receipt = [
|
||
str(item) for item in receipt.get("blocked_reasons", [])
|
||
] if isinstance(receipt.get("blocked_reasons"), list) else []
|
||
|
||
gates = [
|
||
{
|
||
"key": "receipt_payload_received",
|
||
"passed": payload_received,
|
||
"label": "已提供 runtime smoke receipt payload",
|
||
},
|
||
{
|
||
"key": "readiness_execute_true",
|
||
"passed": receipt.get("mode") == "mcp_readiness_read_only"
|
||
and _truthy(receipt.get("execute_requested")),
|
||
"label": "收據來自 execute=true 的 read-only readiness",
|
||
},
|
||
{
|
||
"key": "router_enabled",
|
||
"passed": _truthy(receipt.get("router_enabled")),
|
||
"label": "MCP router 已啟用",
|
||
},
|
||
{
|
||
"key": "external_mcp_complete",
|
||
"passed": _truthy(receipt.get("external_mcp_complete")),
|
||
"label": "外部 MCP runtime 已完整",
|
||
},
|
||
{
|
||
"key": "internal_mcp_complete",
|
||
"passed": _truthy(receipt.get("internal_mcp_complete")),
|
||
"label": "內部 MCP telemetry / contract 已完整",
|
||
},
|
||
{
|
||
"key": "market_intel_mcp_integrated",
|
||
"passed": _truthy(receipt.get("market_intel_mcp_integrated")),
|
||
"label": "market_intel caller 與 MCP tool contract 已整合",
|
||
},
|
||
{
|
||
"key": "all_expected_servers_present",
|
||
"passed": expected_present,
|
||
"label": "四個必要 MCP server 都出現在收據中",
|
||
},
|
||
{
|
||
"key": "all_servers_health_checked",
|
||
"passed": all_checked,
|
||
"label": "四個 MCP server 都已被 health check",
|
||
},
|
||
{
|
||
"key": "all_servers_healthy",
|
||
"passed": all_healthy,
|
||
"label": "四個 MCP server health 全部 healthy",
|
||
},
|
||
{
|
||
"key": "tool_contract_ready_and_read_only",
|
||
"passed": _tool_contract_read_only(contract),
|
||
"label": "MCP tool contract ready,且工具全為 read-only",
|
||
},
|
||
{
|
||
"key": "telemetry_read_only_confirmed",
|
||
"passed": _telemetry_read_only(telemetry),
|
||
"label": "mcp_calls telemetry 只做 read-only 查詢",
|
||
},
|
||
{
|
||
"key": "no_database_write_or_scheduler_flags",
|
||
"passed": not prohibited_flags,
|
||
"label": "收據未宣告 DB write、commit、scheduler 或 writes 已執行",
|
||
},
|
||
{
|
||
"key": "receipt_has_no_blocked_reasons",
|
||
"passed": not blocked_from_receipt,
|
||
"label": "原始 readiness 收據沒有 blocked reasons",
|
||
},
|
||
]
|
||
blocked_reasons = [
|
||
gate["key"] for gate in gates
|
||
if not gate["passed"]
|
||
]
|
||
accepted = payload_received and not blocked_reasons
|
||
|
||
return {
|
||
"mode": (
|
||
"mcp_runtime_smoke_receipt_review"
|
||
if payload_received
|
||
else "mcp_runtime_smoke_receipt_preview"
|
||
),
|
||
"phase": phase,
|
||
"receipt_payload_received": payload_received,
|
||
"runtime_smoke_receipt_accepted": accepted,
|
||
"ready_for_completion_runtime_promotion": accepted,
|
||
"ready_for_manual_fetch_gate_review": accepted,
|
||
"external_mcp_runtime_receipt_complete": accepted,
|
||
"internal_mcp_runtime_receipt_complete": accepted,
|
||
"gate_count": len(gates),
|
||
"passed_gate_count": sum(1 for gate in gates if gate["passed"]),
|
||
"blocked_reasons": blocked_reasons,
|
||
"receipt_blocked_reasons": blocked_from_receipt,
|
||
"gates": gates,
|
||
"server_statuses": statuses,
|
||
"server_summary": {
|
||
"expected_servers": list(EXPECTED_EXTERNAL_SERVERS),
|
||
"present_servers": sorted(status_map.keys()),
|
||
"healthy_count": sum(1 for item in statuses if item["healthy"]),
|
||
"health_checked_count": sum(1 for item in statuses if item["health_checked"]),
|
||
"all_expected_servers_present": expected_present,
|
||
"all_servers_health_checked": all_checked,
|
||
"all_servers_healthy": all_healthy,
|
||
},
|
||
"telemetry_summary": {
|
||
"mode": telemetry.get("mode"),
|
||
"table_exists": bool(telemetry.get("table_exists")),
|
||
"read_only_query_executed": bool(telemetry.get("read_only_query_executed")),
|
||
"database_session_created": bool(telemetry.get("database_session_created")),
|
||
"database_write_executed": bool(telemetry.get("database_write_executed")),
|
||
"database_commit_executed": bool(telemetry.get("database_commit_executed")),
|
||
},
|
||
"prohibited_flags": prohibited_flags,
|
||
"sample_receipt_template": _sample_receipt_template(),
|
||
"next_operator_steps": [
|
||
"若收據通過,回到 MCP 完整度稽核確認 runtime 缺口可被補齊",
|
||
"人工 fetch gate 仍需另行審核,不可因 runtime smoke 通過自動抓外站",
|
||
"保留原始收據於外部變更紀錄,API/UI 不保存 payload",
|
||
],
|
||
"payload_persisted": False,
|
||
"receipt_persisted": False,
|
||
"api_executes_health_check": False,
|
||
"api_executes_docker": False,
|
||
"api_executes_ssh": False,
|
||
"api_opens_database_connection": False,
|
||
"api_writes_database": False,
|
||
"api_uses_external_network": False,
|
||
"database_session_created": False,
|
||
"database_write_executed": False,
|
||
"database_commit_executed": False,
|
||
"external_network_executed": False,
|
||
"scheduler_attached": False,
|
||
"writes_executed": False,
|
||
"would_write_database": False,
|
||
}
|