Class: Phronomy::WorkflowRunner
- Inherits:
-
Object
- Object
- Phronomy::WorkflowRunner
- Includes:
- Runnable
- Defined in:
- lib/phronomy/workflow_runner.rb
Overview
Execution engine for compiled workflows. Manages state entry/exit action execution, phase transitions, halt/resume, and wait states. Instantiated by Phronomy::Workflow and used internally.
== Design principle
State transitions are driven entirely by state_machines. The PhaseTracker holds a reference to the current WorkflowContext via +attr_accessor :context+, and guard lambdas evaluate +m.context+ (the WorkflowContext) rather than the PhaseTracker itself. This ensures that "what happens next" is always determined by the declared state machine topology, never by Phronomy internals.
Entry and exit actions are registered as state_machines +after_transition to:+ and +before_transition from:+ callbacks respectively. The WorkflowContext is mutable; actions receive it and modify fields in place.
The sole exception is the initial state: state_machines does not fire transition callbacks on initialization, so the entry action for the entry point is invoked directly by WorkflowRunner before the main execution loop begins.
== Two transition categories registered in PhaseTracker
state_completed — all auto-fire transitions (with or without guards). Fired when an action state's action completes. Guards are evaluated in declaration order; first match wins. (declared with +transition from: :foo, to: :bar+ or +transition from: :foo, guard: ..., to: :bar+)
— external events triggered by human input, originating from wait states (declared with +transition from: :awaiting, on: :approve, to: :run+)
Constant Summary collapse
- FINISH =
Sentinel value for the terminal state of a workflow.
:__end__
Instance Method Summary collapse
-
#initialize(state_class:, entry_actions:, declared_states:, auto_transitions:, external_events:, entry_point:, exit_actions: {}, wait_state_names: []) ⇒ WorkflowRunner
constructor
A new instance of WorkflowRunner.
-
#invoke(input, config: {}) ⇒ Object
Executes the workflow from the initial state.
-
#resume(state:, input: nil) ⇒ Object
Generic resume.
-
#send_event(state:, event:, input: nil) ⇒ Object
Fires a named event to advance a halted workflow.
-
#stream(input, config: {}) {|Hash| ... } ⇒ Object
Streaming execution.
Methods included from Runnable
Constructor Details
#initialize(state_class:, entry_actions:, declared_states:, auto_transitions:, external_events:, entry_point:, exit_actions: {}, wait_state_names: []) ⇒ WorkflowRunner
Returns a new instance of WorkflowRunner.
44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/phronomy/workflow_runner.rb', line 44 def initialize(state_class:, entry_actions:, declared_states:, auto_transitions:, external_events:, entry_point:, exit_actions: {}, wait_state_names: []) @state_class = state_class @entry_actions = entry_actions # { state_name => [callable, ...] } @declared_states = declared_states # Lookup set: states with at least one auto-fire transition declared. @auto_state_set = auto_transitions.each_with_object({}) { |t, h| h[t[:from]] = true } @external_events = external_events # { name => [{from:, to:, guard:}, ...] } @entry_point = entry_point @wait_state_names = wait_state_names @phase_machine_class = build_phase_machine_class(auto_transitions, exit_actions) end |
Instance Method Details
#invoke(input, config: {}) ⇒ Object
Executes the workflow from the initial state.
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/phronomy/workflow_runner.rb', line 60 def invoke(input, config: {}) = {} [:user_id] = config[:user_id] if config[:user_id] [:session_id] = config[:session_id] if config[:session_id] trace("workflow.invoke", input: input.inspect, **) do |_span| thread_id = config[:thread_id] || SecureRandom.uuid recursion_limit = config.fetch(:recursion_limit, Phronomy.configuration.recursion_limit) state = @state_class.new(**input) state.(thread_id: thread_id) result = if Phronomy.configuration.event_loop run_via_event_loop(state, recursion_limit: recursion_limit) else run_workflow(state, recursion_limit: recursion_limit) end [result, nil] end end |
#resume(state:, input: nil) ⇒ Object
Generic resume. Equivalent to +send_event(state:, event: :resume, input:)+.
83 84 85 |
# File 'lib/phronomy/workflow_runner.rb', line 83 def resume(state:, input: nil) send_event(state: state, event: :resume, input: input) end |
#send_event(state:, event:, input: nil) ⇒ Object
Fires a named event to advance a halted workflow.
The special event +:resume+ selects the first external event registered for the current wait state and fires it.
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/phronomy/workflow_runner.rb', line 96 def send_event(state:, event:, input: nil) state = state.merge(input) if input event = event.to_sym current_phase = state.phase ev_to_fire = if event == :resume # Find the first external event that can originate from the current wait state. name, = @external_events.find { |_, ts| ts.any? { |t| t[:from] == current_phase } } unless name raise ArgumentError, "No external event registered for wait state #{current_phase.inspect}" end name else unless @external_events.key?(event) raise ArgumentError, "Unknown event #{event.inspect}. Valid events: #{@external_events.keys.inspect}" end event end if Phronomy.configuration.event_loop run_via_event_loop(state, recursion_limit: Phronomy.configuration.recursion_limit, resume_event: ev_to_fire, resume_phase: current_phase) else run_workflow(state, resume_event: ev_to_fire, resume_phase: current_phase) end end |
#stream(input, config: {}) {|Hash| ... } ⇒ Object
Streaming execution. Yields { state: Symbol, context: Object } after each state action completes.
131 132 133 134 135 136 137 |
# File 'lib/phronomy/workflow_runner.rb', line 131 def stream(input, config: {}, &block) thread_id = config[:thread_id] || SecureRandom.uuid recursion_limit = config.fetch(:recursion_limit, Phronomy.configuration.recursion_limit) state = @state_class.new(**input) state.(thread_id: thread_id) run_workflow(state, recursion_limit: recursion_limit, &block) end |