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,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/defense/bias/helpers/constants'
4
+ require 'legion/extensions/agentic/defense/bias/helpers/bias_event'
5
+ require 'legion/extensions/agentic/defense/bias/helpers/bias_store'
6
+
7
+ RSpec.describe Legion::Extensions::Agentic::Defense::Bias::Helpers::BiasStore do
8
+ subject(:store) { described_class.new }
9
+
10
+ let(:event) do
11
+ Legion::Extensions::Agentic::Defense::Bias::Helpers::BiasEvent.new(
12
+ bias_type: :anchoring,
13
+ domain: :finance,
14
+ magnitude: 0.6
15
+ )
16
+ end
17
+
18
+ let(:event2) do
19
+ Legion::Extensions::Agentic::Defense::Bias::Helpers::BiasEvent.new(
20
+ bias_type: :confirmation,
21
+ domain: :research,
22
+ magnitude: 0.4
23
+ )
24
+ end
25
+
26
+ describe '#record' do
27
+ it 'stores an event and returns it' do
28
+ result = store.record(event)
29
+ expect(result).to eq(event)
30
+ end
31
+
32
+ it 'trims events at MAX_BIAS_EVENTS' do
33
+ max = Legion::Extensions::Agentic::Defense::Bias::Helpers::Constants::MAX_BIAS_EVENTS
34
+ (max + 10).times do
35
+ store.record(Legion::Extensions::Agentic::Defense::Bias::Helpers::BiasEvent.new(
36
+ bias_type: :recency,
37
+ domain: :test,
38
+ magnitude: 0.1
39
+ ))
40
+ end
41
+ expect(store.recent(max + 10).size).to eq(max)
42
+ end
43
+ end
44
+
45
+ describe '#recent' do
46
+ it 'returns the most recent events as hashes' do
47
+ store.record(event)
48
+ store.record(event2)
49
+ result = store.recent(2)
50
+ expect(result.size).to eq(2)
51
+ expect(result.first).to be_a(Hash)
52
+ end
53
+
54
+ it 'returns fewer than requested when not enough events' do
55
+ store.record(event)
56
+ expect(store.recent(10).size).to eq(1)
57
+ end
58
+ end
59
+
60
+ describe '#by_type' do
61
+ it 'returns only events matching bias_type' do
62
+ store.record(event)
63
+ store.record(event2)
64
+ result = store.by_type(:anchoring)
65
+ expect(result.size).to eq(1)
66
+ expect(result.first[:bias_type]).to eq(:anchoring)
67
+ end
68
+ end
69
+
70
+ describe '#by_domain' do
71
+ it 'returns only events matching domain' do
72
+ store.record(event)
73
+ store.record(event2)
74
+ result = store.by_domain(:finance)
75
+ expect(result.size).to eq(1)
76
+ expect(result.first[:domain]).to eq(:finance)
77
+ end
78
+ end
79
+
80
+ describe '#register_anchor and #anchors_for' do
81
+ it 'stores anchor values for a domain' do
82
+ store.register_anchor(:pricing, value: 99.99)
83
+ anchors = store.anchors_for(:pricing)
84
+ expect(anchors.size).to eq(1)
85
+ expect(anchors.first[:value]).to eq(99.99)
86
+ end
87
+
88
+ it 'returns empty array for unknown domain' do
89
+ expect(store.anchors_for(:nonexistent)).to eq([])
90
+ end
91
+
92
+ it 'trims anchors at MAX_ANCHORS' do
93
+ max = Legion::Extensions::Agentic::Defense::Bias::Helpers::Constants::MAX_ANCHORS
94
+ (max + 5).times { |i| store.register_anchor(:domain, value: i) }
95
+ expect(store.anchors_for(:domain).size).to eq(max)
96
+ end
97
+ end
98
+
99
+ describe '#decay_anchors' do
100
+ it 'reduces anchor influence' do
101
+ store.register_anchor(:pricing, value: 100.0, influence: 1.0)
102
+ store.decay_anchors
103
+ anchor = store.anchors_for(:pricing).first
104
+ expect(anchor[:influence]).to be < 1.0
105
+ end
106
+
107
+ it 'removes anchors with influence at or below 0' do
108
+ store.register_anchor(:pricing, value: 1.0, influence: 0.01)
109
+ store.decay_anchors
110
+ expect(store.anchors_for(:pricing)).to be_empty
111
+ end
112
+ end
113
+
114
+ describe '#stats' do
115
+ it 'returns total 0 with empty store' do
116
+ result = store.stats
117
+ expect(result[:total]).to eq(0)
118
+ end
119
+
120
+ it 'aggregates counts and avg_magnitude per type' do
121
+ store.record(event)
122
+ stats = store.stats
123
+ expect(stats[:by_type][:anchoring][:count]).to eq(1)
124
+ expect(stats[:by_type][:anchoring][:avg_magnitude]).to eq(0.6)
125
+ end
126
+
127
+ it 'aggregates by domain' do
128
+ store.record(event)
129
+ stats = store.stats
130
+ expect(stats[:by_domain][:finance][:count]).to eq(1)
131
+ end
132
+ end
133
+
134
+ describe '#to_h' do
135
+ it 'returns total_events and anchor_domains' do
136
+ store.record(event)
137
+ store.register_anchor(:pricing, value: 50.0)
138
+ h = store.to_h
139
+ expect(h[:total_events]).to eq(1)
140
+ expect(h[:anchor_domains]).to include(:pricing)
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/defense/bias/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Defense::Bias::Runners::Bias do
6
+ let(:client) { Legion::Extensions::Agentic::Defense::Bias::Client.new }
7
+
8
+ describe '#record_anchor' do
9
+ it 'returns success: true' do
10
+ result = client.record_anchor(domain: :finance, value: 100.0)
11
+ expect(result[:success]).to be true
12
+ expect(result[:domain]).to eq(:finance)
13
+ expect(result[:value]).to eq(100.0)
14
+ end
15
+ end
16
+
17
+ describe '#check_for_bias' do
18
+ context 'with no decision context' do
19
+ it 'returns success: true with empty detected list' do
20
+ result = client.check_for_bias(domain: :test)
21
+ expect(result[:success]).to be true
22
+ expect(result[:detected]).to be_empty
23
+ end
24
+ end
25
+
26
+ context 'with anchoring context' do
27
+ before { client.record_anchor(domain: :finance, value: 100.0) }
28
+
29
+ it 'detects anchoring when current value is close to anchor' do
30
+ result = client.check_for_bias(
31
+ domain: :finance,
32
+ decision_context: { current_value: 101.0 }
33
+ )
34
+ expect(result[:success]).to be true
35
+ anchoring = result[:all].find { |b| b[:bias_type] == :anchoring }
36
+ expect(anchoring).not_to be_nil
37
+ expect(anchoring[:magnitude]).to be > 0.0
38
+ end
39
+ end
40
+
41
+ context 'with confirmation bias context' do
42
+ it 'detects confirmation bias when evidence matches hypothesis' do
43
+ result = client.check_for_bias(
44
+ domain: :research,
45
+ decision_context: {
46
+ evidence_direction: :positive,
47
+ hypothesis_direction: :positive
48
+ }
49
+ )
50
+ confirmation = result[:all].find { |b| b[:bias_type] == :confirmation }
51
+ expect(confirmation[:magnitude]).to be > 0.0
52
+ end
53
+ end
54
+
55
+ context 'with availability bias context' do
56
+ it 'detects availability bias with recent events' do
57
+ result = client.check_for_bias(
58
+ domain: :safety,
59
+ decision_context: { recent_events: Array.new(10, :incident) }
60
+ )
61
+ availability = result[:all].find { |b| b[:bias_type] == :availability }
62
+ expect(availability[:magnitude]).to be > 0.0
63
+ end
64
+ end
65
+
66
+ context 'with recency bias context' do
67
+ it 'detects recency bias with skewed data points' do
68
+ data = [1.0, 1.0, 1.0, 1.0, 9.0, 9.0, 9.0, 9.0]
69
+ result = client.check_for_bias(
70
+ domain: :market,
71
+ decision_context: { data_points: data }
72
+ )
73
+ recency = result[:all].find { |b| b[:bias_type] == :recency }
74
+ expect(recency[:magnitude]).to be > 0.0
75
+ end
76
+ end
77
+
78
+ context 'with sunk cost bias context' do
79
+ it 'detects sunk cost bias with high investment and low return' do
80
+ result = client.check_for_bias(
81
+ domain: :project,
82
+ decision_context: { invested: 1_000_000, expected_return: 100 }
83
+ )
84
+ sunk = result[:all].find { |b| b[:bias_type] == :sunk_cost }
85
+ expect(sunk[:magnitude]).to be > 0.0
86
+ end
87
+ end
88
+
89
+ it 'marks bias as corrected when magnitude exceeds threshold' do
90
+ client.record_anchor(domain: :finance, value: 100.0)
91
+ result = client.check_for_bias(
92
+ domain: :finance,
93
+ decision_context: { current_value: 100.0 }
94
+ )
95
+ anchoring = result[:all].find { |b| b[:bias_type] == :anchoring }
96
+ if anchoring[:magnitude] >= Legion::Extensions::Agentic::Defense::Bias::Helpers::Constants::DETECTION_THRESHOLD
97
+ expect(anchoring[:corrected]).to be true
98
+ expect(anchoring[:correction_applied]).to be > 0.0
99
+ end
100
+ end
101
+ end
102
+
103
+ describe '#update_bias' do
104
+ it 'returns success: true' do
105
+ result = client.update_bias
106
+ expect(result[:success]).to be true
107
+ end
108
+
109
+ it 'decays anchor influence' do
110
+ client.record_anchor(domain: :pricing, value: 50.0)
111
+ client.update_bias
112
+ end
113
+ end
114
+
115
+ describe '#bias_report' do
116
+ it 'returns success: true with empty events on fresh client' do
117
+ result = client.bias_report
118
+ expect(result[:success]).to be true
119
+ expect(result[:count]).to eq(0)
120
+ end
121
+
122
+ it 'filters by domain when provided' do
123
+ client.record_anchor(domain: :finance, value: 100.0)
124
+ client.check_for_bias(domain: :finance, decision_context: { current_value: 100.0 })
125
+ result = client.bias_report(domain: :finance)
126
+ expect(result[:domain]).to eq(:finance)
127
+ end
128
+ end
129
+
130
+ describe '#susceptibility_profile' do
131
+ it 'returns success: true with susceptibility hash' do
132
+ result = client.susceptibility_profile
133
+ expect(result[:success]).to be true
134
+ expect(result[:susceptibility]).to be_a(Hash)
135
+ expect(result[:susceptibility].keys).to match_array(
136
+ Legion::Extensions::Agentic::Defense::Bias::Helpers::Constants::BIAS_TYPES
137
+ )
138
+ end
139
+ end
140
+
141
+ describe '#bias_stats' do
142
+ it 'returns success: true' do
143
+ result = client.bias_stats
144
+ expect(result[:success]).to be true
145
+ expect(result[:total]).to eq(0)
146
+ end
147
+
148
+ it 'reflects recorded events' do
149
+ client.record_anchor(domain: :finance, value: 100.0)
150
+ client.check_for_bias(domain: :finance, decision_context: { current_value: 100.0 })
151
+ result = client.bias_stats
152
+ expect(result[:total]).to be >= 0
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/defense/confabulation/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Defense::Confabulation::Client do
6
+ let(:client) { described_class.new }
7
+
8
+ it 'responds to confabulation runner methods' do
9
+ expect(client).to respond_to(:register_claim)
10
+ expect(client).to respond_to(:verify_claim)
11
+ expect(client).to respond_to(:flag_confabulation)
12
+ expect(client).to respond_to(:confabulation_report)
13
+ expect(client).to respond_to(:high_risk_claims)
14
+ expect(client).to respond_to(:confabulation_status)
15
+ end
16
+
17
+ it 'starts with an empty engine' do
18
+ status = client.confabulation_status
19
+ expect(status[:engine][:claim_count]).to eq(0)
20
+ end
21
+
22
+ it 'persists claims across calls within the same instance' do
23
+ client.register_claim(content: 'persistent', claim_type: :factual,
24
+ confidence: 0.7, evidence_strength: 0.3)
25
+ expect(client.confabulation_status[:engine][:claim_count]).to eq(1)
26
+ end
27
+
28
+ it 'maintains separate state between instances' do
29
+ client.register_claim(content: 'instance A claim', claim_type: :factual,
30
+ confidence: 0.7, evidence_strength: 0.3)
31
+ other = described_class.new
32
+ expect(other.confabulation_status[:engine][:claim_count]).to eq(0)
33
+ end
34
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Defense::Confabulation::Helpers::Claim do
4
+ let(:claim) do
5
+ described_class.new(
6
+ content: 'The sky is green',
7
+ claim_type: :factual,
8
+ confidence: 0.8,
9
+ evidence_strength: 0.3
10
+ )
11
+ end
12
+
13
+ describe '#initialize' do
14
+ it 'generates a UUID id' do
15
+ expect(claim.id).to match(/\A[0-9a-f-]{36}\z/)
16
+ end
17
+
18
+ it 'stores content' do
19
+ expect(claim.content).to eq('The sky is green')
20
+ end
21
+
22
+ it 'stores claim_type' do
23
+ expect(claim.claim_type).to eq(:factual)
24
+ end
25
+
26
+ it 'clamps confidence to [0, 1]' do
27
+ c = described_class.new(content: 'x', claim_type: :factual, confidence: 1.5, evidence_strength: 0.0)
28
+ expect(c.confidence).to eq(1.0)
29
+ end
30
+
31
+ it 'clamps evidence_strength to [0, 1]' do
32
+ c = described_class.new(content: 'x', claim_type: :factual, confidence: 0.5, evidence_strength: -0.5)
33
+ expect(c.evidence_strength).to eq(0.0)
34
+ end
35
+
36
+ it 'starts unverified' do
37
+ expect(claim.verified).to be false
38
+ end
39
+
40
+ it 'starts not confabulated' do
41
+ expect(claim.confabulated).to be false
42
+ end
43
+
44
+ it 'sets created_at to utc time' do
45
+ expect(claim.created_at).to be_a(Time)
46
+ end
47
+ end
48
+
49
+ describe '#confabulation_risk' do
50
+ it 'computes the gap between confidence and evidence_strength' do
51
+ expect(claim.confabulation_risk).to be_within(0.001).of(0.5)
52
+ end
53
+
54
+ it 'clamps to 0.0 when evidence >= confidence' do
55
+ c = described_class.new(content: 'x', claim_type: :factual, confidence: 0.3, evidence_strength: 0.9)
56
+ expect(c.confabulation_risk).to eq(0.0)
57
+ end
58
+
59
+ it 'clamps to 1.0 when gap exceeds 1.0' do
60
+ c = described_class.new(content: 'x', claim_type: :factual, confidence: 1.0, evidence_strength: 0.0)
61
+ expect(c.confabulation_risk).to eq(1.0)
62
+ end
63
+ end
64
+
65
+ describe '#verify!' do
66
+ it 'marks the claim as verified' do
67
+ claim.verify!
68
+ expect(claim.verified).to be true
69
+ end
70
+
71
+ it 'returns self' do
72
+ expect(claim.verify!).to eq(claim)
73
+ end
74
+ end
75
+
76
+ describe '#mark_confabulated!' do
77
+ it 'marks the claim as confabulated' do
78
+ claim.mark_confabulated!
79
+ expect(claim.confabulated).to be true
80
+ end
81
+
82
+ it 'returns self' do
83
+ expect(claim.mark_confabulated!).to eq(claim)
84
+ end
85
+ end
86
+
87
+ describe '#risk_label' do
88
+ it 'returns :extreme for risk >= 0.8' do
89
+ c = described_class.new(content: 'x', claim_type: :factual, confidence: 1.0, evidence_strength: 0.1)
90
+ expect(c.risk_label).to eq(:extreme)
91
+ end
92
+
93
+ it 'returns :minimal for risk <= 0.2' do
94
+ c = described_class.new(content: 'x', claim_type: :factual, confidence: 0.2, evidence_strength: 0.1)
95
+ expect(c.risk_label).to eq(:minimal)
96
+ end
97
+
98
+ it 'returns :moderate for mid-range risk' do
99
+ c = described_class.new(content: 'x', claim_type: :factual, confidence: 0.7, evidence_strength: 0.25)
100
+ expect(c.risk_label).to eq(:moderate)
101
+ end
102
+ end
103
+
104
+ describe '#to_h' do
105
+ it 'includes all expected keys' do
106
+ h = claim.to_h
107
+ expect(h.keys).to include(
108
+ :id, :content, :claim_type, :confidence, :evidence_strength,
109
+ :confabulation_risk, :risk_label, :verified, :confabulated, :created_at
110
+ )
111
+ end
112
+
113
+ it 'rounds numeric fields to 10 decimal places' do
114
+ h = claim.to_h
115
+ expect(h[:confidence]).to eq(0.8)
116
+ expect(h[:confabulation_risk]).to be_a(Float)
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Defense::Confabulation::Helpers::ConfabulationEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ describe '#register_claim' do
7
+ it 'stores the claim and returns a Claim object' do
8
+ claim = engine.register_claim(content: 'Cats can fly', claim_type: :factual,
9
+ confidence: 0.9, evidence_strength: 0.1)
10
+ expect(claim).to be_a(Legion::Extensions::Agentic::Defense::Confabulation::Helpers::Claim)
11
+ expect(engine.claims.size).to eq(1)
12
+ end
13
+
14
+ it 'assigns the claim an id' do
15
+ claim = engine.register_claim(content: 'test', claim_type: :causal,
16
+ confidence: 0.5, evidence_strength: 0.5)
17
+ expect(claim.id).not_to be_nil
18
+ end
19
+
20
+ it 'defaults claim_type to :factual for unknown types' do
21
+ claim = engine.register_claim(content: 'x', claim_type: :unknown,
22
+ confidence: 0.5, evidence_strength: 0.5)
23
+ expect(claim.claim_type).to eq(:factual)
24
+ end
25
+
26
+ it 'accepts all valid claim types' do
27
+ Legion::Extensions::Agentic::Defense::Confabulation::Helpers::Constants::CLAIM_TYPES.each do |type|
28
+ claim = engine.register_claim(content: 'x', claim_type: type,
29
+ confidence: 0.5, evidence_strength: 0.5)
30
+ expect(claim.claim_type).to eq(type)
31
+ end
32
+ end
33
+ end
34
+
35
+ describe '#verify_claim' do
36
+ it 'marks claim as verified' do
37
+ claim = engine.register_claim(content: 'x', claim_type: :factual,
38
+ confidence: 0.5, evidence_strength: 0.5)
39
+ result = engine.verify_claim(claim_id: claim.id)
40
+ expect(result[:found]).to be true
41
+ expect(result[:verified]).to be true
42
+ expect(engine.claims[claim.id].verified).to be true
43
+ end
44
+
45
+ it 'returns found: false for unknown id' do
46
+ result = engine.verify_claim(claim_id: 'no-such-id')
47
+ expect(result[:found]).to be false
48
+ end
49
+ end
50
+
51
+ describe '#flag_confabulation' do
52
+ it 'marks claim as confabulated' do
53
+ claim = engine.register_claim(content: 'x', claim_type: :factual,
54
+ confidence: 0.9, evidence_strength: 0.1)
55
+ result = engine.flag_confabulation(claim_id: claim.id)
56
+ expect(result[:found]).to be true
57
+ expect(result[:confabulated]).to be true
58
+ expect(engine.claims[claim.id].confabulated).to be true
59
+ end
60
+
61
+ it 'returns found: false for unknown id' do
62
+ result = engine.flag_confabulation(claim_id: 'no-such-id')
63
+ expect(result[:found]).to be false
64
+ end
65
+ end
66
+
67
+ describe '#high_risk_claims' do
68
+ it 'returns claims above the confabulation threshold' do
69
+ engine.register_claim(content: 'risky', claim_type: :factual, confidence: 0.9, evidence_strength: 0.1)
70
+ engine.register_claim(content: 'safe', claim_type: :factual, confidence: 0.4, evidence_strength: 0.4)
71
+ high_risk = engine.high_risk_claims
72
+ expect(high_risk.size).to eq(1)
73
+ expect(high_risk.first.content).to eq('risky')
74
+ end
75
+
76
+ it 'returns empty array when no high-risk claims exist' do
77
+ engine.register_claim(content: 'safe', claim_type: :factual, confidence: 0.3, evidence_strength: 0.3)
78
+ expect(engine.high_risk_claims).to be_empty
79
+ end
80
+ end
81
+
82
+ describe '#verified_claims' do
83
+ it 'returns only verified claims' do
84
+ c1 = engine.register_claim(content: 'verified', claim_type: :factual, confidence: 0.5, evidence_strength: 0.5)
85
+ engine.register_claim(content: 'unverified', claim_type: :factual, confidence: 0.5, evidence_strength: 0.5)
86
+ engine.verify_claim(claim_id: c1.id)
87
+ expect(engine.verified_claims.size).to eq(1)
88
+ end
89
+ end
90
+
91
+ describe '#confabulation_rate' do
92
+ it 'returns 0.0 when no claims' do
93
+ expect(engine.confabulation_rate).to eq(0.0)
94
+ end
95
+
96
+ it 'returns fraction of confabulated claims' do
97
+ c1 = engine.register_claim(content: 'a', claim_type: :factual, confidence: 0.9, evidence_strength: 0.1)
98
+ engine.register_claim(content: 'b', claim_type: :factual, confidence: 0.4, evidence_strength: 0.4)
99
+ engine.flag_confabulation(claim_id: c1.id)
100
+ expect(engine.confabulation_rate).to be_within(0.001).of(0.5)
101
+ end
102
+ end
103
+
104
+ describe '#average_calibration' do
105
+ it 'returns 0.0 when no claims' do
106
+ expect(engine.average_calibration).to eq(0.0)
107
+ end
108
+
109
+ it 'returns 1.0 when all claims are perfectly calibrated' do
110
+ engine.register_claim(content: 'x', claim_type: :factual, confidence: 0.5, evidence_strength: 0.5)
111
+ expect(engine.average_calibration).to eq(1.0)
112
+ end
113
+
114
+ it 'returns lower value when confidence mismatches evidence' do
115
+ engine.register_claim(content: 'x', claim_type: :factual, confidence: 1.0, evidence_strength: 0.0)
116
+ expect(engine.average_calibration).to be < 1.0
117
+ end
118
+ end
119
+
120
+ describe '#confabulation_report' do
121
+ it 'returns a comprehensive report hash' do
122
+ engine.register_claim(content: 'x', claim_type: :factual, confidence: 0.8, evidence_strength: 0.2)
123
+ report = engine.confabulation_report
124
+ expect(report.keys).to include(
125
+ :total_claims, :high_risk_claims, :verified_claims,
126
+ :confabulated_claims, :confabulation_rate, :average_calibration,
127
+ :overall_risk, :risk_label
128
+ )
129
+ end
130
+
131
+ it 'risk_label is a symbol' do
132
+ report = engine.confabulation_report
133
+ expect(report[:risk_label]).to be_a(Symbol)
134
+ end
135
+
136
+ it 'totals match registered claims' do
137
+ 2.times { |i| engine.register_claim(content: "claim#{i}", claim_type: :factual, confidence: 0.5, evidence_strength: 0.5) }
138
+ expect(engine.confabulation_report[:total_claims]).to eq(2)
139
+ end
140
+ end
141
+
142
+ describe '#prune_if_needed' do
143
+ it 'prunes oldest claim when at MAX_CLAIMS capacity' do
144
+ max = Legion::Extensions::Agentic::Defense::Confabulation::Helpers::Constants::MAX_CLAIMS
145
+ first_claim = engine.register_claim(content: 'first', claim_type: :factual, confidence: 0.5, evidence_strength: 0.5)
146
+ (max - 1).times { |i| engine.register_claim(content: "claim#{i}", claim_type: :factual, confidence: 0.5, evidence_strength: 0.5) }
147
+ expect(engine.claims.size).to eq(max)
148
+ engine.register_claim(content: 'overflow', claim_type: :factual, confidence: 0.5, evidence_strength: 0.5)
149
+ expect(engine.claims.size).to eq(max)
150
+ expect(engine.claims[first_claim.id]).to be_nil
151
+ end
152
+ end
153
+
154
+ describe '#to_h' do
155
+ it 'returns a summary hash' do
156
+ engine.register_claim(content: 'x', claim_type: :factual, confidence: 0.5, evidence_strength: 0.5)
157
+ h = engine.to_h
158
+ expect(h).to have_key(:claim_count)
159
+ expect(h).to have_key(:confabulation_rate)
160
+ expect(h).to have_key(:average_calibration)
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Defense::Confabulation::Helpers::Constants do
4
+ describe 'MAX_CLAIMS' do
5
+ it 'is 500' do
6
+ expect(described_class::MAX_CLAIMS).to eq(500)
7
+ end
8
+ end
9
+
10
+ describe 'CONFABULATION_THRESHOLD' do
11
+ it 'is 0.6' do
12
+ expect(described_class::CONFABULATION_THRESHOLD).to eq(0.6)
13
+ end
14
+ end
15
+
16
+ describe 'EVIDENCE_DECAY' do
17
+ it 'is 0.02' do
18
+ expect(described_class::EVIDENCE_DECAY).to eq(0.02)
19
+ end
20
+ end
21
+
22
+ describe 'RISK_LABELS' do
23
+ it 'covers the full 0.0-1.0 range' do
24
+ expect(described_class::RISK_LABELS.keys.map(&:min).min).to eq(0.0)
25
+ expect(described_class::RISK_LABELS.keys.map(&:max).max).to eq(1.0)
26
+ end
27
+
28
+ it 'includes all five risk levels' do
29
+ labels = described_class::RISK_LABELS.values
30
+ expect(labels).to include(:minimal, :low, :moderate, :high, :extreme)
31
+ end
32
+
33
+ it 'maps 0.0 to minimal' do
34
+ label = described_class::RISK_LABELS.find { |range, _| range.cover?(0.0) }&.last
35
+ expect(label).to eq(:minimal)
36
+ end
37
+
38
+ it 'maps 0.9 to extreme' do
39
+ label = described_class::RISK_LABELS.find { |range, _| range.cover?(0.9) }&.last
40
+ expect(label).to eq(:extreme)
41
+ end
42
+ end
43
+
44
+ describe 'CLAIM_TYPES' do
45
+ it 'includes all five types' do
46
+ expect(described_class::CLAIM_TYPES).to include(
47
+ :factual, :causal, :explanatory, :predictive, :autobiographical
48
+ )
49
+ end
50
+
51
+ it 'is frozen' do
52
+ expect(described_class::CLAIM_TYPES).to be_frozen
53
+ end
54
+ end
55
+ end