Module: Statecraft::Introspection

Defined in:
lib/statecraft/introspection.rb

Overview

The record-facing questions: "would the guards pass if I fired this transition with these metadata right now". Normalization and freeze are identical to the pipeline's, so a guard that mutates metadata fails the same way in a check as in a transition, and the answer never diverges from what fire! would actually do. Every answer is a snapshot: CAS may still reject the transition later.

Defined Under Namespace

Classes: Availability, Refusal

Instance Method Summary collapse

Instance Method Details

#available_events(metadata: Metadata::OMITTED) ⇒ Object



27
28
29
30
31
32
33
34
35
36
# File 'lib/statecraft/introspection.rb', line 27

def available_events(metadata: Metadata::OMITTED)
  consulted = statecraft_graph.events.filter_map do |event_name, branches|
    edge = branches[statecraft_current_state]
    [edge, event_name] if edge
  end
  normalized = (, consulted, question: "available_events")
  consulted.filter_map do |edge, event_name|
    event_name if statecraft_guards_pass?(edge, event_name, normalized)
  end
end

#available_transitions(metadata: Metadata::OMITTED) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/statecraft/introspection.rb', line 38

def available_transitions(metadata: Metadata::OMITTED)
  outgoing = statecraft_graph.edges.filter_map do |(from, _to), edge|
    edge if from == statecraft_current_state
  end
  consulted = outgoing.flat_map do |edge|
    pairs = edge.event_names.map { |event_name| [edge, event_name] }
    statecraft_direct_legal?(edge) ? pairs + [[edge, nil]] : pairs
  end
  normalized = (, consulted, question: "available_transitions")
  outgoing.filter_map do |edge|
    via = statecraft_passable_via(edge, normalized)
    Availability.new(to: edge.to, via: via) unless via.empty?
  end
end

#can_fire?(event_name, metadata: Metadata::OMITTED) ⇒ Boolean

Returns:

  • (Boolean)


14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/statecraft/introspection.rb', line 14

def can_fire?(event_name, metadata: Metadata::OMITTED)
  graph = statecraft_graph
  branches = graph.events[event_name.to_sym]
  return false unless branches

  edge = branches[statecraft_current_state]
  return false unless edge

  normalized = (, [[edge, event_name.to_sym]],
                                            question: "can_fire?(#{event_name.inspect})")
  statecraft_guards_pass?(edge, event_name.to_sym, normalized)
end

#offerable_eventsObject

The offering: which events the graph AND this record allow from here — the record layer alone, so an input-reading guard never hides the form its input arrives through. A snapshot, like every question here.



60
61
62
63
64
65
66
67
# File 'lib/statecraft/introspection.rb', line 60

def offerable_events
  statecraft_graph.events.filter_map do |event_name, branches|
    edge = branches[statecraft_current_state]
    next unless edge

    event_name if statecraft_record_refusals(edge, event_name).empty?
  end
end

#refusals_for(event_name) ⇒ Object

The structured "why not": every record-layer guard refusing the event right now. Guard handlers by name, no words — the words belong to the presentation. An unknown event or a missing branch answers [].



72
73
74
75
76
77
78
79
80
# File 'lib/statecraft/introspection.rb', line 72

def refusals_for(event_name)
  branches = statecraft_graph.events[event_name.to_sym]
  return [].freeze unless branches

  edge = branches[statecraft_current_state]
  return [].freeze unless edge

  statecraft_record_refusals(edge, event_name.to_sym)
end

#transitioned_to?(state_name) ⇒ Boolean

Returns:

  • (Boolean)


53
54
55
# File 'lib/statecraft/introspection.rb', line 53

def transitioned_to?(state_name)
  history.where(to_state: state_name.to_s).exists?
end