fix: evaluate conditions against structured frame context
This commit is contained in:
@@ -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},
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user