Finite-state machines with sandboxed guards, declarative actions, and multi-version publishing.
Workflows
A workflow is a finite-state machine. You draw it as a graph; FastYoke stores it as a strict JSON contract and runs it against jobs — live FSM instances bound to the schema they were spawned from.
Mental model
- Schema — the graph itself. Immutable once saved as a version.
Lives in
fsm_schemas(append-only). - Job — a single running instance of a schema. Carries a
current_stateand an optionalcontext_record_idpointing at anentity_recordsrow. - Transition — a named edge with an
event_type, an optional JSONLogicguard, and an ordered list of side-effectactions. - Event log — every transition writes an append-only
event_logrow (also immutable). Your audit trail is always complete.
Guards are sandboxed
Guard conditions never touch raw string parsing. Every guard runs
through jsonlogic-rs
on the server — the same evaluator the admin composer uses in the
browser for preview. That means:
- No eval. No Function(). No way for an admin to author a transition that reads arbitrary memory.
- Deterministic: the same
(guard, context)pair always resolves the same way. - Typed variable refs:
{ "var": "answer_kind" }resolves against the job's current data; unknown variables yieldnull.
Self-loops are first-class
A transition is allowed to return to the same state it started from
— a self-loop. The engine treats it like any other transition:
the guard evaluates, the current_state column gets a same-value
write, an event_log row appends, and live clients see the
broadcast. The audit row is the artifact.
Reach for a self-loop when you want to record that something happened without advancing the job:
- Audit-only events — a driver checks in, a customer calls, an operator adds a note. The job's lifecycle position is unchanged but the timeline gains a row.
- Idempotent retries — resend a webhook or re-run an algorithm
while a downstream guard hasn't yet cleared. The job stays
pending_delivery(or whatever) until the forward edge becomes fireable. - Counters and accumulators — bump a
retry_countfield, append to anattempt_historyarray, stamp alast_seen_attimestamp. The payload changes; the state doesn't. - Either-or events — same event name on two transitions: one forward edge for the ready path, one self-loop for the not-ready path. The first guard that resolves true wins.
SLA & escalation
Give any workflow state a time limit. If a job sits in that state longer than the limit, FastYoke automatically runs the escalation actions you configure (for example, call a webhook or update a field) — and records the breach on the job's timeline. The job stays where it is; escalation only alerts and acts, it doesn't move the job. Each overstay escalates once; moving the job forward and back resets the timer.
Cancel is an out-of-band override
FastYoke deliberately does NOT wire "cancelled" into the FSM graph.
Admin cancellation is a separate POST /jobs/:id/cancel endpoint
that bypasses guard evaluation, writes current_state directly,
and appends a __admin_cancel__ event log row with the operator
email + mandatory reason. The audit trail captures both the
override and its justification.
Inheritance
Enterprise / ISV tenants participating in org-scoped governance receive read-only mirrors of master templates. The builder renders these as locked — edits happen upstream in the master tenant and propagate via the inheritance sync engine.