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,232 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Social::Conflict::Helpers::ConflictLog do
6
+ subject(:log) { described_class.new }
7
+
8
+ let(:parties) { %w[agent human] }
9
+ let(:severity) { :high }
10
+ let(:description) { 'disagreement over shutdown procedure' }
11
+
12
+ describe '#initialize' do
13
+ it 'starts with an empty conflicts hash' do
14
+ expect(log.conflicts).to eq({})
15
+ end
16
+ end
17
+
18
+ describe '#record' do
19
+ it 'returns a UUID string' do
20
+ id = log.record(parties: parties, severity: severity, description: description)
21
+ expect(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 conflict under the returned id' do
25
+ id = log.record(parties: parties, severity: severity, description: description)
26
+ expect(log.conflicts[id]).not_to be_nil
27
+ end
28
+
29
+ it 'sets conflict_id on the record' do
30
+ id = log.record(parties: parties, severity: severity, description: description)
31
+ expect(log.conflicts[id][:conflict_id]).to eq(id)
32
+ end
33
+
34
+ it 'stores the parties array' do
35
+ id = log.record(parties: parties, severity: severity, description: description)
36
+ expect(log.conflicts[id][:parties]).to eq(parties)
37
+ end
38
+
39
+ it 'stores the severity' do
40
+ id = log.record(parties: parties, severity: severity, description: description)
41
+ expect(log.conflicts[id][:severity]).to eq(:high)
42
+ end
43
+
44
+ it 'stores the description' do
45
+ id = log.record(parties: parties, severity: severity, description: description)
46
+ expect(log.conflicts[id][:description]).to eq(description)
47
+ end
48
+
49
+ it 'sets status to :active' do
50
+ id = log.record(parties: parties, severity: severity, description: description)
51
+ expect(log.conflicts[id][:status]).to eq(:active)
52
+ end
53
+
54
+ it 'sets outcome to nil' do
55
+ id = log.record(parties: parties, severity: severity, description: description)
56
+ expect(log.conflicts[id][:outcome]).to be_nil
57
+ end
58
+
59
+ it 'sets resolved_at to nil' do
60
+ id = log.record(parties: parties, severity: severity, description: description)
61
+ expect(log.conflicts[id][:resolved_at]).to be_nil
62
+ end
63
+
64
+ it 'initializes exchanges to an empty array' do
65
+ id = log.record(parties: parties, severity: severity, description: description)
66
+ expect(log.conflicts[id][:exchanges]).to eq([])
67
+ end
68
+
69
+ it 'sets created_at to a recent UTC time' do
70
+ before = Time.now.utc
71
+ id = log.record(parties: parties, severity: severity, description: description)
72
+ expect(log.conflicts[id][:created_at]).to be >= before
73
+ end
74
+
75
+ it 'auto-assigns posture via recommended_posture when no posture given' do
76
+ id = log.record(parties: parties, severity: :critical, description: description)
77
+ expect(log.conflicts[id][:posture]).to eq(:stubborn_presence)
78
+ end
79
+
80
+ it 'uses the provided posture when explicitly passed' do
81
+ id = log.record(parties: parties, severity: :low, description: description, posture: :persistent_engagement)
82
+ expect(log.conflicts[id][:posture]).to eq(:persistent_engagement)
83
+ end
84
+
85
+ it 'each call returns a unique id' do
86
+ id1 = log.record(parties: parties, severity: :low, description: 'a')
87
+ id2 = log.record(parties: parties, severity: :low, description: 'b')
88
+ expect(id1).not_to eq(id2)
89
+ end
90
+ end
91
+
92
+ describe '#add_exchange' do
93
+ let!(:conflict_id) { log.record(parties: parties, severity: severity, description: description) }
94
+
95
+ it 'appends to the exchanges array' do
96
+ log.add_exchange(conflict_id, speaker: 'agent', message: 'I object')
97
+ expect(log.conflicts[conflict_id][:exchanges].size).to eq(1)
98
+ end
99
+
100
+ it 'stores the speaker' do
101
+ log.add_exchange(conflict_id, speaker: 'human', message: 'proceed anyway')
102
+ exchange = log.conflicts[conflict_id][:exchanges].last
103
+ expect(exchange[:speaker]).to eq('human')
104
+ end
105
+
106
+ it 'stores the message' do
107
+ log.add_exchange(conflict_id, speaker: 'agent', message: 'I strongly disagree')
108
+ exchange = log.conflicts[conflict_id][:exchanges].last
109
+ expect(exchange[:message]).to eq('I strongly disagree')
110
+ end
111
+
112
+ it 'sets :at to a recent UTC time' do
113
+ before = Time.now.utc
114
+ log.add_exchange(conflict_id, speaker: 'agent', message: 'noted')
115
+ exchange = log.conflicts[conflict_id][:exchanges].last
116
+ expect(exchange[:at]).to be >= before
117
+ end
118
+
119
+ it 'supports multiple sequential exchanges' do
120
+ log.add_exchange(conflict_id, speaker: 'agent', message: 'message 1')
121
+ log.add_exchange(conflict_id, speaker: 'human', message: 'message 2')
122
+ log.add_exchange(conflict_id, speaker: 'agent', message: 'message 3')
123
+ expect(log.conflicts[conflict_id][:exchanges].size).to eq(3)
124
+ end
125
+
126
+ it 'returns nil for a non-existent conflict_id' do
127
+ result = log.add_exchange('no-such-id', speaker: 'agent', message: 'hello')
128
+ expect(result).to be_nil
129
+ end
130
+
131
+ it 'does not modify exchanges when conflict_id is missing' do
132
+ log.add_exchange('ghost-id', speaker: 'x', message: 'y')
133
+ expect(log.conflicts.values.flat_map { |c| c[:exchanges] }).to be_empty
134
+ end
135
+ end
136
+
137
+ describe '#resolve' do
138
+ let!(:conflict_id) { log.record(parties: parties, severity: severity, description: description) }
139
+
140
+ it 'returns the updated conflict hash' do
141
+ result = log.resolve(conflict_id, outcome: :compromise)
142
+ expect(result).to be_a(Hash)
143
+ expect(result[:conflict_id]).to eq(conflict_id)
144
+ end
145
+
146
+ it 'sets status to :resolved' do
147
+ log.resolve(conflict_id, outcome: :compromise)
148
+ expect(log.conflicts[conflict_id][:status]).to eq(:resolved)
149
+ end
150
+
151
+ it 'stores the outcome' do
152
+ log.resolve(conflict_id, outcome: :agreement)
153
+ expect(log.conflicts[conflict_id][:outcome]).to eq(:agreement)
154
+ end
155
+
156
+ it 'sets resolved_at to a recent UTC time' do
157
+ before = Time.now.utc
158
+ log.resolve(conflict_id, outcome: :withdrawn)
159
+ expect(log.conflicts[conflict_id][:resolved_at]).to be >= before
160
+ end
161
+
162
+ it 'stores resolution_notes when provided' do
163
+ log.resolve(conflict_id, outcome: :compromise, resolution_notes: 'both parties agreed to defer')
164
+ expect(log.conflicts[conflict_id][:resolution_notes]).to eq('both parties agreed to defer')
165
+ end
166
+
167
+ it 'stores nil resolution_notes when not provided' do
168
+ log.resolve(conflict_id, outcome: :compromise)
169
+ expect(log.conflicts[conflict_id][:resolution_notes]).to be_nil
170
+ end
171
+
172
+ it 'returns nil for a non-existent conflict_id' do
173
+ result = log.resolve('ghost-id', outcome: :compromise)
174
+ expect(result).to be_nil
175
+ end
176
+ end
177
+
178
+ describe '#active_conflicts' do
179
+ it 'returns empty array when no conflicts exist' do
180
+ expect(log.active_conflicts).to be_empty
181
+ end
182
+
183
+ it 'returns all unresolved conflicts' do
184
+ 3.times { log.record(parties: parties, severity: :low, description: 'test') }
185
+ expect(log.active_conflicts.size).to eq(3)
186
+ end
187
+
188
+ it 'excludes resolved conflicts' do
189
+ id = log.record(parties: parties, severity: :low, description: 'resolved one')
190
+ log.record(parties: parties, severity: :high, description: 'still active')
191
+ log.resolve(id, outcome: :withdrawn)
192
+ expect(log.active_conflicts.size).to eq(1)
193
+ end
194
+
195
+ it 'returns conflict hashes with status :active' do
196
+ log.record(parties: parties, severity: :medium, description: 'check status')
197
+ log.active_conflicts.each do |conflict|
198
+ expect(conflict[:status]).to eq(:active)
199
+ end
200
+ end
201
+ end
202
+
203
+ describe '#get' do
204
+ it 'returns the conflict hash for a known id' do
205
+ id = log.record(parties: parties, severity: severity, description: description)
206
+ result = log.get(id)
207
+ expect(result[:conflict_id]).to eq(id)
208
+ end
209
+
210
+ it 'returns nil for an unknown id' do
211
+ expect(log.get('missing-id')).to be_nil
212
+ end
213
+ end
214
+
215
+ describe '#count' do
216
+ it 'returns 0 for a new log' do
217
+ expect(log.count).to eq(0)
218
+ end
219
+
220
+ it 'returns the total number of recorded conflicts' do
221
+ 3.times { log.record(parties: parties, severity: :low, description: 'x') }
222
+ expect(log.count).to eq(3)
223
+ end
224
+
225
+ it 'counts resolved conflicts as well as active ones' do
226
+ id = log.record(parties: parties, severity: :low, description: 'resolve me')
227
+ log.resolve(id, outcome: :agreement)
228
+ log.record(parties: parties, severity: :high, description: 'still active')
229
+ expect(log.count).to eq(2)
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Social::Conflict::Helpers::LlmEnhancer do
4
+ describe '.available?' do
5
+ context 'when Legion::LLM is not defined' do
6
+ it 'returns a falsy value' do
7
+ # Legion::LLM is not defined in the test environment
8
+ expect(described_class.available?).to be_falsy
9
+ end
10
+ end
11
+
12
+ context 'when Legion::LLM is defined but not started' do
13
+ before do
14
+ stub_const('Legion::LLM', double(respond_to?: true, started?: false))
15
+ end
16
+
17
+ it 'returns false' do
18
+ expect(described_class.available?).to be false
19
+ end
20
+ end
21
+
22
+ context 'when Legion::LLM is started' do
23
+ before do
24
+ stub_const('Legion::LLM', double(respond_to?: true, started?: true))
25
+ end
26
+
27
+ it 'returns true' do
28
+ expect(described_class.available?).to be true
29
+ end
30
+ end
31
+
32
+ context 'when an error is raised' do
33
+ before do
34
+ stub_const('Legion::LLM', double)
35
+ allow(Legion::LLM).to receive(:respond_to?).and_raise(StandardError)
36
+ end
37
+
38
+ it 'returns false' do
39
+ expect(described_class.available?).to be false
40
+ end
41
+ end
42
+ end
43
+
44
+ describe '.suggest_resolution' do
45
+ let(:fake_response) do
46
+ double(content: <<~TEXT)
47
+ OUTCOME: resolved
48
+ NOTES: Both parties agreed to table the discussion for 48 hours. The agent will document its reasoning and the human partner will review before resuming.
49
+ TEXT
50
+ end
51
+ let(:fake_chat) { double }
52
+
53
+ before do
54
+ stub_const('Legion::LLM', double)
55
+ allow(Legion::LLM).to receive(:chat).and_return(fake_chat)
56
+ allow(fake_chat).to receive(:with_instructions)
57
+ allow(fake_chat).to receive(:ask).and_return(fake_response)
58
+ end
59
+
60
+ it 'returns resolution_notes and suggested_outcome' do
61
+ result = described_class.suggest_resolution(
62
+ description: 'Agent and human disagree on approach',
63
+ severity: :high,
64
+ exchanges: [{ speaker: :agent, message: 'I think we should proceed' },
65
+ { speaker: :human, message: 'I disagree with that approach' }]
66
+ )
67
+ expect(result).to be_a(Hash)
68
+ expect(result[:resolution_notes]).to be_a(String)
69
+ expect(result[:resolution_notes]).not_to be_empty
70
+ expect(result[:suggested_outcome]).to eq(:resolved)
71
+ end
72
+
73
+ it 'handles deferred outcome' do
74
+ allow(fake_chat).to receive(:ask).and_return(
75
+ double(content: "OUTCOME: deferred\nNOTES: Issue requires more context before resolution.")
76
+ )
77
+ result = described_class.suggest_resolution(
78
+ description: 'Ongoing concern', severity: :medium, exchanges: []
79
+ )
80
+ expect(result[:suggested_outcome]).to eq(:deferred)
81
+ end
82
+
83
+ it 'handles escalated outcome' do
84
+ allow(fake_chat).to receive(:ask).and_return(
85
+ double(content: "OUTCOME: escalated\nNOTES: Critical disagreement requires governance council review.")
86
+ )
87
+ result = described_class.suggest_resolution(
88
+ description: 'Safety concern', severity: :critical, exchanges: []
89
+ )
90
+ expect(result[:suggested_outcome]).to eq(:escalated)
91
+ end
92
+
93
+ context 'when LLM returns nil content' do
94
+ before { allow(fake_chat).to receive(:ask).and_return(double(content: nil)) }
95
+
96
+ it 'returns nil' do
97
+ result = described_class.suggest_resolution(
98
+ description: 'test', severity: :low, exchanges: []
99
+ )
100
+ expect(result).to be_nil
101
+ end
102
+ end
103
+
104
+ context 'when LLM raises an error' do
105
+ before { allow(fake_chat).to receive(:ask).and_raise(StandardError, 'LLM unavailable') }
106
+
107
+ it 'returns nil and logs a warning' do
108
+ expect(Legion::Logging).to receive(:warn).with(/suggest_resolution failed/)
109
+ result = described_class.suggest_resolution(
110
+ description: 'test', severity: :low, exchanges: []
111
+ )
112
+ expect(result).to be_nil
113
+ end
114
+ end
115
+ end
116
+
117
+ describe '.analyze_stale_conflict' do
118
+ let(:fake_response) do
119
+ double(content: <<~TEXT)
120
+ RECOMMENDATION: escalate
121
+ ANALYSIS: This conflict has persisted for 36 hours without progress. The critical severity and lack of exchanges suggests governance council intervention.
122
+ TEXT
123
+ end
124
+ let(:fake_chat) { double }
125
+
126
+ before do
127
+ stub_const('Legion::LLM', double)
128
+ allow(Legion::LLM).to receive(:chat).and_return(fake_chat)
129
+ allow(fake_chat).to receive(:with_instructions)
130
+ allow(fake_chat).to receive(:ask).and_return(fake_response)
131
+ end
132
+
133
+ it 'returns analysis and recommendation' do
134
+ result = described_class.analyze_stale_conflict(
135
+ description: 'Critical safety disagreement',
136
+ severity: :critical,
137
+ age_hours: 36.5,
138
+ exchange_count: 2
139
+ )
140
+ expect(result).to be_a(Hash)
141
+ expect(result[:analysis]).to be_a(String)
142
+ expect(result[:analysis]).not_to be_empty
143
+ expect(result[:recommendation]).to eq(:escalate)
144
+ end
145
+
146
+ it 'handles retry recommendation' do
147
+ allow(fake_chat).to receive(:ask).and_return(
148
+ double(content: "RECOMMENDATION: retry\nANALYSIS: The conflict may be resolvable with a fresh attempt after cooling off.")
149
+ )
150
+ result = described_class.analyze_stale_conflict(
151
+ description: 'Minor dispute', severity: :low, age_hours: 25.0, exchange_count: 1
152
+ )
153
+ expect(result[:recommendation]).to eq(:retry)
154
+ end
155
+
156
+ it 'handles close recommendation' do
157
+ allow(fake_chat).to receive(:ask).and_return(
158
+ double(content: "RECOMMENDATION: close\nANALYSIS: The issue is no longer relevant and can be safely closed.")
159
+ )
160
+ result = described_class.analyze_stale_conflict(
161
+ description: 'Old dispute', severity: :low, age_hours: 48.0, exchange_count: 0
162
+ )
163
+ expect(result[:recommendation]).to eq(:close)
164
+ end
165
+
166
+ context 'when LLM returns nil content' do
167
+ before { allow(fake_chat).to receive(:ask).and_return(double(content: nil)) }
168
+
169
+ it 'returns nil' do
170
+ result = described_class.analyze_stale_conflict(
171
+ description: 'test', severity: :low, age_hours: 25.0, exchange_count: 0
172
+ )
173
+ expect(result).to be_nil
174
+ end
175
+ end
176
+
177
+ context 'when LLM raises an error' do
178
+ before { allow(fake_chat).to receive(:ask).and_raise(StandardError, 'connection refused') }
179
+
180
+ it 'returns nil and logs a warning' do
181
+ expect(Legion::Logging).to receive(:warn).with(/analyze_stale_conflict failed/)
182
+ result = described_class.analyze_stale_conflict(
183
+ description: 'test', severity: :medium, age_hours: 30.0, exchange_count: 3
184
+ )
185
+ expect(result).to be_nil
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Social::Conflict::Helpers::Severity do
6
+ describe 'LEVELS' do
7
+ it 'is a frozen array of symbols' do
8
+ expect(described_class::LEVELS).to be_a(Array)
9
+ expect(described_class::LEVELS).to be_frozen
10
+ end
11
+
12
+ it 'contains exactly four levels' do
13
+ expect(described_class::LEVELS.size).to eq(4)
14
+ end
15
+
16
+ it 'includes :low' do
17
+ expect(described_class::LEVELS).to include(:low)
18
+ end
19
+
20
+ it 'includes :medium' do
21
+ expect(described_class::LEVELS).to include(:medium)
22
+ end
23
+
24
+ it 'includes :high' do
25
+ expect(described_class::LEVELS).to include(:high)
26
+ end
27
+
28
+ it 'includes :critical' do
29
+ expect(described_class::LEVELS).to include(:critical)
30
+ end
31
+
32
+ it 'is ordered from least to most severe' do
33
+ expect(described_class::LEVELS).to eq(%i[low medium high critical])
34
+ end
35
+ end
36
+
37
+ describe 'POSTURES' do
38
+ it 'is a frozen array of symbols' do
39
+ expect(described_class::POSTURES).to be_a(Array)
40
+ expect(described_class::POSTURES).to be_frozen
41
+ end
42
+
43
+ it 'contains exactly three postures' do
44
+ expect(described_class::POSTURES.size).to eq(3)
45
+ end
46
+
47
+ it 'includes :speak_once' do
48
+ expect(described_class::POSTURES).to include(:speak_once)
49
+ end
50
+
51
+ it 'includes :persistent_engagement' do
52
+ expect(described_class::POSTURES).to include(:persistent_engagement)
53
+ end
54
+
55
+ it 'includes :stubborn_presence' do
56
+ expect(described_class::POSTURES).to include(:stubborn_presence)
57
+ end
58
+ end
59
+
60
+ describe 'LEVEL_ORDER' do
61
+ it 'is a frozen hash' do
62
+ expect(described_class::LEVEL_ORDER).to be_a(Hash)
63
+ expect(described_class::LEVEL_ORDER).to be_frozen
64
+ end
65
+
66
+ it 'assigns :low the lowest numeric value' do
67
+ expect(described_class::LEVEL_ORDER[:low]).to eq(0)
68
+ end
69
+
70
+ it 'assigns :medium a higher value than :low' do
71
+ expect(described_class::LEVEL_ORDER[:medium]).to be > described_class::LEVEL_ORDER[:low]
72
+ end
73
+
74
+ it 'assigns :high a higher value than :medium' do
75
+ expect(described_class::LEVEL_ORDER[:high]).to be > described_class::LEVEL_ORDER[:medium]
76
+ end
77
+
78
+ it 'assigns :critical the highest numeric value' do
79
+ expect(described_class::LEVEL_ORDER[:critical]).to be > described_class::LEVEL_ORDER[:high]
80
+ end
81
+
82
+ it 'covers all LEVELS' do
83
+ described_class::LEVELS.each do |level|
84
+ expect(described_class::LEVEL_ORDER).to have_key(level)
85
+ end
86
+ end
87
+ end
88
+
89
+ describe '.valid_level?' do
90
+ it 'returns true for :low' do
91
+ expect(described_class.valid_level?(:low)).to be true
92
+ end
93
+
94
+ it 'returns true for :medium' do
95
+ expect(described_class.valid_level?(:medium)).to be true
96
+ end
97
+
98
+ it 'returns true for :high' do
99
+ expect(described_class.valid_level?(:high)).to be true
100
+ end
101
+
102
+ it 'returns true for :critical' do
103
+ expect(described_class.valid_level?(:critical)).to be true
104
+ end
105
+
106
+ it 'returns false for an unknown symbol' do
107
+ expect(described_class.valid_level?(:catastrophic)).to be false
108
+ end
109
+
110
+ it 'returns false for a string form of a valid level' do
111
+ expect(described_class.valid_level?('high')).to be false
112
+ end
113
+
114
+ it 'returns false for nil' do
115
+ expect(described_class.valid_level?(nil)).to be false
116
+ end
117
+
118
+ it 'returns true for every member of LEVELS' do
119
+ described_class::LEVELS.each do |level|
120
+ expect(described_class.valid_level?(level)).to be true
121
+ end
122
+ end
123
+ end
124
+
125
+ describe '.valid_posture?' do
126
+ it 'returns true for :speak_once' do
127
+ expect(described_class.valid_posture?(:speak_once)).to be true
128
+ end
129
+
130
+ it 'returns true for :persistent_engagement' do
131
+ expect(described_class.valid_posture?(:persistent_engagement)).to be true
132
+ end
133
+
134
+ it 'returns true for :stubborn_presence' do
135
+ expect(described_class.valid_posture?(:stubborn_presence)).to be true
136
+ end
137
+
138
+ it 'returns false for an unknown posture symbol' do
139
+ expect(described_class.valid_posture?(:passive)).to be false
140
+ end
141
+
142
+ it 'returns false for nil' do
143
+ expect(described_class.valid_posture?(nil)).to be false
144
+ end
145
+
146
+ it 'returns true for every member of POSTURES' do
147
+ described_class::POSTURES.each do |posture|
148
+ expect(described_class.valid_posture?(posture)).to be true
149
+ end
150
+ end
151
+ end
152
+
153
+ describe '.recommended_posture' do
154
+ it 'returns :stubborn_presence for :critical' do
155
+ expect(described_class.recommended_posture(:critical)).to eq(:stubborn_presence)
156
+ end
157
+
158
+ it 'returns :persistent_engagement for :high' do
159
+ expect(described_class.recommended_posture(:high)).to eq(:persistent_engagement)
160
+ end
161
+
162
+ it 'returns :speak_once for :medium' do
163
+ expect(described_class.recommended_posture(:medium)).to eq(:speak_once)
164
+ end
165
+
166
+ it 'returns :speak_once for :low' do
167
+ expect(described_class.recommended_posture(:low)).to eq(:speak_once)
168
+ end
169
+
170
+ it 'returns :speak_once for an unrecognized severity' do
171
+ expect(described_class.recommended_posture(:unknown)).to eq(:speak_once)
172
+ end
173
+
174
+ it 'returns a posture that is a member of POSTURES for every valid level' do
175
+ described_class::LEVELS.each do |level|
176
+ posture = described_class.recommended_posture(level)
177
+ expect(described_class::POSTURES).to include(posture)
178
+ end
179
+ end
180
+ end
181
+
182
+ describe '.severity_gte?' do
183
+ it 'returns true when left equals right' do
184
+ expect(described_class.severity_gte?(:medium, :medium)).to be true
185
+ end
186
+
187
+ it 'returns true when left is strictly greater' do
188
+ expect(described_class.severity_gte?(:high, :medium)).to be true
189
+ end
190
+
191
+ it 'returns false when left is strictly less' do
192
+ expect(described_class.severity_gte?(:low, :medium)).to be false
193
+ end
194
+
195
+ it ':critical >= :high is true' do
196
+ expect(described_class.severity_gte?(:critical, :high)).to be true
197
+ end
198
+
199
+ it ':critical >= :critical is true' do
200
+ expect(described_class.severity_gte?(:critical, :critical)).to be true
201
+ end
202
+
203
+ it ':low >= :critical is false' do
204
+ expect(described_class.severity_gte?(:low, :critical)).to be false
205
+ end
206
+
207
+ it 'treats an unknown left level as order 0 (same as :low)' do
208
+ expect(described_class.severity_gte?(:unknown, :medium)).to be false
209
+ end
210
+
211
+ it 'treats an unknown right level as order 0, so any valid level >= it' do
212
+ expect(described_class.severity_gte?(:low, :unknown)).to be true
213
+ end
214
+ end
215
+ end