Standard 05 · Closed-set lifecycle

State Modeling

Every module's lifecycle is a closed set of states. Validation states are disjoint from terminal failures. Forbidden post-submit causes are named explicitly. Invented states are violations.

Authority

Binding markdown

Binding source: 01-STANDARDS/State-Modeling-Standard.md.

Lifecycle shape

One direction, four phases

Every wallet movement follows the same four-phase lifecycle. Backwards transitions are forbidden; phase mixing is forbidden.

1
DEFINE
User assembles intent. Validation only — never produces a terminal failure.
2
REVIEW
Read-only restatement; commitment line. Cancel = pure pre-submission navigation, NOT a terminal cause.
3
IN_FLIGHT
Submitted; awaiting backend / chain. Optional in-flight actions (e.g., user-submitted tx hash for Deposit).
4
TERMINAL
COMPLETED · FAILED · EXPIRED · CANCELLED. Closed set. Each carries Reference ID; COMPLETED carries Transaction ID.
Disjointness

Validation states ≠ terminal failures

The biggest source of state-model bugs is conflating a validation block with a post-submit failure. They live in different enums and mean different things.

Validation states (DEFINE-stage)

CTA gates

  • EMPTY
  • NO_ASSET
  • INVALID_AMOUNT
  • INSUFFICIENT_BALANCE
  • BELOW_MINIMUM (Deposit only)
  • NO_ADDRESS (Withdraw only)
Forbidden post-submit causes

Never terminal

  • USER_CANCELLED — pre-submission navigation
  • USER_REJECTED — out of scope unless signing surface exists
  • INSUFFICIENT_BALANCE — DEFINE-stage only
  • BELOW_MINIMUM — DEFINE-stage only
Rule: a value that appears in the validation enum MUST NOT appear in the terminal failure enum, and vice versa. Tests assert this disjointness explicitly.
No invented states

Closed sets, end to end

The state union is declared once, in the spec, and mirrored in TSX, preview, tests, and reports.
Each state has a documented entry condition, exit condition, and required UI surface.
Terminal states are append-only — adding one requires a spec amendment + author-history row.
A backend that emits a state outside the closed set produces an invalid event; the wrapper refuses to render it.