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.
Semantic tags: the accounting contract
Section titled “Semantic tags: the accounting contract”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.
Registering a kind
Section titled “Registering a kind”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.
Publish-time validation
Section titled “Publish-time validation”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.
Customization tiers
Section titled “Customization tiers”| 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 |
Also in the engine
Section titled “Also in the engine”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.