Module: Brainiac::Plugins::Basecamp::TaskState
- Defined in:
- lib/brainiac/plugins/basecamp/task_state.rb
Overview
The single authority for an epic task's lifecycle. Task hashes remain serializable so existing persisted epics continue to work unchanged.
Defined Under Namespace
Classes: InvalidTransition
Constant Summary collapse
- STATES =
%w[pending in_flight in_review final_decision complete merge_failed].freeze
- TRANSITIONS =
{ dispatch: { "pending" => "in_flight" }, submit_for_review: { "pending" => "in_review", "in_flight" => "in_review" }, request_changes: { "in_review" => "in_flight" }, approve: { "in_review" => "final_decision" }, complete: { "pending" => "complete", "in_flight" => "complete", "in_review" => "complete", "final_decision" => "complete" }, merge_failed: { "final_decision" => "merge_failed" }, retry_merge: { "merge_failed" => "final_decision" } }.freeze
Class Method Summary collapse
- .in?(task, *states) ⇒ Boolean
-
.migrate!(task, triggered_by: "state_machine_migration", at: Time.now) ⇒ Object
Adds a history entry for state created before this state machine existed, without changing its current status.
- .state(task) ⇒ Object
- .transition!(task, event, triggered_by:, at: Time.now, guard: true) ⇒ Object
Class Method Details
.in?(task, *states) ⇒ Boolean
34 35 36 |
# File 'lib/brainiac/plugins/basecamp/task_state.rb', line 34 def in?(task, *states) states.flatten.map(&:to_s).include?(state(task)) end |
.migrate!(task, triggered_by: "state_machine_migration", at: Time.now) ⇒ Object
Adds a history entry for state created before this state machine existed, without changing its current status.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/brainiac/plugins/basecamp/task_state.rb', line 68 def migrate!(task, triggered_by: "state_machine_migration", at: Time.now) return task if task.key?("transitions") current = state(task) raise InvalidTransition, "unknown task state #{current}" unless STATES.include?(current) task["transitions"] = [{ "from_state" => nil, "to_state" => current, "triggered_by" => triggered_by, "timestamp" => at.iso8601 }] task end |
.state(task) ⇒ Object
30 31 32 |
# File 'lib/brainiac/plugins/basecamp/task_state.rb', line 30 def state(task) (task["status"] || "pending").to_s end |
.transition!(task, event, triggered_by:, at: Time.now, guard: true) ⇒ Object
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/brainiac/plugins/basecamp/task_state.rb', line 38 def transition!(task, event, triggered_by:, at: Time.now, guard: true) event = event.to_sym from_state = state(task) transitions = TRANSITIONS.fetch(event, {}) # Idempotent no-op: if the task is already in the target state for this # event, silently return. This is intentional for recovery paths where a # task may be re-processed after a restart — the audit trail already # captured the original transition, so logging again would be noise. return task if transitions.value?(from_state) to_state = transitions[from_state] raise InvalidTransition, "cannot #{event} task from #{from_state}" unless to_state raise InvalidTransition, "guard failed for #{event} from #{from_state}" unless guard migrate!(task, at: at) = at.iso8601 task["status"] = to_state task["transitions"] ||= [] task["transitions"] << { "from_state" => from_state, "to_state" => to_state, "triggered_by" => triggered_by.to_s, "timestamp" => } task end |