"""Admission path through the run API: persist before dispatch (T06).""" from __future__ import annotations import asyncio from pathlib import Path from tests.wf_mcp.test_support import echo_tool from tests.wf_mcp.workflow_surface.conftest import echo_artifact from wf_api.runs import WorkflowRunApi from wf_artifacts import ( FileRunStore, FileWorkflowArtifactStore, StoredRunStatus, WorkflowDeployment, ) from wf_mcp.broker import WfMcpService from wf_mcp.broker.service.workflow_operation_context import context_from_service from wf_mcp.models import ConnectionConfig from wf_mcp.storage import FileStore def _api_with_echo(root: Path) -> tuple[WorkflowRunApi, FileRunStore]: artifact_store = FileWorkflowArtifactStore(root) artifact_store.save_artifact(echo_artifact()) artifact_store.save_deployment( WorkflowDeployment( id="echo.personal", artifact_id="echo", artifact_version=1, bindings=[{"logical_source": "demo", "concrete_source": "demo.personal"}], ) ) service = WfMcpService( store=FileStore(root / "mcp"), artifact_store=artifact_store, run_store=FileRunStore(root / "mcp"), ) service.register_connection( ConnectionConfig(id="demo.personal", server="demo", account="personal") ) service.register_specs("demo.personal", echo_tool) context = context_from_service(service) assert context.run_store is not None assert isinstance(context.run_store, FileRunStore) return WorkflowRunApi(context), context.run_store def test_manual_run_persists_admission_before_dispatch(tmp_path: Path) -> None: api, store = _api_with_echo(tmp_path / "admit") result = asyncio.run( api.run_deployment( deployment_id="echo.personal", workflow_input={"text": "hi"}, ) ) assert result["run_id"] is not None admission = store.get_admission(result["run_id"]) assert admission.resolved_input == {"text": "hi"} assert admission.environment.deployment.id == "echo.personal" record = store.get_run(result["run_id"]) assert record.status is not StoredRunStatus.ADMITTED assert record.latest_checkpoint_id is not None def test_fault_before_admission_never_dispatches(tmp_path: Path, monkeypatch) -> None: import wf_api.runs as runs_module api, store = _api_with_echo(tmp_path / "fault-before") real_write = store._write_json dispatched: list[str] = [] real_plan = runs_module.raw_plan_from_artifact def _fail_on_admission(path: Path, payload: object) -> None: if path.name == "admission.json": raise OSError("injected admission failure") real_write(path, payload) def _spy_plan(artifact): # type: ignore[no-untyped-def] dispatched.append("dispatch") return real_plan(artifact) # Real serialization fault at the admission file: nothing after it # (plan building, dispatch, stopped persist) may run. monkeypatch.setattr(store, "_write_json", _fail_on_admission) monkeypatch.setattr(runs_module, "raw_plan_from_artifact", _spy_plan) try: asyncio.run( api.run_deployment( deployment_id="echo.personal", workflow_input={"text": "hi"}, ) ) raise AssertionError("fault must propagate") except OSError: pass assert dispatched == [] fresh = FileRunStore(store.root) assert fresh.list_admissions() == [] assert fresh.list_runs() == [] def test_captured_invocation_freezes_input(tmp_path: Path) -> None: api, store = _api_with_echo(tmp_path / "freeze") payload = {"text": "hi", "nested": {"n": 1}} result = asyncio.run( api.run_deployment( deployment_id="echo.personal", workflow_input=payload, ) ) assert result["run_id"] is not None payload["text"] = "mutated" payload["nested"]["n"] = 999 admission = store.get_admission(result["run_id"]) assert admission.resolved_input == {"text": "hi", "nested": {"n": 1}}