even MORE code review

stateful to not deadhang, graph context -> output, more explicit state errors, other misc changes idfk any of those good thing theyre fixed tho
This commit is contained in:
lda
2026-05-26 02:33:41 +07:00 Verified
parent 9d11f78111
commit 95ec73d518
12 changed files with 159 additions and 14 deletions
+16
View File
@@ -10,6 +10,7 @@ from wf_core.runtime.foreach_state import (
ForeachBarrierState,
ItemErrorRecord,
PendingItemResult,
_state_write_from_metadata,
)
from wf_core.runtime.lineage import LineageStateView, lineage_writes_for_frame
from wf_core.runtime.ops.state import StatePatch
@@ -88,6 +89,21 @@ def test_foreach_barrier_state_round_trips_reducer_write_records() -> None:
assert write.reducer.name == "wf.std.add"
def test_pending_write_metadata_preserves_invalid_reducer_detail() -> None:
with pytest.raises(WorkflowExecutionError, match="mutually exclusive"):
_state_write_from_metadata(
{
"path": {"root": "state", "parts": ["count"]},
"incoming_value": 1,
"visible_value": 1,
"reducer": {
"name": "wf.std.add",
"ref": {"source": "wf.std", "capability_key": "add"},
},
}
)
def test_lineage_state_view_materializes_visible_values_without_mutating_base() -> None:
base_state = {"count": 2, "nested": {"value": "old"}}
view = LineageStateView(
+11
View File
@@ -1,5 +1,7 @@
from __future__ import annotations
import pytest
from wf_core import (
END,
Edge,
@@ -9,6 +11,7 @@ from wf_core import (
StateField,
StateSchema,
Workflow,
WorkflowExecutionError,
)
from wf_core.models.reducers import ReducerRef
from wf_core.paths import StatePath
@@ -158,6 +161,14 @@ def test_state_view_for_frame_overlays_writes_onto_frame_scope_state() -> None:
assert run.state["value"] == "root"
def test_lineage_state_view_rejects_corrupt_parent_cycle() -> None:
run = create_run_state(_minimal_workflow(), {"value": "root"})
run.lineages["root"].parent_id = "root"
with pytest.raises(WorkflowExecutionError, match="cycle"):
lineage_state_view(run, scope_id="root", lineage_id="root")
def test_lineage_state_view_handles_deep_ancestry_without_recursion() -> None:
run = create_run_state(_minimal_workflow(), {"value": "root"})
parent_id = "root"
+30 -1
View File
@@ -24,8 +24,9 @@ from wf_core import (
resume_workflow_async,
resume_workflow,
)
from wf_core.models.steps import InputPathBinding, Step
from wf_core.paths import GraphSourcePath, LocalPath
from wf_core.validation.issues import ValidationIssueCode
from wf_core.models.steps import Step
def test_subgraph_step_validates_boundary_bindings_and_outcomes() -> None:
@@ -158,6 +159,34 @@ def test_subgraph_step_executes_caller_prepared_saved_child_ref() -> None:
assert run.trace[-1].step_type == "subgraph"
def test_subgraph_step_projects_child_context_output() -> None:
child = Workflow(
name="context_child",
input_schema=_schema({"text": {"type": "string"}}),
state_schema=StateSchema.from_field_map({}),
output_schema=_schema({"answer": {"type": "string"}}),
output=[
InputPathBinding(
target=LocalPath.of("answer"),
path=GraphSourcePath.parse("context.scope_id"),
)
],
outcomes=["ok"],
start="done",
nodes=[EndNode(id="done", type="end", outcome="ok")],
edges=[],
)
run = execute_workflow(
_workflow(output_schema=_schema({"answer": {"type": "string"}})),
{"text": "hello"},
{},
subgraphs={"child.workflow": PreparedSubgraph(workflow=child, registry={})},
)
assert run.output["answer"] == "root:subgraph:child"
def test_subgraph_step_executes_prepared_async_child() -> None:
async def answer(payload: dict[str, object], _ctx: object) -> dict[str, object]:
return {"answer": f"async:{payload['text']}"}
+64
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
import asyncio
from contextlib import AsyncExitStack
from dataclasses import dataclass, field
from typing import Any, cast
@@ -11,6 +12,7 @@ from wf_core import RuntimeContext
from wf_mcp.capabilities import DiscoveredTool
from wf_mcp.models import AuthRecord, ConnectionConfig
from wf_mcp.runtime import McpRuntimePool, PersistentMcpSession
from wf_mcp.runtime.factory import PersistentSessionFactory
from wf_mcp.sdk import ToolCallResult
from wf_mcp.workflow import wrap_discovered_tool
@@ -73,6 +75,36 @@ class FakeStatefulClient:
self.closed = True
class OwnerCrash(BaseException):
"""Simulate transport-owner death outside normal per-request exceptions."""
@dataclass(slots=True)
class CrashingClient:
started: asyncio.Event
crash: asyncio.Event
async def call_tool(
self, tool_name: str, payload: dict[str, object]
) -> CallToolResult:
self.started.set()
await self.crash.wait()
raise OwnerCrash("transport owner died")
class CrashingSessionFactory(PersistentSessionFactory):
def __init__(self, client: CrashingClient) -> None:
self.client = client
async def _create_with_stack(
self,
stack: AsyncExitStack,
connection: ConnectionConfig,
auth: AuthRecord | None,
) -> ClientSession:
return cast(ClientSession, self.client)
def _tool(name: str) -> DiscoveredTool:
return DiscoveredTool(
name=name,
@@ -199,3 +231,35 @@ def test_runtime_pool_replaces_session_when_fingerprint_changes() -> None:
assert len(created_clients) == 2
assert created_clients[0].closed is True
def test_persistent_session_fails_inflight_and_queued_calls_if_owner_dies() -> None:
connection = ConnectionConfig(
id="failing.default",
server="failing",
account="default",
metadata={},
)
async def exercise() -> tuple[
BaseException | ToolCallResult, BaseException | ToolCallResult
]:
started = asyncio.Event()
crash = asyncio.Event()
session = await CrashingSessionFactory(
CrashingClient(started=started, crash=crash)
).create(connection, None)
first = asyncio.create_task(session.call_tool("first", {}))
await started.wait()
second = asyncio.create_task(session.call_tool("second", {}))
await asyncio.sleep(0)
crash.set()
return await asyncio.wait_for(
asyncio.gather(first, second, return_exceptions=True),
timeout=0.2,
)
results = asyncio.run(exercise())
assert isinstance(results[0], OwnerCrash)
assert isinstance(results[1], OwnerCrash)