StateSTAGE-SPECIFICCONTESTEDILLUSTRATIVE

The Order Lifecycle, Built

PENDING → PAID → FULFILLED, with CANCELLED and REFUNDED added later. Which transitions are legal, who triggers each, and in what order to build them so that the store works before the machine is complete — the state module applied end to end on the capstone's order.

The situation, the reflex, and why it stalls

Every lesson starts where being stuck starts: someone has a problem, and the first move that comes to mind feels like progress.

The question

You know how to discover a state machine. In what order do you build the order's lifecycle so that every step leaves a working store, and which transitions can wait?

The situation

I have the order machine on paper. I could build all of it — every state, every transition, cancellation, refunds — before showing anyone anything, or I could build the three states checkout needs and add the rest as they are asked for. I do not know which, and I suspect "build all of it" is the reflex talking.

The reflex

Implement the whole machine at once. It is on paper, it is finite, and half-built state machines feel dangerous — surely the safe thing is to have every transition in place before the first order exists.

Why it stalls

The whole machine includes refunds, which need the provider's refund API, which is an integration nobody has done. The first order cannot be placed until the last transition is written, and the store is late for a feature no customer has used.

What the reflex produces — and fails to produce
  • The whole machine includes refunds, which need the provider's refund API, which is an integration nobody has done. The first order cannot be placed until the last transition is written, and the store is late for a feature no customer has used.
  • Every transition is built against a guess about who triggers it. "Warehouse ships" is implemented as an admin button because there is no warehouse system yet, and when one arrives the transition's actor is wrong.
  • The states that were guessed — is there a PACKED between PAID and SHIPPED? — are built and then argued about, when building the minimum would have let the requirement decide.
  • Nothing is learned from a real order about what the machine is missing, because there are no real orders until everything is done.
ProblemUnderstandRequirementsConstraintsUnknownsDecompositionSmallest StepModelExperimentObserveDebugLearnIterate

The move

Precisely enough to apply it to a problem you have never seen — not a slogan.

  • Build the machine along the happy path first: the states an order must pass through for the store to have done its job. For the capstone store that is PENDING (created, unpaid), PAID and FULFILLED. Build the transition function with only those rows, and reject everything else. A machine with three states and two transitions is a complete machine; it is not a half-built one, because it refuses what it does not know.
  • Add exits as the requirements arrive, and add each with its actor. CANCELLED from PENDING is cheap — nothing to undo — and usually arrives first. CANCELLED-after-payment is a refund, needs the provider, and is a different piece of work; it becomes REFUNDED and it waits until the refund integration is justified by a customer asking.
  • For each new transition, write who triggers it before writing the code, and build it for that actor. If the actor is a system that does not exist yet — the warehouse — build the transition behind an interface and trigger it from the admin page for now, so the actor can change without the machine changing.
  • At each step the store works end to end, and each real order teaches something about the next transition. The machine grows by requirements, in the same way the architecture does (Architecture From Requirements).

The machine at step one, and at step four

Two views of the same order machine. The first is what exists after step one: three states, two transitions, and everything else rejected. The second is after the warehouse and refunds arrived. Every node in the second was added by a requirement, and every transition names its actor.

Order lifecycle after step four
provider confirms (step 1)customer / timeout (step 2)refund succeeds (step 3)warehouse (step 4)carrier (step 4)PENDINGPAIDCANCELLEDSHIPPEDREFUNDEDDELIVERED
UserLLMAgentToolDataDecisionHumanGuardrail
FromEventActorToAdded because
PENDINGpayment confirmedproviderPAIDthe store must sell
PAIDfulfilledadmin (later: warehouse adapter)FULFILLED → SHIPPEDthe store must deliver
PENDINGcancelcustomer, or system on timeoutCANCELLEDabandoned checkouts held stock
PAIDrefund succeededsystem, after admin or customer requestREFUNDEDa paid order had to be undone
SHIPPEDdeliveredcarrierDELIVEREDa real carrier reports it

The order of building it

The sequence is one defensible order and it says why each step is where it is. The alternative — the complete machine first — is the contested view above, and the device names when it wins.

Growing the order machine
  1. 1
    PENDING → PAID → FULFILLED, with a transition function that rejects everything else

    because The store sells with the smallest honest machine; the refusal is the part that cannot be retrofitted cheaply.

  2. 2
    Record every transition with actor and time

    because The history is what every later step, and every support question, reads.

  3. 3
    PENDING → CANCELLED

    because Cheapest exit, arrives with the first abandoned checkout, and it is the first transition that touches stock.

  4. 4
    Interface for the fulfilment actor

    because The warehouse does not exist; an admin button behind an interface lets the actor change without the machine changing.

  5. 5
    PAID → REFUNDED via the provider

    because An integration with all five external-system questions; built when a paid order actually needs undoing.

  6. 6
    Split FULFILLED into SHIPPED / DELIVERED

    because Only when a carrier reports delivery does the distinction have an actor.

a different valid order Complete machine first: when the team has built this lifecycle before and the states are known, build all six states and the refund path before launch, accepting that the provider's refund integration delays the first order. Choose this when a later migration of live orders is more expensive than the delay — high order volume from day one, or reporting that must be stable from the first sale.

The slice that proves step one

The smallest thing that shows the machine exists and refuses: an order placed, paid in test mode, and a refund attempt rejected by name. It proves enforcement and connectivity; it does not prove anything about the transitions not yet built, and it should not pretend to.

Vertical slice
Order placed, paid, and protected
  1. BrowserChecks out a cart; sees the order as pending, then paid.
  2. CheckoutCreates the order in PENDING; on the provider's confirmation calls transition(order, PaymentConfirmed, provider).
  3. Order moduleHolds the table with two rows; rejects transition(order, Refund, admin) with a named reason; records each transition.
  4. DatabaseOrder row with status; transition history rows.
proves
The happy path moves through the machine, the history is recorded, and an illegal transition is refused rather than performed.
does not prove
That cancellation releases stock, that refunds work, that a repeated confirmation is a no-op, or that two simultaneous transitions are serialised. Each is a later slice.

How to do it

Most important first.

  • Take the discovered machine (Finding the State Machine) and mark the happy path: the states a successful order must pass through. Build those, and only those, with a transition function that rejects the rest.
  • Add the cheapest exit next — cancel before payment — and confirm the function rejects cancel after payment with a clear message.
  • For each waiting state, decide the actor of the transition out of it and put an interface between the machine and that actor (Interfaces Emerge From Boundaries).
  • Add refund only when there is a requirement with a customer behind it; it brings a provider integration with all five external-system questions.
  • After each addition, re-ask "from here, what can happen?" against a real order's history — the transitions you were missing are in the support inbox.

Worked on a concrete problem

The move has to produce something. This is what it produced.

  • Step one: PENDING → PAID (provider confirms) → FULFILLED (admin marks fulfilled). Two rows in the table. Checkout creates a PENDING order, payment moves it to PAID, an admin button moves it to FULFILLED. The store sells things. Refund, cancel and shipping states do not exist, and an attempt to refund is rejected with "no transition from PAID on Refund by admin" — a correct, honest answer.
  • Step two, when a customer abandons at payment: PENDING → CANCELLED (customer, or system after a timeout). Now stock reserved for a pending order must be released, which is the first time the machine touches inventory, and the release is written into the transition's record so stock history can explain it. Step three, when a paid order needs undoing: PAID → REFUNDED, triggered by the refund succeeding, not by the cancel request. This step is the provider integration and gets its own slice.
  • Step four, when a real warehouse appears: FULFILLED splits into SHIPPED and DELIVERED, with the carrier as an actor from outside, and the admin button becomes an adapter over the warehouse's notification. The machine changed because the requirement did; nothing built in step one had to be guessed.

How you know it worked

What now exists that did not before, and what question you can now ask.

  • After the first step there is a working store with a three-state order, and every other transition is rejected by name rather than silently allowed.
  • Each transition added since names its actor and arrived with a requirement, and you can say which customer or event caused it.
  • A refund attempt on an unpaid order has always been impossible, at every step, because the table never had the row.
  • The order's history shows the transitions taken, with actors, and support can read it.

The questions you can now ask

The field this whole domain exists for. After this lesson, these are the questions to put to an unfamiliar problem.

Next questions
  • ?Which states must a successful order pass through, and can the store work with only those?
  • ?Which transition is being asked for by an actual requirement, and who triggers it?
  • ?Which actor of a transition does not exist yet, and what interface stands in for it?
  • ?What did the last real order's history show that the machine was missing?

What can go wrong

How the move itself fails
  • The happy path is built without the rejection. A three-state machine that lets any handler set any of the three values is the reflex with fewer values; the refusal is what makes it a machine.
  • Exits are added when imagined rather than when required, and the machine grows to the full paper version anyway, one guess at a time.
  • The actor of a transition is not put behind an interface, so when the warehouse arrives the transition is rewritten instead of re-wired.
  • The machine is grown and the state history is not, so when the first refund dispute arrives nobody can see which transitions the order took.
What the move costs
  • Building transitions on demand means the first customer who needs a refund waits for it to be built; building it early means building an integration before its first use. The choice is which delay you would rather explain.
  • A machine that rejects the unknown is honest and occasionally rejects something legitimate that nobody foresaw; a permissive column never rejects and never protects.
  • Splitting FULFILLED into SHIPPED and DELIVERED later is a migration of existing orders, which is a real cost that building the split early would have avoided — if the split had been guessed right.
Misreads
  • "Build the minimum" means "skip the transition function." It means the minimum number of states and transitions, enforced. The enforcement is the cheap part and the part that cannot be added later without a migration of every bad value.
  • "Cancel and refund are the same transition with a flag." They have different actors, different preconditions and different side effects. The machine that merges them has one word for three things, which is where this module started.
  • "Once the warehouse arrives, the old FULFILLED orders are wrong." They are complete under the machine that existed when they were placed. A migration decides what the old state maps to; the history records that the mapping happened.

Where this applies

Problem-solving advice is stated as universal far more often than it is. These labels say what each method is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view.

  • STAGE-SPECIFICGreenfield: the machine starts with the happy path and grows. In an existing store the machine is recovered from the data, and "grow by requirements" becomes "add the refusal and migrate the values that should never have existed".
  • CONTESTEDThe strongest opposing view: lifecycles are cheap to design and expensive to migrate, so build the complete machine you can foresee — including CANCELLED and REFUNDED — before the first order, because adding a state later means migrating live orders and re-deriving every report. Practitioners who hold this view point out that the order lifecycle of a store is well known, not a novel unknown, and that "grow by requirements" is right for uncertain domains and wrong for solved ones. They are right when the domain is genuinely known and the team has built it before.
  • ILLUSTRATIVEThe four steps and the three-state starting machine are the capstone's teaching sequence; a real store's order of additions follows its customers, not this list.

Where the depth lives

This domain asks the question and hands the answer off by name.