lex-agentic-social 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +12 -0
  3. data/Gemfile +5 -0
  4. data/LICENSE +21 -0
  5. data/README.md +13 -0
  6. data/lex-agentic-social.gemspec +30 -0
  7. data/lib/legion/extensions/agentic/social/apprenticeship/client.rb +28 -0
  8. data/lib/legion/extensions/agentic/social/apprenticeship/helpers/apprenticeship.rb +90 -0
  9. data/lib/legion/extensions/agentic/social/apprenticeship/helpers/apprenticeship_engine.rb +109 -0
  10. data/lib/legion/extensions/agentic/social/apprenticeship/helpers/apprenticeship_model.rb +93 -0
  11. data/lib/legion/extensions/agentic/social/apprenticeship/runners/cognitive_apprenticeship.rb +112 -0
  12. data/lib/legion/extensions/agentic/social/apprenticeship/version.rb +13 -0
  13. data/lib/legion/extensions/agentic/social/apprenticeship.rb +19 -0
  14. data/lib/legion/extensions/agentic/social/conflict/actors/stale_check.rb +45 -0
  15. data/lib/legion/extensions/agentic/social/conflict/client.rb +27 -0
  16. data/lib/legion/extensions/agentic/social/conflict/helpers/conflict_log.rb +70 -0
  17. data/lib/legion/extensions/agentic/social/conflict/helpers/llm_enhancer.rb +130 -0
  18. data/lib/legion/extensions/agentic/social/conflict/helpers/severity.rb +47 -0
  19. data/lib/legion/extensions/agentic/social/conflict/runners/conflict.rb +112 -0
  20. data/lib/legion/extensions/agentic/social/conflict/version.rb +13 -0
  21. data/lib/legion/extensions/agentic/social/conflict.rb +19 -0
  22. data/lib/legion/extensions/agentic/social/conscience/client.rb +26 -0
  23. data/lib/legion/extensions/agentic/social/conscience/helpers/constants.rb +53 -0
  24. data/lib/legion/extensions/agentic/social/conscience/helpers/moral_evaluator.rb +178 -0
  25. data/lib/legion/extensions/agentic/social/conscience/helpers/moral_store.rb +116 -0
  26. data/lib/legion/extensions/agentic/social/conscience/runners/conscience.rb +117 -0
  27. data/lib/legion/extensions/agentic/social/conscience/version.rb +13 -0
  28. data/lib/legion/extensions/agentic/social/conscience.rb +19 -0
  29. data/lib/legion/extensions/agentic/social/consent/actors/tier_evaluation.rb +45 -0
  30. data/lib/legion/extensions/agentic/social/consent/client.rb +27 -0
  31. data/lib/legion/extensions/agentic/social/consent/helpers/consent_map.rb +199 -0
  32. data/lib/legion/extensions/agentic/social/consent/helpers/tiers.rb +54 -0
  33. data/lib/legion/extensions/agentic/social/consent/local_migrations/20260316000010_create_consent_domains.rb +16 -0
  34. data/lib/legion/extensions/agentic/social/consent/runners/consent.rb +228 -0
  35. data/lib/legion/extensions/agentic/social/consent/version.rb +13 -0
  36. data/lib/legion/extensions/agentic/social/consent.rb +25 -0
  37. data/lib/legion/extensions/agentic/social/entrainment/client.rb +19 -0
  38. data/lib/legion/extensions/agentic/social/entrainment/helpers/constants.rb +40 -0
  39. data/lib/legion/extensions/agentic/social/entrainment/helpers/entrainment_engine.rb +120 -0
  40. data/lib/legion/extensions/agentic/social/entrainment/helpers/pairing.rb +86 -0
  41. data/lib/legion/extensions/agentic/social/entrainment/runners/cognitive_entrainment.rb +89 -0
  42. data/lib/legion/extensions/agentic/social/entrainment/version.rb +13 -0
  43. data/lib/legion/extensions/agentic/social/entrainment.rb +19 -0
  44. data/lib/legion/extensions/agentic/social/governance/actors/shadow_ai_scan.rb +19 -0
  45. data/lib/legion/extensions/agentic/social/governance/actors/vote_timeout.rb +45 -0
  46. data/lib/legion/extensions/agentic/social/governance/client.rb +27 -0
  47. data/lib/legion/extensions/agentic/social/governance/helpers/layers.rb +40 -0
  48. data/lib/legion/extensions/agentic/social/governance/helpers/proposal.rb +94 -0
  49. data/lib/legion/extensions/agentic/social/governance/runners/governance.rb +87 -0
  50. data/lib/legion/extensions/agentic/social/governance/runners/shadow_ai.rb +93 -0
  51. data/lib/legion/extensions/agentic/social/governance/version.rb +13 -0
  52. data/lib/legion/extensions/agentic/social/governance.rb +20 -0
  53. data/lib/legion/extensions/agentic/social/joint_attention/actors/decay.rb +45 -0
  54. data/lib/legion/extensions/agentic/social/joint_attention/client.rb +28 -0
  55. data/lib/legion/extensions/agentic/social/joint_attention/helpers/attention_target.rb +124 -0
  56. data/lib/legion/extensions/agentic/social/joint_attention/helpers/constants.rb +34 -0
  57. data/lib/legion/extensions/agentic/social/joint_attention/helpers/joint_focus_manager.rb +157 -0
  58. data/lib/legion/extensions/agentic/social/joint_attention/runners/joint_attention.rb +88 -0
  59. data/lib/legion/extensions/agentic/social/joint_attention/version.rb +13 -0
  60. data/lib/legion/extensions/agentic/social/joint_attention.rb +20 -0
  61. data/lib/legion/extensions/agentic/social/mentalizing/actors/decay.rb +45 -0
  62. data/lib/legion/extensions/agentic/social/mentalizing/client.rb +28 -0
  63. data/lib/legion/extensions/agentic/social/mentalizing/helpers/belief_attribution.rb +58 -0
  64. data/lib/legion/extensions/agentic/social/mentalizing/helpers/constants.rb +33 -0
  65. data/lib/legion/extensions/agentic/social/mentalizing/helpers/mental_model.rb +137 -0
  66. data/lib/legion/extensions/agentic/social/mentalizing/runners/mentalizing.rb +93 -0
  67. data/lib/legion/extensions/agentic/social/mentalizing/version.rb +13 -0
  68. data/lib/legion/extensions/agentic/social/mentalizing.rb +20 -0
  69. data/lib/legion/extensions/agentic/social/mirror/client.rb +33 -0
  70. data/lib/legion/extensions/agentic/social/mirror/helpers/constants.rb +65 -0
  71. data/lib/legion/extensions/agentic/social/mirror/helpers/mirror_engine.rb +132 -0
  72. data/lib/legion/extensions/agentic/social/mirror/helpers/mirror_event.rb +46 -0
  73. data/lib/legion/extensions/agentic/social/mirror/helpers/simulation.rb +43 -0
  74. data/lib/legion/extensions/agentic/social/mirror/runners/observe.rb +52 -0
  75. data/lib/legion/extensions/agentic/social/mirror/runners/resonance.rb +79 -0
  76. data/lib/legion/extensions/agentic/social/mirror/runners/simulate.rb +63 -0
  77. data/lib/legion/extensions/agentic/social/mirror/version.rb +13 -0
  78. data/lib/legion/extensions/agentic/social/mirror.rb +22 -0
  79. data/lib/legion/extensions/agentic/social/mirror_system/actors/decay.rb +45 -0
  80. data/lib/legion/extensions/agentic/social/mirror_system/client.rb +28 -0
  81. data/lib/legion/extensions/agentic/social/mirror_system/helpers/constants.rb +62 -0
  82. data/lib/legion/extensions/agentic/social/mirror_system/helpers/mirror_system.rb +162 -0
  83. data/lib/legion/extensions/agentic/social/mirror_system/helpers/observed_behavior.rb +67 -0
  84. data/lib/legion/extensions/agentic/social/mirror_system/runners/mirror.rb +99 -0
  85. data/lib/legion/extensions/agentic/social/mirror_system/version.rb +13 -0
  86. data/lib/legion/extensions/agentic/social/mirror_system.rb +20 -0
  87. data/lib/legion/extensions/agentic/social/moral_reasoning/client.rb +19 -0
  88. data/lib/legion/extensions/agentic/social/moral_reasoning/helpers/constants.rb +49 -0
  89. data/lib/legion/extensions/agentic/social/moral_reasoning/helpers/dilemma.rb +68 -0
  90. data/lib/legion/extensions/agentic/social/moral_reasoning/helpers/llm_enhancer.rb +140 -0
  91. data/lib/legion/extensions/agentic/social/moral_reasoning/helpers/moral_engine.rb +239 -0
  92. data/lib/legion/extensions/agentic/social/moral_reasoning/helpers/moral_foundation.rb +45 -0
  93. data/lib/legion/extensions/agentic/social/moral_reasoning/runners/moral_reasoning.rb +121 -0
  94. data/lib/legion/extensions/agentic/social/moral_reasoning/version.rb +13 -0
  95. data/lib/legion/extensions/agentic/social/moral_reasoning.rb +21 -0
  96. data/lib/legion/extensions/agentic/social/perspective_shifting/client.rb +29 -0
  97. data/lib/legion/extensions/agentic/social/perspective_shifting/helpers/constants.rb +67 -0
  98. data/lib/legion/extensions/agentic/social/perspective_shifting/helpers/perspective.rb +45 -0
  99. data/lib/legion/extensions/agentic/social/perspective_shifting/helpers/perspective_view.rb +57 -0
  100. data/lib/legion/extensions/agentic/social/perspective_shifting/helpers/shifting_engine.rb +166 -0
  101. data/lib/legion/extensions/agentic/social/perspective_shifting/runners/perspective_shifting.rb +167 -0
  102. data/lib/legion/extensions/agentic/social/perspective_shifting/version.rb +13 -0
  103. data/lib/legion/extensions/agentic/social/perspective_shifting.rb +20 -0
  104. data/lib/legion/extensions/agentic/social/social/client.rb +25 -0
  105. data/lib/legion/extensions/agentic/social/social/helpers/constants.rb +84 -0
  106. data/lib/legion/extensions/agentic/social/social/helpers/social_graph.rb +172 -0
  107. data/lib/legion/extensions/agentic/social/social/runners/social.rb +146 -0
  108. data/lib/legion/extensions/agentic/social/social/version.rb +13 -0
  109. data/lib/legion/extensions/agentic/social/social.rb +18 -0
  110. data/lib/legion/extensions/agentic/social/social_learning/client.rb +25 -0
  111. data/lib/legion/extensions/agentic/social/social_learning/helpers/constants.rb +42 -0
  112. data/lib/legion/extensions/agentic/social/social_learning/helpers/model_agent.rb +82 -0
  113. data/lib/legion/extensions/agentic/social/social_learning/helpers/observed_behavior.rb +61 -0
  114. data/lib/legion/extensions/agentic/social/social_learning/helpers/social_learning_engine.rb +134 -0
  115. data/lib/legion/extensions/agentic/social/social_learning/runners/social_learning.rb +105 -0
  116. data/lib/legion/extensions/agentic/social/social_learning/version.rb +13 -0
  117. data/lib/legion/extensions/agentic/social/social_learning.rb +20 -0
  118. data/lib/legion/extensions/agentic/social/symbiosis/client.rb +23 -0
  119. data/lib/legion/extensions/agentic/social/symbiosis/helpers/constants.rb +50 -0
  120. data/lib/legion/extensions/agentic/social/symbiosis/helpers/ecosystem.rb +113 -0
  121. data/lib/legion/extensions/agentic/social/symbiosis/helpers/symbiosis_engine.rb +104 -0
  122. data/lib/legion/extensions/agentic/social/symbiosis/helpers/symbiotic_bond.rb +112 -0
  123. data/lib/legion/extensions/agentic/social/symbiosis/runners/cognitive_symbiosis.rb +101 -0
  124. data/lib/legion/extensions/agentic/social/symbiosis/version.rb +13 -0
  125. data/lib/legion/extensions/agentic/social/symbiosis.rb +22 -0
  126. data/lib/legion/extensions/agentic/social/theory_of_mind/client.rb +26 -0
  127. data/lib/legion/extensions/agentic/social/theory_of_mind/helpers/agent_model.rb +173 -0
  128. data/lib/legion/extensions/agentic/social/theory_of_mind/helpers/constants.rb +70 -0
  129. data/lib/legion/extensions/agentic/social/theory_of_mind/helpers/mental_state_tracker.rb +169 -0
  130. data/lib/legion/extensions/agentic/social/theory_of_mind/runners/theory_of_mind.rb +159 -0
  131. data/lib/legion/extensions/agentic/social/theory_of_mind/version.rb +13 -0
  132. data/lib/legion/extensions/agentic/social/theory_of_mind.rb +19 -0
  133. data/lib/legion/extensions/agentic/social/trust/actors/decay.rb +45 -0
  134. data/lib/legion/extensions/agentic/social/trust/client.rb +27 -0
  135. data/lib/legion/extensions/agentic/social/trust/helpers/trust_map.rb +160 -0
  136. data/lib/legion/extensions/agentic/social/trust/helpers/trust_model.rb +52 -0
  137. data/lib/legion/extensions/agentic/social/trust/local_migrations/20260316000020_create_trust_entries.rb +23 -0
  138. data/lib/legion/extensions/agentic/social/trust/runners/trust.rb +80 -0
  139. data/lib/legion/extensions/agentic/social/trust/version.rb +13 -0
  140. data/lib/legion/extensions/agentic/social/trust.rb +25 -0
  141. data/lib/legion/extensions/agentic/social/version.rb +11 -0
  142. data/lib/legion/extensions/agentic/social.rb +34 -0
  143. data/spec/legion/extensions/agentic/social/apprenticeship/client_spec.rb +20 -0
  144. data/spec/legion/extensions/agentic/social/apprenticeship/cognitive_apprenticeship_spec.rb +11 -0
  145. data/spec/legion/extensions/agentic/social/apprenticeship/helpers/apprenticeship_engine_spec.rb +146 -0
  146. data/spec/legion/extensions/agentic/social/apprenticeship/helpers/apprenticeship_model_spec.rb +124 -0
  147. data/spec/legion/extensions/agentic/social/apprenticeship/helpers/apprenticeship_spec.rb +136 -0
  148. data/spec/legion/extensions/agentic/social/apprenticeship/runners/cognitive_apprenticeship_spec.rb +154 -0
  149. data/spec/legion/extensions/agentic/social/conflict/actors/stale_check_spec.rb +45 -0
  150. data/spec/legion/extensions/agentic/social/conflict/client_spec.rb +15 -0
  151. data/spec/legion/extensions/agentic/social/conflict/helpers/conflict_log_spec.rb +232 -0
  152. data/spec/legion/extensions/agentic/social/conflict/helpers/llm_enhancer_spec.rb +189 -0
  153. data/spec/legion/extensions/agentic/social/conflict/helpers/severity_spec.rb +215 -0
  154. data/spec/legion/extensions/agentic/social/conflict/runners/conflict_spec.rb +151 -0
  155. data/spec/legion/extensions/agentic/social/conscience/client_spec.rb +58 -0
  156. data/spec/legion/extensions/agentic/social/conscience/helpers/constants_spec.rb +124 -0
  157. data/spec/legion/extensions/agentic/social/conscience/helpers/moral_evaluator_spec.rb +253 -0
  158. data/spec/legion/extensions/agentic/social/conscience/helpers/moral_store_spec.rb +230 -0
  159. data/spec/legion/extensions/agentic/social/conscience/runners/conscience_spec.rb +239 -0
  160. data/spec/legion/extensions/agentic/social/consent/actors/tier_evaluation_spec.rb +46 -0
  161. data/spec/legion/extensions/agentic/social/consent/client_spec.rb +33 -0
  162. data/spec/legion/extensions/agentic/social/consent/helpers/tiers_spec.rb +49 -0
  163. data/spec/legion/extensions/agentic/social/consent/local_persistence_spec.rb +234 -0
  164. data/spec/legion/extensions/agentic/social/consent/runners/consent_spec.rb +224 -0
  165. data/spec/legion/extensions/agentic/social/entrainment/client_spec.rb +21 -0
  166. data/spec/legion/extensions/agentic/social/entrainment/helpers/entrainment_engine_spec.rb +116 -0
  167. data/spec/legion/extensions/agentic/social/entrainment/helpers/pairing_spec.rb +103 -0
  168. data/spec/legion/extensions/agentic/social/entrainment/runners/cognitive_entrainment_spec.rb +87 -0
  169. data/spec/legion/extensions/agentic/social/governance/actors/vote_timeout_spec.rb +45 -0
  170. data/spec/legion/extensions/agentic/social/governance/client_spec.rb +14 -0
  171. data/spec/legion/extensions/agentic/social/governance/helpers/layers_spec.rb +190 -0
  172. data/spec/legion/extensions/agentic/social/governance/helpers/proposal_spec.rb +188 -0
  173. data/spec/legion/extensions/agentic/social/governance/runners/governance_spec.rb +101 -0
  174. data/spec/legion/extensions/agentic/social/governance/runners/shadow_ai_spec.rb +65 -0
  175. data/spec/legion/extensions/agentic/social/joint_attention/client_spec.rb +36 -0
  176. data/spec/legion/extensions/agentic/social/joint_attention/helpers/attention_target_spec.rb +258 -0
  177. data/spec/legion/extensions/agentic/social/joint_attention/helpers/joint_focus_manager_spec.rb +238 -0
  178. data/spec/legion/extensions/agentic/social/joint_attention/runners/joint_attention_spec.rb +228 -0
  179. data/spec/legion/extensions/agentic/social/mentalizing/client_spec.rb +19 -0
  180. data/spec/legion/extensions/agentic/social/mentalizing/helpers/belief_attribution_spec.rb +108 -0
  181. data/spec/legion/extensions/agentic/social/mentalizing/helpers/mental_model_spec.rb +179 -0
  182. data/spec/legion/extensions/agentic/social/mentalizing/runners/mentalizing_spec.rb +162 -0
  183. data/spec/legion/extensions/agentic/social/mirror/client_spec.rb +92 -0
  184. data/spec/legion/extensions/agentic/social/mirror/helpers/constants_spec.rb +123 -0
  185. data/spec/legion/extensions/agentic/social/mirror/helpers/mirror_engine_spec.rb +217 -0
  186. data/spec/legion/extensions/agentic/social/mirror/helpers/mirror_event_spec.rb +102 -0
  187. data/spec/legion/extensions/agentic/social/mirror/helpers/simulation_spec.rb +100 -0
  188. data/spec/legion/extensions/agentic/social/mirror/runners/observe_spec.rb +77 -0
  189. data/spec/legion/extensions/agentic/social/mirror/runners/resonance_spec.rb +123 -0
  190. data/spec/legion/extensions/agentic/social/mirror/runners/simulate_spec.rb +103 -0
  191. data/spec/legion/extensions/agentic/social/mirror_system/client_spec.rb +40 -0
  192. data/spec/legion/extensions/agentic/social/mirror_system/helpers/mirror_system_spec.rb +144 -0
  193. data/spec/legion/extensions/agentic/social/mirror_system/helpers/observed_behavior_spec.rb +98 -0
  194. data/spec/legion/extensions/agentic/social/mirror_system/runners/mirror_spec.rb +122 -0
  195. data/spec/legion/extensions/agentic/social/moral_reasoning/client_spec.rb +34 -0
  196. data/spec/legion/extensions/agentic/social/moral_reasoning/helpers/dilemma_spec.rb +108 -0
  197. data/spec/legion/extensions/agentic/social/moral_reasoning/helpers/llm_enhancer_spec.rb +232 -0
  198. data/spec/legion/extensions/agentic/social/moral_reasoning/helpers/moral_engine_spec.rb +266 -0
  199. data/spec/legion/extensions/agentic/social/moral_reasoning/helpers/moral_foundation_spec.rb +70 -0
  200. data/spec/legion/extensions/agentic/social/moral_reasoning/runners/moral_reasoning_spec.rb +275 -0
  201. data/spec/legion/extensions/agentic/social/perspective_shifting/client_spec.rb +30 -0
  202. data/spec/legion/extensions/agentic/social/perspective_shifting/helpers/constants_spec.rb +99 -0
  203. data/spec/legion/extensions/agentic/social/perspective_shifting/helpers/perspective_spec.rb +77 -0
  204. data/spec/legion/extensions/agentic/social/perspective_shifting/helpers/perspective_view_spec.rb +105 -0
  205. data/spec/legion/extensions/agentic/social/perspective_shifting/helpers/shifting_engine_spec.rb +246 -0
  206. data/spec/legion/extensions/agentic/social/perspective_shifting/runners/perspective_shifting_spec.rb +277 -0
  207. data/spec/legion/extensions/agentic/social/social/client_spec.rb +72 -0
  208. data/spec/legion/extensions/agentic/social/social/helpers/constants_spec.rb +99 -0
  209. data/spec/legion/extensions/agentic/social/social/helpers/social_graph_spec.rb +322 -0
  210. data/spec/legion/extensions/agentic/social/social/runners/social_spec.rb +220 -0
  211. data/spec/legion/extensions/agentic/social/social_learning/client_spec.rb +25 -0
  212. data/spec/legion/extensions/agentic/social/social_learning/helpers/constants_spec.rb +44 -0
  213. data/spec/legion/extensions/agentic/social/social_learning/helpers/model_agent_spec.rb +120 -0
  214. data/spec/legion/extensions/agentic/social/social_learning/helpers/observed_behavior_spec.rb +81 -0
  215. data/spec/legion/extensions/agentic/social/social_learning/helpers/social_learning_engine_spec.rb +196 -0
  216. data/spec/legion/extensions/agentic/social/social_learning/runners/social_learning_spec.rb +150 -0
  217. data/spec/legion/extensions/agentic/social/symbiosis/client_spec.rb +45 -0
  218. data/spec/legion/extensions/agentic/social/symbiosis/helpers/constants_spec.rb +73 -0
  219. data/spec/legion/extensions/agentic/social/symbiosis/helpers/ecosystem_spec.rb +185 -0
  220. data/spec/legion/extensions/agentic/social/symbiosis/helpers/symbiosis_engine_spec.rb +182 -0
  221. data/spec/legion/extensions/agentic/social/symbiosis/helpers/symbiotic_bond_spec.rb +209 -0
  222. data/spec/legion/extensions/agentic/social/symbiosis/runners/cognitive_symbiosis_spec.rb +182 -0
  223. data/spec/legion/extensions/agentic/social/theory_of_mind/client_spec.rb +63 -0
  224. data/spec/legion/extensions/agentic/social/theory_of_mind/helpers/agent_model_spec.rb +244 -0
  225. data/spec/legion/extensions/agentic/social/theory_of_mind/helpers/constants_spec.rb +71 -0
  226. data/spec/legion/extensions/agentic/social/theory_of_mind/helpers/mental_state_tracker_spec.rb +228 -0
  227. data/spec/legion/extensions/agentic/social/theory_of_mind/runners/theory_of_mind_spec.rb +221 -0
  228. data/spec/legion/extensions/agentic/social/trust/actors/decay_spec.rb +62 -0
  229. data/spec/legion/extensions/agentic/social/trust/client_spec.rb +17 -0
  230. data/spec/legion/extensions/agentic/social/trust/helpers/trust_map_spec.rb +299 -0
  231. data/spec/legion/extensions/agentic/social/trust/helpers/trust_model_spec.rb +179 -0
  232. data/spec/legion/extensions/agentic/social/trust/local_persistence_spec.rb +359 -0
  233. data/spec/legion/extensions/agentic/social/trust/runners/trust_spec.rb +84 -0
  234. data/spec/spec_helper.rb +54 -0
  235. metadata +319 -0
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Social::Entrainment::Runners::CognitiveEntrainment do
4
+ let(:runner_host) do
5
+ obj = Object.new
6
+ obj.extend(described_class)
7
+ obj
8
+ end
9
+
10
+ describe '#create_entrainment_pairing' do
11
+ it 'creates a pairing' do
12
+ result = runner_host.create_entrainment_pairing(
13
+ agent_a: 'alpha', agent_b: 'beta', domain: :reasoning
14
+ )
15
+ expect(result[:success]).to be true
16
+ expect(result[:pairing_id]).to be_a(String)
17
+ end
18
+ end
19
+
20
+ describe '#record_entrainment_interaction' do
21
+ it 'records an interaction' do
22
+ created = runner_host.create_entrainment_pairing(
23
+ agent_a: 'alpha', agent_b: 'beta', domain: :reasoning
24
+ )
25
+ result = runner_host.record_entrainment_interaction(
26
+ pairing_id: created[:pairing_id], aligned: true
27
+ )
28
+ expect(result[:success]).to be true
29
+ end
30
+ end
31
+
32
+ describe '#pairings_for_agent' do
33
+ it 'finds pairings for an agent' do
34
+ runner_host.create_entrainment_pairing(
35
+ agent_a: 'alpha', agent_b: 'beta', domain: :reasoning
36
+ )
37
+ result = runner_host.pairings_for_agent(agent_id: 'alpha')
38
+ expect(result[:success]).to be true
39
+ expect(result[:count]).to eq(1)
40
+ end
41
+ end
42
+
43
+ describe '#entrained_partners' do
44
+ it 'returns entrained partners' do
45
+ result = runner_host.entrained_partners(agent_id: 'alpha')
46
+ expect(result[:success]).to be true
47
+ end
48
+ end
49
+
50
+ describe '#strongest_entrainment_pairings' do
51
+ it 'returns strongest pairings' do
52
+ result = runner_host.strongest_entrainment_pairings(limit: 3)
53
+ expect(result[:success]).to be true
54
+ end
55
+ end
56
+
57
+ describe '#domain_entrainment' do
58
+ it 'returns pairings by domain' do
59
+ result = runner_host.domain_entrainment(domain: :reasoning)
60
+ expect(result[:success]).to be true
61
+ end
62
+ end
63
+
64
+ describe '#overall_entrainment_level' do
65
+ it 'returns overall synchrony' do
66
+ result = runner_host.overall_entrainment_level
67
+ expect(result[:success]).to be true
68
+ expect(result).to include(:overall_synchrony)
69
+ end
70
+ end
71
+
72
+ describe '#update_cognitive_entrainment' do
73
+ it 'runs drift and prune' do
74
+ result = runner_host.update_cognitive_entrainment
75
+ expect(result[:success]).to be true
76
+ expect(result).to include(:pruned)
77
+ end
78
+ end
79
+
80
+ describe '#cognitive_entrainment_stats' do
81
+ it 'returns stats' do
82
+ result = runner_host.cognitive_entrainment_stats
83
+ expect(result[:success]).to be true
84
+ expect(result).to include(:total_pairings, :entrained_count)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Actors
6
+ class Every; end # rubocop:disable Lint/EmptyClass
7
+ end
8
+ end
9
+ end
10
+
11
+ $LOADED_FEATURES << 'legion/extensions/actors/every'
12
+
13
+ require_relative '../../../../../../../lib/legion/extensions/agentic/social/governance/actors/vote_timeout'
14
+
15
+ RSpec.describe Legion::Extensions::Agentic::Social::Governance::Actor::VoteTimeout do
16
+ subject(:actor) { described_class.new }
17
+
18
+ describe '#runner_class' do
19
+ it { expect(actor.runner_class).to eq Legion::Extensions::Agentic::Social::Governance::Runners::Governance }
20
+ end
21
+
22
+ describe '#runner_function' do
23
+ it { expect(actor.runner_function).to eq 'timeout_proposals' }
24
+ end
25
+
26
+ describe '#time' do
27
+ it { expect(actor.time).to eq 300 }
28
+ end
29
+
30
+ describe '#run_now?' do
31
+ it { expect(actor.run_now?).to be false }
32
+ end
33
+
34
+ describe '#use_runner?' do
35
+ it { expect(actor.use_runner?).to be false }
36
+ end
37
+
38
+ describe '#check_subtask?' do
39
+ it { expect(actor.check_subtask?).to be false }
40
+ end
41
+
42
+ describe '#generate_task?' do
43
+ it { expect(actor.generate_task?).to be false }
44
+ end
45
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/social/governance/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Social::Governance::Client do
6
+ it 'responds to governance runner methods' do
7
+ client = described_class.new
8
+ expect(client).to respond_to(:create_proposal)
9
+ expect(client).to respond_to(:vote_on_proposal)
10
+ expect(client).to respond_to(:get_proposal)
11
+ expect(client).to respond_to(:open_proposals)
12
+ expect(client).to respond_to(:validate_action)
13
+ end
14
+ end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/social/governance/helpers/layers'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Social::Governance::Helpers::Layers do
6
+ describe 'GOVERNANCE_LAYERS' do
7
+ it 'contains four layers as symbols' do
8
+ expect(described_class::GOVERNANCE_LAYERS.size).to eq(4)
9
+ end
10
+
11
+ it 'includes agent_validation' do
12
+ expect(described_class::GOVERNANCE_LAYERS).to include(:agent_validation)
13
+ end
14
+
15
+ it 'includes anomaly_detection' do
16
+ expect(described_class::GOVERNANCE_LAYERS).to include(:anomaly_detection)
17
+ end
18
+
19
+ it 'includes human_deliberation' do
20
+ expect(described_class::GOVERNANCE_LAYERS).to include(:human_deliberation)
21
+ end
22
+
23
+ it 'includes transparency' do
24
+ expect(described_class::GOVERNANCE_LAYERS).to include(:transparency)
25
+ end
26
+
27
+ it 'is frozen' do
28
+ expect(described_class::GOVERNANCE_LAYERS).to be_frozen
29
+ end
30
+ end
31
+
32
+ describe 'PROPOSAL_CATEGORIES' do
33
+ it 'contains five categories' do
34
+ expect(described_class::PROPOSAL_CATEGORIES.size).to eq(5)
35
+ end
36
+
37
+ it 'includes policy_change' do
38
+ expect(described_class::PROPOSAL_CATEGORIES).to include(:policy_change)
39
+ end
40
+
41
+ it 'includes resource_allocation' do
42
+ expect(described_class::PROPOSAL_CATEGORIES).to include(:resource_allocation)
43
+ end
44
+
45
+ it 'includes access_control' do
46
+ expect(described_class::PROPOSAL_CATEGORIES).to include(:access_control)
47
+ end
48
+
49
+ it 'includes emergency' do
50
+ expect(described_class::PROPOSAL_CATEGORIES).to include(:emergency)
51
+ end
52
+
53
+ it 'includes protocol_update' do
54
+ expect(described_class::PROPOSAL_CATEGORIES).to include(:protocol_update)
55
+ end
56
+
57
+ it 'is frozen' do
58
+ expect(described_class::PROPOSAL_CATEGORIES).to be_frozen
59
+ end
60
+ end
61
+
62
+ describe 'numeric constants' do
63
+ it 'sets MIN_COUNCIL_SIZE to 3' do
64
+ expect(described_class::MIN_COUNCIL_SIZE).to eq(3)
65
+ end
66
+
67
+ it 'sets QUORUM_FRACTION to 0.66' do
68
+ expect(described_class::QUORUM_FRACTION).to eq(0.66)
69
+ end
70
+
71
+ it 'sets VOTE_TIMEOUT to 86400' do
72
+ expect(described_class::VOTE_TIMEOUT).to eq(86_400)
73
+ end
74
+ end
75
+
76
+ describe '.valid_layer?' do
77
+ it 'returns true for agent_validation' do
78
+ expect(described_class.valid_layer?(:agent_validation)).to be true
79
+ end
80
+
81
+ it 'returns true for anomaly_detection' do
82
+ expect(described_class.valid_layer?(:anomaly_detection)).to be true
83
+ end
84
+
85
+ it 'returns true for human_deliberation' do
86
+ expect(described_class.valid_layer?(:human_deliberation)).to be true
87
+ end
88
+
89
+ it 'returns true for transparency' do
90
+ expect(described_class.valid_layer?(:transparency)).to be true
91
+ end
92
+
93
+ it 'returns false for unknown layer symbol' do
94
+ expect(described_class.valid_layer?(:unknown)).to be false
95
+ end
96
+
97
+ it 'returns false for nil' do
98
+ expect(described_class.valid_layer?(nil)).to be false
99
+ end
100
+
101
+ it 'returns false for string version of valid layer' do
102
+ expect(described_class.valid_layer?('agent_validation')).to be false
103
+ end
104
+ end
105
+
106
+ describe '.valid_category?' do
107
+ it 'returns true for policy_change' do
108
+ expect(described_class.valid_category?(:policy_change)).to be true
109
+ end
110
+
111
+ it 'returns true for resource_allocation' do
112
+ expect(described_class.valid_category?(:resource_allocation)).to be true
113
+ end
114
+
115
+ it 'returns true for access_control' do
116
+ expect(described_class.valid_category?(:access_control)).to be true
117
+ end
118
+
119
+ it 'returns true for emergency' do
120
+ expect(described_class.valid_category?(:emergency)).to be true
121
+ end
122
+
123
+ it 'returns true for protocol_update' do
124
+ expect(described_class.valid_category?(:protocol_update)).to be true
125
+ end
126
+
127
+ it 'returns false for unknown category' do
128
+ expect(described_class.valid_category?(:invalid)).to be false
129
+ end
130
+
131
+ it 'returns false for nil' do
132
+ expect(described_class.valid_category?(nil)).to be false
133
+ end
134
+
135
+ it 'returns false for string version of valid category' do
136
+ expect(described_class.valid_category?('emergency')).to be false
137
+ end
138
+ end
139
+
140
+ describe '.quorum_met?' do
141
+ context 'when council_size is below minimum' do
142
+ it 'returns false for council_size of 2' do
143
+ expect(described_class.quorum_met?(2, 2)).to be false
144
+ end
145
+
146
+ it 'returns false for council_size of 1' do
147
+ expect(described_class.quorum_met?(1, 1)).to be false
148
+ end
149
+
150
+ it 'returns false for council_size of 0' do
151
+ expect(described_class.quorum_met?(0, 0)).to be false
152
+ end
153
+ end
154
+
155
+ context 'with minimum council size of 3' do
156
+ it 'returns true when 2 of 3 vote (meets ceil(3 * 0.66) = 2)' do
157
+ expect(described_class.quorum_met?(2, 3)).to be true
158
+ end
159
+
160
+ it 'returns true when all 3 vote' do
161
+ expect(described_class.quorum_met?(3, 3)).to be true
162
+ end
163
+
164
+ it 'returns false when only 1 of 3 vote' do
165
+ expect(described_class.quorum_met?(1, 3)).to be false
166
+ end
167
+ end
168
+
169
+ context 'with larger council sizes' do
170
+ it 'requires ceil(4 * 0.66) = 3 votes for council of 4' do
171
+ expect(described_class.quorum_met?(3, 4)).to be true
172
+ expect(described_class.quorum_met?(2, 4)).to be false
173
+ end
174
+
175
+ it 'requires ceil(5 * 0.66) = 4 votes for council of 5' do
176
+ expect(described_class.quorum_met?(4, 5)).to be true
177
+ expect(described_class.quorum_met?(3, 5)).to be false
178
+ end
179
+
180
+ it 'requires ceil(9 * 0.66) = 6 votes for council of 9' do
181
+ expect(described_class.quorum_met?(6, 9)).to be true
182
+ expect(described_class.quorum_met?(5, 9)).to be false
183
+ end
184
+ end
185
+
186
+ it 'returns false for zero votes regardless of council size' do
187
+ expect(described_class.quorum_met?(0, 3)).to be false
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/social/governance/helpers/layers'
4
+ require 'legion/extensions/agentic/social/governance/helpers/proposal'
5
+
6
+ RSpec.describe Legion::Extensions::Agentic::Social::Governance::Helpers::Proposal do
7
+ subject(:proposal_store) { described_class.new }
8
+
9
+ describe '#initialize' do
10
+ it 'starts with an empty proposals hash' do
11
+ expect(proposal_store.proposals).to eq({})
12
+ end
13
+ end
14
+
15
+ describe '#create' do
16
+ let(:created_id) do
17
+ proposal_store.create(category: :policy_change, description: 'Enable new policy', proposer: 'agent-1')
18
+ end
19
+
20
+ it 'returns a UUID string' do
21
+ expect(created_id).to match(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/)
22
+ end
23
+
24
+ it 'stores the proposal under the returned ID' do
25
+ expect(proposal_store.proposals[created_id]).not_to be_nil
26
+ end
27
+
28
+ it 'stores the correct category' do
29
+ expect(proposal_store.proposals[created_id][:category]).to eq(:policy_change)
30
+ end
31
+
32
+ it 'stores the correct description' do
33
+ expect(proposal_store.proposals[created_id][:description]).to eq('Enable new policy')
34
+ end
35
+
36
+ it 'stores the correct proposer' do
37
+ expect(proposal_store.proposals[created_id][:proposer]).to eq('agent-1')
38
+ end
39
+
40
+ it 'defaults council_size to MIN_COUNCIL_SIZE' do
41
+ expect(proposal_store.proposals[created_id][:council_size])
42
+ .to eq(Legion::Extensions::Agentic::Social::Governance::Helpers::Layers::MIN_COUNCIL_SIZE)
43
+ end
44
+
45
+ it 'uses the provided council_size when specified' do
46
+ id = proposal_store.create(category: :emergency, description: 'urgent', proposer: 'agent-x', council_size: 7)
47
+ expect(proposal_store.proposals[id][:council_size]).to eq(7)
48
+ end
49
+
50
+ it 'initializes with status :open' do
51
+ expect(proposal_store.proposals[created_id][:status]).to eq(:open)
52
+ end
53
+
54
+ it 'initializes votes_for as empty array' do
55
+ expect(proposal_store.proposals[created_id][:votes_for]).to eq([])
56
+ end
57
+
58
+ it 'initializes votes_against as empty array' do
59
+ expect(proposal_store.proposals[created_id][:votes_against]).to eq([])
60
+ end
61
+
62
+ it 'sets created_at to a Time object' do
63
+ expect(proposal_store.proposals[created_id][:created_at]).to be_a(Time)
64
+ end
65
+
66
+ it 'sets resolved_at to nil initially' do
67
+ expect(proposal_store.proposals[created_id][:resolved_at]).to be_nil
68
+ end
69
+
70
+ it 'generates unique IDs for separate calls' do
71
+ id1 = proposal_store.create(category: :policy_change, description: 'a', proposer: 'x')
72
+ id2 = proposal_store.create(category: :policy_change, description: 'b', proposer: 'y')
73
+ expect(id1).not_to eq(id2)
74
+ end
75
+ end
76
+
77
+ describe '#get' do
78
+ it 'returns the proposal hash for a valid ID' do
79
+ id = proposal_store.create(category: :policy_change, description: 'test', proposer: 'agent-1')
80
+ result = proposal_store.get(id)
81
+ expect(result[:proposal_id]).to eq(id)
82
+ end
83
+
84
+ it 'returns nil for an unknown ID' do
85
+ expect(proposal_store.get('nonexistent-uuid')).to be_nil
86
+ end
87
+ end
88
+
89
+ describe '#open_proposals' do
90
+ it 'returns empty array when no proposals exist' do
91
+ expect(proposal_store.open_proposals).to eq([])
92
+ end
93
+
94
+ it 'returns all open proposals' do
95
+ proposal_store.create(category: :policy_change, description: 'first', proposer: 'a1')
96
+ proposal_store.create(category: :emergency, description: 'second', proposer: 'a2')
97
+ expect(proposal_store.open_proposals.size).to eq(2)
98
+ end
99
+
100
+ it 'excludes resolved proposals' do
101
+ id = proposal_store.create(category: :policy_change, description: 'vote me', proposer: 'a1', council_size: 3)
102
+ proposal_store.vote(id, voter: 'v1', approve: true)
103
+ proposal_store.vote(id, voter: 'v2', approve: true)
104
+ expect(proposal_store.open_proposals).to be_empty
105
+ end
106
+ end
107
+
108
+ describe '#vote' do
109
+ let(:proposal_id) do
110
+ proposal_store.create(category: :policy_change, description: 'test', proposer: 'agent-1', council_size: 3)
111
+ end
112
+
113
+ it 'returns nil for an unknown proposal ID' do
114
+ expect(proposal_store.vote('no-such-id', voter: 'v1', approve: true)).to be_nil
115
+ end
116
+
117
+ it 'returns :already_voted when the same voter votes twice' do
118
+ proposal_store.vote(proposal_id, voter: 'v1', approve: true)
119
+ result = proposal_store.vote(proposal_id, voter: 'v1', approve: false)
120
+ expect(result).to eq(:already_voted)
121
+ end
122
+
123
+ it 'prevents double voting even when switching approve/reject' do
124
+ proposal_store.vote(proposal_id, voter: 'v1', approve: false)
125
+ result = proposal_store.vote(proposal_id, voter: 'v1', approve: true)
126
+ expect(result).to eq(:already_voted)
127
+ end
128
+
129
+ it 'adds approve vote to votes_for' do
130
+ proposal_store.vote(proposal_id, voter: 'v1', approve: true)
131
+ expect(proposal_store.proposals[proposal_id][:votes_for]).to include('v1')
132
+ end
133
+
134
+ it 'adds reject vote to votes_against' do
135
+ proposal_store.vote(proposal_id, voter: 'v1', approve: false)
136
+ expect(proposal_store.proposals[proposal_id][:votes_against]).to include('v1')
137
+ end
138
+
139
+ it 'returns :pending when quorum is not yet met' do
140
+ result = proposal_store.vote(proposal_id, voter: 'v1', approve: true)
141
+ expect(result).to eq(:pending)
142
+ end
143
+
144
+ context 'when quorum for approval is met' do
145
+ it 'returns :approved and sets status' do
146
+ proposal_store.vote(proposal_id, voter: 'v1', approve: true)
147
+ result = proposal_store.vote(proposal_id, voter: 'v2', approve: true)
148
+ expect(result).to eq(:approved)
149
+ expect(proposal_store.proposals[proposal_id][:status]).to eq(:approved)
150
+ end
151
+
152
+ it 'sets resolved_at on approval' do
153
+ proposal_store.vote(proposal_id, voter: 'v1', approve: true)
154
+ proposal_store.vote(proposal_id, voter: 'v2', approve: true)
155
+ expect(proposal_store.proposals[proposal_id][:resolved_at]).to be_a(Time)
156
+ end
157
+ end
158
+
159
+ context 'when quorum for rejection is met' do
160
+ it 'returns :rejected when enough votes against' do
161
+ proposal_store.vote(proposal_id, voter: 'v1', approve: false)
162
+ result = proposal_store.vote(proposal_id, voter: 'v2', approve: false)
163
+ expect(result).to eq(:rejected)
164
+ expect(proposal_store.proposals[proposal_id][:status]).to eq(:rejected)
165
+ end
166
+
167
+ it 'sets resolved_at on rejection' do
168
+ proposal_store.vote(proposal_id, voter: 'v1', approve: false)
169
+ proposal_store.vote(proposal_id, voter: 'v2', approve: false)
170
+ expect(proposal_store.proposals[proposal_id][:resolved_at]).to be_a(Time)
171
+ end
172
+
173
+ it 'returns :rejected when all council members voted and no quorum for approval' do
174
+ proposal_store.vote(proposal_id, voter: 'v1', approve: true)
175
+ proposal_store.vote(proposal_id, voter: 'v2', approve: false)
176
+ result = proposal_store.vote(proposal_id, voter: 'v3', approve: false)
177
+ expect(result).to eq(:rejected)
178
+ end
179
+ end
180
+
181
+ it 'returns nil when voting on a resolved (approved) proposal' do
182
+ proposal_store.vote(proposal_id, voter: 'v1', approve: true)
183
+ proposal_store.vote(proposal_id, voter: 'v2', approve: true)
184
+ result = proposal_store.vote(proposal_id, voter: 'v3', approve: true)
185
+ expect(result).to be_nil
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/social/governance/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Social::Governance::Runners::Governance do
6
+ let(:client) { Legion::Extensions::Agentic::Social::Governance::Client.new }
7
+
8
+ describe '#create_proposal' do
9
+ it 'creates a proposal' do
10
+ result = client.create_proposal(category: :policy_change, description: 'test', proposer: 'agent-1')
11
+ expect(result[:proposal_id]).to match(/\A[0-9a-f-]{36}\z/)
12
+ expect(result[:status]).to eq(:open)
13
+ end
14
+
15
+ it 'rejects invalid category' do
16
+ result = client.create_proposal(category: :invalid, description: 'test', proposer: 'agent-1')
17
+ expect(result[:error]).to eq(:invalid_category)
18
+ end
19
+ end
20
+
21
+ describe '#vote_on_proposal' do
22
+ it 'records a vote' do
23
+ prop = client.create_proposal(category: :policy_change, description: 'test', proposer: 'agent-1', council_size: 3)
24
+ result = client.vote_on_proposal(proposal_id: prop[:proposal_id], voter: 'agent-2', approve: true)
25
+ expect(result[:voted]).to be true
26
+ end
27
+
28
+ it 'prevents double voting' do
29
+ prop = client.create_proposal(category: :policy_change, description: 'test', proposer: 'agent-1', council_size: 3)
30
+ client.vote_on_proposal(proposal_id: prop[:proposal_id], voter: 'agent-2', approve: true)
31
+ result = client.vote_on_proposal(proposal_id: prop[:proposal_id], voter: 'agent-2', approve: true)
32
+ expect(result[:error]).to eq(:already_voted)
33
+ end
34
+
35
+ it 'approves with quorum' do
36
+ prop = client.create_proposal(category: :policy_change, description: 'test', proposer: 'agent-1', council_size: 3)
37
+ client.vote_on_proposal(proposal_id: prop[:proposal_id], voter: 'v1', approve: true)
38
+ result = client.vote_on_proposal(proposal_id: prop[:proposal_id], voter: 'v2', approve: true)
39
+ expect(result[:resolution]).to eq(:approved)
40
+ end
41
+ end
42
+
43
+ describe '#validate_action' do
44
+ it 'allows agent_validation' do
45
+ result = client.validate_action(layer: :agent_validation, action: 'test')
46
+ expect(result[:allowed]).to be true
47
+ end
48
+
49
+ it 'blocks human_deliberation' do
50
+ result = client.validate_action(layer: :human_deliberation, action: 'test')
51
+ expect(result[:allowed]).to be false
52
+ end
53
+
54
+ it 'rejects invalid layer' do
55
+ result = client.validate_action(layer: :invalid, action: 'test')
56
+ expect(result[:error]).to eq(:invalid_layer)
57
+ end
58
+ end
59
+
60
+ describe '#open_proposals' do
61
+ it 'lists open proposals' do
62
+ client.create_proposal(category: :policy_change, description: 'test', proposer: 'agent-1')
63
+ result = client.open_proposals
64
+ expect(result[:count]).to eq(1)
65
+ end
66
+ end
67
+
68
+ describe '#timeout_proposals' do
69
+ it 'returns zero timed_out when no proposals exist' do
70
+ result = client.timeout_proposals
71
+ expect(result[:checked]).to eq(0)
72
+ expect(result[:timed_out]).to eq(0)
73
+ expect(result[:timed_out_ids]).to eq([])
74
+ end
75
+
76
+ it 'returns zero timed_out for recently created proposals' do
77
+ client.create_proposal(category: :policy_change, description: 'fresh', proposer: 'agent-1')
78
+ result = client.timeout_proposals
79
+ expect(result[:timed_out]).to eq(0)
80
+ end
81
+
82
+ it 'times out proposals older than VOTE_TIMEOUT' do
83
+ prop = client.create_proposal(category: :policy_change, description: 'old', proposer: 'agent-1')
84
+ store = client.instance_variable_get(:@proposal_store)
85
+ store.proposals[prop[:proposal_id]][:created_at] = Time.now.utc - (Legion::Extensions::Agentic::Social::Governance::Helpers::Layers::VOTE_TIMEOUT + 1)
86
+ result = client.timeout_proposals
87
+ expect(result[:timed_out]).to eq(1)
88
+ expect(result[:timed_out_ids]).to include(prop[:proposal_id])
89
+ end
90
+
91
+ it 'does not time out already-resolved proposals' do
92
+ prop = client.create_proposal(category: :policy_change, description: 'resolved', proposer: 'agent-1', council_size: 3)
93
+ store = client.instance_variable_get(:@proposal_store)
94
+ store.proposals[prop[:proposal_id]][:created_at] = Time.now.utc - (Legion::Extensions::Agentic::Social::Governance::Helpers::Layers::VOTE_TIMEOUT + 1)
95
+ client.vote_on_proposal(proposal_id: prop[:proposal_id], voter: 'v1', approve: true)
96
+ client.vote_on_proposal(proposal_id: prop[:proposal_id], voter: 'v2', approve: true)
97
+ result = client.timeout_proposals
98
+ expect(result[:timed_out]).to eq(0)
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Social::Governance::Runners::ShadowAi do
6
+ let(:host) { Object.new.extend(described_class) }
7
+
8
+ describe '#check_llm_bypass_indicators' do
9
+ it 'detects direct API key when provider not enabled' do
10
+ allow(ENV).to receive(:key?).and_call_original
11
+ allow(ENV).to receive(:key?).with('OPENAI_API_KEY').and_return(true)
12
+ allow(ENV).to receive(:key?).with('ANTHROPIC_API_KEY').and_return(false)
13
+ allow(Legion::Settings).to receive(:[]).with(:llm).and_return(
14
+ { providers: { openai: { enabled: false } } }
15
+ )
16
+ result = host.check_llm_bypass_indicators
17
+ expect(result[:bypassed]).to be true
18
+ expect(result[:indicators]).to include(:direct_openai_key)
19
+ end
20
+
21
+ it 'returns clean when no bypass indicators' do
22
+ allow(ENV).to receive(:key?).with('OPENAI_API_KEY').and_return(false)
23
+ allow(ENV).to receive(:key?).with('ANTHROPIC_API_KEY').and_return(false)
24
+ result = host.check_llm_bypass_indicators
25
+ expect(result[:bypassed]).to be false
26
+ expect(result[:indicators]).to be_empty
27
+ end
28
+
29
+ it 'does not flag when provider is enabled' do
30
+ allow(ENV).to receive(:key?).and_call_original
31
+ allow(ENV).to receive(:key?).with('OPENAI_API_KEY').and_return(true)
32
+ allow(ENV).to receive(:key?).with('ANTHROPIC_API_KEY').and_return(false)
33
+ allow(Legion::Settings).to receive(:[]).with(:llm).and_return(
34
+ { providers: { openai: { enabled: true } } }
35
+ )
36
+ result = host.check_llm_bypass_indicators
37
+ expect(result[:bypassed]).to be false
38
+ end
39
+ end
40
+
41
+ describe '#check_airb_compliance' do
42
+ it 'returns unavailable when data model not loaded' do
43
+ result = host.check_airb_compliance
44
+ expect(result[:source]).to eq(:unavailable)
45
+ end
46
+ end
47
+
48
+ describe '#full_scan' do
49
+ it 'returns combined results' do
50
+ allow(host).to receive(:scan_unregistered_extensions).and_return(
51
+ { installed: 5, registered: 5, unregistered: [] }
52
+ )
53
+ allow(host).to receive(:check_llm_bypass_indicators).and_return(
54
+ { indicators: [], bypassed: false }
55
+ )
56
+ allow(host).to receive(:check_airb_compliance).and_return(
57
+ { checked: 0, source: :unavailable }
58
+ )
59
+
60
+ result = host.full_scan
61
+ expect(result[:issues_found]).to be_falsey
62
+ expect(result[:extensions][:installed]).to eq(5)
63
+ end
64
+ end
65
+ end