"""市場情報 AI 受控 fetch 的 MCP gate preview。 這裡只計算外部 fetch 是否具備前置條件;不呼叫電商平台、不寫 DB、不掛排程。 """ from services.market_intel.ai_controlled_service_compat import compatibility_flag from services.market_intel.mcp_readiness import build_mcp_readiness_plan _FETCH_PREREQUISITES_MET_KEY = compatibility_flag("fetch_prerequisites_met") _FETCH_GATE_OPEN_KEY = compatibility_flag("fetch_gate_open") def _status_value(runtime_status, name, default=False): if isinstance(runtime_status, dict): return runtime_status.get(name, default) return getattr(runtime_status, name, default) def build_mcp_fetch_gate_preview( runtime_status, *, fetch_requested=False, execute_readiness=False, readiness=None, ): """建立 AI 受控 fetch 前的 MCP gate;預設不做 health check、不連 DB。""" fetch_requested = bool(fetch_requested) execute_readiness = bool(execute_readiness) readiness = readiness or build_mcp_readiness_plan( execute_requested=execute_readiness, ) readiness_checks = readiness.get("readiness_checks") or {} gate_checks = { "market_intel_enabled": bool(_status_value(runtime_status, "enabled")), "market_intel_crawler_enabled": bool( _status_value(runtime_status, "crawler_enabled") ), "database_write_still_blocked": not bool( _status_value(runtime_status, "database_write_allowed") ), "scheduler_detached": not bool( _status_value(runtime_status, "scheduler_attached") ), "mcp_readiness_executed": bool(readiness.get("execute_requested")), "mcp_router_enabled": bool(readiness.get("router_enabled")), "external_mcp_complete": bool(readiness.get("external_mcp_complete")), "internal_mcp_complete": bool(readiness.get("internal_mcp_complete")), "market_intel_mcp_integrated": bool( readiness.get("market_intel_mcp_integrated") ), "market_intel_tool_contract_ready": bool( readiness_checks.get("market_intel_tool_contract_ready") ), "external_servers_all_healthy": bool( readiness_checks.get("external_servers_all_healthy") ), } blocked_reasons = [ key for key, passed in gate_checks.items() if not passed ] if not fetch_requested: blocked_reasons.insert(0, "fetch_false_planned_only") prerequisites_met = not blocked_reasons network_request_allowed = bool(fetch_requested and prerequisites_met) return { "mode": ( "mcp_fetch_gate_read_only" if execute_readiness else "mcp_fetch_gate_planned" ), "fetch_requested": fetch_requested, "readiness_execute_requested": bool(readiness.get("execute_requested")), "ai_controlled_fetch_prerequisites_met": prerequisites_met, "ai_controlled_fetch_gate_open": network_request_allowed, _FETCH_PREREQUISITES_MET_KEY: prerequisites_met, _FETCH_GATE_OPEN_KEY: network_request_allowed, "network_request_allowed": network_request_allowed, "would_use_external_network": network_request_allowed, "gate_checks": gate_checks, "blocked_reasons": blocked_reasons, "operator_message": ( "AI 受控 fetch 已通過 MCP gate;仍只允許公開頁面、限速、不得寫 DB。" if network_request_allowed else "AI 受控 fetch 仍被 MCP gate 阻擋;需 feature flags、MCP health、router 與 tool contract 全部通過。" ), "required_sequence": [ "MARKET_INTEL_ENABLED 與 MARKET_INTEL_CRAWLER_ENABLED 需由操作員明確開啟", "MCP deploy preflight 必須通過必要 env、compose、localhost port 與 fallback 檢查", "外部 MCP stack 四個 health endpoint 需全部 200", "MCP_ROUTER_ENABLED 只能在 health 全過後才打開", "AI controlled discovery fetch 才能進入公開頁面限速探測,且仍不得寫 DB", ], "mcp_readiness_summary": { "mode": readiness.get("mode"), "router_enabled": bool(readiness.get("router_enabled")), "external_mcp_complete": bool(readiness.get("external_mcp_complete")), "internal_mcp_complete": bool(readiness.get("internal_mcp_complete")), "market_intel_mcp_integrated": bool( readiness.get("market_intel_mcp_integrated") ), "blocked_reasons": readiness.get("blocked_reasons", []), }, "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, }