fix: evaluate conditions against structured frame context

This commit is contained in:
lda
2026-09-05 00:34:59 +07:00 Verified
parent 0530d1120a
commit 3bfb3f440b
3 changed files with 178 additions and 6 deletions
+26 -6
View File
@@ -21,25 +21,43 @@ def eval_condition(
state: Mapping[str, Any],
workflow_input: Mapping[str, Any],
context_data: str | None,
*,
context: Mapping[str, Any] | None = None,
) -> bool:
"""Evaluate a condition against state, input, and context.
``context`` is the frame's structured context graph (see
``frame_context_view``); when omitted, evaluation falls back to the
legacy ``{"prior_outcome": context_data}`` stub so direct unit callers
keep working. The graph always carries ``prior_outcome``, so passing it
preserves legacy behavior while also resolving structured paths such as
``context.foreach.<id>.item``.
"""
resolved = context if context is not None else {"prior_outcome": context_data}
if isinstance(condition, ExistsCondition):
return path_exists(
condition.path,
state=state,
workflow_input=workflow_input,
context={"prior_outcome": context_data},
context=resolved,
)
if isinstance(condition, NotCondition):
return not eval_condition(condition.arg, state, workflow_input, context_data)
return not eval_condition(
condition.arg, state, workflow_input, context_data, context=resolved
)
if isinstance(condition, VariadicCondition):
values = [
eval_condition(arg, state, workflow_input, context_data)
eval_condition(arg, state, workflow_input, context_data, context=resolved)
for arg in condition.args
]
return all(values) if condition.op == "and" else any(values)
if isinstance(condition, BinaryCondition):
left = resolve_operand(condition.left, state, workflow_input, context_data)
right = resolve_operand(condition.right, state, workflow_input, context_data)
left = resolve_operand(
condition.left, state, workflow_input, context_data, context=resolved
)
right = resolve_operand(
condition.right, state, workflow_input, context_data, context=resolved
)
if condition.op == "eq":
return left == right
if condition.op == "ne":
@@ -60,6 +78,8 @@ def resolve_operand(
state: Mapping[str, Any],
workflow_input: Mapping[str, Any],
context_data: str | None,
*,
context: Mapping[str, Any] | None = None,
) -> Any:
if isinstance(operand, LiteralOperand):
return operand.value
@@ -67,7 +87,7 @@ def resolve_operand(
str(operand.path),
state=state,
workflow_input=workflow_input,
context={"prior_outcome": context_data},
context=context if context is not None else {"prior_outcome": context_data},
)
+3
View File
@@ -29,6 +29,9 @@ def handle_condition_step(
state_view_for_frame(run, frame),
scope_input_for_frame(run, frame),
frame.prior_outcome,
# Structured context must match what validation blesses: conditions
# inside a foreach body can read context.foreach.* / loop aliases.
context=frame_context_view(run, frame).graph,
)
outcome = "true" if predicate else "false"
return StepExecutionResult(