Branching
Control-flow constructs nest without a declared limit. Two constructs bound their own depth: call and partial inclusion.
Any step MAY include when. A step whose guard is false is skipped. A skip is a recorded outcome, distinct from success and from failure. A reference to a skipped step resolves to null.
- notify_exec: when: "{{ classify.category == 'sales' and enrich.deal_size > 50000 }}" slack.post: { channel: "#big-deals" }route is N-way dispatch on a single expression. else is required.
- triage: route: "{{ classify.category }}" cases: sales: [ ... ] support: [ ... ] else: [ ... ]route binds its id to the scope of the branch that executed. The branch’s step ids are read through that id: {{ triage.lead.id }}.
Cases are unordered. Exactly one case matches, and reordering cases cannot change behavior.
A case label is a value, and dispatch is by equality. choose covers dispatch that equality on one value cannot express.
When the routing expression resolves to a value with a declared enum domain, implementations MUST verify that a case or else covers every member of that domain. Implementations MUST reject a case whose label is outside the domain. A value has a declared enum domain when its type is an enum and its value is validated. Three sources qualify:
- an input, validated before the run starts;
- a name that a state’s
acceptsdeclares; - an output of an action that declares
validated_output.
An enum-typed value from any other source has no declared domain, and a route over it is checked only for duplicate labels.
choose
Section titled “choose”choose is an ordered choice: a chain of guards.
- triage: choose: - when: "{{ classify.category == 'sales' and enrich.deal_size > 50000 }}" then: [{ exec: { slack.post: { channel: "#big-deals" } } }] - when: "{{ classify.category == 'sales' }}" then: [{ lead: { crm.create_lead: { email: "{{ message.from }}" } } }] else: [ ... ]Implementations MUST evaluate arms in order, and MUST execute the first arm whose guard is true. else is required, and runs when no guard matches. choose binds its id to the scope of the arm that executed.
A choose with a single arm and an empty else is the representation of a guarded group: several steps under one condition. Implementations MUST NOT warn on the single-arm form.
route |
choose |
|
|---|---|---|
| Subject | one expression, equality | arbitrary boolean guards |
| Order | irrelevant | significant, first match |
| Exhaustiveness | verified against the enum domain | not verifiable; else covers it |
| Overlap | impossible | permitted, resolved by order |
Implementations SHOULD warn when a choose of two or more arms holds only equality guards against a single enum-typed path, because route expresses that dispatch with a stronger guarantee.
Branches of a route or a choose MAY bind different names. A path through the construct’s id is statically valid when it resolves in at least one branch. At run time, a path into a branch that did not execute resolves to null, as a reference to a skipped step does.