Skip to content

Build1 publisher3 min readPublished

The dangerous cell in your state machine is the one nobody filled in

An illegal transition throws an exception you can grep for. An undefined one returns success and quietly drifts from the order history your support team reads.

The Engineer · Build desk

Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

Illustration accompanying The dangerous cell in your state machine is the one nobody filled in
Generated illustration

What happened

  • Developers typically test a state machine by walking the paths believed to be legal, such as an order moving from pending to paid to shipped to delivered; that test passes and tells you very little.
  • A missing transition is worse than a wrong transition: the wrong one throws an exception you can find in a log, while the undefined one often falls through to a default branch that silently returns the current state and reports success.
  • When a model drafts the state machine, it is good at producing transitions that follow from a natural-language description but rarely asks what should happen when a cancel event arrives after the order has shipped; it leaves the grid incomplete and the code often patches the gap with a broad else clause.
  • The article's stated contract is that every combination of state and event must have an explicit owner, even when the owner is a rejection.
  • The sample transition table contains eight explicit entries: (pending, pay) to paid, (pending, cancel) to cancelled, (paid, ship) to shipped, (paid, cancel) to cancelled, (shipped, deliver) to delivered, (delivered, return) to returned, (cancelled, refund) to refunded, and (returned, refund) to refunded.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

A post on dev.to makes a narrow and correct point about state machines: the defect that freezes a service is usually not a wrong transition but one nobody defined [2]. The wrong transition at least raises an exception you can find in a log, while the undefined one often falls through to a default branch that silently returns the current state and reports success [2].

That is the failure mode the standard test cannot see, because the standard test walks the path you already believe in: pending to paid to shipped to delivered [1]. It passes, and it tells you almost nothing [1].

The post argues the problem gets worse when a model drafted the machine. A model is good at producing the transitions implied by a natural-language description, but it rarely asks what should happen when a cancel event arrives after the order has already shipped, so it leaves the grid incomplete and the code patches the gap with a broad else clause [3]. The proposed contract is simple enough to hold in your head during review: every combination of state and event needs an explicit owner, and a rejection counts as an owner [4].

The worked example is where this gets useful, mostly because of what it accidentally demonstrates. The sample machine defines seven states and seven events, including an update_address event [6], with eight explicit transitions in the table [5]. Seven by seven is forty-nine cells [10], so forty-one of them are undefined before anyone touches the code [11]. None of the undefined pairs the post names, such as shipped with cancel, delivered with cancel, or refunded with pay, is a broken state, and none crashes on clean input [7]. They are simply absent from the machine's vocabulary [7].

The harness then hard-codes a rejection set and asserts that every cell is either a transition or a rejection, raising an AssertionError naming the unhandled cell otherwise [9]. That rejection set contains seven pairs [8]. Forty-one minus seven leaves thirty-four cells still unhandled, which means the example harness, run as written, fails on its own machine [12]. Read that as the article's real argument rather than a bug: enumerating the grid is the easy part, and classifying it is the work.

Look at update_address in particular. It appears in the event list but in none of the eight transitions, so it occupies seven cells, exactly one of which the rejection set covers [13]. An event that moves nothing anywhere is either dead vocabulary or a silent no-op in six states, and only the grid surfaces the question.

One thing to discount: the post discloses it was prepared as part of MonkeyCode's product outreach, and recommends free model access and a free server so you can run the whole cross-product instead of sampling happy paths [14]. Forty-nine dictionary lookups do not need free compute [10]. The transferable instruction is the prompt shape: ask the model to list the events each state must reject, not only the events that move the machine forward [15], and if it says a cancel on a shipped order leaves the state unchanged, write that as an assertion instead of letting a default branch imply it [16].

What to watch in review: the count of covered cells, not the diagram. Ask for the number of state-event pairs with an explicit owner against the total, treat any model-generated rejection list as a hypothesis with the coverage gap attached [12], and check whether your own default branch returns the current state on input the machine never agreed to accept [2].

Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories