Repository navigation
Conversation
Demo: authored conditional edgesFrom this branch, create a small local workflow: cat > /tmp/forge-conditional-demo.yaml <<'YAML'
apiVersion: forge/v1
kind: Workflow
metadata:
name: conditional-demo
revision: 1
spec:
state: feature
entry: generate_prd
steps:
generate_prd:
cases:
- when: {fact: workflow.has_error, op: equals, value: true}
next: escalate_blocked
- when: {fact: workflow.paused, op: equals, value: false}
next: generate_spec
otherwise: __end__
escalate_blocked:
next: __end__
generate_spec:
next: __end__
YAML
uv run forge workflow catalog feature # See the approved conditional facts
uv run forge workflow validate /tmp/forge-conditional-demo.yaml
uv run forge workflow render /tmp/forge-conditional-demo.yaml
uv run pytest -q tests/unit/workflow/test_conditional_edges.py
uv run python - <<'PY'
from forge.workflow.declarative.compiler import DeclarativeWorkflowCompiler
from forge.workflow.declarative.loader import load_workflow_file
definition = load_workflow_file('/tmp/forge-conditional-demo.yaml')
step = definition.spec.steps['generate_prd']
route = DeclarativeWorkflowCompiler(definition)._conditional_route(step.cases, step.otherwise)
for label, state in [
('generation failed', {'last_error': 'failed', 'is_paused': False}),
('ready to continue', {'is_paused': False}),
('waiting for input', {'is_paused': True}),
]:
print(f'{label}: {route(state)}')
PYExpected targets: |
| outcome: replacements.get(target, target) | ||
| for outcome, target in step.branches.items() | ||
| }, | ||
| "cases": tuple( |
There was a problem hiding this comment.
P1 — include conditional terminals in normal completion detection. These new case/otherwise targets can be rewritten to returnTo, but the included-Workflow path at line 132 selects terminals only when step.next == "__end__". A child whose conditional edge is its only normal completion is rejected with has no normal completion; if another fixed terminal exists, the conditional __end__ remains unchanged and ends the parent early. Include conditional terminal edges when selecting steps to rewrite.
| def validate_transition(self) -> WorkflowStep: | ||
| if bool(self.next) == bool(self.route): | ||
| raise ValueError("exactly one of 'next' or 'route' is required") | ||
| if sum((bool(self.next), bool(self.route), bool(self.cases))) != 1: |
There was a problem hiding this comment.
P2 — reject branches on a conditional step. This validator requires one of next, route, or cases, but the later branches check only rejects them with next. cases plus branches is accepted; the compiler follows cases and silently ignores the authored branches, even when a branch names an undeclared step. Please require branches only when route is set.
Summary
Adds typed, declarative
casesandotherwisetransitions so composed workflows can choose edges from Forge-owned facts. The compiler validates and records the selected case; composition, rendering, diffs, and the workflow authoring docs support the new form. Static routers now have explicit reviewed outcome contracts, while protected approval and review gates retain trusted routing.Validation
uv run pytest -q tests/unit/workflow— 1,087 passeduv run ruff check src/forge/workflow/declarative/{models,predicates,compiler,composition,manifest,cli,router_contracts}.py tests/unit/workflow/test_conditional_edges.pygit diff --checkStacked on #346 (
feature/reusable-workflows).