Result codes

Every advance and rehydrate returns a result, never an exception. On the unhappy path the result carries a code. Only the code is contract; the detail text is free to differ across runtimes and is for humans, not branching. The table lists every code the engine and the snapshot mutations return. A client should still treat a code it does not recognise as a refusal, so that a code added later does not break it.

CodeReturned byMeaning
no-transitionadvanceno edge matches the (state, trigger) pair
guard-failedadvancean edge matched but its guard rejected the trigger; the detail is the Because(...) message
invalid-contextadvance, rehydratethe resulting (advance) or stored (rehydrate) context failed the target state's rule
malformedrehydrate, advance (persisted)the snapshot JSON could not be parsed, or its context holds a value no store can keep: a number outside the range of a double (1e400) or a NUL character in a string or key. On a persisted advance, the trigger input is not valid JSON, or the result held such a value; nothing was written
unknown-staterehydratethe snapshot names a state the definition does not have. Only the exact declared name is a state: "1", " Unlocked" and "Locked, Unlocked" are unknown, and an unknown trigger token is no-transition
version-mismatchrehydratethe snapshot version is newer than the definition, or a migration is missing
unknown-machinerehydrate, save, advance, load, sendno registered machine has that name
unauthenticatedsave, advance, load, sendthe request carries no user: ISnapshotPrincipal.CurrentUserKey is null. Checked before anything else, so nothing was read or written
not-foundadvance, load, sendno draft with that id exists for this user and machine, or it expired and was deleted; start a new one
schema-mismatchsave, advance, load, sendthe client's machine schema hash differs from the server's; the client is out of date and should reload
client-divergenceadvancethe client's computed result differs from the server's authoritative result (divergence detection); nothing was written, reload
too-largesave, advancethe snapshot, the trigger input, or the advanced snapshot exceeds SnapshotLimits.MaxSnapshotBytes (64 KiB); nothing was written
request-id-reusedadvance, sendthe request id was last used for a different trigger, so this is not a retry of it; send a new id. A send is refused before its effect runs
effect-boundadvancethe trigger runs the machine's irreversible effect from this state, so only a send fires it; nothing was written
state-reservedsavethe snapshot is in a committed state or an effect's target and the stored draft is not; only a send puts a draft there, and nothing was written
draft-committedsavethe stored draft is in a committed state or an effect's target and the save would change it; only a reset to the initial state that the machine declares from that state is accepted, and a save identical to the stored draft succeeds without a write
draft-unreadablesavethe stored draft fails rehydration, so only a reset to the initial state may overwrite it
conflictsave, advance, sendanother write changed the draft between this request reading it and writing it, for example a second tab saving at the same moment; nothing was written, reload and retry. On a send, the draft was written while the effect ran: the effect's receipt is kept, and sending again replays it without running the effect once the draft holds the content the effect ran on (draft-changed until then)
internal-erroradvance, senda guard, reducer or validator threw. The message is fixed and carries a reference; the exception is logged on the server under that reference
no-effectsendthe machine binds no irreversible effect (RunsOnce), so there is nothing to send; drive it with advance
draft-changedsendthis draft's effect already ran, on content the draft no longer holds (it was edited after the effect loaded it). Nothing was run and nothing was written, and the receipt stays with the claim. Restore the content the effect ran on, for example by saving the snapshot the send was made from, and send again to record it
effect-in-progresssendanother send is running this draft's effect right now and holds its lease; nothing was run, retry with the same request id once it finishes
delivery-failedsendthe effect threw, or returned no receipt; the draft was not advanced, so the send can be retried. The message is fixed and carries a reference; the exception is logged on the server under that reference. A cancelled request is not a failed delivery: it propagates as a cancellation. An OperationCanceledException from the effect itself, such as an outbound call's timeout, is a delivery-failed, and its claim stays in flight until the lease passes

Over GraphQL these surface on the mutation's problem field, so a client reads the code and reacts (re-enable a control on guard-failed, start fresh on version-mismatch) without ever seeing a stack trace. When the refusal came from an exception, a guard, reducer or migration that threw or an effect that failed, the message is a fixed sentence ending in Reference: {id}, and the server logs the exception under that id at error level: the exception's own text never reaches the client.