Class: Ace::Assign::Models::QueueState

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/assign/models/queue_state.rb

Overview

Queue state model representing a snapshot of the work queue.

Pure data carrier with no business logic (ATOM pattern). Provides convenient accessors for queue analysis.

Examples:

state = QueueState.new(steps: steps, assignment: assignment)
state.current  # => Deepest active step
state.pending  # => Array of pending steps

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(steps:, assignment:) ⇒ QueueState

Returns a new instance of QueueState.

Parameters:

  • steps (Array<Step>)

    All steps in queue order

  • assignment (Assignment)

    Assignment metadata



20
21
22
23
24
# File 'lib/ace/assign/models/queue_state.rb', line 20

def initialize(steps:, assignment:)
  @steps = steps.freeze
  @assignment = assignment
  @children_index = build_children_index(steps)
end

Instance Attribute Details

#assignmentObject (readonly)

Returns the value of attribute assignment.



16
17
18
# File 'lib/ace/assign/models/queue_state.rb', line 16

def assignment
  @assignment
end

#stepsObject (readonly)

Returns the value of attribute steps.



16
17
18
# File 'lib/ace/assign/models/queue_state.rb', line 16

def steps
  @steps
end

Instance Method Details

#active_branch_conflict_in_subtree?(root_number, extra_active: []) ⇒ Boolean

Check whether active steps inside a subtree fan out across more than one branch.

Parameters:

  • root_number (String)

    Subtree root step number

  • extra_active (Array<String>) (defaults to: [])

    Additional active step numbers to include

Returns:

  • (Boolean)

    True when active steps inside the subtree are not on a single path



309
310
311
312
313
314
315
# File 'lib/ace/assign/models/queue_state.rb', line 309

def active_branch_conflict_in_subtree?(root_number, extra_active: [])
  active_numbers = active_in_subtree(root_number).map(&:number) + Array(extra_active).map(&:to_s)
  unique_active = active_numbers.uniq
  unique_active.combination(2).any? do |left, right|
    !(in_subtree?(left, right) || in_subtree?(right, left))
  end
end

#active_fork_rootsArray<Step>

Get active fork roots in queue order.

Returns:

  • (Array<Step>)

    Active fork-scoped steps



300
301
302
# File 'lib/ace/assign/models/queue_state.rb', line 300

def active_fork_roots
  active_steps.select(&:fork?)
end

#active_in_subtree(root_number) ⇒ Array<Step>

Get all active steps within a subtree.

Parameters:

  • root_number (String)

    Subtree root step number

Returns:

  • (Array<Step>)

    Active steps inside subtree



220
221
222
223
# File 'lib/ace/assign/models/queue_state.rb', line 220

def active_in_subtree(root_number)
  subtree_steps(root_number)
    .select { |s| s.status == :active }
end

#active_stepsArray<Step>

Get all active steps in queue order.

Returns:

  • (Array<Step>)

    Active steps



39
40
41
# File 'lib/ace/assign/models/queue_state.rb', line 39

def active_steps
  steps.select { |s| s.status == :active }
end

#all_numbersArray<String>

Get all step numbers as an array

Returns:

  • (Array<String>)

    All step numbers



319
320
321
# File 'lib/ace/assign/models/queue_state.rb', line 319

def all_numbers
  steps.map(&:number)
end

#ancestor_chain(number) ⇒ Array<String>

Build ancestor chain from closest parent to root.

Parameters:

  • number (String)

    Step number

Returns:

  • (Array<String>)

    Ancestor numbers, nearest first



245
246
247
248
249
250
251
252
253
# File 'lib/ace/assign/models/queue_state.rb', line 245

def ancestor_chain(number)
  chain = []
  parent = Atoms::StepNumbering.parent_of(number)
  while parent
    chain << parent
    parent = Atoms::StepNumbering.parent_of(parent)
  end
  chain
end

#assignment_stateSymbol

Computed assignment state based on step statuses

States (checked in priority order):

  • :empty - No steps in queue

  • :completed - All steps complete (done or failed)

  • :failed - Has failed step(s) but NOT all complete (stuck)

  • :running - Has active step(s) with recent activity (< 1 hour)

  • :stalled - Has active step(s) but all are stale (> 1 hour)

  • :paused - Has pending but no active step (interrupted)

Returns:

  • (Symbol)

    Assignment state



120
121
122
123
124
125
126
127
128
# File 'lib/ace/assign/models/queue_state.rb', line 120

def assignment_state
  return :empty if empty?
  return :completed if complete?
  return :failed if failed.any?
  return :running if active_steps.any? && recently_active?
  return :stalled if active_steps.any?

  :paused
end

#children_of(parent_number) ⇒ Array<Step>

Get all direct children of a step (O(1) via index)

Parameters:

  • parent_number (String)

    Parent step number

Returns:

  • (Array<Step>)

    Direct child steps



161
162
163
# File 'lib/ace/assign/models/queue_state.rb', line 161

def children_of(parent_number)
  @children_index[parent_number] || []
end

#complete?Boolean

Check if all steps are complete (no pending or active)

Returns:

  • (Boolean)


75
76
77
# File 'lib/ace/assign/models/queue_state.rb', line 75

def complete?
  steps.all?(&:complete?)
end

#currentStep?

Get the deterministic single-step active target.

Returns the deepest active step in queue order. When several active branches exist globally, this is an internal convenience only; public status should use active_steps.

Returns:

  • (Step, nil)

    Deepest active step or nil



33
34
35
# File 'lib/ace/assign/models/queue_state.rb', line 33

def current
  deepest_active
end

#current_in_subtree(root_number) ⇒ Step?

Get the deterministic active target within a subtree.

Parameters:

  • root_number (String)

    Subtree root step number

Returns:

  • (Step, nil)

    Deepest active step inside subtree, if any



212
213
214
# File 'lib/ace/assign/models/queue_state.rb', line 212

def current_in_subtree(root_number)
  deepest_active_in_subtree(root_number)
end

#deepest_activeStep?

Get deepest active step in queue order.

Returns:

  • (Step, nil)

    Deepest active step



154
155
156
# File 'lib/ace/assign/models/queue_state.rb', line 154

def deepest_active
  deepest_active_from(active_steps)
end

#deepest_active_in_subtree(root_number) ⇒ Step?

Get the deepest active step within a subtree.

Parameters:

  • root_number (String)

    Subtree root step number

Returns:

  • (Step, nil)

    Deepest active step in subtree



229
230
231
# File 'lib/ace/assign/models/queue_state.rb', line 229

def deepest_active_in_subtree(root_number)
  deepest_active_from(active_in_subtree(root_number))
end

#descendants_of(parent_number) ⇒ Array<Step>

Get all descendants (children, grandchildren, etc.) of a step

Parameters:

  • parent_number (String)

    Parent step number

Returns:

  • (Array<Step>)

    All descendant steps



168
169
170
# File 'lib/ace/assign/models/queue_state.rb', line 168

def descendants_of(parent_number)
  steps.select { |s| Atoms::StepNumbering.child_of?(s.number, parent_number) }
end

#doneArray<Step>

Get all done steps

Returns:

  • (Array<Step>)

    Completed steps



51
52
53
# File 'lib/ace/assign/models/queue_state.rb', line 51

def done
  steps.select { |s| s.status == :done }
end

#empty?Boolean

Check if queue is empty

Returns:

  • (Boolean)


69
70
71
# File 'lib/ace/assign/models/queue_state.rb', line 69

def empty?
  steps.empty?
end

#failedArray<Step>

Get all failed steps

Returns:

  • (Array<Step>)

    Failed steps



57
58
59
# File 'lib/ace/assign/models/queue_state.rb', line 57

def failed
  steps.select { |s| s.status == :failed }
end

#find_by_number(number) ⇒ Step?

Get step by number

Parameters:

  • number (String)

    Step number (e.g., “010”, “040”)

Returns:

  • (Step, nil)

    Found step



82
83
84
85
86
87
88
89
# File 'lib/ace/assign/models/queue_state.rb', line 82

def find_by_number(number)
  # Normalize to string without leading zeros for comparison
  normalized = number.to_s.sub(/^0+/, "")
  steps.find do |s|
    next unless s.number
    s.number.sub(/^0+/, "") == normalized || s.number == number.to_s
  end
end

#has_incomplete_children?(parent_number) ⇒ Boolean

Check if a step has any incomplete children

Parameters:

  • parent_number (String)

    Parent step number

Returns:

  • (Boolean)

    True if any child is not done



276
277
278
# File 'lib/ace/assign/models/queue_state.rb', line 276

def has_incomplete_children?(parent_number)
  children_of(parent_number).any? { |s| s.status != :done }
end

#hierarchicalArray<Hash>

Build hierarchical structure for display

Returns:

  • (Array<Hash>)

    Nested structure with :step and :children keys



331
332
333
# File 'lib/ace/assign/models/queue_state.rb', line 331

def hierarchical
  build_hierarchy(nil)
end

#in_subtree?(root_number, step_number) ⇒ Boolean

Check whether a step number belongs to a subtree rooted at root_number.

Parameters:

  • root_number (String)

    Subtree root step number

  • step_number (String)

    Candidate step number

Returns:

  • (Boolean)

    True when candidate is root or descendant of root



177
178
179
# File 'lib/ace/assign/models/queue_state.rb', line 177

def in_subtree?(root_number, step_number)
  step_number == root_number || Atoms::StepNumbering.child_of?(step_number, root_number)
end

#lastStep?

Get last step in queue

Returns:

  • (Step, nil)

    Last step



93
94
95
# File 'lib/ace/assign/models/queue_state.rb', line 93

def last
  steps.last
end

#last_doneStep?

Get last completed step

Returns:

  • (Step, nil)

    Last done step



99
100
101
# File 'lib/ace/assign/models/queue_state.rb', line 99

def last_done
  done.last
end

#nearest_fork_ancestor(number) ⇒ Step?

Find nearest ancestor (or self) that has context: fork.

Parameters:

  • number (String)

    Step number

Returns:

  • (Step, nil)

    Nearest fork-scoped step



259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/ace/assign/models/queue_state.rb', line 259

def nearest_fork_ancestor(number)
  step = find_by_number(number)
  return nil unless step

  return step if step.fork?

  ancestor_chain(number).each do |ancestor_number|
    ancestor = find_by_number(ancestor_number)
    return ancestor if ancestor&.fork?
  end

  nil
end

#next_pendingStep?

Get next pending step

Returns:

  • (Step, nil)

    Next step to work on



63
64
65
# File 'lib/ace/assign/models/queue_state.rb', line 63

def next_pending
  pending.first
end

#next_workable(scope_root: nil) ⇒ Step?

Get next workable pending step considering hierarchy and active fork ownership. A step is workable if it’s pending and has no incomplete children.

Pending descendants under an active fork root are hidden unless the caller is explicitly scoped inside that root.

Parameters:

  • scope_root (String, nil) (defaults to: nil)

    Optional subtree scope

Returns:

  • (Step, nil)

    Next step to work on



288
289
290
291
292
293
294
295
# File 'lib/ace/assign/models/queue_state.rb', line 288

def next_workable(scope_root: nil)
  candidate_steps = scope_root ? subtree_steps(scope_root) : steps
  candidate_steps
    .select { |s| s.status == :pending }
    .reject { |s| has_incomplete_children?(s.number) && !runnable_delegation_parent?(s, scope_root: scope_root) }
    .reject { |s| hidden_by_active_fork_root?(s.number, scope_root: scope_root) }
    .first
end

#next_workable_in_subtree(root_number) ⇒ Step?

Get next workable step constrained to a subtree.

Parameters:

  • root_number (String)

    Subtree root step number

Returns:

  • (Step, nil)

    Next pending workable step inside subtree



237
238
239
# File 'lib/ace/assign/models/queue_state.rb', line 237

def next_workable_in_subtree(root_number)
  next_workable(scope_root: root_number)
end

#pendingArray<Step>

Get all pending steps

Returns:

  • (Array<Step>)

    Pending steps



45
46
47
# File 'lib/ace/assign/models/queue_state.rb', line 45

def pending
  steps.select { |s| s.status == :pending }
end

#recently_active?(threshold: 3600) ⇒ Boolean

Check if any active step has recent activity.

Parameters:

  • threshold (Integer) (defaults to: 3600)

    Seconds since started_at to consider active (default: 1 hour)

Returns:

  • (Boolean)


133
134
135
136
137
# File 'lib/ace/assign/models/queue_state.rb', line 133

def recently_active?(threshold: 3600)
  active_steps.any? do |step|
    step.started_at && ((Time.now - step.started_at) < threshold)
  end
end

#sizeInteger

Total step count

Returns:

  • (Integer)

    Number of steps



105
106
107
# File 'lib/ace/assign/models/queue_state.rb', line 105

def size
  steps.size
end

#subtree_complete?(root_number) ⇒ Boolean

Check whether all steps in a subtree are complete.

Parameters:

  • root_number (String)

    Subtree root step number

Returns:

  • (Boolean)

    True when every subtree step is complete



193
194
195
196
197
198
# File 'lib/ace/assign/models/queue_state.rb', line 193

def subtree_complete?(root_number)
  scoped = subtree_steps(root_number)
  return false if scoped.empty?

  scoped.all?(&:complete?)
end

#subtree_failed?(root_number) ⇒ Boolean

Check whether a subtree has at least one failed step.

Parameters:

  • root_number (String)

    Subtree root step number

Returns:

  • (Boolean)

    True when any subtree step failed



204
205
206
# File 'lib/ace/assign/models/queue_state.rb', line 204

def subtree_failed?(root_number)
  subtree_steps(root_number).any? { |s| s.status == :failed }
end

#subtree_steps(root_number) ⇒ Array<Step>

Get all steps in a subtree (root + descendants), preserving queue order.

Parameters:

  • root_number (String)

    Subtree root step number

Returns:

  • (Array<Step>)

    Subtree steps in queue order



185
186
187
# File 'lib/ace/assign/models/queue_state.rb', line 185

def subtree_steps(root_number)
  steps.select { |s| in_subtree?(root_number, s.number) }
end

#summaryHash

Summary for display

Returns:

  • (Hash)

    Summary statistics



141
142
143
144
145
146
147
148
149
# File 'lib/ace/assign/models/queue_state.rb', line 141

def summary
  {
    total: size,
    done: done.size,
    active: active_steps.size,
    pending: pending.size,
    failed: failed.size
  }
end

#top_levelArray<Step>

Get top-level (root) steps only

Returns:

  • (Array<Step>)

    Steps with no parent



325
326
327
# File 'lib/ace/assign/models/queue_state.rb', line 325

def top_level
  steps.select { |s| Atoms::StepNumbering.top_level?(s.number) }
end