Traditional state machines store a state field and mutate it on transitions. This works until history matters — and in any system with billing, subscriptions, or compliance, history always matters.

The failure mode is drift: state says one thing, the record of how it got there says another. An invoice marked "paid" with no successful payment in the log. A subscription in "active" that skipped the activation step. When state is stored separately from the transitions that produced it, the two can disagree — and you discover the disagreement at the worst possible time.

State gates actions Action produces outcomes Outcome resolves to state
The SAO cycle — each concept feeds the next.

SAO eliminates the category. Each outcome is a node in an immutable linked list. Every node points to its predecessor. The chain is append-only — outcomes are never modified or deleted. The head of the chain is the current state. The full chain is the complete history. There is no separate audit log — the data structure that drives the machine IS the record of everything that happened.

Each outcome also carries a kind that classifies what should happen next:

KindMeaning
SettledThe machine is quiescent. Nothing is required.
ReactiveThe system should continue automatically. Missing continuation is a bug.
SuspendedThe chain pauses for an external actor — a human, a webhook, a payment processor.

The framework records the kind but does not enforce it. The consumer decides what "reactive" means in their domain — the vocabulary without the coupling.

A (state, action) pair declares its possible outcomes. An outcome resolves to a state. State gates actions. The cycle is closed.

State

A coarse-grained mode that gates which actions are available. An invoice in draft can be finalized; an invoice in paid cannot. State is never a field on a record — it is computed from the last outcome.

Action

An operation attempted against a resource in a particular state. The machine validates that the action is legal before it proceeds. If the current state doesn't permit the action, execution fails.

Outcome

The classified result of an action. Not "something happened" — a specific, named result from a finite set declared in the schema. Each outcome carries immutable data (what triggered it) and resolves to exactly one state (where the machine goes next).

A schema declares the full topology: states, terminals, outcomes, transitions. compile() validates it exhaustively before any instance is created.

CheckWhat it prevents
Dead statesA non-terminal state with no outgoing transitions. The machine would get stuck.
Orphan outcomesDeclared but never referenced in any transition. Dead code in the schema.
Terminal violationsA terminal with outgoing transitions, or a terminal no outcome resolves to.
Missing creationNo transition from the initial state. The machine can never start.
Undeclared referencesA transition naming an outcome that doesn't exist.
Duplicate definitionsAmbiguous schemas — same outcome or transition declared twice.
The guarantee

If the process starts, the schema is structurally sound. The category of "the machine definition is wrong" is eliminated at startup.