audit: walk serial owners in write routing, harden result identity

This commit is contained in:
lda
2026-09-04 11:43:41 +07:00 Verified
parent 8a0737fe8b
commit 08cd4e1e5e
6 changed files with 278 additions and 35 deletions
+18 -7
View File
@@ -138,16 +138,21 @@ class PendingItemResult:
raise WorkflowExecutionError(
"malformed pending foreach result lineage id"
)
error = (
ItemErrorRecord.from_metadata(raw_error) if raw_error is not None else None
)
if error is not None and (error.index != index or error.frame_id != frame_id):
raise WorkflowExecutionError(
"malformed pending foreach result: error identity "
f"(index {error.index!r}, frame {error.frame_id!r}) does not "
f"match enclosing result (index {index!r}, frame {frame_id!r})"
)
return cls(
index=index,
frame_id=frame_id,
status=status,
lineage_id=lineage_id,
error=(
ItemErrorRecord.from_metadata(raw_error)
if raw_error is not None
else None
),
error=error,
)
def to_metadata(self) -> dict[str, Any]:
@@ -443,10 +448,16 @@ def item_frame_owner(frame: ExecutionFrame) -> ForeachItemOwner | None:
"""Return the named foreach ownership record for item frames.
Malformed item metadata fails closed via ``ForeachIterationMetadata``;
only non-item frames return ``None``.
only genuinely non-item frames return ``None``. An item frame without
a parent is corrupt state and raises rather than masquerading as an
ordinary frame.
"""
if frame.kind != "foreach_iteration" or frame.parent_frame_id is None:
if frame.kind != "foreach_iteration":
return None
if frame.parent_frame_id is None:
raise WorkflowExecutionError(
f"foreach item frame {frame.id!r} is missing its parent frame"
)
metadata = ForeachIterationMetadata.from_frame(frame)
if metadata is None:
return None
+34 -25
View File
@@ -94,38 +94,47 @@ def commit_foreach_aware_patch(
) -> dict[str, Any]:
"""Commit one write patch with foreach-aware routing.
Ordinary frames commit (or buffer) through their own lineage. Serial
item writes commit through the parent scope so they land in root state;
concurrent item writes stay buffered in the item lineage for the barrier
to merge. Malformed ownership, missing parents, and closed or
superseded activations fail closed.
Ordinary frames commit (or buffer) through their own lineage. The walk
climbs through every serial item owner until it reaches either the
workflow/subgraph scope root, where it commits, or a concurrent item
boundary, where it buffers in that item lineage for the barrier to
merge. Malformed ownership, missing parents, parent cycles, and closed
or superseded activations fail closed.
"""
from wf_core.runtime.foreach_state import (
item_frame_owner,
require_foreach_activation,
)
owner = item_frame_owner(frame)
if owner is None:
return commit_patch_for_frame(run, frame, patch)
parent_frame = run.frames.get(owner.parent_frame_id)
if parent_frame is None:
raise WorkflowExecutionError(
"foreach item state references missing parent frame "
f"{owner.parent_frame_id!r} for child frame {frame.id!r}"
current = frame
seen: set[str] = set()
while True:
owner = item_frame_owner(current)
if owner is None:
return commit_patch_for_frame(run, current, patch)
if current.id in seen:
raise WorkflowExecutionError(
f"cycle detected in foreach parent chain at frame {current.id!r}"
)
seen.add(current.id)
parent_frame = run.frames.get(owner.parent_frame_id)
if parent_frame is None:
raise WorkflowExecutionError(
"foreach item state references missing parent frame "
f"{owner.parent_frame_id!r} for child frame {current.id!r}"
)
activation = require_foreach_activation(
parent_frame, owner.foreach_node_id, owner.activation_id
)
activation = require_foreach_activation(
parent_frame, owner.foreach_node_id, owner.activation_id
)
if activation.barrier.mode == "concurrent":
append_lineage_writes(
run,
scope_id=frame.scope_id,
lineage_id=frame.lineage_id,
writes=patch.writes,
)
return {}
return commit_patch_for_frame(run, parent_frame, patch)
if activation.barrier.mode == "concurrent":
append_lineage_writes(
run,
scope_id=current.scope_id,
lineage_id=current.lineage_id,
writes=patch.writes,
)
return {}
current = parent_frame
def scope_state_for_frame(run: RunState, frame: ExecutionFrame) -> dict[str, Any]:
+2 -2
View File
@@ -18,7 +18,7 @@ from wf_core.runtime.foreach_state import (
)
from wf_core.runtime.lineage import (
add_lineage,
commit_patch_for_frame,
commit_foreach_aware_patch,
lineage_patch,
scope_input_for_frame,
)
@@ -379,7 +379,7 @@ def _finish_concurrent_foreach(
state_view_for_frame(run, frame),
reducers=reducers,
)
state_changes = commit_patch_for_frame(run, frame, combined)
state_changes = commit_foreach_aware_patch(run, frame, combined)
append_step_result_trace(
run,
frame_id=frame.id,