sched: route manual runs through durable admission before dispatch (T06)

This commit is contained in:
lda
2026-09-08 10:22:17 +07:00 Verified
parent d0d9fd581e
commit 3cd61be96d
2 changed files with 138 additions and 13 deletions
+35 -14
View File
@@ -31,6 +31,8 @@ from .run_lifecycle import (
has_blocking_diagnostics, has_blocking_diagnostics,
load_stored_run, load_stored_run,
mark_resume_blocked, mark_resume_blocked,
materialize_admitted_view,
persist_admission,
persist_stopped_run, persist_stopped_run,
restore_interrupted_run, restore_interrupted_run,
validate_pinned_resume_environment, validate_pinned_resume_environment,
@@ -98,27 +100,46 @@ class WorkflowRunApi:
max_steps=limits.max_steps, max_steps=limits.max_steps,
) )
plan = raw_plan_from_artifact(artifact) # Durable admission ordering: recheck -> allocate/freeze -> persist
run = await self.context.runtime.run_workflow_from_plan( # admission -> materialize view -> dispatch captured -> persist
plan, # stopped -> reconcile. A failed durable admission never dispatches,
workflow_input, # and dispatch never re-resolves the deployment.
deployment=deployment, store = self._run_store()
artifact=artifact, run_id = store.allocate_run_id()
saved_subgraph_tree=tree,
limits=limits,
)
record = persist_stopped_run(
store=self._run_store(),
environment = create_pinned_environment( environment = create_pinned_environment(
deployment=deployment, deployment=deployment,
artifact=artifact, artifact=artifact,
tree=tree, tree=tree,
), )
admission = persist_admission(
store=store,
run_id=run_id,
environment=environment,
resolved_input=workflow_input,
max_steps=limits.max_steps,
)
materialize_admitted_view(store=store, admission=admission)
plan = raw_plan_from_artifact(admission.environment.root_artifact)
captured_tree = saved_subgraph_tree_from_snapshots(
admission.environment.child_artifacts
)
run = await self.context.runtime.run_workflow_from_plan(
plan,
dict(admission.resolved_input),
deployment=admission.environment.deployment,
artifact=admission.environment.root_artifact,
saved_subgraph_tree=captured_tree,
limits=limits,
)
record = persist_stopped_run(
store=store,
environment=admission.environment,
run=run, run=run,
run_id=run_id,
) )
return _run_payload( return _run_payload(
deployment=deployment, deployment=admission.environment.deployment,
artifact=artifact, artifact=admission.environment.root_artifact,
status=run.status.value, status=run.status.value,
run_id=record.id, run_id=record.id,
resume_readiness=record.resume_readiness.value, resume_readiness=record.resume_readiness.value,
+104
View File
@@ -0,0 +1,104 @@
"""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:
api, store = _api_with_echo(tmp_path / "fault-before")
def _fail_save(admission) -> None: # type: ignore[no-untyped-def]
raise OSError("injected admission failure")
# Admission persist precedes dispatch in run_deployment ordering: a failure
# here must propagate before any run view exists, so no dispatch could
# have produced a stopped checkpoint.
monkeypatch.setattr(store, "save_admission", _fail_save)
try:
asyncio.run(
api.run_deployment(
deployment_id="echo.personal",
workflow_input={"text": "hi"},
)
)
raise AssertionError("fault must propagate")
except OSError:
pass
assert store.list_admissions() == []
assert store.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}}