# `Runic.Workflow.FSM`
[🔗](https://github.com/zblanco/runic/blob/main/lib/workflow/fsm.ex#L1)

A finite state machine component with discrete named states and event-driven transitions.

An FSM models a system that is always in exactly one of a finite set of states.
Transitions between states are triggered by named events and may have entry
actions that fire when a state is entered.

## How It Works

At compile time an FSM is lowered into standard Runic primitives:

- An **Accumulator** that holds the current state as an atom value, initialized
  to the declared `initial_state`.
- One **Rule** per transition. Each rule uses `state_of()` meta-references to
  gate on the current state and matches against the incoming event. The rule's
  reaction produces the target state atom which feeds back into the accumulator.
- One **Rule** per `on_entry` action. Entry rules fire when the accumulator
  transitions into the associated state.

Each transition rule is named `:"<fsm_name>_<from_state>_on_<event>"`, making
individual transitions addressable by name.

Compile-time validation ensures:
- The `initial_state` refers to a declared state
- All transition `:to` targets refer to declared states
- No duplicate `{state, event}` transition pairs exist

The content hash is deterministic — identical FSM definitions produce the
same hash regardless of compilation order.

## DSL Syntax

FSMs are created with the `Runic.fsm/2` macro using a block DSL:

    Runic.fsm name: :name do
      initial_state :state_name

      state :state_name do
        on :event_name, to: :target_state
        on_entry fn -> side_effect_value end
      end
    end

### Directives

- `initial_state :atom` — declares the starting state (required, must match
  a declared state).
- `state :name do ... end` — declares a state with its transitions and
  optional entry action.
- `on :event, to: :target` — declares a transition from the enclosing state
  to `:target` when `:event` is received.
- `on_entry fn -> value end` — declares a side-effect function that fires
  when this state is entered.

## Examples

    require Runic

    # Traffic light FSM
    fsm = Runic.fsm name: :traffic_light do
      initial_state :red

      state :red do
        on :timer, to: :green
        on :emergency, to: :red
        on_entry fn -> {:notify, :traffic_stopped} end
      end

      state :green do
        on :timer, to: :yellow
        on :emergency, to: :red
      end

      state :yellow do
        on :timer, to: :red
        on :emergency, to: :red
      end
    end

    # Add to workflow and run
    alias Runic.Workflow

    wrk = Workflow.new() |> Workflow.add(fsm)
    wrk = Workflow.react(wrk, :timer)
    # Current state transitions from :red -> :green

## Sub-Component Access

After adding an FSM to a workflow, its internal primitives can be retrieved
via `Workflow.get_component/2` using a `{name, kind}` tuple:

    alias Runic.Workflow

    wrk = Workflow.new() |> Workflow.add(fsm)

    # Get the underlying accumulator (holds current state atom)
    [accumulator] = Workflow.get_component(wrk, {:traffic_light, :accumulator})

    # Get all transition rules
    transitions = Workflow.get_component(wrk, {:traffic_light, :transition})

---

*Consult [api-reference.md](api-reference.md) for complete listing*
