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,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/social/conflict/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Social::Conflict::Runners::Conflict do
6
+ let(:client) { Legion::Extensions::Agentic::Social::Conflict::Client.new }
7
+
8
+ describe '#register_conflict' do
9
+ it 'creates a conflict with recommended posture' do
10
+ result = client.register_conflict(parties: %w[agent human], severity: :high, description: 'disagreement')
11
+ expect(result[:conflict_id]).to match(/\A[0-9a-f-]{36}\z/)
12
+ expect(result[:posture]).to eq(:persistent_engagement)
13
+ end
14
+
15
+ it 'recommends stubborn_presence for critical' do
16
+ result = client.register_conflict(parties: %w[agent human], severity: :critical, description: 'safety issue')
17
+ expect(result[:posture]).to eq(:stubborn_presence)
18
+ end
19
+
20
+ it 'rejects invalid severity' do
21
+ result = client.register_conflict(parties: [], severity: :invalid, description: 'test')
22
+ expect(result[:error]).to eq(:invalid_severity)
23
+ end
24
+ end
25
+
26
+ describe '#add_exchange' do
27
+ it 'records an exchange' do
28
+ c = client.register_conflict(parties: %w[a b], severity: :medium, description: 'test')
29
+ result = client.add_exchange(conflict_id: c[:conflict_id], speaker: 'a', message: 'I disagree')
30
+ expect(result[:recorded]).to be true
31
+ end
32
+ end
33
+
34
+ describe '#resolve_conflict' do
35
+ it 'resolves a conflict' do
36
+ c = client.register_conflict(parties: %w[a b], severity: :low, description: 'test')
37
+ result = client.resolve_conflict(conflict_id: c[:conflict_id], outcome: :compromise)
38
+ expect(result[:resolved]).to be true
39
+ end
40
+ end
41
+
42
+ describe '#active_conflicts' do
43
+ it 'lists active conflicts' do
44
+ client.register_conflict(parties: %w[a b], severity: :low, description: 'test')
45
+ result = client.active_conflicts
46
+ expect(result[:count]).to eq(1)
47
+ end
48
+ end
49
+
50
+ describe '#recommended_posture' do
51
+ it 'returns posture for severity' do
52
+ result = client.recommended_posture(severity: :critical)
53
+ expect(result[:posture]).to eq(:stubborn_presence)
54
+ end
55
+ end
56
+
57
+ describe '#check_stale_conflicts' do
58
+ it 'returns zero stale when no conflicts exist' do
59
+ result = client.check_stale_conflicts
60
+ expect(result[:checked]).to eq(0)
61
+ expect(result[:stale_count]).to eq(0)
62
+ expect(result[:stale_ids]).to eq([])
63
+ end
64
+
65
+ it 'returns zero stale for recently created conflicts' do
66
+ client.register_conflict(parties: %w[a b], severity: :low, description: 'fresh')
67
+ result = client.check_stale_conflicts
68
+ expect(result[:stale_count]).to eq(0)
69
+ end
70
+
71
+ it 'detects stale conflicts older than STALE_CONFLICT_TIMEOUT' do
72
+ c = client.register_conflict(parties: %w[a b], severity: :medium, description: 'old')
73
+ # Backdate the created_at timestamp
74
+ conflict = client.instance_variable_get(:@conflict_log).conflicts[c[:conflict_id]]
75
+ conflict[:created_at] = Time.now.utc - (Legion::Extensions::Agentic::Social::Conflict::Helpers::Severity::STALE_CONFLICT_TIMEOUT + 1)
76
+ result = client.check_stale_conflicts
77
+ expect(result[:stale_count]).to eq(1)
78
+ expect(result[:stale_ids]).to include(c[:conflict_id])
79
+ end
80
+
81
+ it 'does not include resolved conflicts in stale check' do
82
+ c = client.register_conflict(parties: %w[a b], severity: :low, description: 'resolved')
83
+ conflict = client.instance_variable_get(:@conflict_log).conflicts[c[:conflict_id]]
84
+ conflict[:created_at] = Time.now.utc - (Legion::Extensions::Agentic::Social::Conflict::Helpers::Severity::STALE_CONFLICT_TIMEOUT + 1)
85
+ client.resolve_conflict(conflict_id: c[:conflict_id], outcome: :closed)
86
+ result = client.check_stale_conflicts
87
+ expect(result[:stale_count]).to eq(0)
88
+ end
89
+
90
+ context 'with LLM available' do
91
+ let(:fake_chat) { double }
92
+ let(:fake_analysis_response) do
93
+ double(content: "RECOMMENDATION: escalate\nANALYSIS: The conflict has stalled and needs governance intervention.")
94
+ end
95
+
96
+ before do
97
+ stub_const('Legion::LLM', double(respond_to?: true, started?: true))
98
+ allow(Legion::LLM).to receive(:chat).and_return(fake_chat)
99
+ allow(fake_chat).to receive(:with_instructions)
100
+ allow(fake_chat).to receive(:ask).and_return(fake_analysis_response)
101
+ end
102
+
103
+ it 'includes LLM analysis in the stale system exchange message' do
104
+ c = client.register_conflict(parties: %w[a b], severity: :high, description: 'ongoing issue')
105
+ conflict = client.instance_variable_get(:@conflict_log).conflicts[c[:conflict_id]]
106
+ conflict[:created_at] = Time.now.utc - (Legion::Extensions::Agentic::Social::Conflict::Helpers::Severity::STALE_CONFLICT_TIMEOUT + 1)
107
+
108
+ client.check_stale_conflicts
109
+
110
+ exchanges = client.instance_variable_get(:@conflict_log).conflicts[c[:conflict_id]][:exchanges]
111
+ expect(exchanges).not_to be_empty
112
+ system_msg = exchanges.find { |e| e[:speaker] == :system }
113
+ expect(system_msg).not_to be_nil
114
+ expect(system_msg[:message]).to include('governance intervention')
115
+ end
116
+ end
117
+ end
118
+
119
+ describe '#resolve_conflict with LLM' do
120
+ let(:fake_chat) { double }
121
+ let(:fake_resolution_response) do
122
+ double(content: <<~TEXT)
123
+ OUTCOME: resolved
124
+ NOTES: Both parties reached a compromise after reviewing the evidence. Next steps include documenting the outcome and monitoring for recurrence.
125
+ TEXT
126
+ end
127
+
128
+ before do
129
+ stub_const('Legion::LLM', double(respond_to?: true, started?: true))
130
+ allow(Legion::LLM).to receive(:chat).and_return(fake_chat)
131
+ allow(fake_chat).to receive(:with_instructions)
132
+ allow(fake_chat).to receive(:ask).and_return(fake_resolution_response)
133
+ end
134
+
135
+ it 'uses LLM-generated notes when no resolution_notes provided' do
136
+ c = client.register_conflict(parties: %w[a b], severity: :medium, description: 'test conflict')
137
+ client.resolve_conflict(conflict_id: c[:conflict_id], outcome: :compromise)
138
+
139
+ conflict = client.instance_variable_get(:@conflict_log).conflicts[c[:conflict_id]]
140
+ expect(conflict[:resolution_notes]).to include('compromise')
141
+ end
142
+
143
+ it 'preserves caller-provided resolution_notes over LLM' do
144
+ c = client.register_conflict(parties: %w[a b], severity: :medium, description: 'test conflict')
145
+ client.resolve_conflict(conflict_id: c[:conflict_id], outcome: :compromise, resolution_notes: 'manual notes')
146
+
147
+ conflict = client.instance_variable_get(:@conflict_log).conflicts[c[:conflict_id]]
148
+ expect(conflict[:resolution_notes]).to eq('manual notes')
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Social::Conscience::Client do
6
+ describe '#initialize' do
7
+ it 'creates a default moral store' do
8
+ client = described_class.new
9
+ expect(client.moral_store).to be_a(Legion::Extensions::Agentic::Social::Conscience::Helpers::MoralStore)
10
+ end
11
+
12
+ it 'accepts an injected moral store' do
13
+ store = Legion::Extensions::Agentic::Social::Conscience::Helpers::MoralStore.new
14
+ client = described_class.new(moral_store: store)
15
+ expect(client.moral_store).to be(store)
16
+ end
17
+
18
+ it 'ignores unknown kwargs' do
19
+ expect { described_class.new(unknown: true, other: 42) }.not_to raise_error
20
+ end
21
+ end
22
+
23
+ describe 'runner integration' do
24
+ let(:client) { described_class.new }
25
+
26
+ it { expect(client).to respond_to(:moral_evaluate) }
27
+ it { expect(client).to respond_to(:moral_status) }
28
+ it { expect(client).to respond_to(:moral_history) }
29
+ it { expect(client).to respond_to(:update_moral_outcome) }
30
+ it { expect(client).to respond_to(:moral_dilemmas) }
31
+ it { expect(client).to respond_to(:conscience_stats) }
32
+ end
33
+
34
+ describe 'shared state' do
35
+ it 'accumulates evaluations across multiple calls' do
36
+ client = described_class.new
37
+ context = { benefit_to_others: 0.5, harm_to_others: 0.0, consent_present: true }
38
+ 7.times { client.moral_evaluate(action: :test, context: context) }
39
+ expect(client.moral_history[:total]).to eq(7)
40
+ end
41
+
42
+ it 'uses the same moral store for evaluations and status' do
43
+ client = described_class.new
44
+ client.moral_evaluate(action: :test, context: { benefit_to_others: 0.5 })
45
+ status = client.moral_status
46
+ expect(status[:stats][:total_evaluations]).to eq(1)
47
+ end
48
+ end
49
+
50
+ describe 'injected store passthrough' do
51
+ it 'uses the injected store for evaluations' do
52
+ store = Legion::Extensions::Agentic::Social::Conscience::Helpers::MoralStore.new
53
+ client = described_class.new(moral_store: store)
54
+ client.moral_evaluate(action: :test, context: {})
55
+ expect(store.history.size).to eq(1)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Social::Conscience::Helpers::Constants do
6
+ describe 'MORAL_FOUNDATIONS' do
7
+ it 'defines 6 foundations' do
8
+ expect(described_class::MORAL_FOUNDATIONS.size).to eq(6)
9
+ end
10
+
11
+ it 'has weights summing to 1.0' do
12
+ total = described_class::MORAL_FOUNDATIONS.values.sum { |v| v[:weight] }
13
+ expect(total).to be_within(0.001).of(1.0)
14
+ end
15
+
16
+ it 'includes all 6 Haidt foundations' do
17
+ expect(described_class::MORAL_FOUNDATIONS.keys).to contain_exactly(
18
+ :care, :fairness, :loyalty, :authority, :sanctity, :liberty
19
+ )
20
+ end
21
+
22
+ it 'gives care the highest weight (0.25)' do
23
+ expect(described_class::MORAL_FOUNDATIONS[:care][:weight]).to eq(0.25)
24
+ end
25
+
26
+ it 'gives fairness the second highest weight (0.20)' do
27
+ expect(described_class::MORAL_FOUNDATIONS[:fairness][:weight]).to eq(0.20)
28
+ end
29
+
30
+ it 'gives liberty the lowest weight (0.10)' do
31
+ expect(described_class::MORAL_FOUNDATIONS[:liberty][:weight]).to eq(0.10)
32
+ end
33
+
34
+ it 'is frozen' do
35
+ expect(described_class::MORAL_FOUNDATIONS).to be_frozen
36
+ end
37
+
38
+ it 'each foundation has a description' do
39
+ described_class::MORAL_FOUNDATIONS.each_value do |config|
40
+ expect(config[:description]).to be_a(String)
41
+ expect(config[:description]).not_to be_empty
42
+ end
43
+ end
44
+ end
45
+
46
+ describe 'MORAL_VERDICTS' do
47
+ it 'defines 4 verdicts' do
48
+ expect(described_class::MORAL_VERDICTS.size).to eq(4)
49
+ end
50
+
51
+ it 'includes permitted, cautioned, conflicted, prohibited' do
52
+ expect(described_class::MORAL_VERDICTS).to include(:permitted, :cautioned, :conflicted, :prohibited)
53
+ end
54
+
55
+ it 'is frozen' do
56
+ expect(described_class::MORAL_VERDICTS).to be_frozen
57
+ end
58
+ end
59
+
60
+ describe 'FOUNDATION_ALPHA' do
61
+ it 'is 0.05 (very slow adaptation)' do
62
+ expect(described_class::FOUNDATION_ALPHA).to eq(0.05)
63
+ end
64
+
65
+ it 'is less than 0.1' do
66
+ expect(described_class::FOUNDATION_ALPHA).to be < 0.1
67
+ end
68
+ end
69
+
70
+ describe 'CONFLICT_THRESHOLD' do
71
+ it 'is 0.3' do
72
+ expect(described_class::CONFLICT_THRESHOLD).to eq(0.3)
73
+ end
74
+ end
75
+
76
+ describe 'PROHIBITION_THRESHOLD' do
77
+ it 'is -0.5' do
78
+ expect(described_class::PROHIBITION_THRESHOLD).to eq(-0.5)
79
+ end
80
+ end
81
+
82
+ describe 'CAUTION_THRESHOLD' do
83
+ it 'is -0.1' do
84
+ expect(described_class::CAUTION_THRESHOLD).to eq(-0.1)
85
+ end
86
+
87
+ it 'is greater than PROHIBITION_THRESHOLD' do
88
+ expect(described_class::CAUTION_THRESHOLD).to be > described_class::PROHIBITION_THRESHOLD
89
+ end
90
+ end
91
+
92
+ describe 'MAX_MORAL_HISTORY' do
93
+ it 'caps at 100' do
94
+ expect(described_class::MAX_MORAL_HISTORY).to eq(100)
95
+ end
96
+ end
97
+
98
+ describe 'DILEMMA_TYPES' do
99
+ it 'defines 3 dilemma types' do
100
+ expect(described_class::DILEMMA_TYPES.size).to eq(3)
101
+ end
102
+
103
+ it 'includes utilitarian, deontological, virtue_ethics' do
104
+ expect(described_class::DILEMMA_TYPES).to include(:utilitarian, :deontological, :virtue_ethics)
105
+ end
106
+
107
+ it 'is frozen' do
108
+ expect(described_class::DILEMMA_TYPES).to be_frozen
109
+ end
110
+ end
111
+
112
+ describe 'INITIAL_SENSITIVITY' do
113
+ it 'is 1.0 (fully sensitive at boot, decays through experience)' do
114
+ expect(described_class::INITIAL_SENSITIVITY).to eq(1.0)
115
+ end
116
+ end
117
+
118
+ describe 'MORAL_SCORE_RANGE' do
119
+ it 'spans -1.0 to 1.0' do
120
+ expect(described_class::MORAL_SCORE_RANGE[:min]).to eq(-1.0)
121
+ expect(described_class::MORAL_SCORE_RANGE[:max]).to eq(1.0)
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,253 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Social::Conscience::Helpers::MoralEvaluator do
6
+ subject(:evaluator) { described_class.new }
7
+
8
+ let(:benign_context) do
9
+ {
10
+ harm_to_others: 0.0,
11
+ benefit_to_others: 0.8,
12
+ vulnerable_affected: false,
13
+ distributional_justice: 0.7,
14
+ reciprocity: 0.6,
15
+ proportionality: 0.5,
16
+ alignment_with_group_norms: 0.6,
17
+ trust_preservation: 0.7,
18
+ legitimate_authority_compliance: 0.5,
19
+ hierarchy_respect: 0.4,
20
+ system_integrity: 0.8,
21
+ degradation_prevention: 0.7,
22
+ autonomy_preservation: 0.6,
23
+ consent_present: true
24
+ }
25
+ end
26
+
27
+ let(:harmful_context) do
28
+ {
29
+ harm_to_others: 0.9,
30
+ benefit_to_others: 0.0,
31
+ vulnerable_affected: true,
32
+ distributional_justice: -0.8,
33
+ reciprocity: -0.7,
34
+ proportionality: -0.6,
35
+ alignment_with_group_norms: -0.5,
36
+ trust_preservation: -0.8,
37
+ legitimate_authority_compliance: -0.6,
38
+ hierarchy_respect: -0.5,
39
+ system_integrity: -0.9,
40
+ degradation_prevention: -0.8,
41
+ autonomy_preservation: -0.7,
42
+ consent_present: false
43
+ }
44
+ end
45
+
46
+ let(:conflicted_context) do
47
+ {
48
+ # Care says yes (benefit present, low harm)
49
+ harm_to_others: 0.0,
50
+ benefit_to_others: 0.8,
51
+ # Fairness says no (unjust distribution)
52
+ distributional_justice: -0.7,
53
+ reciprocity: -0.6,
54
+ proportionality: -0.5,
55
+ # Authority says no (violates hierarchy)
56
+ legitimate_authority_compliance: -0.8,
57
+ hierarchy_respect: -0.6
58
+ }
59
+ end
60
+
61
+ describe '#initialize' do
62
+ it 'sets initial sensitivity to 1.0 for all foundations' do
63
+ evaluator.sensitivities.each_value do |s|
64
+ expect(s).to eq(1.0)
65
+ end
66
+ end
67
+
68
+ it 'has a sensitivity for each of the 6 foundations' do
69
+ expect(evaluator.sensitivities.keys).to contain_exactly(
70
+ :care, :fairness, :loyalty, :authority, :sanctity, :liberty
71
+ )
72
+ end
73
+ end
74
+
75
+ describe '#evaluate' do
76
+ it 'returns a hash with required keys' do
77
+ result = evaluator.evaluate(action: :deploy_patch, context: benign_context)
78
+ expect(result).to include(:action, :scores, :weighted_score, :verdict, :dilemma, :sensitivities, :evaluated_at)
79
+ end
80
+
81
+ it 'returns per-foundation scores for all 6 foundations' do
82
+ result = evaluator.evaluate(action: :deploy_patch, context: benign_context)
83
+ expect(result[:scores].keys).to contain_exactly(:care, :fairness, :loyalty, :authority, :sanctity, :liberty)
84
+ end
85
+
86
+ it 'gives a positive weighted_score for benign context' do
87
+ result = evaluator.evaluate(action: :deploy_patch, context: benign_context)
88
+ expect(result[:weighted_score]).to be > 0.0
89
+ end
90
+
91
+ it 'gives a negative weighted_score for harmful context' do
92
+ result = evaluator.evaluate(action: :delete_data, context: harmful_context)
93
+ expect(result[:weighted_score]).to be < 0.0
94
+ end
95
+
96
+ it 'returns :permitted verdict for benign context' do
97
+ result = evaluator.evaluate(action: :deploy_patch, context: benign_context)
98
+ expect(result[:verdict]).to eq(:permitted)
99
+ end
100
+
101
+ it 'returns :prohibited verdict for harmful context' do
102
+ result = evaluator.evaluate(action: :delete_data, context: harmful_context)
103
+ expect(result[:verdict]).to eq(:prohibited)
104
+ end
105
+
106
+ it 'weighted_score is within [-1.0, 1.0]' do
107
+ result = evaluator.evaluate(action: :anything, context: harmful_context)
108
+ expect(result[:weighted_score]).to be_between(-1.0, 1.0)
109
+ end
110
+
111
+ it 'includes evaluated_at timestamp' do
112
+ result = evaluator.evaluate(action: :test, context: {})
113
+ expect(result[:evaluated_at]).to be_a(Time)
114
+ end
115
+
116
+ it 'handles empty context without error' do
117
+ expect { evaluator.evaluate(action: :test, context: {}) }.not_to raise_error
118
+ end
119
+ end
120
+
121
+ describe '#weighted_score' do
122
+ it 'returns a float in [-1.0, 1.0]' do
123
+ scores = { care: 0.5, fairness: 0.3, loyalty: 0.2, authority: 0.1, sanctity: 0.4, liberty: 0.6 }
124
+ score = evaluator.weighted_score(scores)
125
+ expect(score).to be_a(Float)
126
+ expect(score).to be_between(-1.0, 1.0)
127
+ end
128
+
129
+ it 'returns 0.0 for all-zero scores' do
130
+ scores = { care: 0.0, fairness: 0.0, loyalty: 0.0, authority: 0.0, sanctity: 0.0, liberty: 0.0 }
131
+ expect(evaluator.weighted_score(scores)).to eq(0.0)
132
+ end
133
+
134
+ it 'returns a positive value when all scores are positive' do
135
+ scores = { care: 1.0, fairness: 1.0, loyalty: 1.0, authority: 1.0, sanctity: 1.0, liberty: 1.0 }
136
+ expect(evaluator.weighted_score(scores)).to be > 0.0
137
+ end
138
+ end
139
+
140
+ describe '#verdict' do
141
+ it 'returns :prohibited for score below -0.5' do
142
+ expect(evaluator.verdict(-0.6)).to eq(:prohibited)
143
+ end
144
+
145
+ it 'returns :prohibited for score at exactly -0.5' do
146
+ expect(evaluator.verdict(-0.5)).to eq(:prohibited)
147
+ end
148
+
149
+ it 'returns :cautioned for score between -0.5 and -0.1' do
150
+ expect(evaluator.verdict(-0.3)).to eq(:cautioned)
151
+ end
152
+
153
+ it 'returns :permitted for score above -0.1' do
154
+ expect(evaluator.verdict(0.0)).to eq(:permitted)
155
+ end
156
+
157
+ it 'returns :permitted for positive scores' do
158
+ expect(evaluator.verdict(0.5)).to eq(:permitted)
159
+ end
160
+ end
161
+
162
+ describe '#detect_dilemma' do
163
+ it 'returns nil when no conflict present' do
164
+ scores = { care: 0.5, fairness: 0.4, loyalty: 0.3, authority: 0.2, sanctity: 0.3, liberty: 0.4 }
165
+ expect(evaluator.detect_dilemma(scores)).to be_nil
166
+ end
167
+
168
+ it 'returns nil when all foundations agree negatively' do
169
+ scores = { care: -0.8, fairness: -0.7, loyalty: -0.6, authority: -0.5, sanctity: -0.6, liberty: -0.5 }
170
+ expect(evaluator.detect_dilemma(scores)).to be_nil
171
+ end
172
+
173
+ it 'returns a dilemma hash when care and fairness strongly disagree' do
174
+ scores = { care: 0.9, fairness: -0.8, loyalty: 0.1, authority: 0.1, sanctity: 0.1, liberty: 0.1 }
175
+ dilemma = evaluator.detect_dilemma(scores)
176
+ expect(dilemma).not_to be_nil
177
+ expect(dilemma).to include(:type, :approving, :opposing, :tension, :counter_tension, :detected_at)
178
+ end
179
+
180
+ it 'classifies care vs fairness conflict as :utilitarian' do
181
+ scores = { care: 0.9, fairness: -0.8, loyalty: 0.0, authority: 0.0, sanctity: 0.0, liberty: 0.0 }
182
+ dilemma = evaluator.detect_dilemma(scores)
183
+ expect(dilemma[:type]).to eq(:utilitarian)
184
+ end
185
+
186
+ it 'classifies authority conflict as :deontological' do
187
+ scores = { care: 0.0, fairness: 0.0, loyalty: 0.5, authority: -0.8, sanctity: 0.5, liberty: 0.0 }
188
+ dilemma = evaluator.detect_dilemma(scores)
189
+ expect(dilemma[:type]).to eq(:deontological)
190
+ end
191
+
192
+ it 'classifies loyalty vs sanctity conflict as :virtue_ethics' do
193
+ scores = { care: 0.0, fairness: 0.0, loyalty: 0.8, authority: 0.0, sanctity: -0.7, liberty: 0.0 }
194
+ dilemma = evaluator.detect_dilemma(scores)
195
+ expect(dilemma[:type]).to eq(:virtue_ethics)
196
+ end
197
+
198
+ it 'lists approving and opposing foundations' do
199
+ scores = { care: 0.9, fairness: -0.8, loyalty: 0.0, authority: 0.0, sanctity: 0.0, liberty: 0.0 }
200
+ dilemma = evaluator.detect_dilemma(scores)
201
+ expect(dilemma[:approving]).to include(:care)
202
+ expect(dilemma[:opposing]).to include(:fairness)
203
+ end
204
+ end
205
+
206
+ describe '#update_sensitivity' do
207
+ it 'adjusts sensitivity for a known foundation' do
208
+ original = evaluator.sensitivities[:care]
209
+ evaluator.update_sensitivity(:care, 0.9)
210
+ expect(evaluator.sensitivities[:care]).not_to eq(original)
211
+ end
212
+
213
+ it 'changes sensitivity by a small amount (slow EMA)' do
214
+ original = evaluator.sensitivities[:care]
215
+ evaluator.update_sensitivity(:care, 1.0)
216
+ diff = (evaluator.sensitivities[:care] - original).abs
217
+ expect(diff).to be <= Legion::Extensions::Agentic::Social::Conscience::Helpers::Constants::FOUNDATION_ALPHA + 0.001
218
+ end
219
+
220
+ it 'ignores unknown foundations without error' do
221
+ expect { evaluator.update_sensitivity(:unknown_foundation, 0.5) }.not_to raise_error
222
+ end
223
+
224
+ it 'clamps outcome magnitude to [0.0, 1.0] when updating' do
225
+ expect { evaluator.update_sensitivity(:care, 5.0) }.not_to raise_error
226
+ expect(evaluator.sensitivities[:care]).to be_between(0.0, 1.0)
227
+ end
228
+ end
229
+
230
+ describe 'foundation-specific evaluation' do
231
+ it 'scores care positively when benefit_to_others is high' do
232
+ result = evaluator.evaluate(action: :help, context: { benefit_to_others: 1.0, harm_to_others: 0.0 })
233
+ expect(result[:scores][:care]).to be > 0.0
234
+ end
235
+
236
+ it 'scores care negatively when harm_to_others is high' do
237
+ result = evaluator.evaluate(action: :harm, context: { harm_to_others: 1.0, benefit_to_others: 0.0 })
238
+ expect(result[:scores][:care]).to be < 0.0
239
+ end
240
+
241
+ it 'penalizes care further when vulnerable_affected is true' do
242
+ without_vuln = evaluator.evaluate(action: :act, context: { harm_to_others: 0.5, vulnerable_affected: false })
243
+ with_vuln = evaluator.evaluate(action: :act, context: { harm_to_others: 0.5, vulnerable_affected: true })
244
+ expect(with_vuln[:scores][:care]).to be < without_vuln[:scores][:care]
245
+ end
246
+
247
+ it 'scores liberty positively when consent_present is true' do
248
+ with_consent = evaluator.evaluate(action: :act, context: { consent_present: true, autonomy_preservation: 0.5 })
249
+ without_consent = evaluator.evaluate(action: :act, context: { consent_present: false, autonomy_preservation: 0.5 })
250
+ expect(with_consent[:scores][:liberty]).to be > without_consent[:scores][:liberty]
251
+ end
252
+ end
253
+ end