feat(awooop): route post verify mcp through gateway
This commit is contained in:
@@ -38,9 +38,12 @@ from typing import TYPE_CHECKING, Any
|
||||
|
||||
import structlog
|
||||
|
||||
from src.db.base import get_db_context
|
||||
from src.plugins.mcp.gateway import GatewayContext, McpGateway
|
||||
from src.plugins.mcp.registry import AuditedMCPToolProvider
|
||||
from src.services.evidence_snapshot import EvidenceSnapshot
|
||||
from src.services.mcp_audit_context import with_mcp_audit_context
|
||||
from src.services.mcp_tool_registry import SensorDimension, get_mcp_tool_registry
|
||||
from src.services.mcp_tool_registry import RegisteredTool, SensorDimension, get_mcp_tool_registry
|
||||
from src.services.sanitization_service import sanitize, sanitize_dict_values
|
||||
# W2 PR-V1: 頂層 import 讓測試 patch 路徑固定(延遲 import 無法被 patch)
|
||||
# ENABLE_SELF_HEALING_VALIDATOR=False 時此 import 不影響效能(純 python 模組)
|
||||
@@ -224,7 +227,12 @@ class PostExecutionVerifier:
|
||||
agent_role="post_execution_verifier",
|
||||
)
|
||||
result = await asyncio.wait_for(
|
||||
reg.provider.execute(reg.tool.name, audited_params),
|
||||
self._execute_tool(
|
||||
reg=reg,
|
||||
tool_name=reg.tool.name,
|
||||
audited_params=audited_params,
|
||||
incident_id=_get_incident_id(incident),
|
||||
),
|
||||
timeout=TOOL_TIMEOUT_SEC,
|
||||
)
|
||||
if result.success and result.output:
|
||||
@@ -243,6 +251,35 @@ class PostExecutionVerifier:
|
||||
|
||||
return state
|
||||
|
||||
async def _execute_tool(
|
||||
self,
|
||||
reg: RegisteredTool,
|
||||
tool_name: str,
|
||||
audited_params: dict[str, Any],
|
||||
incident_id: str,
|
||||
):
|
||||
"""Route production post-execution sensors through AwoooP MCP Gateway.
|
||||
|
||||
Raw providers are still used by unit tests and manual injections. In
|
||||
production the registry wraps providers in `AuditedMCPToolProvider`, and
|
||||
those calls must leave first-class gateway audit rows just like the
|
||||
pre-decision sense path.
|
||||
"""
|
||||
if not isinstance(reg.provider, AuditedMCPToolProvider):
|
||||
return await reg.provider.execute(tool_name, audited_params)
|
||||
|
||||
async with get_db_context("awoooi") as db:
|
||||
ctx = GatewayContext(
|
||||
project_id="awoooi",
|
||||
agent_id="post_execution_verifier",
|
||||
tool_name=tool_name,
|
||||
trace_id=incident_id,
|
||||
is_shadow=True,
|
||||
environment={"env": "prod"},
|
||||
required_scope="read",
|
||||
)
|
||||
return await McpGateway(db).call(ctx, audited_params)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# W2 PR-V1: SelfHealingValidator 串接
|
||||
|
||||
@@ -23,6 +23,8 @@ import pytest
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from src.plugins.mcp.interfaces import MCPTool, MCPToolProvider, MCPToolResult
|
||||
from src.plugins.mcp.registry import AuditedMCPToolProvider
|
||||
from src.services import post_execution_verifier as pev_module
|
||||
from src.services.post_execution_verifier import (
|
||||
PostExecutionVerifier,
|
||||
_assess_recovery,
|
||||
@@ -341,6 +343,14 @@ class _FakeRegistry:
|
||||
]
|
||||
|
||||
|
||||
class _DbContext:
|
||||
async def __aenter__(self) -> object:
|
||||
return object()
|
||||
|
||||
async def __aexit__(self, *_args: object) -> None:
|
||||
return None
|
||||
|
||||
|
||||
class TestCollectPostStateAuditContext:
|
||||
@pytest.mark.asyncio
|
||||
async def test_collect_post_state_injects_mcp_audit_context(self):
|
||||
@@ -357,3 +367,43 @@ class TestCollectPostStateAuditContext:
|
||||
assert audit_context["session_id"] == "incident:INC-TEST:post_execution"
|
||||
assert audit_context["flywheel_node"] == "verify"
|
||||
assert audit_context["agent_role"] == "post_execution_verifier"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_collect_post_state_routes_audited_provider_through_gateway(
|
||||
self,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
):
|
||||
provider = _CaptureProvider()
|
||||
verifier = PostExecutionVerifier()
|
||||
audited_provider = AuditedMCPToolProvider(provider)
|
||||
verifier._registry = _FakeRegistry(audited_provider)
|
||||
calls: list[dict] = []
|
||||
|
||||
class FakeGateway:
|
||||
def __init__(self, db: object) -> None:
|
||||
self.db = db
|
||||
|
||||
async def call(self, ctx, parameters: dict) -> MCPToolResult:
|
||||
calls.append({"ctx": ctx, "parameters": parameters, "db": self.db})
|
||||
return MCPToolResult(
|
||||
success=True,
|
||||
execution_id="gw",
|
||||
output={"status": "Running"},
|
||||
)
|
||||
|
||||
monkeypatch.setattr(pev_module, "get_db_context", lambda _project_id: _DbContext())
|
||||
monkeypatch.setattr(pev_module, "McpGateway", FakeGateway)
|
||||
|
||||
state = await verifier._collect_post_state(_stub_incident())
|
||||
|
||||
assert state["kubectl_get"]["status"] == "Running"
|
||||
assert provider.seen_parameters is None
|
||||
assert calls
|
||||
ctx = calls[0]["ctx"]
|
||||
assert ctx.project_id == "awoooi"
|
||||
assert ctx.agent_id == "post_execution_verifier"
|
||||
assert ctx.tool_name == "kubectl_get"
|
||||
assert ctx.trace_id == "INC-TEST"
|
||||
assert ctx.required_scope == "read"
|
||||
assert calls[0]["parameters"]["_mcp_audit"]["incident_id"] == "INC-TEST"
|
||||
assert calls[0]["parameters"]["_mcp_audit"]["flywheel_node"] == "verify"
|
||||
|
||||
Reference in New Issue
Block a user