Steps and control flow

A step is a node in a business process. Beyond the plain task step (does work, requires a capability, may present a ui), a step can take a control-flow type that shapes how the flow moves through it. One flow — Chowk's FulfilOrder — exercises the whole vocabulary:

sub-domains/ordering/_precise/business_processes.vishwakarma
business_process FulfilOrder {
  title   "Fulfil a paid order"
  actor   platform.v1.System
  subject ordering.v1.Order
  trigger event ordering.v1.OrderPaid
  goal    "A paid order is reserved against stock, packed, and shipped — with the customer notified in parallel."

  step reserve call ReserveStock {
    on success -> route
    on error   -> end "out-of-stock"
  }
  step route gateway {
    when "subject.status == 'CANCELLED'" -> end "cancelled"
    otherwise                            -> dispatch
  }
  step dispatch fork {
    fork -> pack
    fork -> notify
  }
  step pack by platform.v1.Operator {
    requires [fulfillment.v1.ShipmentFulfillment]
    timeout  "PT24H"
    on success -> settle
    on error   -> end "fulfilment-failed"
  }
  step notify {
    on success -> settle
  }
  step settle join {
    join [pack, notify]
    on success -> end "shipped"
  }
}

Rendered, that flow reads:

flowchart TD
  reserve["reserve · call ReserveStock"] -->|success| route
  reserve -->|error| oos([out-of-stock])
  route{"route · gateway"} -->|"status = CANCELLED"| cx([cancelled])
  route -->|otherwise| dispatch["dispatch · fork"]
  dispatch --> pack["pack · by Operator"]
  dispatch --> notify["notify"]
  pack --> settle{{"settle · join"}}
  notify --> settle
  settle --> shipped([shipped])

The step types

TypeRole
(task)the default — does work; requires capabilities, may present ui
gatewayan exclusive (XOR) decision — routes to exactly one target by evaluating its when guards top to bottom, falling through to otherwise
forka parallel split — activates all of its fork edges at once
joina parallel join — waits for the named branches (join [pack, notify]) to complete, then continues
callinvokes a sub-process by name (call ReserveStock) — composition inside one flow
waitpauses for a timer or event, bounded by timeout

Transitions and terminals

A step's outgoing edges are conditioned on its outcome:

  • on success -> <step> / on error -> <step> — the two task outcomes.
  • when "<expr>" -> <step> — a guarded edge, the primary form on a gateway. When the flow declares a typed subject, the guard reads real fields (subject.status == 'CANCELLED') and is type-checked against them.
  • otherwise -> <step> — the fall-through arm of a gateway.
  • fork -> <step> — an unconditional parallel-split edge from a fork node.

Any target is either another step name or end "<label>" — a labelled terminal. Labelled ends are how a flow names its distinct outcomes ("shipped", "out-of-stock", "cancelled").

Swimlanes: by <Actor>

FulfilOrder runs as the System actor, but the physical pack step is performed by an Operatorstep pack by platform.v1.Operator. A change of actor between steps is a handoff, rendered as a swimlane boundary. It is how one flow crosses personas without splitting into two.

Iteration: repeat over

The reserve step calls ReserveStock, a sub-process that reserves each order line. That per-element repetition is a repeat over marker:

sub-domains/ordering/_precise/business_processes.vishwakarma
business_process ReserveStock {
  title   "Reserve stock for an order"
  actor   platform.v1.System
  subject ordering.v1.Order
  trigger call FulfilOrder

  step reserve {
    requires [inventory.v1.StockReservation]
    repeat over "subject.lines" as line
  }
}

repeat over marks a step as running once per element of a collection — distinct from a flow back-edge (a transition pointing at an earlier step), which repeats the flow. There is no loop keyword; looping is one of those two things.

Client-resolved branches: decision

Not every branch is a server gateway. A decision is a branch the actor or client resolves — a choice with no backend operation behind it:

sub-domains/ordering/_precise/business_processes.vishwakarma
  step review "Review the delivered order" {
    requires [OrderTracking]
    ui       CheckoutScreen
    decision "keep it, or return it?"
    when "intent == 'return'" -> request_return
    otherwise                 -> end "kept"
  }

The guard here reads the actor's choice, not a server outcome — the flow branches on what the person decides.