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,266 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::MoralEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ let(:options) do
7
+ [
8
+ { id: 'opt_a', description: 'Help the many', foundations: %i[care fairness] },
9
+ { id: 'opt_b', description: 'Follow the rule', foundations: %i[authority loyalty] }
10
+ ]
11
+ end
12
+
13
+ describe '#initialize' do
14
+ it 'starts at :social_contract stage' do
15
+ expect(engine.stage).to eq(:social_contract)
16
+ end
17
+
18
+ it 'initializes all moral foundations' do
19
+ profile = engine.foundation_profile
20
+ Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::Constants::MORAL_FOUNDATIONS.each do |f|
21
+ expect(profile).to have_key(f)
22
+ end
23
+ end
24
+
25
+ it 'starts with empty dilemmas and principles' do
26
+ expect(engine.dilemmas).to be_empty
27
+ expect(engine.principles).to be_empty
28
+ end
29
+ end
30
+
31
+ describe '#evaluate_action' do
32
+ it 'returns a score for an action' do
33
+ result = engine.evaluate_action(action: 'help_stranger', affected_foundations: %i[care fairness])
34
+ expect(result[:score]).to be_a(Float)
35
+ expect(result[:score]).to be >= 0.0
36
+ end
37
+
38
+ it 'returns 0.0 for empty foundations' do
39
+ result = engine.evaluate_action(action: 'noop', affected_foundations: [])
40
+ expect(result[:score]).to eq(0.0)
41
+ end
42
+
43
+ it 'includes action and domain in result' do
44
+ result = engine.evaluate_action(action: 'report', affected_foundations: %i[loyalty], domain: :workplace)
45
+ expect(result[:action]).to eq('report')
46
+ expect(result[:domain]).to eq(:workplace)
47
+ end
48
+ end
49
+
50
+ describe '#pose_dilemma' do
51
+ it 'creates a dilemma and returns success' do
52
+ result = engine.pose_dilemma(description: 'Trolley problem', options: options)
53
+ expect(result[:success]).to be true
54
+ expect(result[:dilemma][:description]).to eq('Trolley problem')
55
+ end
56
+
57
+ it 'assigns a unique id' do
58
+ r1 = engine.pose_dilemma(description: 'A', options: options)
59
+ r2 = engine.pose_dilemma(description: 'B', options: options)
60
+ expect(r1[:dilemma][:id]).not_to eq(r2[:dilemma][:id])
61
+ end
62
+
63
+ it 'stores the dilemma in @dilemmas' do
64
+ engine.pose_dilemma(description: 'Test', options: options)
65
+ expect(engine.dilemmas).not_to be_empty
66
+ end
67
+
68
+ it 'returns failure when max dilemmas reached' do
69
+ Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::Constants::MAX_DILEMMAS.times do |i|
70
+ engine.pose_dilemma(description: "Dilemma #{i}", options: options)
71
+ end
72
+ result = engine.pose_dilemma(description: 'One more', options: options)
73
+ expect(result[:success]).to be false
74
+ expect(result[:reason]).to eq(:max_dilemmas_reached)
75
+ end
76
+ end
77
+
78
+ describe '#resolve_dilemma' do
79
+ let(:dilemma_id) do
80
+ engine.pose_dilemma(description: 'Test dilemma', options: options)[:dilemma][:id]
81
+ end
82
+
83
+ it 'resolves an existing dilemma' do
84
+ result = engine.resolve_dilemma(
85
+ dilemma_id: dilemma_id, option_id: 'opt_a',
86
+ reasoning: 'Most good', framework: :utilitarian
87
+ )
88
+ expect(result[:success]).to be true
89
+ expect(result[:dilemma][:resolved]).to be true
90
+ end
91
+
92
+ it 'returns failure for unknown dilemma_id' do
93
+ result = engine.resolve_dilemma(
94
+ dilemma_id: 'nonexistent', option_id: 'opt_a',
95
+ reasoning: 'N/A', framework: :utilitarian
96
+ )
97
+ expect(result[:success]).to be false
98
+ expect(result[:reason]).to eq(:not_found)
99
+ end
100
+
101
+ it 'returns failure for invalid option_id' do
102
+ result = engine.resolve_dilemma(
103
+ dilemma_id: dilemma_id, option_id: 'bad_opt',
104
+ reasoning: 'N/A', framework: :utilitarian
105
+ )
106
+ expect(result[:success]).to be false
107
+ expect(result[:reason]).to eq(:invalid_option)
108
+ end
109
+
110
+ it 'returns failure if already resolved' do
111
+ engine.resolve_dilemma(
112
+ dilemma_id: dilemma_id, option_id: 'opt_a',
113
+ reasoning: 'First', framework: :utilitarian
114
+ )
115
+ result = engine.resolve_dilemma(
116
+ dilemma_id: dilemma_id, option_id: 'opt_b',
117
+ reasoning: 'Second', framework: :deontological
118
+ )
119
+ expect(result[:success]).to be false
120
+ expect(result[:reason]).to eq(:already_resolved)
121
+ end
122
+ end
123
+
124
+ describe '#apply_framework' do
125
+ let(:dilemma_id) do
126
+ engine.pose_dilemma(description: 'Framework test', options: options)[:dilemma][:id]
127
+ end
128
+
129
+ it 'returns rankings for a valid framework' do
130
+ result = engine.apply_framework(dilemma_id: dilemma_id, framework: :utilitarian)
131
+ expect(result[:success]).to be true
132
+ expect(result[:rankings]).to be_an(Array)
133
+ expect(result[:rankings].size).to eq(2)
134
+ end
135
+
136
+ it 'returns failure for unknown framework' do
137
+ result = engine.apply_framework(dilemma_id: dilemma_id, framework: :made_up)
138
+ expect(result[:success]).to be false
139
+ expect(result[:reason]).to eq(:unknown_framework)
140
+ end
141
+
142
+ it 'returns failure for unknown dilemma' do
143
+ result = engine.apply_framework(dilemma_id: 'nope', framework: :utilitarian)
144
+ expect(result[:success]).to be false
145
+ expect(result[:reason]).to eq(:not_found)
146
+ end
147
+
148
+ Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::Constants::ETHICAL_FRAMEWORKS.each do |framework|
149
+ it "applies #{framework} framework" do
150
+ result = engine.apply_framework(dilemma_id: dilemma_id, framework: framework)
151
+ expect(result[:success]).to be true
152
+ expect(result[:framework]).to eq(framework)
153
+ end
154
+ end
155
+ end
156
+
157
+ describe '#add_principle' do
158
+ it 'adds a custom moral principle' do
159
+ result = engine.add_principle(
160
+ name: 'Do no harm',
161
+ description: 'Avoid causing harm to others',
162
+ foundation: :care
163
+ )
164
+ expect(result[:success]).to be true
165
+ expect(result[:principle][:name]).to eq('Do no harm')
166
+ end
167
+
168
+ it 'returns failure for unknown foundation' do
169
+ result = engine.add_principle(name: 'Test', description: 'Test', foundation: :made_up)
170
+ expect(result[:success]).to be false
171
+ expect(result[:reason]).to eq(:unknown_foundation)
172
+ end
173
+
174
+ it 'returns failure when max principles reached' do
175
+ Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::Constants::MAX_PRINCIPLES.times do |i|
176
+ engine.add_principle(name: "P#{i}", description: "Desc #{i}", foundation: :care)
177
+ end
178
+ result = engine.add_principle(name: 'One more', description: 'Extra', foundation: :care)
179
+ expect(result[:success]).to be false
180
+ expect(result[:reason]).to eq(:max_principles_reached)
181
+ end
182
+ end
183
+
184
+ describe '#moral_development' do
185
+ context 'with insufficient resolved dilemmas' do
186
+ it 'does not advance stage' do
187
+ result = engine.moral_development
188
+ expect(result[:advanced]).to be false
189
+ end
190
+ end
191
+
192
+ context 'with enough high-severity resolved dilemmas' do
193
+ before do
194
+ 5.times do |i|
195
+ r = engine.pose_dilemma(description: "D#{i}", options: options, severity: 0.8)
196
+ engine.resolve_dilemma(
197
+ dilemma_id: r[:dilemma][:id], option_id: 'opt_a',
198
+ reasoning: 'Because', framework: :utilitarian
199
+ )
200
+ end
201
+ end
202
+
203
+ it 'advances to the next Kohlberg stage' do
204
+ result = engine.moral_development
205
+ expect(result[:advanced]).to be true
206
+ expect(result[:stage]).not_to eq(:social_contract)
207
+ end
208
+ end
209
+ end
210
+
211
+ describe '#foundation_profile' do
212
+ it 'returns a hash keyed by foundation ids' do
213
+ profile = engine.foundation_profile
214
+ expect(profile.keys).to match_array(Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::Constants::MORAL_FOUNDATIONS)
215
+ end
216
+ end
217
+
218
+ describe '#stage_info' do
219
+ it 'returns stage, level, and description' do
220
+ info = engine.stage_info
221
+ expect(info).to include(:stage, :level, :description)
222
+ expect(info[:stage]).to eq(:social_contract)
223
+ expect(info[:level]).to eq(:postconventional)
224
+ end
225
+ end
226
+
227
+ describe '#unresolved_dilemmas / #resolved_dilemmas' do
228
+ before do
229
+ engine.pose_dilemma(description: 'Unresolved', options: options)
230
+ r = engine.pose_dilemma(description: 'Resolved', options: options)
231
+ engine.resolve_dilemma(
232
+ dilemma_id: r[:dilemma][:id], option_id: 'opt_a',
233
+ reasoning: 'Because', framework: :utilitarian
234
+ )
235
+ end
236
+
237
+ it 'returns only unresolved dilemmas' do
238
+ expect(engine.unresolved_dilemmas.size).to eq(1)
239
+ expect(engine.unresolved_dilemmas.first.resolved?).to be false
240
+ end
241
+
242
+ it 'returns only resolved dilemmas' do
243
+ expect(engine.resolved_dilemmas.size).to eq(1)
244
+ expect(engine.resolved_dilemmas.first.resolved?).to be true
245
+ end
246
+ end
247
+
248
+ describe '#decay_all' do
249
+ it 'decreases foundation weights' do
250
+ profile_before = engine.foundation_profile.transform_values { |f| f[:weight] }
251
+ engine.decay_all
252
+ profile_after = engine.foundation_profile.transform_values { |f| f[:weight] }
253
+ profile_before.each do |fid, before_weight|
254
+ expect(profile_after[fid]).to be <= before_weight
255
+ end
256
+ end
257
+ end
258
+
259
+ describe '#to_h' do
260
+ it 'returns a stats summary hash' do
261
+ h = engine.to_h
262
+ expect(h).to include(:stage, :total_dilemmas, :resolved_dilemmas,
263
+ :unresolved_dilemmas, :principles, :foundation_profile)
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::MoralFoundation do
4
+ subject(:foundation) { described_class.new(id: :care) }
5
+
6
+ describe '#initialize' do
7
+ it 'sets id' do
8
+ expect(foundation.id).to eq(:care)
9
+ end
10
+
11
+ it 'defaults weight to DEFAULT_WEIGHT' do
12
+ expect(foundation.weight).to eq(Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::Constants::DEFAULT_WEIGHT)
13
+ end
14
+
15
+ it 'clamps weight to WEIGHT_FLOOR if below floor' do
16
+ f = described_class.new(id: :care, weight: 0.0)
17
+ expect(f.weight).to eq(Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::Constants::WEIGHT_FLOOR)
18
+ end
19
+
20
+ it 'clamps weight to WEIGHT_CEILING if above ceiling' do
21
+ f = described_class.new(id: :care, weight: 2.0)
22
+ expect(f.weight).to eq(Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::Constants::WEIGHT_CEILING)
23
+ end
24
+ end
25
+
26
+ describe '#reinforce' do
27
+ it 'increases weight' do
28
+ before = foundation.weight
29
+ foundation.reinforce(amount: 1.0)
30
+ expect(foundation.weight).to be > before
31
+ end
32
+
33
+ it 'does not exceed WEIGHT_CEILING' do
34
+ 10.times { foundation.reinforce(amount: 10.0) }
35
+ expect(foundation.weight).to eq(Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::Constants::WEIGHT_CEILING)
36
+ end
37
+ end
38
+
39
+ describe '#weaken' do
40
+ it 'decreases weight' do
41
+ before = foundation.weight
42
+ foundation.weaken(amount: 1.0)
43
+ expect(foundation.weight).to be < before
44
+ end
45
+
46
+ it 'does not go below WEIGHT_FLOOR' do
47
+ 10.times { foundation.weaken(amount: 10.0) }
48
+ expect(foundation.weight).to eq(Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::Constants::WEIGHT_FLOOR)
49
+ end
50
+ end
51
+
52
+ describe '#decay' do
53
+ it 'decreases weight by DECAY_RATE' do
54
+ before = foundation.weight
55
+ foundation.decay
56
+ expected = (before - Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::Constants::DECAY_RATE)
57
+ .clamp(Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::Constants::WEIGHT_FLOOR,
58
+ Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::Constants::WEIGHT_CEILING)
59
+ expect(foundation.weight).to eq(expected)
60
+ end
61
+ end
62
+
63
+ describe '#to_h' do
64
+ it 'returns a hash with id, weight, sensitivity' do
65
+ h = foundation.to_h
66
+ expect(h).to include(:id, :weight, :sensitivity)
67
+ expect(h[:id]).to eq(:care)
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,275 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Social::MoralReasoning::Runners::MoralReasoning do
4
+ let(:client) { Legion::Extensions::Agentic::Social::MoralReasoning::Client.new }
5
+ let(:enhancer) { Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::LlmEnhancer }
6
+
7
+ let(:options) do
8
+ [
9
+ { id: 'opt_a', description: 'Care approach', foundations: %i[care fairness] },
10
+ { id: 'opt_b', description: 'Rule approach', foundations: %i[authority loyalty] }
11
+ ]
12
+ end
13
+
14
+ describe '#evaluate_moral_action' do
15
+ it 'returns success with a score' do
16
+ result = client.evaluate_moral_action(
17
+ action: 'help_stranger',
18
+ affected_foundations: %i[care fairness]
19
+ )
20
+ expect(result[:success]).to be true
21
+ expect(result[:score]).to be_a(Float)
22
+ end
23
+
24
+ it 'accepts a domain parameter' do
25
+ result = client.evaluate_moral_action(
26
+ action: 'whistleblow',
27
+ affected_foundations: %i[fairness],
28
+ domain: :workplace
29
+ )
30
+ expect(result[:domain]).to eq(:workplace)
31
+ end
32
+
33
+ context 'when LLM is available' do
34
+ let(:llm_result) do
35
+ {
36
+ reasoning: 'LLM ethical analysis of this action.',
37
+ foundation_impacts: { care: 0.2, fairness: 0.1, loyalty: -0.05,
38
+ authority: 0.0, sanctity: 0.1, liberty: 0.05 }
39
+ }
40
+ end
41
+
42
+ before do
43
+ allow(enhancer).to receive(:available?).and_return(true)
44
+ allow(enhancer).to receive(:evaluate_action).and_return(llm_result)
45
+ end
46
+
47
+ it 'returns source: :llm' do
48
+ result = client.evaluate_moral_action(
49
+ action: 'help_stranger',
50
+ affected_foundations: %i[care fairness]
51
+ )
52
+ expect(result[:source]).to eq(:llm)
53
+ end
54
+
55
+ it 'includes LLM reasoning' do
56
+ result = client.evaluate_moral_action(
57
+ action: 'help_stranger',
58
+ affected_foundations: %i[care fairness]
59
+ )
60
+ expect(result[:reasoning]).to eq('LLM ethical analysis of this action.')
61
+ end
62
+
63
+ it 'includes foundation_impacts from LLM' do
64
+ result = client.evaluate_moral_action(
65
+ action: 'help_stranger',
66
+ affected_foundations: %i[care fairness]
67
+ )
68
+ expect(result[:foundation_impacts]).to eq(llm_result[:foundation_impacts])
69
+ end
70
+ end
71
+
72
+ context 'when LLM is unavailable' do
73
+ before do
74
+ allow(enhancer).to receive(:available?).and_return(false)
75
+ end
76
+
77
+ it 'returns source: :mechanical' do
78
+ result = client.evaluate_moral_action(
79
+ action: 'help_stranger',
80
+ affected_foundations: %i[care fairness]
81
+ )
82
+ expect(result[:source]).to eq(:mechanical)
83
+ end
84
+
85
+ it 'still returns success: true' do
86
+ result = client.evaluate_moral_action(
87
+ action: 'help_stranger',
88
+ affected_foundations: %i[care fairness]
89
+ )
90
+ expect(result[:success]).to be true
91
+ end
92
+ end
93
+
94
+ context 'when LLM returns nil' do
95
+ before do
96
+ allow(enhancer).to receive(:available?).and_return(true)
97
+ allow(enhancer).to receive(:evaluate_action).and_return(nil)
98
+ end
99
+
100
+ it 'falls back to mechanical and returns source: :mechanical' do
101
+ result = client.evaluate_moral_action(
102
+ action: 'help_stranger',
103
+ affected_foundations: %i[care fairness]
104
+ )
105
+ expect(result[:source]).to eq(:mechanical)
106
+ end
107
+ end
108
+ end
109
+
110
+ describe '#pose_moral_dilemma' do
111
+ it 'creates a dilemma' do
112
+ result = client.pose_moral_dilemma(
113
+ description: 'Test dilemma',
114
+ options: options
115
+ )
116
+ expect(result[:success]).to be true
117
+ expect(result[:dilemma][:description]).to eq('Test dilemma')
118
+ end
119
+ end
120
+
121
+ describe '#resolve_moral_dilemma' do
122
+ let(:dilemma_id) do
123
+ client.pose_moral_dilemma(description: 'Resolve test', options: options)[:dilemma][:id]
124
+ end
125
+
126
+ it 'resolves a dilemma' do
127
+ result = client.resolve_moral_dilemma(
128
+ dilemma_id: dilemma_id,
129
+ option_id: 'opt_a',
130
+ reasoning: 'Greatest good',
131
+ framework: :utilitarian
132
+ )
133
+ expect(result[:success]).to be true
134
+ expect(result[:dilemma][:resolved]).to be true
135
+ end
136
+
137
+ it 'returns failure for unknown dilemma' do
138
+ result = client.resolve_moral_dilemma(
139
+ dilemma_id: 'bad_id',
140
+ option_id: 'opt_a',
141
+ reasoning: 'N/A',
142
+ framework: :utilitarian
143
+ )
144
+ expect(result[:success]).to be false
145
+ end
146
+
147
+ context 'when LLM is available' do
148
+ let(:llm_result) do
149
+ {
150
+ chosen_option: 'opt_a',
151
+ confidence: 0.88,
152
+ reasoning: 'Utilitarian calculus favors this option for the greatest collective benefit.'
153
+ }
154
+ end
155
+
156
+ before do
157
+ allow(enhancer).to receive(:available?).and_return(true)
158
+ allow(enhancer).to receive(:resolve_dilemma).and_return(llm_result)
159
+ end
160
+
161
+ it 'returns source: :llm' do
162
+ result = client.resolve_moral_dilemma(
163
+ dilemma_id: dilemma_id,
164
+ option_id: 'opt_a',
165
+ reasoning: 'manual reasoning',
166
+ framework: :utilitarian
167
+ )
168
+ expect(result[:source]).to eq(:llm)
169
+ end
170
+
171
+ it 'includes llm_chosen and llm_confidence' do
172
+ result = client.resolve_moral_dilemma(
173
+ dilemma_id: dilemma_id,
174
+ option_id: 'opt_a',
175
+ reasoning: 'manual reasoning',
176
+ framework: :utilitarian
177
+ )
178
+ expect(result[:llm_chosen]).to eq('opt_a')
179
+ expect(result[:llm_confidence]).to eq(0.88)
180
+ end
181
+
182
+ it 'resolves successfully using LLM reasoning' do
183
+ result = client.resolve_moral_dilemma(
184
+ dilemma_id: dilemma_id,
185
+ option_id: 'opt_a',
186
+ reasoning: 'manual reasoning',
187
+ framework: :utilitarian
188
+ )
189
+ expect(result[:success]).to be true
190
+ end
191
+ end
192
+
193
+ context 'when LLM is unavailable' do
194
+ before do
195
+ allow(enhancer).to receive(:available?).and_return(false)
196
+ end
197
+
198
+ it 'resolves mechanically without source key from LLM' do
199
+ result = client.resolve_moral_dilemma(
200
+ dilemma_id: dilemma_id,
201
+ option_id: 'opt_a',
202
+ reasoning: 'manual reasoning',
203
+ framework: :utilitarian
204
+ )
205
+ expect(result[:success]).to be true
206
+ expect(result[:source]).to be_nil
207
+ end
208
+ end
209
+ end
210
+
211
+ describe '#apply_ethical_framework' do
212
+ let(:dilemma_id) do
213
+ client.pose_moral_dilemma(description: 'Framework test', options: options)[:dilemma][:id]
214
+ end
215
+
216
+ it 'applies a framework and returns rankings' do
217
+ result = client.apply_ethical_framework(dilemma_id: dilemma_id, framework: :care)
218
+ expect(result[:success]).to be true
219
+ expect(result[:rankings]).to be_an(Array)
220
+ end
221
+ end
222
+
223
+ describe '#add_moral_principle' do
224
+ it 'adds a principle' do
225
+ result = client.add_moral_principle(
226
+ name: 'Non-maleficence',
227
+ description: 'Do not harm',
228
+ foundation: :care
229
+ )
230
+ expect(result[:success]).to be true
231
+ end
232
+ end
233
+
234
+ describe '#check_moral_development' do
235
+ it 'returns success with current stage' do
236
+ result = client.check_moral_development
237
+ expect(result[:success]).to be true
238
+ expect(result[:stage]).to be_a(Symbol)
239
+ end
240
+ end
241
+
242
+ describe '#moral_foundation_profile' do
243
+ it 'returns all foundations' do
244
+ result = client.moral_foundation_profile
245
+ expect(result[:success]).to be true
246
+ expect(result[:foundations].keys).to match_array(
247
+ Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::Constants::MORAL_FOUNDATIONS
248
+ )
249
+ end
250
+ end
251
+
252
+ describe '#moral_stage_info' do
253
+ it 'returns stage, level, and description' do
254
+ result = client.moral_stage_info
255
+ expect(result[:success]).to be true
256
+ expect(result).to include(:stage, :level, :description)
257
+ end
258
+ end
259
+
260
+ describe '#update_moral_reasoning' do
261
+ it 'decays foundations and returns profile' do
262
+ result = client.update_moral_reasoning
263
+ expect(result[:success]).to be true
264
+ expect(result[:foundations]).to be_a(Hash)
265
+ end
266
+ end
267
+
268
+ describe '#moral_reasoning_stats' do
269
+ it 'returns stats including stage and counts' do
270
+ result = client.moral_reasoning_stats
271
+ expect(result[:success]).to be true
272
+ expect(result).to include(:stage, :total_dilemmas, :resolved_dilemmas)
273
+ end
274
+ end
275
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/social/perspective_shifting/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Social::PerspectiveShifting::Client do
6
+ it 'responds to all runner methods' do
7
+ client = described_class.new
8
+ expect(client).to respond_to(:add_perspective)
9
+ expect(client).to respond_to(:list_perspectives)
10
+ expect(client).to respond_to(:get_perspective)
11
+ expect(client).to respond_to(:add_situation)
12
+ expect(client).to respond_to(:list_situations)
13
+ expect(client).to respond_to(:generate_view)
14
+ expect(client).to respond_to(:views_for_situation)
15
+ expect(client).to respond_to(:perspective_agreement)
16
+ expect(client).to respond_to(:blind_spots)
17
+ expect(client).to respond_to(:coverage_score)
18
+ expect(client).to respond_to(:dominant_view)
19
+ expect(client).to respond_to(:synthesize)
20
+ expect(client).to respond_to(:most_divergent_pair)
21
+ expect(client).to respond_to(:engine_status)
22
+ end
23
+
24
+ it 'initializes with its own isolated engine per instance' do
25
+ c1 = described_class.new
26
+ c2 = described_class.new
27
+ c1.add_perspective(name: 'CEO', type: :stakeholder)
28
+ expect(c2.list_perspectives[:count]).to eq(0)
29
+ end
30
+ end