Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion docs/spec/processes.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ processes:

Generates one process definition per `processes[]` entry (a standard workflow model plus its diagram layout, so a modeller renders it).

Step kinds: `userTask`, `serviceTask`, `decision`, `script`, `wait`, `end`.
Step kinds: `userTask`, `serviceTask`, `decision`, `script`, `wait`, `parallel`, `end`.

### Step routing — the linear chain and `next:`

Expand All @@ -39,6 +39,37 @@ A `notify` service task stands alone: it cannot carry another action (`setField`

A decision condition may walk **one hop** off the trigger entity (`customer.creditLimit > 10000`): a resolver step is generated before the gateway to load the related entity and rewrite the condition.

### parallel — concurrent branches (fork/join)

A `parallel` step runs several branch steps **at the same time** and rejoins before the next step — two independent reviews of one order, say, instead of one after the other. It declares the `branches` to run concurrently and the `next` step to continue at once every branch is done:

```yaml
- { name: reviews, kind: parallel, args: { branches: [techReview, commercialReview], next: consolidate } }
- { name: techReview, kind: userTask, args: { assignee: engineer, form: ReviewOrder } }
- { name: commercialReview, kind: userTask, args: { assignee: sales, form: ReviewOrder } }
- { name: consolidate, kind: serviceTask, args: { setRelationField: Status, value: 2, next: done } }
```

It is a **parallel-gateway fork/join**: a diverging gateway fans an unconditioned flow to each branch, and a converging gateway waits for **all** branches before continuing.

A branch is a **chain**, not a single step. It starts at the declared branch step and continues through that step's own routing — its `next`, a decision's `then` / `else`, a boundary `timeout` / `expire` branch — and it may itself be a nested `parallel`, which contributes its own fork/join pair. Everything reachable that way belongs to the branch and runs concurrently with the sibling branches:

```yaml
- { name: reviews, kind: parallel, args: { branches: [techReview, commercial], next: consolidate } }
# a two-step chain: the second step declares no routing, so it joins
- { name: techReview, kind: userTask, args: { assignee: engineer, form: ReviewOrder, next: techSignoff } }
- { name: techSignoff, kind: serviceTask, args: { setRelationField: TechStatus, value: 2 } }
# a nested fork: it declares no `next`, so its join flows into the enclosing one
- { name: commercial, kind: parallel, args: { branches: [pricing, legal] } }
- { name: pricing, kind: decision, args: { if: "amount > 1000", then: escalate, else: join } }
- { name: escalate, kind: userTask, args: { assignee: manager, form: ReviewOrder } }
- { name: legal, kind: userTask, args: { assignee: legal, form: ReviewOrder } }
```

A branch and everything it reaches are off the linear chain (like decision targets), so their declaration order carries no meaning — and inside a branch there is **no positional fall-through**. A step routes explicitly, or, declaring no routing at all, is a branch **terminal** and flows into the join. The routing literal **`join`** converges on the innermost enclosing join gateway, which is how a decision inside a branch rejoins from both arms.

Rules: at least two distinct `branches`, each a declared step other than the fork itself. `join` is valid only inside a branch, and no step may be named `join`. A branch must never route to `end` — the join would wait forever for a token that ended; end the process after the fork instead. A step may belong to only one branch: a step two concurrent tokens reach runs twice and still leaves the join waiting. A branch is entered through its fork only, so a branch converges on `join`, never on the fork's own `next` directly. A top-level fork declares `next` (a declared step or `end`); a **nested** fork may omit it, and then joins into its own enclosing join.

### wait — park the process on a data event

A `wait` step parks the process until an entity lifecycle event resumes it — a case waiting for a reply, a flow waiting for a payment, an order waiting for its goods receipt:
Expand Down