Current section

Files

Jump to
squid_mesh lib squid_mesh runtime state_machine.ex
Raw

lib/squid_mesh/runtime/state_machine.ex

defmodule SquidMesh.Runtime.StateMachine do
@moduledoc """
Explicit run lifecycle state machine for the Squid Mesh runtime.
The runtime coordinator and the later Jido-backed executor should consume
this module as the single source of truth for valid run-state transitions.
It defines the workflow run lifecycle without mixing in step execution or
persistence concerns.
"""
alias SquidMesh.Run
@type state :: Run.status()
@type transition_error ::
{:unknown_state, atom()}
| {:invalid_transition, from_state :: state(), to_state :: state()}
@states [:pending, :running, :retrying, :paused, :failed, :completed, :cancelling, :cancelled]
@transitions %{
pending: [:running, :failed, :cancelled],
running: [:retrying, :paused, :failed, :completed, :cancelling],
retrying: [:running, :failed, :cancelling],
paused: [:running, :failed, :completed, :cancelled],
failed: [],
completed: [],
cancelling: [:cancelled, :failed],
cancelled: []
}
@doc """
Returns all valid run states.
"""
@spec states() :: [state()]
def states do
@states
end
@doc """
Returns the states that may be reached directly from the current state.
"""
@spec allowed_transitions(state()) :: {:ok, [state()]} | {:error, {:unknown_state, atom()}}
def allowed_transitions(state) do
case Map.fetch(@transitions, state) do
{:ok, transitions} -> {:ok, transitions}
:error -> {:error, {:unknown_state, state}}
end
end
@doc """
Reports whether a state is terminal.
"""
@spec terminal?(state()) :: boolean()
def terminal?(state) do
case allowed_transitions(state) do
{:ok, []} -> true
{:ok, _transitions} -> false
{:error, _reason} -> false
end
end
@doc """
Reports whether the runtime may schedule another step while the run is in
the given state.
"""
@spec schedule_next_step?(state()) :: boolean()
def schedule_next_step?(state) do
case state do
state when state in [:pending, :running, :retrying] -> true
_other_state -> false
end
end
@doc """
Reports whether a transition is valid.
"""
@spec can_transition?(state(), state()) :: boolean()
def can_transition?(from_state, to_state) do
match?({:ok, ^to_state}, transition(from_state, to_state))
end
@doc """
Validates a requested state transition.
"""
@spec transition(state(), state()) :: {:ok, state()} | {:error, transition_error()}
def transition(from_state, to_state) do
with {:ok, transitions} <- allowed_transitions(from_state),
{:ok, _next_transitions} <- allowed_transitions(to_state) do
if to_state in transitions do
{:ok, to_state}
else
{:error, {:invalid_transition, from_state, to_state}}
end
end
end
end