Skip to content

Workflow engine

One platform package, internal/platform/workflow, runs every approval flow in the system. Modules do not grow their own state machines; they register a graph. The design is §5 of the architecture spec.

One definition per kind, per organization, versioned. A definition has states, edges (actions) between them, and grants (who may take an edge).

  • States are string keys, scoped to a definition version. There is no global number space.
  • Every instance is pinned to the version it was submitted under. Publishing v(n+1) touches nothing in flight.
  • Each kind has its own definition, so an advance’s graph has an acquittal edge and a receipt’s does not.

Each edge carries a gate that decides who may take it.

Gate Authorized by
reporter The submitter, and nobody else
target Grants on the destination state
source Grants on the state being left
advance Grants on the state this rejection replaces

Grants point at a member, a position, the claimant’s superior, the unit head, or a position n levels up the tree, and carry an optional amount floor. Floors only, never ceilings: a band ends at the next larger floor on the same step. A claimant never picks their own approver.

Every state declares a semantic from a closed set (draft, pending, accrual, payout, acknowledged, dispute, rejected, cancelled, custom). accounting/ reacts to the semantic, never to a state name. That is what lets an organization rename, translate and insert steps without touching the ledger.

A package registers a seeded graph from init():

workflow.RegisterSeededGraph(KindInvoice, InvoiceGraph)

The graph is built for the organization’s root seat and seeded when the organization is provisioned. Kinds registered today include expense claims (built in), advances, petty cash spends, top-ups and close-outs, trade documents, receivables documents, inventory movements, vacancies, leave requests, payroll runs and payslips, and CRM leads and opportunities. RegisterSeededGraph panics on an invalid key, a duplicate kind or a nil builder: each is a wiring bug. Prove a new graph with workflowtest.RequireSeededGraphValid.

A definition cannot be published unless it has exactly one draft state and a terminal state, every state is reachable and has an exit, at most one accrual and one payout lie on any path (ordering declared per definition), every gated edge resolves to a grant, rejections past accrual carry a reversal, and the grants on each gated step reach down to zero.

Tier An organization can Gate
Presentation Rename, translate, reorder, relabel Self-serve
Structure Toggle optional steps, insert custom states, set grants and thresholds Self-serve, must validate
Semantics Move accrual or payout, add a terminal branch Blocked in the editor

Multi-level chains, amount bands, role sets, delegation, escalation, return for correction, dispute with a mandatory reason, and an immutable decision log. For the user-facing view see the user guide’s How workflows work page.