Architecture
FSM as the spine of operations software
The status column starts simple and quietly becomes the load-bearing primitive of every operations app. Here's what changes when you treat it as one from the start.
The pattern every ops app converges on
Pick any operations-heavy application that's been in production for two years and you'll find some version of this:
- A table that holds the records.
- A
statuscolumn on that table — a string, an enum, a foreign key into a "stages" lookup. - A pile of code that reads and writes that column.
That code starts small: when status moves from "Pending" to "Assigned," send a notification. Then it grows: only allow "Assigned → InProgress" if the assignee is qualified. Then: don't allow "Cancelled → Anything." Then: when "Pending" has been the status for more than two days, escalate. Then: log every status change with who, when, and why.
By the time the app is mature, that pile of code is a state machine pretending it isn't one. The transitions are scattered across handlers. The guards are re-implemented in three different places. The audit log is partial because two of the writes forgot to record themselves.
FastYoke makes the state machine the primary primitive of every workflow. Not a derived view of the data — the contract the data has to obey.
What changes when you start with an FSM
1. Transitions are checked, not hoped
Every transition has explicit from and to states. The
engine refuses any write that doesn't match a declared
transition. The "Cancelled → Anything" bug doesn't get
caught in code review — it gets caught at the schema
layer, because there's no transition declared and the
write fails.
This isn't bureaucracy. It's the same lock that the type system gives you for function signatures, applied to the domain model.
2. Guards are first-class
A guard is a JSONLogic expression evaluated against the
entity's payload before the transition fires. "Assigned →
InProgress" is gated by {"==": [{"var": "qualified"}, true]}. The guard is part of the schema, not part of the
handler. Every code path that fires the transition gets
the guard for free.
When you need richer guard logic, the scripting tier runs QuickJS inside a wasmtime sandbox. You can write a real function, version it, test it. It still lives in the schema, not in a handler.
3. The audit log writes itself
Every successful transition appends a row to the
event log. No UPDATE, no DELETE — append-only by
construction. The auditor's question "what happened to
this job between Tuesday and Friday?" has a literal answer
in the database, ordered, with payloads and actors.
You don't have to remember to log it. The engine does.
4. Side effects fire on transitions, not on writes
A webhook to a partner, a notification, a payment capture — these are consequences of a transition, not consequences of a row write. Declaring them at the transition level means the webhook fires once when the transition fires once, even if two operators race to flip the status. The engine serializes the transition; the side effect inherits that guarantee.
This kills the "we fired the partner webhook twice" class of bug at the schema layer.
5. The visual builder is the schema
FSM Designer ships in the platform. Operators can see their workflow as a graph, drag a transition, ship a new state. The graph is the schema — there's no "the visual designer drifted from the code" failure mode because they share the same source of truth.
For workflows simple enough to author this way, the operator never touches code. For workflows that need code, the schema is still the authoritative document; the code is the implementation of one guard or one side effect.
What this lets you skip
The list of problems an FSM-first architecture doesn't have is long:
- No "developer forgot the status check" bugs.
- No "the audit log is missing entries for X" gaps.
- No "the webhook fired three times" race conditions.
- No "the bulk-update tool moved 4,000 records into an invalid state" outages.
- No "rebuild the audit log from server logs because the app didn't record it" forensics.
These aren't theoretical wins. Every operations team that's been in production for three years has the scars from at least three of them.
What this costs
The honest tradeoffs:
- You think about the workflow upfront. Schema-first design asks you to enumerate the states and transitions before you start writing handlers. That's upstream work the "add a status column and figure it out later" approach doesn't make you do.
- One-off operations get heavier. Bulk-updating
10,000 records to a new state requires the bulk
operation to declare itself as a transition. That's
good for safety; it's slower than a
UPDATE ... SET status = ?would be. - The mental model is graph-shaped, not list-shaped. Engineers used to thinking "records and statuses" have to rewire to "entities and transitions." The payoff is real, but there's a ramp.
If those costs are deal-breakers for your team's preferred shape, FastYoke is the wrong substrate. If they read as "yes, finally," you're the audience.
Where this shows up across FastYoke
- Every marketplace app is built around one or more FSMs. Patient flow, warehouse management, AR invoicing, field service — same primitive, different workflow shape.
- The admin "cancel override" sits outside the FSM on purpose, because cancellation is a privileged bypass — not a workflow transition. The schema says what the workflow is; the override says what an admin can do despite the workflow.
- Self-loop transitions (where
from == to) are a legitimate primitive for audit-only events, idempotent retries, and counter increments. The schema accommodates the cases the textbook doesn't.
The honest summary
FSMs aren't the right shape for every application. They are the right shape for operations software, because operations work is journeys through states with rules. Bolting a state machine onto an app that started without one is the most common refactor on the planet. Starting with one — and getting the audit log, the guards, the side effects, and the visual designer for free — is the unlock FastYoke is built around.
For the procurement-grade detail on how state changes become the contractual audit trail, see /security. For an architectural conversation with someone who'd be the engineer behind your tenant, request early access.