Module: Legion::Extensions::MindGrowth::Runners::Governance

Extended by:
Governance
Includes:
Helpers::Lex
Included in:
Governance
Defined in:
lib/legion/extensions/mind_growth/runners/governance.rb

Constant Summary collapse

VOTE_VALUES =
%i[approve reject].freeze

Instance Method Summary collapse

Instance Method Details

#approve_proposal(proposal_id:, _reason: nil) ⇒ Object



99
100
101
102
103
104
105
106
107
108
# File 'lib/legion/extensions/mind_growth/runners/governance.rb', line 99

def approve_proposal(proposal_id:, _reason: nil, **)
  proposal = Runners::Proposer.get_proposal_object(proposal_id)
  return { success: false, error: :not_found } unless proposal

  proposal.transition!(:approved)
  Runners::Proposer.persist_proposal(proposal)
  { success: true, proposal_id: proposal_id, status: :approved }
rescue ArgumentError => e
  { success: false, error: e.message }
end

#governance_resolved(proposal_id:, base_path: nil) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/legion/extensions/mind_growth/runners/governance.rb', line 55

def governance_resolved(proposal_id:, base_path: nil, **)
  tally = tally_votes(proposal_id: proposal_id)
  return { action: :tally_failed } unless tally[:success]

  case tally[:verdict]
  when :approved
    log.info "[governance] build triggered for #{proposal_id}"
    build_result = Runners::Builder.build_extension(proposal_id: proposal_id, base_path: base_path)
    { action: :build_triggered, build: build_result, tally: tally }
  when :rejected
    log.info "[governance] proposal rejected: #{proposal_id}"
    proposal = Runners::Proposer.get_proposal_object(proposal_id)
    if proposal
      proposal.transition!(:rejected)
      Runners::Proposer.persist_proposal(proposal)
    end
    { action: :rejected, tally: tally }
  else
    { action: :pending, tally: tally }
  end
rescue StandardError => e
  log.error "[governance] governance_resolved failed for #{proposal_id}: #{e.message}"
  { action: :error, error: e.message }
end

#governance_statsObject



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/legion/extensions/mind_growth/runners/governance.rb', line 121

def governance_stats(**)
  all_votes = votes_mutex.synchronize { votes_store.dup }

  total_votes = all_votes.values.sum(&:size)
  proposals_with_votes = all_votes.size

  vote_summary = all_votes.transform_values do |ballots|
    {
      approve: ballots.count { |b| b[:vote] == :approve },
      reject:  ballots.count { |b| b[:vote] == :reject },
      total:   ballots.size
    }
  end

  proposal_stats = Runners::Proposer.proposal_stats
  by_status = proposal_stats[:stats][:by_status]

  governance_breakdown = Helpers::Constants::GOVERNANCE_STATUSES.to_h do |s|
    [s, by_status[s] || 0]
  end

  {
    success:              true,
    total_votes:          total_votes,
    proposals_with_votes: proposals_with_votes,
    vote_summary:         vote_summary,
    governance_breakdown: governance_breakdown
  }
end

#reject_proposal(proposal_id:, reason: nil) ⇒ Object



110
111
112
113
114
115
116
117
118
119
# File 'lib/legion/extensions/mind_growth/runners/governance.rb', line 110

def reject_proposal(proposal_id:, reason: nil, **)
  proposal = Runners::Proposer.get_proposal_object(proposal_id)
  return { success: false, error: :not_found } unless proposal

  proposal.transition!(:rejected)
  Runners::Proposer.persist_proposal(proposal)
  { success: true, proposal_id: proposal_id, status: :rejected, reason: reason }
rescue ArgumentError => e
  { success: false, error: e.message }
end

#submit_proposal(proposal_id:) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/legion/extensions/mind_growth/runners/governance.rb', line 15

def submit_proposal(proposal_id:, **)
  proposal = Runners::Proposer.get_proposal_object(proposal_id)
  return { success: false, error: :not_found } unless proposal

  return { success: false, error: :invalid_status, current_status: proposal.status } unless %i[proposed evaluating].include?(proposal.status)

  proposal.transition!(:evaluating)
  Runners::Proposer.persist_proposal(proposal)
  { success: true, proposal_id: proposal_id, status: :evaluating }
rescue ArgumentError => e
  { success: false, error: e.message }
end

#tally_votes(proposal_id:) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/legion/extensions/mind_growth/runners/governance.rb', line 80

def tally_votes(proposal_id:, **)
  ballots = votes_mutex.synchronize { (votes_store[proposal_id] || []).dup }

  approve_count = ballots.count { |b| b[:vote] == :approve }
  reject_count  = ballots.count { |b| b[:vote] == :reject }
  total         = ballots.size

  verdict = if total < Helpers::Constants::QUORUM
              :pending
            elsif approve_count > reject_count
              :approved
            else
              :rejected
            end

  { success: true, proposal_id: proposal_id, approve_count: approve_count,
    reject_count: reject_count, total: total, verdict: verdict }
end

#vote_on_proposal(proposal_id:, vote:, agent_id: 'default', rationale: nil) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/legion/extensions/mind_growth/runners/governance.rb', line 28

def vote_on_proposal(proposal_id:, vote:, agent_id: 'default', rationale: nil, **)
  vote_sym = vote.to_sym
  return { success: false, error: :invalid_vote } unless VOTE_VALUES.include?(vote_sym)

  snapshot = votes_mutex.synchronize do
    votes_store[proposal_id] ||= []
    votes_store[proposal_id] << { vote: vote_sym, agent_id: agent_id.to_s, rationale: rationale,
                                  cast_at: Time.now.utc }
    votes_store.transform_values(&:dup)
  end

  Helpers::ProposalPersistence.new.save_votes(snapshot)

  tally = tally_votes(proposal_id: proposal_id)
  if tally[:verdict] != :pending
    log.info "[governance] quorum reached for #{proposal_id}: #{tally[:verdict]}"
    if defined?(Legion::Events)
      Legion::Events.emit('governance.quorum_reached',
                          proposal_id: proposal_id, verdict: tally[:verdict])
    end
    return { success: true, proposal_id: proposal_id, vote: vote_sym,
             agent_id: agent_id.to_s, verdict: tally[:verdict], quorum_reached: true }
  end

  { success: true, proposal_id: proposal_id, vote: vote_sym, agent_id: agent_id.to_s }
end