Back to Blog

Compiling Workflow YAML to Deterministic State Machines

• By ObjectOS Engineering

Compiling Workflow YAML to Deterministic State Machines

In ObjectOS, business processes are defined in YAML. But at runtime, we aren't just "interpreting" this YAML. We compile it into a graph structure known as a Hierarchical State Machine (HSM), or Statechart.

This article details the compilation process, the cycle detection algorithms, and how we ensure Atomic State Transitions in a distributed environment.

1. The Compiler Pipeline

Converting a YAML file into an executable state machine involves several passes.

  1. Parse & Validate: We use ajv to validate the YAML against the Workflow JSON Schema.
  2. Graph Construction: We build an adjacency list representing the states and transitions.
  3. Static Analysis (The Safety Check):
    • Unreachable States: We run a Breadth-First Search (BFS) starting from initial. Any node not visited is dead code.
    • Determinism Check: We verify that no two transitions from the same state have overlapping triggers and overlapping guards (which would create race conditions).

Cycle Detection

While cycles are allowed in Workflows (e.g., Request Changes -> Resubmit), Infinite loops in automated actions are dangerous.

We use Tarjan's Algorithm to identify Strongly Connected Components (SCCs) in the graph of automatic transitions (transitions without user input). If an automatic SCC exists, the compiler throws an error, preventing a Stack Overflow at runtime.

2. Hierarchical States (Sub-States)

Real-world processes are nested. An "Approved" state for an Order might actually contain sub-states: "Payment Pending", "Packing", "Shipped".

ObjectOS supports Statecharts (Harel State Machines).

states:
  processing:
    initial: payment
    states:
      payment: ...
      packing: ...

The Flattening Algorithm: Internally, the Kernel flattens this into a single lookup table using dot-notation (processing.payment). When a transition targets processing, the engine automatically resolves it to processing.payment (the initial child).

3. Transactional Integrity (The "Runner")

Executing a transition is a critical section. We cannot allow an order to be "Shipped" if the inventory reduction fails.

The WorkflowRunner executes a transition as a single ACID Transaction.

await ctx.transaction(async (trx) => {
  // 1. Lock the record (SELECT FOR UPDATE)
  const record = await trx.find(id, { lock: true });
  
  // 2. Evaluate Guards
  if (!guards.every(g => g(record, ctx))) throw new Error('Guard failed');
  
  // 3. Execute 'on_exit' actions of old state
  await executeHooks(oldState.onExit, trx);
  
  // 4. Update Status Field
  await trx.update(id, { status: newState });
  
  // 5. Execute 'on_enter' actions of new state
  await executeHooks(newState.onEnter, trx);
});

By passing the transaction handle trx to all side-effects (hooks), we ensure that if a webhook fails or a script throws, the entire state transition rolls back. The record remains in the old state.

4. Conclusion

ObjectOS Workflows are more than just a switch statement. They are compiled, statically analyzed programs running within a transactional container. This guarantees that your business logic remains consistent, predictable, and devoid of "impossible states."