lex-agentic-defense 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 (219) 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-defense.gemspec +30 -0
  7. data/lib/legion/extensions/agentic/defense/avalanche/client.rb +22 -0
  8. data/lib/legion/extensions/agentic/defense/avalanche/helpers/avalanche_engine.rb +132 -0
  9. data/lib/legion/extensions/agentic/defense/avalanche/helpers/cascade.rb +76 -0
  10. data/lib/legion/extensions/agentic/defense/avalanche/helpers/constants.rb +44 -0
  11. data/lib/legion/extensions/agentic/defense/avalanche/helpers/snowpack.rb +86 -0
  12. data/lib/legion/extensions/agentic/defense/avalanche/runners/cognitive_avalanche.rb +75 -0
  13. data/lib/legion/extensions/agentic/defense/avalanche/version.rb +13 -0
  14. data/lib/legion/extensions/agentic/defense/avalanche.rb +22 -0
  15. data/lib/legion/extensions/agentic/defense/bias/actors/update.rb +45 -0
  16. data/lib/legion/extensions/agentic/defense/bias/client.rb +30 -0
  17. data/lib/legion/extensions/agentic/defense/bias/helpers/bias_detector.rb +107 -0
  18. data/lib/legion/extensions/agentic/defense/bias/helpers/bias_event.rb +44 -0
  19. data/lib/legion/extensions/agentic/defense/bias/helpers/bias_store.rb +84 -0
  20. data/lib/legion/extensions/agentic/defense/bias/helpers/constants.rb +28 -0
  21. data/lib/legion/extensions/agentic/defense/bias/runners/bias.rb +151 -0
  22. data/lib/legion/extensions/agentic/defense/bias/version.rb +13 -0
  23. data/lib/legion/extensions/agentic/defense/bias.rb +20 -0
  24. data/lib/legion/extensions/agentic/defense/confabulation/actors/decay.rb +45 -0
  25. data/lib/legion/extensions/agentic/defense/confabulation/client.rb +28 -0
  26. data/lib/legion/extensions/agentic/defense/confabulation/helpers/claim.rb +67 -0
  27. data/lib/legion/extensions/agentic/defense/confabulation/helpers/confabulation_engine.rb +120 -0
  28. data/lib/legion/extensions/agentic/defense/confabulation/helpers/constants.rb +29 -0
  29. data/lib/legion/extensions/agentic/defense/confabulation/runners/confabulation.rb +74 -0
  30. data/lib/legion/extensions/agentic/defense/confabulation/version.rb +13 -0
  31. data/lib/legion/extensions/agentic/defense/confabulation.rb +19 -0
  32. data/lib/legion/extensions/agentic/defense/dissonance/client.rb +32 -0
  33. data/lib/legion/extensions/agentic/defense/dissonance/helpers/belief.rb +46 -0
  34. data/lib/legion/extensions/agentic/defense/dissonance/helpers/constants.rb +27 -0
  35. data/lib/legion/extensions/agentic/defense/dissonance/helpers/dissonance_event.rb +52 -0
  36. data/lib/legion/extensions/agentic/defense/dissonance/helpers/dissonance_model.rb +159 -0
  37. data/lib/legion/extensions/agentic/defense/dissonance/runners/dissonance.rb +163 -0
  38. data/lib/legion/extensions/agentic/defense/dissonance/version.rb +13 -0
  39. data/lib/legion/extensions/agentic/defense/dissonance.rb +20 -0
  40. data/lib/legion/extensions/agentic/defense/epistemic_vigilance/actors/update.rb +45 -0
  41. data/lib/legion/extensions/agentic/defense/epistemic_vigilance/client.rb +27 -0
  42. data/lib/legion/extensions/agentic/defense/epistemic_vigilance/helpers/claim.rb +78 -0
  43. data/lib/legion/extensions/agentic/defense/epistemic_vigilance/helpers/client.rb +23 -0
  44. data/lib/legion/extensions/agentic/defense/epistemic_vigilance/helpers/constants.rb +37 -0
  45. data/lib/legion/extensions/agentic/defense/epistemic_vigilance/helpers/source.rb +64 -0
  46. data/lib/legion/extensions/agentic/defense/epistemic_vigilance/helpers/vigilance_engine.rb +195 -0
  47. data/lib/legion/extensions/agentic/defense/epistemic_vigilance/runners/epistemic_vigilance.rb +91 -0
  48. data/lib/legion/extensions/agentic/defense/epistemic_vigilance/version.rb +13 -0
  49. data/lib/legion/extensions/agentic/defense/epistemic_vigilance.rb +20 -0
  50. data/lib/legion/extensions/agentic/defense/erosion/client.rb +23 -0
  51. data/lib/legion/extensions/agentic/defense/erosion/helpers/channel.rb +84 -0
  52. data/lib/legion/extensions/agentic/defense/erosion/helpers/constants.rb +47 -0
  53. data/lib/legion/extensions/agentic/defense/erosion/helpers/erosion_engine.rb +134 -0
  54. data/lib/legion/extensions/agentic/defense/erosion/helpers/formation.rb +100 -0
  55. data/lib/legion/extensions/agentic/defense/erosion/runners/cognitive_erosion.rb +93 -0
  56. data/lib/legion/extensions/agentic/defense/erosion/version.rb +13 -0
  57. data/lib/legion/extensions/agentic/defense/erosion.rb +21 -0
  58. data/lib/legion/extensions/agentic/defense/error_monitoring/actors/tick.rb +45 -0
  59. data/lib/legion/extensions/agentic/defense/error_monitoring/client.rb +28 -0
  60. data/lib/legion/extensions/agentic/defense/error_monitoring/helpers/constants.rb +50 -0
  61. data/lib/legion/extensions/agentic/defense/error_monitoring/helpers/error_monitor.rb +174 -0
  62. data/lib/legion/extensions/agentic/defense/error_monitoring/helpers/error_signal.rb +60 -0
  63. data/lib/legion/extensions/agentic/defense/error_monitoring/runners/error_monitoring.rb +102 -0
  64. data/lib/legion/extensions/agentic/defense/error_monitoring/version.rb +13 -0
  65. data/lib/legion/extensions/agentic/defense/error_monitoring.rb +19 -0
  66. data/lib/legion/extensions/agentic/defense/extinction/actors/protocol_monitor.rb +45 -0
  67. data/lib/legion/extensions/agentic/defense/extinction/client.rb +27 -0
  68. data/lib/legion/extensions/agentic/defense/extinction/helpers/levels.rb +43 -0
  69. data/lib/legion/extensions/agentic/defense/extinction/helpers/protocol_state.rb +125 -0
  70. data/lib/legion/extensions/agentic/defense/extinction/local_migrations/20260316000040_create_extinction_state.rb +13 -0
  71. data/lib/legion/extensions/agentic/defense/extinction/runners/extinction.rb +130 -0
  72. data/lib/legion/extensions/agentic/defense/extinction/version.rb +13 -0
  73. data/lib/legion/extensions/agentic/defense/extinction.rb +25 -0
  74. data/lib/legion/extensions/agentic/defense/friction/client.rb +15 -0
  75. data/lib/legion/extensions/agentic/defense/friction/helpers/constants.rb +38 -0
  76. data/lib/legion/extensions/agentic/defense/friction/helpers/friction_engine.rb +131 -0
  77. data/lib/legion/extensions/agentic/defense/friction/helpers/state_transition.rb +73 -0
  78. data/lib/legion/extensions/agentic/defense/friction/runners/cognitive_friction.rb +82 -0
  79. data/lib/legion/extensions/agentic/defense/friction/version.rb +13 -0
  80. data/lib/legion/extensions/agentic/defense/friction.rb +19 -0
  81. data/lib/legion/extensions/agentic/defense/immune_response/client.rb +19 -0
  82. data/lib/legion/extensions/agentic/defense/immune_response/helpers/antibody.rb +72 -0
  83. data/lib/legion/extensions/agentic/defense/immune_response/helpers/antigen.rb +87 -0
  84. data/lib/legion/extensions/agentic/defense/immune_response/helpers/constants.rb +75 -0
  85. data/lib/legion/extensions/agentic/defense/immune_response/helpers/immune_engine.rb +184 -0
  86. data/lib/legion/extensions/agentic/defense/immune_response/helpers/immune_response.rb +76 -0
  87. data/lib/legion/extensions/agentic/defense/immune_response/runners/cognitive_immune_response.rb +114 -0
  88. data/lib/legion/extensions/agentic/defense/immune_response/version.rb +13 -0
  89. data/lib/legion/extensions/agentic/defense/immune_response.rb +21 -0
  90. data/lib/legion/extensions/agentic/defense/immunology/client.rb +29 -0
  91. data/lib/legion/extensions/agentic/defense/immunology/helpers/antibody.rb +55 -0
  92. data/lib/legion/extensions/agentic/defense/immunology/helpers/constants.rb +43 -0
  93. data/lib/legion/extensions/agentic/defense/immunology/helpers/immune_engine.rb +187 -0
  94. data/lib/legion/extensions/agentic/defense/immunology/helpers/threat.rb +67 -0
  95. data/lib/legion/extensions/agentic/defense/immunology/runners/cognitive_immunology.rb +92 -0
  96. data/lib/legion/extensions/agentic/defense/immunology/version.rb +13 -0
  97. data/lib/legion/extensions/agentic/defense/immunology.rb +20 -0
  98. data/lib/legion/extensions/agentic/defense/phantom/client.rb +29 -0
  99. data/lib/legion/extensions/agentic/defense/phantom/helpers/constants.rb +54 -0
  100. data/lib/legion/extensions/agentic/defense/phantom/helpers/phantom_engine.rb +106 -0
  101. data/lib/legion/extensions/agentic/defense/phantom/helpers/phantom_limb.rb +103 -0
  102. data/lib/legion/extensions/agentic/defense/phantom/helpers/phantom_signal.rb +40 -0
  103. data/lib/legion/extensions/agentic/defense/phantom/runners/cognitive_phantom.rb +79 -0
  104. data/lib/legion/extensions/agentic/defense/phantom/version.rb +13 -0
  105. data/lib/legion/extensions/agentic/defense/phantom.rb +21 -0
  106. data/lib/legion/extensions/agentic/defense/quicksand/client.rb +15 -0
  107. data/lib/legion/extensions/agentic/defense/quicksand/helpers/constants.rb +48 -0
  108. data/lib/legion/extensions/agentic/defense/quicksand/helpers/pit.rb +82 -0
  109. data/lib/legion/extensions/agentic/defense/quicksand/helpers/quicksand_engine.rb +137 -0
  110. data/lib/legion/extensions/agentic/defense/quicksand/helpers/trap.rb +101 -0
  111. data/lib/legion/extensions/agentic/defense/quicksand/runners/cognitive_quicksand.rb +84 -0
  112. data/lib/legion/extensions/agentic/defense/quicksand/version.rb +13 -0
  113. data/lib/legion/extensions/agentic/defense/quicksand.rb +22 -0
  114. data/lib/legion/extensions/agentic/defense/quicksilver/client.rb +29 -0
  115. data/lib/legion/extensions/agentic/defense/quicksilver/helpers/constants.rb +50 -0
  116. data/lib/legion/extensions/agentic/defense/quicksilver/helpers/droplet.rb +126 -0
  117. data/lib/legion/extensions/agentic/defense/quicksilver/helpers/pool.rb +83 -0
  118. data/lib/legion/extensions/agentic/defense/quicksilver/helpers/quicksilver_engine.rb +124 -0
  119. data/lib/legion/extensions/agentic/defense/quicksilver/runners/cognitive_quicksilver.rb +130 -0
  120. data/lib/legion/extensions/agentic/defense/quicksilver/version.rb +13 -0
  121. data/lib/legion/extensions/agentic/defense/quicksilver.rb +21 -0
  122. data/lib/legion/extensions/agentic/defense/version.rb +11 -0
  123. data/lib/legion/extensions/agentic/defense/whirlpool/client.rb +65 -0
  124. data/lib/legion/extensions/agentic/defense/whirlpool/helpers/captured_thought.rb +67 -0
  125. data/lib/legion/extensions/agentic/defense/whirlpool/helpers/constants.rb +45 -0
  126. data/lib/legion/extensions/agentic/defense/whirlpool/helpers/vortex.rb +91 -0
  127. data/lib/legion/extensions/agentic/defense/whirlpool/helpers/whirlpool_engine.rb +92 -0
  128. data/lib/legion/extensions/agentic/defense/whirlpool/runners/cognitive_whirlpool.rb +117 -0
  129. data/lib/legion/extensions/agentic/defense/whirlpool/version.rb +13 -0
  130. data/lib/legion/extensions/agentic/defense/whirlpool.rb +22 -0
  131. data/lib/legion/extensions/agentic/defense.rb +32 -0
  132. data/spec/legion/extensions/agentic/defense/avalanche/client_spec.rb +96 -0
  133. data/spec/legion/extensions/agentic/defense/avalanche/helpers/avalanche_engine_spec.rb +276 -0
  134. data/spec/legion/extensions/agentic/defense/avalanche/helpers/cascade_spec.rb +190 -0
  135. data/spec/legion/extensions/agentic/defense/avalanche/helpers/constants_spec.rb +129 -0
  136. data/spec/legion/extensions/agentic/defense/avalanche/helpers/snowpack_spec.rb +197 -0
  137. data/spec/legion/extensions/agentic/defense/avalanche/runners/cognitive_avalanche_spec.rb +211 -0
  138. data/spec/legion/extensions/agentic/defense/bias/client_spec.rb +16 -0
  139. data/spec/legion/extensions/agentic/defense/bias/helpers/bias_detector_spec.rb +160 -0
  140. data/spec/legion/extensions/agentic/defense/bias/helpers/bias_event_spec.rb +64 -0
  141. data/spec/legion/extensions/agentic/defense/bias/helpers/bias_store_spec.rb +143 -0
  142. data/spec/legion/extensions/agentic/defense/bias/runners/bias_spec.rb +155 -0
  143. data/spec/legion/extensions/agentic/defense/confabulation/client_spec.rb +34 -0
  144. data/spec/legion/extensions/agentic/defense/confabulation/helpers/claim_spec.rb +119 -0
  145. data/spec/legion/extensions/agentic/defense/confabulation/helpers/confabulation_engine_spec.rb +163 -0
  146. data/spec/legion/extensions/agentic/defense/confabulation/helpers/constants_spec.rb +55 -0
  147. data/spec/legion/extensions/agentic/defense/confabulation/runners/confabulation_spec.rb +119 -0
  148. data/spec/legion/extensions/agentic/defense/dissonance/client_spec.rb +51 -0
  149. data/spec/legion/extensions/agentic/defense/dissonance/helpers/belief_spec.rb +103 -0
  150. data/spec/legion/extensions/agentic/defense/dissonance/helpers/constants_spec.rb +60 -0
  151. data/spec/legion/extensions/agentic/defense/dissonance/helpers/dissonance_event_spec.rb +113 -0
  152. data/spec/legion/extensions/agentic/defense/dissonance/helpers/dissonance_model_spec.rb +252 -0
  153. data/spec/legion/extensions/agentic/defense/dissonance/runners/dissonance_spec.rb +323 -0
  154. data/spec/legion/extensions/agentic/defense/epistemic_vigilance/client_spec.rb +28 -0
  155. data/spec/legion/extensions/agentic/defense/epistemic_vigilance/helpers/claim_spec.rb +135 -0
  156. data/spec/legion/extensions/agentic/defense/epistemic_vigilance/helpers/constants_spec.rb +59 -0
  157. data/spec/legion/extensions/agentic/defense/epistemic_vigilance/helpers/source_spec.rb +117 -0
  158. data/spec/legion/extensions/agentic/defense/epistemic_vigilance/helpers/vigilance_engine_spec.rb +273 -0
  159. data/spec/legion/extensions/agentic/defense/epistemic_vigilance/runners/epistemic_vigilance_spec.rb +157 -0
  160. data/spec/legion/extensions/agentic/defense/erosion/client_spec.rb +90 -0
  161. data/spec/legion/extensions/agentic/defense/erosion/helpers/channel_spec.rb +173 -0
  162. data/spec/legion/extensions/agentic/defense/erosion/helpers/constants_spec.rb +137 -0
  163. data/spec/legion/extensions/agentic/defense/erosion/helpers/erosion_engine_spec.rb +263 -0
  164. data/spec/legion/extensions/agentic/defense/erosion/helpers/formation_spec.rb +206 -0
  165. data/spec/legion/extensions/agentic/defense/erosion/runners/cognitive_erosion_spec.rb +153 -0
  166. data/spec/legion/extensions/agentic/defense/error_monitoring/client_spec.rb +40 -0
  167. data/spec/legion/extensions/agentic/defense/error_monitoring/helpers/error_monitor_spec.rb +178 -0
  168. data/spec/legion/extensions/agentic/defense/error_monitoring/helpers/error_signal_spec.rb +76 -0
  169. data/spec/legion/extensions/agentic/defense/error_monitoring/runners/error_monitoring_spec.rb +87 -0
  170. data/spec/legion/extensions/agentic/defense/extinction/actors/protocol_monitor_spec.rb +45 -0
  171. data/spec/legion/extensions/agentic/defense/extinction/client_spec.rb +13 -0
  172. data/spec/legion/extensions/agentic/defense/extinction/helpers/levels_spec.rb +180 -0
  173. data/spec/legion/extensions/agentic/defense/extinction/helpers/protocol_state_spec.rb +291 -0
  174. data/spec/legion/extensions/agentic/defense/extinction/local_persistence_spec.rb +188 -0
  175. data/spec/legion/extensions/agentic/defense/extinction/runners/extinction_spec.rb +114 -0
  176. data/spec/legion/extensions/agentic/defense/friction/helpers/constants_spec.rb +46 -0
  177. data/spec/legion/extensions/agentic/defense/friction/helpers/friction_engine_spec.rb +175 -0
  178. data/spec/legion/extensions/agentic/defense/friction/helpers/state_transition_spec.rb +124 -0
  179. data/spec/legion/extensions/agentic/defense/friction/runners/cognitive_friction_spec.rb +89 -0
  180. data/spec/legion/extensions/agentic/defense/immune_response/client_spec.rb +32 -0
  181. data/spec/legion/extensions/agentic/defense/immune_response/cognitive_immune_response_spec.rb +7 -0
  182. data/spec/legion/extensions/agentic/defense/immune_response/helpers/antibody_spec.rb +117 -0
  183. data/spec/legion/extensions/agentic/defense/immune_response/helpers/antigen_spec.rb +125 -0
  184. data/spec/legion/extensions/agentic/defense/immune_response/helpers/constants_spec.rb +45 -0
  185. data/spec/legion/extensions/agentic/defense/immune_response/helpers/immune_engine_spec.rb +222 -0
  186. data/spec/legion/extensions/agentic/defense/immune_response/helpers/immune_response_spec.rb +84 -0
  187. data/spec/legion/extensions/agentic/defense/immune_response/runners_spec.rb +141 -0
  188. data/spec/legion/extensions/agentic/defense/immunology/client_spec.rb +61 -0
  189. data/spec/legion/extensions/agentic/defense/immunology/helpers/antibody_spec.rb +98 -0
  190. data/spec/legion/extensions/agentic/defense/immunology/helpers/constants_spec.rb +86 -0
  191. data/spec/legion/extensions/agentic/defense/immunology/helpers/immune_engine_spec.rb +275 -0
  192. data/spec/legion/extensions/agentic/defense/immunology/helpers/threat_spec.rb +133 -0
  193. data/spec/legion/extensions/agentic/defense/immunology/runners/cognitive_immunology_spec.rb +177 -0
  194. data/spec/legion/extensions/agentic/defense/phantom/client_spec.rb +53 -0
  195. data/spec/legion/extensions/agentic/defense/phantom/helpers/constants_spec.rb +87 -0
  196. data/spec/legion/extensions/agentic/defense/phantom/helpers/phantom_engine_spec.rb +222 -0
  197. data/spec/legion/extensions/agentic/defense/phantom/helpers/phantom_limb_spec.rb +180 -0
  198. data/spec/legion/extensions/agentic/defense/phantom/helpers/phantom_signal_spec.rb +59 -0
  199. data/spec/legion/extensions/agentic/defense/phantom/runners/cognitive_phantom_spec.rb +193 -0
  200. data/spec/legion/extensions/agentic/defense/quicksand/client_spec.rb +35 -0
  201. data/spec/legion/extensions/agentic/defense/quicksand/helpers/constants_spec.rb +58 -0
  202. data/spec/legion/extensions/agentic/defense/quicksand/helpers/pit_spec.rb +103 -0
  203. data/spec/legion/extensions/agentic/defense/quicksand/helpers/quicksand_engine_spec.rb +153 -0
  204. data/spec/legion/extensions/agentic/defense/quicksand/helpers/trap_spec.rb +166 -0
  205. data/spec/legion/extensions/agentic/defense/quicksand/runners/cognitive_quicksand_spec.rb +90 -0
  206. data/spec/legion/extensions/agentic/defense/quicksilver/client_spec.rb +72 -0
  207. data/spec/legion/extensions/agentic/defense/quicksilver/helpers/constants_spec.rb +105 -0
  208. data/spec/legion/extensions/agentic/defense/quicksilver/helpers/droplet_spec.rb +310 -0
  209. data/spec/legion/extensions/agentic/defense/quicksilver/helpers/pool_spec.rb +174 -0
  210. data/spec/legion/extensions/agentic/defense/quicksilver/helpers/quicksilver_engine_spec.rb +226 -0
  211. data/spec/legion/extensions/agentic/defense/quicksilver/runners/cognitive_quicksilver_spec.rb +227 -0
  212. data/spec/legion/extensions/agentic/defense/whirlpool/client_spec.rb +63 -0
  213. data/spec/legion/extensions/agentic/defense/whirlpool/helpers/captured_thought_spec.rb +171 -0
  214. data/spec/legion/extensions/agentic/defense/whirlpool/helpers/constants_spec.rb +65 -0
  215. data/spec/legion/extensions/agentic/defense/whirlpool/helpers/vortex_spec.rb +189 -0
  216. data/spec/legion/extensions/agentic/defense/whirlpool/helpers/whirlpool_engine_spec.rb +227 -0
  217. data/spec/legion/extensions/agentic/defense/whirlpool/runners/cognitive_whirlpool_spec.rb +226 -0
  218. data/spec/spec_helper.rb +46 -0
  219. metadata +303 -0
@@ -0,0 +1,252 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Defense::Dissonance::Helpers::DissonanceModel do
4
+ subject(:model) { described_class.new }
5
+
6
+ describe '#initialize' do
7
+ it 'starts with empty beliefs' do
8
+ expect(model.beliefs).to be_empty
9
+ end
10
+
11
+ it 'starts with empty events' do
12
+ expect(model.events).to be_empty
13
+ end
14
+
15
+ it 'starts with zero stress' do
16
+ expect(model.stress).to eq(0.0)
17
+ end
18
+ end
19
+
20
+ describe '#add_belief' do
21
+ it 'adds a belief to the model' do
22
+ model.add_belief(domain: 'ethics', content: 'honesty matters', confidence: 0.8, importance: :core)
23
+ expect(model.beliefs.size).to eq(1)
24
+ end
25
+
26
+ it 'returns the new belief and empty events for first belief in domain' do
27
+ result = model.add_belief(domain: 'ethics', content: 'honesty matters')
28
+ expect(result[:belief]).to be_a(Legion::Extensions::Agentic::Defense::Dissonance::Helpers::Belief)
29
+ expect(result[:new_dissonance_events]).to be_empty
30
+ end
31
+
32
+ it 'detects contradiction between beliefs in the same domain' do
33
+ model.add_belief(domain: 'safety', content: 'always ask permission')
34
+ result = model.add_belief(domain: 'safety', content: 'act autonomously')
35
+ expect(result[:new_dissonance_events].size).to eq(1)
36
+ end
37
+
38
+ it 'does not detect contradiction for same content' do
39
+ model.add_belief(domain: 'safety', content: 'always ask permission')
40
+ result = model.add_belief(domain: 'safety', content: 'always ask permission')
41
+ expect(result[:new_dissonance_events]).to be_empty
42
+ end
43
+
44
+ it 'does not create duplicate contradiction events for the same pair' do
45
+ model.add_belief(domain: 'safety', content: 'always ask permission')
46
+ result1 = model.add_belief(domain: 'safety', content: 'act autonomously')
47
+ ev_id = result1[:new_dissonance_events].first.id
48
+ # Artificially call detect_contradictions again — no new events since pair is tracked
49
+ new_evs = model.detect_contradictions
50
+ expect(new_evs).to be_empty
51
+ expect(model.events.size).to eq(1)
52
+ expect(model.events.key?(ev_id)).to be true
53
+ end
54
+
55
+ it 'does not create contradiction across different domains' do
56
+ model.add_belief(domain: 'ethics', content: 'be transparent')
57
+ result = model.add_belief(domain: 'safety', content: 'act differently')
58
+ expect(result[:new_dissonance_events]).to be_empty
59
+ end
60
+
61
+ it 'stores the belief with the correct importance weight' do
62
+ result = model.add_belief(domain: 'ethics', content: 'test', importance: :significant)
63
+ expect(result[:belief].importance).to eq(:significant)
64
+ end
65
+ end
66
+
67
+ describe '#detect_contradictions' do
68
+ it 'returns empty array when no contradictions exist' do
69
+ model.add_belief(domain: 'ethics', content: 'honesty matters')
70
+ expect(model.detect_contradictions).to be_empty
71
+ end
72
+
73
+ it 'finds untracked contradictions among existing beliefs' do
74
+ b1 = Legion::Extensions::Agentic::Defense::Dissonance::Helpers::Belief.new(domain: 'ethics', content: 'be honest')
75
+ b2 = Legion::Extensions::Agentic::Defense::Dissonance::Helpers::Belief.new(domain: 'ethics', content: 'deceive when needed')
76
+ model.beliefs[b1.id] = b1
77
+ model.beliefs[b2.id] = b2
78
+ new_events = model.detect_contradictions
79
+ expect(new_events.size).to eq(1)
80
+ end
81
+
82
+ it 'does not re-detect already tracked contradictions' do
83
+ model.add_belief(domain: 'ethics', content: 'tell truth')
84
+ model.add_belief(domain: 'ethics', content: 'lie sometimes')
85
+ model.detect_contradictions
86
+ second_run = model.detect_contradictions
87
+ expect(second_run).to be_empty
88
+ end
89
+ end
90
+
91
+ describe '#resolve' do
92
+ let(:event_id) do
93
+ model.add_belief(domain: 'ethics', content: 'be honest')
94
+ result = model.add_belief(domain: 'ethics', content: 'deceive when useful')
95
+ result[:new_dissonance_events].first.id
96
+ end
97
+
98
+ it 'returns the resolved event' do
99
+ event = model.resolve(event_id, strategy: :belief_revision)
100
+ expect(event).to be_a(Legion::Extensions::Agentic::Defense::Dissonance::Helpers::DissonanceEvent)
101
+ expect(event.resolved).to be true
102
+ end
103
+
104
+ it 'reduces stress after belief_revision resolution' do
105
+ model.add_belief(domain: 'ethics', content: 'be honest')
106
+ result = model.add_belief(domain: 'ethics', content: 'deceive when useful')
107
+ ev_id = result[:new_dissonance_events].first.id
108
+ model.decay
109
+ stress_before = model.stress
110
+ model.resolve(ev_id, strategy: :belief_revision)
111
+ expect(model.stress).to be < stress_before
112
+ end
113
+
114
+ it 'reduces stress less with rationalization than belief_revision' do
115
+ model2 = described_class.new
116
+ model.add_belief(domain: 'domain', content: 'claim a')
117
+ ev_id_m1 = model.add_belief(domain: 'domain', content: 'claim b')[:new_dissonance_events].first.id
118
+ model2.add_belief(domain: 'domain', content: 'claim a')
119
+ ev_id_m2 = model2.add_belief(domain: 'domain', content: 'claim b')[:new_dissonance_events].first.id
120
+ # Build up enough stress so that relief amounts differ meaningfully after clamping
121
+ 15.times { model.decay }
122
+ 15.times { model2.decay }
123
+ model.resolve(ev_id_m1, strategy: :belief_revision)
124
+ model2.resolve(ev_id_m2, strategy: :rationalization)
125
+ expect(model.stress).to be < model2.stress
126
+ end
127
+
128
+ it 'returns nil for unknown event_id' do
129
+ expect(model.resolve('non-existent-id', strategy: :belief_revision)).to be_nil
130
+ end
131
+
132
+ it 'returns nil if already resolved' do
133
+ model.add_belief(domain: 'ethics', content: 'be honest')
134
+ result = model.add_belief(domain: 'ethics', content: 'deceive when useful')
135
+ ev_id = result[:new_dissonance_events].first.id
136
+ model.resolve(ev_id, strategy: :belief_revision)
137
+ expect(model.resolve(ev_id, strategy: :rationalization)).to be_nil
138
+ end
139
+
140
+ it 'returns nil for invalid strategy' do
141
+ model.add_belief(domain: 'x', content: 'a')
142
+ result = model.add_belief(domain: 'x', content: 'b')
143
+ ev_id = result[:new_dissonance_events].first.id
144
+ expect(model.resolve(ev_id, strategy: :invalid_strategy)).to be_nil
145
+ end
146
+ end
147
+
148
+ describe '#stress_level' do
149
+ it 'returns 0.0 initially' do
150
+ expect(model.stress_level).to eq(0.0)
151
+ end
152
+
153
+ it 'increases after decay with unresolved events' do
154
+ model.add_belief(domain: 'x', content: 'a')
155
+ model.add_belief(domain: 'x', content: 'b')
156
+ model.decay
157
+ expect(model.stress_level).to be > 0.0
158
+ end
159
+ end
160
+
161
+ describe '#domain_stress' do
162
+ it 'returns 0.0 for a domain with no unresolved events' do
163
+ expect(model.domain_stress('ethics')).to eq(0.0)
164
+ end
165
+
166
+ it 'returns positive stress for domain with unresolved events' do
167
+ model.add_belief(domain: 'ethics', content: 'be honest')
168
+ model.add_belief(domain: 'ethics', content: 'hide truth')
169
+ expect(model.domain_stress('ethics')).to be > 0.0
170
+ end
171
+
172
+ it 'returns 0.0 for a domain after all events resolved' do
173
+ model.add_belief(domain: 'ethics', content: 'be honest')
174
+ result = model.add_belief(domain: 'ethics', content: 'hide truth')
175
+ ev_id = result[:new_dissonance_events].first.id
176
+ model.resolve(ev_id, strategy: :belief_revision)
177
+ expect(model.domain_stress('ethics')).to eq(0.0)
178
+ end
179
+ end
180
+
181
+ describe '#unresolved_events' do
182
+ it 'returns empty list when no events' do
183
+ expect(model.unresolved_events).to be_empty
184
+ end
185
+
186
+ it 'returns only unresolved events' do
187
+ model.add_belief(domain: 'x', content: 'a')
188
+ result = model.add_belief(domain: 'x', content: 'b')
189
+ ev_id = result[:new_dissonance_events].first.id
190
+ model.resolve(ev_id, strategy: :belief_revision)
191
+
192
+ model.add_belief(domain: 'y', content: 'p')
193
+ model.add_belief(domain: 'y', content: 'q')
194
+
195
+ unresolved = model.unresolved_events
196
+ expect(unresolved.all? { |ev| !ev.resolved }).to be true
197
+ end
198
+ end
199
+
200
+ describe '#decay' do
201
+ it 'increases stress when there are unresolved events' do
202
+ model.add_belief(domain: 'x', content: 'a')
203
+ model.add_belief(domain: 'x', content: 'b')
204
+ initial_stress = model.stress
205
+ model.decay
206
+ expect(model.stress).to be > initial_stress
207
+ end
208
+
209
+ it 'decreases stress when there are no unresolved events' do
210
+ model.instance_variable_set(:@stress, 0.5)
211
+ model.decay
212
+ expect(model.stress).to be < 0.5
213
+ end
214
+
215
+ it 'does not exceed STRESS_CEILING' do
216
+ model.instance_variable_set(:@stress, 0.99)
217
+ 100.times { model.add_belief(domain: 'x', content: "belief_#{rand}") }
218
+ model.decay
219
+ expect(model.stress).to be <= 1.0
220
+ end
221
+
222
+ it 'does not go below STRESS_FLOOR' do
223
+ model.decay
224
+ expect(model.stress).to be >= 0.0
225
+ end
226
+
227
+ it 'returns the current stress value' do
228
+ result = model.decay
229
+ expect(result).to eq(model.stress)
230
+ end
231
+ end
232
+
233
+ describe '#to_h' do
234
+ it 'returns a snapshot hash' do
235
+ model.add_belief(domain: 'ethics', content: 'test')
236
+ h = model.to_h
237
+ expect(h[:beliefs]).to be_an(Array)
238
+ expect(h[:events]).to be_an(Array)
239
+ expect(h[:stress]).to eq(model.stress)
240
+ expect(h[:total_beliefs]).to eq(1)
241
+ expect(h[:total_events]).to eq(0)
242
+ expect(h[:unresolved_count]).to eq(0)
243
+ end
244
+
245
+ it 'counts unresolved events correctly' do
246
+ model.add_belief(domain: 'x', content: 'a')
247
+ model.add_belief(domain: 'x', content: 'b')
248
+ h = model.to_h
249
+ expect(h[:unresolved_count]).to eq(1)
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,323 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/defense/dissonance/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Defense::Dissonance::Runners::Dissonance do
6
+ let(:client) { Legion::Extensions::Agentic::Defense::Dissonance::Client.new }
7
+
8
+ describe '#add_belief' do
9
+ it 'returns success true with a belief_id' do
10
+ result = client.add_belief(domain: 'ethics', content: 'honesty is required')
11
+ expect(result[:success]).to be true
12
+ expect(result[:belief_id]).to match(/\A[0-9a-f-]{36}\z/)
13
+ end
14
+
15
+ it 'returns the domain' do
16
+ result = client.add_belief(domain: 'ethics', content: 'test')
17
+ expect(result[:domain]).to eq('ethics')
18
+ end
19
+
20
+ it 'returns empty new_dissonance_events for first belief in domain' do
21
+ result = client.add_belief(domain: 'ethics', content: 'honesty is required')
22
+ expect(result[:new_dissonance_events]).to be_empty
23
+ end
24
+
25
+ it 'returns dissonance events when contradiction detected' do
26
+ client.add_belief(domain: 'safety', content: 'always ask permission')
27
+ result = client.add_belief(domain: 'safety', content: 'act without asking')
28
+ expect(result[:new_dissonance_events].size).to eq(1)
29
+ end
30
+
31
+ it 'sets dissonance_triggered when magnitude >= threshold' do
32
+ client.add_belief(domain: 'ethics', content: 'tell the truth', confidence: 1.0, importance: :core)
33
+ result = client.add_belief(domain: 'ethics', content: 'lie when convenient', confidence: 1.0, importance: :core)
34
+ expect(result[:dissonance_triggered]).to be true
35
+ end
36
+
37
+ it 'returns success false for invalid importance' do
38
+ result = client.add_belief(domain: 'ethics', content: 'test', importance: :invalid)
39
+ expect(result[:success]).to be false
40
+ expect(result[:error]).to eq(:invalid_importance)
41
+ end
42
+
43
+ it 'returns valid importance levels on error' do
44
+ result = client.add_belief(domain: 'ethics', content: 'test', importance: :invalid)
45
+ expect(result[:valid]).to contain_exactly(:core, :significant, :moderate, :peripheral)
46
+ end
47
+
48
+ it 'accepts all valid importance levels' do
49
+ %i[core significant moderate peripheral].each do |imp|
50
+ result = client.add_belief(domain: 'test', content: "content_#{imp}", importance: imp)
51
+ expect(result[:success]).to be true
52
+ end
53
+ end
54
+ end
55
+
56
+ describe '#update_dissonance' do
57
+ it 'returns success true' do
58
+ result = client.update_dissonance
59
+ expect(result[:success]).to be true
60
+ end
61
+
62
+ it 'returns current stress' do
63
+ result = client.update_dissonance
64
+ expect(result[:stress]).to be_a(Float)
65
+ expect(result[:stress]).to be_between(0.0, 1.0)
66
+ end
67
+
68
+ it 'returns unresolved_count' do
69
+ result = client.update_dissonance
70
+ expect(result[:unresolved_count]).to eq(0)
71
+ end
72
+
73
+ it 'above_threshold is false when no unresolved events' do
74
+ result = client.update_dissonance
75
+ expect(result[:above_threshold]).to be false
76
+ end
77
+
78
+ it 'above_threshold is true when high-magnitude unresolved events exist' do
79
+ client.add_belief(domain: 'x', content: 'a', confidence: 1.0, importance: :core)
80
+ client.add_belief(domain: 'x', content: 'b', confidence: 1.0, importance: :core)
81
+ result = client.update_dissonance
82
+ expect(result[:above_threshold]).to be true
83
+ end
84
+
85
+ it 'increases unresolved_count after adding contradictions' do
86
+ client.add_belief(domain: 'y', content: 'claim1')
87
+ client.add_belief(domain: 'y', content: 'claim2')
88
+ result = client.update_dissonance
89
+ expect(result[:unresolved_count]).to eq(1)
90
+ end
91
+ end
92
+
93
+ describe '#resolve_dissonance' do
94
+ let(:event_id) do
95
+ client.add_belief(domain: 'ethics', content: 'be honest')
96
+ result = client.add_belief(domain: 'ethics', content: 'deceive when convenient')
97
+ result[:new_dissonance_events].first[:id]
98
+ end
99
+
100
+ it 'returns success true on valid resolution' do
101
+ result = client.resolve_dissonance(event_id: event_id, strategy: :belief_revision)
102
+ expect(result[:success]).to be true
103
+ expect(result[:resolved]).to be true
104
+ end
105
+
106
+ it 'returns the strategy used' do
107
+ result = client.resolve_dissonance(event_id: event_id, strategy: :rationalization)
108
+ expect(result[:strategy]).to eq(:rationalization)
109
+ end
110
+
111
+ it 'returns the event hash' do
112
+ result = client.resolve_dissonance(event_id: event_id, strategy: :belief_revision)
113
+ expect(result[:event]).to be_a(Hash)
114
+ expect(result[:event][:resolved]).to be true
115
+ end
116
+
117
+ it 'returns success false for invalid strategy' do
118
+ result = client.resolve_dissonance(event_id: event_id, strategy: :magic)
119
+ expect(result[:success]).to be false
120
+ expect(result[:error]).to eq(:invalid_strategy)
121
+ end
122
+
123
+ it 'returns valid strategies on invalid strategy error' do
124
+ result = client.resolve_dissonance(event_id: event_id, strategy: :magic)
125
+ expect(result[:valid]).to eq(Legion::Extensions::Agentic::Defense::Dissonance::Helpers::Constants::RESOLUTION_STRATEGIES)
126
+ end
127
+
128
+ it 'returns success false for unknown event_id' do
129
+ result = client.resolve_dissonance(event_id: 'no-such-id', strategy: :belief_revision)
130
+ expect(result[:success]).to be false
131
+ expect(result[:error]).to eq(:not_found_or_already_resolved)
132
+ end
133
+
134
+ it 'returns success false when event already resolved' do
135
+ client.resolve_dissonance(event_id: event_id, strategy: :belief_revision)
136
+ result = client.resolve_dissonance(event_id: event_id, strategy: :rationalization)
137
+ expect(result[:success]).to be false
138
+ end
139
+
140
+ it 'defaults strategy to belief_revision' do
141
+ result = client.resolve_dissonance(event_id: event_id)
142
+ expect(result[:strategy]).to eq(:belief_revision)
143
+ end
144
+
145
+ it 'works with all three resolution strategies' do
146
+ %i[belief_revision rationalization avoidance].each_with_index do |strategy, i|
147
+ c = Legion::Extensions::Agentic::Defense::Dissonance::Client.new
148
+ c.add_belief(domain: 'domain', content: "a#{i}")
149
+ inner_result = c.add_belief(domain: 'domain', content: "b#{i}")
150
+ ev_id = inner_result[:new_dissonance_events].first[:id]
151
+ result = c.resolve_dissonance(event_id: ev_id, strategy: strategy)
152
+ expect(result[:success]).to be true
153
+ end
154
+ end
155
+ end
156
+
157
+ describe '#dissonance_status' do
158
+ it 'returns success true' do
159
+ result = client.dissonance_status
160
+ expect(result[:success]).to be true
161
+ end
162
+
163
+ it 'returns stress value' do
164
+ result = client.dissonance_status
165
+ expect(result[:stress]).to be_a(Float)
166
+ end
167
+
168
+ it 'returns total_beliefs count' do
169
+ client.add_belief(domain: 'x', content: 'test')
170
+ result = client.dissonance_status
171
+ expect(result[:total_beliefs]).to eq(1)
172
+ end
173
+
174
+ it 'returns total_events count' do
175
+ client.add_belief(domain: 'x', content: 'a')
176
+ client.add_belief(domain: 'x', content: 'b')
177
+ result = client.dissonance_status
178
+ expect(result[:total_events]).to eq(1)
179
+ end
180
+
181
+ it 'returns unresolved_count' do
182
+ result = client.dissonance_status
183
+ expect(result[:unresolved_count]).to eq(0)
184
+ end
185
+ end
186
+
187
+ describe '#domain_dissonance' do
188
+ it 'returns success true' do
189
+ result = client.domain_dissonance(domain: 'ethics')
190
+ expect(result[:success]).to be true
191
+ end
192
+
193
+ it 'returns the domain' do
194
+ result = client.domain_dissonance(domain: 'ethics')
195
+ expect(result[:domain]).to eq('ethics')
196
+ end
197
+
198
+ it 'returns 0.0 stress for domain with no beliefs' do
199
+ result = client.domain_dissonance(domain: 'ethics')
200
+ expect(result[:stress]).to eq(0.0)
201
+ end
202
+
203
+ it 'returns positive stress for domain with contradictions' do
204
+ client.add_belief(domain: 'ethics', content: 'be honest')
205
+ client.add_belief(domain: 'ethics', content: 'hide truth')
206
+ result = client.domain_dissonance(domain: 'ethics')
207
+ expect(result[:stress]).to be > 0.0
208
+ end
209
+
210
+ it 'returns unresolved events for the domain' do
211
+ client.add_belief(domain: 'ethics', content: 'be honest')
212
+ client.add_belief(domain: 'ethics', content: 'hide truth')
213
+ result = client.domain_dissonance(domain: 'ethics')
214
+ expect(result[:events].size).to eq(1)
215
+ expect(result[:events].first[:domain]).to eq('ethics')
216
+ end
217
+
218
+ it 'does not include events from other domains' do
219
+ client.add_belief(domain: 'ethics', content: 'be honest')
220
+ client.add_belief(domain: 'ethics', content: 'hide truth')
221
+ client.add_belief(domain: 'safety', content: 'claim x')
222
+ client.add_belief(domain: 'safety', content: 'claim y')
223
+ result = client.domain_dissonance(domain: 'ethics')
224
+ expect(result[:events].all? { |ev| ev[:domain] == 'ethics' }).to be true
225
+ end
226
+ end
227
+
228
+ describe '#beliefs_for' do
229
+ it 'returns success true' do
230
+ result = client.beliefs_for(domain: 'ethics')
231
+ expect(result[:success]).to be true
232
+ end
233
+
234
+ it 'returns empty beliefs for unknown domain' do
235
+ result = client.beliefs_for(domain: 'unknown')
236
+ expect(result[:beliefs]).to be_empty
237
+ expect(result[:count]).to eq(0)
238
+ end
239
+
240
+ it 'returns beliefs for a domain' do
241
+ client.add_belief(domain: 'ethics', content: 'test belief')
242
+ result = client.beliefs_for(domain: 'ethics')
243
+ expect(result[:count]).to eq(1)
244
+ expect(result[:beliefs].first[:content]).to eq('test belief')
245
+ end
246
+
247
+ it 'does not include beliefs from other domains' do
248
+ client.add_belief(domain: 'ethics', content: 'ethics belief')
249
+ client.add_belief(domain: 'safety', content: 'safety belief')
250
+ result = client.beliefs_for(domain: 'ethics')
251
+ expect(result[:count]).to eq(1)
252
+ end
253
+
254
+ it 'returns the domain in the result' do
255
+ result = client.beliefs_for(domain: 'ethics')
256
+ expect(result[:domain]).to eq('ethics')
257
+ end
258
+ end
259
+
260
+ describe '#unresolved' do
261
+ it 'returns success true' do
262
+ result = client.unresolved
263
+ expect(result[:success]).to be true
264
+ end
265
+
266
+ it 'returns empty events initially' do
267
+ result = client.unresolved
268
+ expect(result[:events]).to be_empty
269
+ expect(result[:count]).to eq(0)
270
+ end
271
+
272
+ it 'returns unresolved events after contradiction' do
273
+ client.add_belief(domain: 'x', content: 'claim a')
274
+ client.add_belief(domain: 'x', content: 'claim b')
275
+ result = client.unresolved
276
+ expect(result[:count]).to eq(1)
277
+ end
278
+
279
+ it 'does not include resolved events' do
280
+ client.add_belief(domain: 'x', content: 'claim a')
281
+ inner_result = client.add_belief(domain: 'x', content: 'claim b')
282
+ ev_id = inner_result[:new_dissonance_events].first[:id]
283
+ client.resolve_dissonance(event_id: ev_id, strategy: :belief_revision)
284
+ result = client.unresolved
285
+ expect(result[:count]).to eq(0)
286
+ end
287
+ end
288
+
289
+ describe '#dissonance_stats' do
290
+ it 'returns success true' do
291
+ result = client.dissonance_stats
292
+ expect(result[:success]).to be true
293
+ end
294
+
295
+ it 'returns comprehensive stats hash' do
296
+ result = client.dissonance_stats
297
+ expect(result).to include(:stress, :total_beliefs, :total_events, :unresolved_count,
298
+ :resolved_count, :domain_stresses, :resolution_breakdown, :above_threshold)
299
+ end
300
+
301
+ it 'resolution_breakdown contains all three strategies' do
302
+ result = client.dissonance_stats
303
+ expect(result[:resolution_breakdown].keys).to contain_exactly(:belief_revision, :rationalization, :avoidance)
304
+ end
305
+
306
+ it 'counts resolved events by strategy' do
307
+ client.add_belief(domain: 'x', content: 'a')
308
+ inner_result = client.add_belief(domain: 'x', content: 'b')
309
+ ev_id = inner_result[:new_dissonance_events].first[:id]
310
+ client.resolve_dissonance(event_id: ev_id, strategy: :rationalization)
311
+ result = client.dissonance_stats
312
+ expect(result[:resolution_breakdown][:rationalization]).to eq(1)
313
+ expect(result[:resolved_count]).to eq(1)
314
+ end
315
+
316
+ it 'domain_stresses includes all domains with beliefs' do
317
+ client.add_belief(domain: 'domain_a', content: 'alpha')
318
+ client.add_belief(domain: 'domain_b', content: 'beta')
319
+ result = client.dissonance_stats
320
+ expect(result[:domain_stresses].keys).to include('domain_a', 'domain_b')
321
+ end
322
+ end
323
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/defense/epistemic_vigilance/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Defense::EpistemicVigilance::Client do
6
+ let(:client) { described_class.new }
7
+
8
+ it 'responds to all runner methods' do
9
+ expect(client).to respond_to(:register_epistemic_source)
10
+ expect(client).to respond_to(:submit_epistemic_claim)
11
+ expect(client).to respond_to(:assess_epistemic_claim)
12
+ expect(client).to respond_to(:support_epistemic_claim)
13
+ expect(client).to respond_to(:challenge_epistemic_claim)
14
+ expect(client).to respond_to(:adjudicate_epistemic_claim)
15
+ expect(client).to respond_to(:source_reliability_report)
16
+ expect(client).to respond_to(:contested_claims_report)
17
+ expect(client).to respond_to(:domain_vigilance_report)
18
+ expect(client).to respond_to(:update_epistemic_vigilance)
19
+ expect(client).to respond_to(:epistemic_vigilance_stats)
20
+ end
21
+
22
+ it 'maintains independent engine state per instance' do
23
+ client2 = described_class.new
24
+ client.register_epistemic_source(name: 'A', domain: :test)
25
+ expect(client.epistemic_vigilance_stats[:sources_count]).to eq(1)
26
+ expect(client2.epistemic_vigilance_stats[:sources_count]).to eq(0)
27
+ end
28
+ end