Flow creation
Flows are always standalone *.flow.mdoc files. Never embed a flow inside a feature document. A flow sequences domain actions and models the steps an actor takes to achieve an outcome.
Phase 1 — Context
Which feature does this flow belong to? Which domain actions or operations does it orchestrate? Which roles participate?
Phase 2 — Pre/Post Conditions
Precondition: what must be true before the flow can start?
Postcondition: what is true after the flow completes successfully?
Add additional postconditions with outcome="..." for each branch result.
Phase 3 — Steps & Branches
Map each step to a domain action or operation where one exists. Not every step needs an action — omit it for client-side interactions or narrative context steps that don’t cross an API boundary (e.g. “User opens the modal”, “System validates session”).
- Use
action="name"for fire-and-forget actions, referencing a declared{% action %}by its bare name - Use
operation="name"for request/response operations, referencing a declared{% operation %}by its bare name - Omit
actionfor purely client-side or observational steps
For each decision point: list the possible outcomes.
For success paths: add emit="name" if the action emits an event on success, using the bare event name.
For failure paths: add throws="name" referencing a declared error by its bare id from the domain’s api. When the parent step references an operation, path outcomes (other than "success") must exactly match a declared error id from that operation’s throws list.
Every path has exactly one exit strategy:
| Exit | When to use |
|---|---|
| (nothing — auto-join) | Path continues to the next sibling step |
throws="name" | Path terminates with an error |
{% join target="step-id" /%} | Path routes explicitly to a named step |
Named steps and joins — use id="name" on a step to make it reachable by a join. Common patterns:
- Retry loop:
join target="validate"routes a failed-validation path back to the validation step - Forward skip:
join target="confirm"bypasses intermediate steps when they are not needed - Convergence: multiple paths from different branches all join to the same step
Keep the happy path minimal. Branches should model real decision points, not UI validation noise.
Phase 4 — Draft & Confirm
Output: content/flows/{id}.flow.mdoc
Do’s and Don’ts
Do:
- Map each step to a domain action or operation where one exists
- Use
action=for fire-and-forget andoperation=for request/response interactions - Keep the happy path minimal and model real decision points as branches
- Use named steps and joins for retry loops, forward skips, and convergence
- Ensure every path has exactly one exit strategy (auto-join, throws, or explicit join)
Don’t:
- Don’t embed flows inside feature documents; flows are always standalone files
- Don’t add
action=to purely client-side or observational steps - Don’t model UI validation noise as branches; reserve branches for real decision points
- Don’t create paths with ambiguous exits — each path needs exactly one exit strategy
- Don’t invent domain actions that are not declared in the referenced domain
Definition of Done
- Draft presented to user for confirmation
- Document written as
content/flows/{id}.flow.mdocwith correct frontmatter and root tag - Every step with
action=oroperation=references a declared domain action/operation - All branch paths have exactly one exit strategy
- Pre-conditions and post-conditions are defined
- All cross-references resolve to existing documents
pnpm compilesucceeds