Skip to main content
Amends 0097, decision 1: the state machine moves out of @stackra/model into its own package.

Context

A state machine was first placed inside @stackra/model, and the first draft used a transition(entity, ...) call on a field. The owner’s state-machine answers SM1 and SM2 (mem L857–858, 2026-10-20) give the final shape: a package of its own, typed class definitions, and a call through stateMachine.apply. The earlier shape (L405) is replaced (contradiction C-17). The package uses no XState.

Decision

  1. Package. @stackra/state-machine is a data package (0127). It uses no XState.
  2. Definitions (SM1). A machine is a typed class declared with @StateMachine({ key, initial }). States are a const object plus a union type. Each transition has from, to, a registered guard, a permission and effects (an event, a workflow or a notification).
  3. Attachment (SM1). A machine attaches to a model field with @UsesStateMachine(Flow). It runs with stateMachine.apply(entity, 'approve', ctx). A direct write to the field is blocked by a lint and by a model hook.
  4. History and surfaces (SM2). Each transition is recorded in the history and the audit log. The available transitions appear as panel actions and as MCP tools. A Mermaid diagram comes from the registry.
  5. Timed transitions (SM2). They use the scheduler (0148).
  6. Tenant customisation (SM2). Tenants can customise states and transitions at the allowed extension points, from the console. Such customisation references registered guards and effects only.

Consequences

  • A field cannot be changed by a direct write, so every transition has a guard, a permission and a history entry.
  • A machine can run without a model, for UI flows or workflows, because it is its own package.

Enforcement

planned — the lint and model hook that block direct field writes, and the history check in the package’s tests.

Alternatives

  • The transition(entity, ...) call with the machine on the field (L405): replaced by SM1 (C-17).
  • XState: not chosen; the package is typed in-house.

Supersedes and sources

Supersedes: none. Amends: 0097 decision 1. Sources: owner answers SM1 and SM2 (stackra-decisions-2026-10-19.md L857–858, 2026-10-20), placement and no-XState rule (L405); contradiction C-17.