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,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Defense::Whirlpool::Helpers::CapturedThought do
4
+ let(:thought) { described_class.new(content: 'test thought', domain: :analytical) }
5
+
6
+ describe '#initialize' do
7
+ it 'generates a UUID thought_id' do
8
+ expect(thought.thought_id).to match(/\A[0-9a-f-]{36}\z/)
9
+ end
10
+
11
+ it 'stores content and domain' do
12
+ expect(thought.content).to eq('test thought')
13
+ expect(thought.domain).to eq(:analytical)
14
+ end
15
+
16
+ it 'defaults spiral_depth to 0.0' do
17
+ expect(thought.spiral_depth).to eq(0.0)
18
+ end
19
+
20
+ it 'defaults distance_from_core to 1.0' do
21
+ expect(thought.distance_from_core).to eq(1.0)
22
+ end
23
+
24
+ it 'sets captured_at to a UTC time' do
25
+ expect(thought.captured_at).to be_a(Time)
26
+ end
27
+
28
+ it 'clamps spiral_depth above maximum' do
29
+ t = described_class.new(content: 'x', spiral_depth: 5.0)
30
+ expect(t.spiral_depth).to eq(1.0)
31
+ end
32
+
33
+ it 'clamps spiral_depth below zero' do
34
+ t = described_class.new(content: 'x', spiral_depth: -1.0)
35
+ expect(t.spiral_depth).to eq(0.0)
36
+ end
37
+
38
+ it 'clamps distance_from_core above maximum' do
39
+ t = described_class.new(content: 'x', distance_from_core: 3.0)
40
+ expect(t.distance_from_core).to eq(1.0)
41
+ end
42
+
43
+ it 'clamps distance_from_core below zero' do
44
+ t = described_class.new(content: 'x', distance_from_core: -1.0)
45
+ expect(t.distance_from_core).to eq(0.0)
46
+ end
47
+
48
+ it 'defaults domain to :general' do
49
+ t = described_class.new(content: 'x')
50
+ expect(t.domain).to eq(:general)
51
+ end
52
+ end
53
+
54
+ describe '#spiral!' do
55
+ it 'increases spiral_depth' do
56
+ before = thought.spiral_depth
57
+ thought.spiral!
58
+ expect(thought.spiral_depth).to be > before
59
+ end
60
+
61
+ it 'decreases distance_from_core' do
62
+ before = thought.distance_from_core
63
+ thought.spiral!
64
+ expect(thought.distance_from_core).to be < before
65
+ end
66
+
67
+ it 'accepts custom rate' do
68
+ thought.spiral!(rate: 0.2)
69
+ expect(thought.spiral_depth).to be_within(0.001).of(0.2)
70
+ end
71
+
72
+ it 'does not exceed DEPTH_MAX' do
73
+ 20.times { thought.spiral!(rate: 0.3) }
74
+ expect(thought.spiral_depth).to eq(1.0)
75
+ end
76
+
77
+ it 'does not go below 0.0 for distance_from_core' do
78
+ 20.times { thought.spiral!(rate: 0.3) }
79
+ expect(thought.distance_from_core).to eq(0.0)
80
+ end
81
+
82
+ it 'does nothing if thought has escaped' do
83
+ thought.escape!
84
+ depth_before = thought.spiral_depth
85
+ distance_before = thought.distance_from_core
86
+ thought.spiral!
87
+ expect(thought.spiral_depth).to eq(depth_before)
88
+ expect(thought.distance_from_core).to eq(distance_before)
89
+ end
90
+ end
91
+
92
+ describe '#at_core?' do
93
+ it 'returns false when distance_from_core is large' do
94
+ expect(thought.at_core?).to be false
95
+ end
96
+
97
+ it 'returns true when distance_from_core is near zero' do
98
+ t = described_class.new(content: 'x', distance_from_core: 0.02)
99
+ expect(t.at_core?).to be true
100
+ end
101
+
102
+ it 'returns true after spiraling all the way in' do
103
+ 20.times { thought.spiral!(rate: 0.4) }
104
+ expect(thought.at_core?).to be true
105
+ end
106
+ end
107
+
108
+ describe '#escaped?' do
109
+ it 'returns false initially' do
110
+ expect(thought.escaped?).to be false
111
+ end
112
+
113
+ it 'returns true after escape!' do
114
+ thought.escape!
115
+ expect(thought.escaped?).to be true
116
+ end
117
+ end
118
+
119
+ describe '#depth_label' do
120
+ it 'returns :surface for depth 0.0' do
121
+ t = described_class.new(content: 'x', spiral_depth: 0.0)
122
+ expect(t.depth_label).to eq(:surface)
123
+ end
124
+
125
+ it 'returns :insight_core for depth near 1.0' do
126
+ t = described_class.new(content: 'x', spiral_depth: 0.95)
127
+ expect(t.depth_label).to eq(:insight_core)
128
+ end
129
+
130
+ it 'returns :deep for depth 0.7' do
131
+ t = described_class.new(content: 'x', spiral_depth: 0.7)
132
+ expect(t.depth_label).to eq(:deep)
133
+ end
134
+
135
+ it 'returns :mid for depth 0.5' do
136
+ t = described_class.new(content: 'x', spiral_depth: 0.5)
137
+ expect(t.depth_label).to eq(:mid)
138
+ end
139
+
140
+ it 'returns :shallow for depth 0.3' do
141
+ t = described_class.new(content: 'x', spiral_depth: 0.3)
142
+ expect(t.depth_label).to eq(:shallow)
143
+ end
144
+ end
145
+
146
+ describe '#to_h' do
147
+ it 'returns a hash with all fields' do
148
+ h = thought.to_h
149
+ expect(h).to have_key(:thought_id)
150
+ expect(h).to have_key(:content)
151
+ expect(h).to have_key(:domain)
152
+ expect(h).to have_key(:spiral_depth)
153
+ expect(h).to have_key(:distance_from_core)
154
+ expect(h).to have_key(:depth_label)
155
+ expect(h).to have_key(:at_core)
156
+ expect(h).to have_key(:escaped)
157
+ expect(h).to have_key(:captured_at)
158
+ end
159
+
160
+ it 'rounds spiral_depth to 10 decimal places' do
161
+ thought.spiral!(rate: 0.1)
162
+ h = thought.to_h
163
+ expect(h[:spiral_depth]).to be_a(Float)
164
+ end
165
+
166
+ it 'reflects escaped state' do
167
+ thought.escape!
168
+ expect(thought.to_h[:escaped]).to be true
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Defense::Whirlpool::Helpers::Constants do
4
+ describe 'VORTEX_TYPES' do
5
+ it 'is a frozen array of symbols' do
6
+ expect(described_class::VORTEX_TYPES).to be_a(Array)
7
+ expect(described_class::VORTEX_TYPES).to be_frozen
8
+ expect(described_class::VORTEX_TYPES).to all(be_a(Symbol))
9
+ end
10
+
11
+ it 'includes the five cognitive vortex types' do
12
+ expect(described_class::VORTEX_TYPES).to include(:analytical, :creative, :emotional, :procedural, :associative)
13
+ end
14
+ end
15
+
16
+ describe 'MAX_VORTICES' do
17
+ it 'is a positive integer' do
18
+ expect(described_class::MAX_VORTICES).to be_a(Integer)
19
+ expect(described_class::MAX_VORTICES).to be > 0
20
+ end
21
+ end
22
+
23
+ describe 'VELOCITY_DECAY' do
24
+ it 'is a positive float less than 1' do
25
+ expect(described_class::VELOCITY_DECAY).to be_a(Float)
26
+ expect(described_class::VELOCITY_DECAY).to be > 0
27
+ expect(described_class::VELOCITY_DECAY).to be < 1
28
+ end
29
+ end
30
+
31
+ describe 'DEPTH_LABELS' do
32
+ it 'is a frozen array' do
33
+ expect(described_class::DEPTH_LABELS).to be_a(Array)
34
+ end
35
+
36
+ it 'each entry has a range and label' do
37
+ described_class::DEPTH_LABELS.each do |entry|
38
+ expect(entry).to have_key(:range)
39
+ expect(entry).to have_key(:label)
40
+ expect(entry[:range]).to be_a(Range)
41
+ expect(entry[:label]).to be_a(Symbol)
42
+ end
43
+ end
44
+
45
+ it 'includes insight_core label' do
46
+ labels = described_class::DEPTH_LABELS.map { |e| e[:label] }
47
+ expect(labels).to include(:insight_core)
48
+ end
49
+
50
+ it 'covers 0.0 to 1.0' do
51
+ min = described_class::DEPTH_LABELS.map { |e| e[:range].min }.min
52
+ expect(min).to eq(0.0)
53
+ end
54
+ end
55
+
56
+ describe 'thresholds' do
57
+ it 'POWERFUL_VELOCITY > CALM_VELOCITY' do
58
+ expect(described_class::POWERFUL_VELOCITY).to be > described_class::CALM_VELOCITY
59
+ end
60
+
61
+ it 'DISSIPATION_THRESHOLD < ANGULAR_VELOCITY_MIN' do
62
+ expect(described_class::DISSIPATION_THRESHOLD).to be < described_class::ANGULAR_VELOCITY_MIN
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Defense::Whirlpool::Helpers::Vortex do
4
+ let(:vortex) { described_class.new(vortex_type: :analytical, angular_velocity: 0.5, depth: 0.0, capture_radius: 0.5) }
5
+
6
+ let(:thought) do
7
+ Legion::Extensions::Agentic::Defense::Whirlpool::Helpers::CapturedThought.new(
8
+ content: 'test thought', domain: :analytical
9
+ )
10
+ end
11
+
12
+ describe '#initialize' do
13
+ it 'generates a vortex_id' do
14
+ expect(vortex.vortex_id).to match(/\A[0-9a-f-]{36}\z/)
15
+ end
16
+
17
+ it 'stores vortex_type' do
18
+ expect(vortex.vortex_type).to eq(:analytical)
19
+ end
20
+
21
+ it 'clamps angular_velocity within bounds' do
22
+ v = described_class.new(vortex_type: :creative, angular_velocity: 5.0)
23
+ expect(v.angular_velocity).to eq(1.0)
24
+ end
25
+
26
+ it 'clamps depth within bounds' do
27
+ v = described_class.new(vortex_type: :creative, depth: -1.0)
28
+ expect(v.depth).to eq(0.0)
29
+ end
30
+
31
+ it 'raises ArgumentError for unknown vortex_type' do
32
+ expect { described_class.new(vortex_type: :unknown) }.to raise_error(ArgumentError)
33
+ end
34
+
35
+ it 'starts with empty captured_thoughts' do
36
+ expect(vortex.captured_thoughts).to be_empty
37
+ end
38
+
39
+ it 'sets created_at' do
40
+ expect(vortex.created_at).to be_a(Time)
41
+ end
42
+ end
43
+
44
+ describe '#capture!' do
45
+ it 'adds thought to captured_thoughts' do
46
+ vortex.capture!(thought)
47
+ expect(vortex.captured_thoughts.size).to eq(1)
48
+ end
49
+
50
+ it 'returns the thought' do
51
+ result = vortex.capture!(thought)
52
+ expect(result).to eq(thought)
53
+ end
54
+
55
+ it 'raises ArgumentError for non-CapturedThought' do
56
+ expect { vortex.capture!('not a thought') }.to raise_error(ArgumentError)
57
+ end
58
+ end
59
+
60
+ describe '#tick!' do
61
+ before { vortex.capture!(thought) }
62
+
63
+ it 'spirals all captured thoughts deeper' do
64
+ depth_before = thought.spiral_depth
65
+ vortex.tick!
66
+ expect(thought.spiral_depth).to be > depth_before
67
+ end
68
+
69
+ it 'increases the vortex depth' do
70
+ depth_before = vortex.depth
71
+ vortex.tick!
72
+ expect(vortex.depth).to be > depth_before
73
+ end
74
+
75
+ it 'returns self' do
76
+ expect(vortex.tick!).to eq(vortex)
77
+ end
78
+
79
+ it 'accepts custom spiral_rate' do
80
+ vortex.tick!(spiral_rate: 0.3)
81
+ expect(thought.spiral_depth).to be_within(0.001).of(0.3)
82
+ end
83
+ end
84
+
85
+ describe '#dissipate!' do
86
+ it 'reduces angular_velocity' do
87
+ before = vortex.angular_velocity
88
+ vortex.dissipate!
89
+ expect(vortex.angular_velocity).to be < before
90
+ end
91
+
92
+ it 'reduces capture_radius' do
93
+ before = vortex.capture_radius
94
+ vortex.dissipate!
95
+ expect(vortex.capture_radius).to be < before
96
+ end
97
+
98
+ it 'returns self' do
99
+ expect(vortex.dissipate!).to eq(vortex)
100
+ end
101
+
102
+ it 'accepts custom rate' do
103
+ vortex.dissipate!(rate: 0.4)
104
+ expect(vortex.angular_velocity).to be < 0.5
105
+ end
106
+ end
107
+
108
+ describe '#powerful?' do
109
+ it 'returns true when angular_velocity is above POWERFUL_VELOCITY' do
110
+ v = described_class.new(vortex_type: :creative, angular_velocity: 0.9)
111
+ expect(v.powerful?).to be true
112
+ end
113
+
114
+ it 'returns false when angular_velocity is below POWERFUL_VELOCITY' do
115
+ v = described_class.new(vortex_type: :creative, angular_velocity: 0.4)
116
+ expect(v.powerful?).to be false
117
+ end
118
+ end
119
+
120
+ describe '#calm?' do
121
+ it 'returns true when angular_velocity is at or below CALM_VELOCITY' do
122
+ v = described_class.new(vortex_type: :creative, angular_velocity: 0.1)
123
+ expect(v.calm?).to be true
124
+ end
125
+
126
+ it 'returns false when angular_velocity is above CALM_VELOCITY' do
127
+ expect(vortex.calm?).to be false
128
+ end
129
+ end
130
+
131
+ describe '#dissipated?' do
132
+ it 'returns false for a fresh vortex' do
133
+ expect(vortex.dissipated?).to be false
134
+ end
135
+
136
+ it 'returns true after many dissipation cycles' do
137
+ 50.times { vortex.dissipate!(rate: 0.2) }
138
+ expect(vortex.dissipated?).to be true
139
+ end
140
+ end
141
+
142
+ describe '#thoughts_at_core' do
143
+ it 'returns only thoughts at core distance' do
144
+ core_thought = Legion::Extensions::Agentic::Defense::Whirlpool::Helpers::CapturedThought.new(
145
+ content: 'core', distance_from_core: 0.02
146
+ )
147
+ vortex.capture!(thought)
148
+ vortex.capture!(core_thought)
149
+ expect(vortex.thoughts_at_core.size).to eq(1)
150
+ expect(vortex.thoughts_at_core.first).to eq(core_thought)
151
+ end
152
+ end
153
+
154
+ describe '#active_thoughts' do
155
+ it 'excludes escaped thoughts' do
156
+ vortex.capture!(thought)
157
+ escaped = Legion::Extensions::Agentic::Defense::Whirlpool::Helpers::CapturedThought.new(content: 'escaped')
158
+ escaped.escape!
159
+ vortex.capture!(escaped)
160
+ expect(vortex.active_thoughts.size).to eq(1)
161
+ end
162
+ end
163
+
164
+ describe '#depth_label' do
165
+ it 'returns :surface for a new vortex' do
166
+ expect(vortex.depth_label).to eq(:surface)
167
+ end
168
+
169
+ it 'returns :insight_core after deep tick cycles' do
170
+ 50.times { vortex.tick!(spiral_rate: 0.1) }
171
+ expect(vortex.depth_label).to eq(:insight_core)
172
+ end
173
+ end
174
+
175
+ describe '#to_h' do
176
+ it 'returns a hash with all expected keys' do
177
+ h = vortex.to_h
178
+ %i[vortex_id vortex_type angular_velocity depth capture_radius
179
+ depth_label powerful calm dissipated thought_count core_count created_at].each do |key|
180
+ expect(h).to have_key(key)
181
+ end
182
+ end
183
+
184
+ it 'reflects current powerful state' do
185
+ v = described_class.new(vortex_type: :analytical, angular_velocity: 0.9)
186
+ expect(v.to_h[:powerful]).to be true
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Defense::Whirlpool::Helpers::WhirlpoolEngine do
4
+ let(:engine) { described_class.new }
5
+
6
+ describe '#create_vortex' do
7
+ it 'creates a vortex and adds it to the engine' do
8
+ vortex = engine.create_vortex(vortex_type: :analytical)
9
+ expect(engine.vortices.size).to eq(1)
10
+ expect(vortex).to be_a(Legion::Extensions::Agentic::Defense::Whirlpool::Helpers::Vortex)
11
+ end
12
+
13
+ it 'respects vortex_type' do
14
+ vortex = engine.create_vortex(vortex_type: :creative)
15
+ expect(vortex.vortex_type).to eq(:creative)
16
+ end
17
+
18
+ it 'passes angular_velocity' do
19
+ vortex = engine.create_vortex(vortex_type: :analytical, angular_velocity: 0.8)
20
+ expect(vortex.angular_velocity).to eq(0.8)
21
+ end
22
+
23
+ it 'passes depth' do
24
+ vortex = engine.create_vortex(vortex_type: :analytical, depth: 0.3)
25
+ expect(vortex.depth).to eq(0.3)
26
+ end
27
+
28
+ it 'passes capture_radius' do
29
+ vortex = engine.create_vortex(vortex_type: :analytical, capture_radius: 0.7)
30
+ expect(vortex.capture_radius).to eq(0.7)
31
+ end
32
+
33
+ it 'raises ArgumentError at MAX_VORTICES' do
34
+ max = Legion::Extensions::Agentic::Defense::Whirlpool::Helpers::Constants::MAX_VORTICES
35
+ max.times { engine.create_vortex(vortex_type: :analytical) }
36
+ expect { engine.create_vortex(vortex_type: :creative) }.to raise_error(ArgumentError)
37
+ end
38
+
39
+ it 'raises ArgumentError for unknown vortex_type' do
40
+ expect { engine.create_vortex(vortex_type: :unknown) }.to raise_error(ArgumentError)
41
+ end
42
+ end
43
+
44
+ describe '#inject_thought' do
45
+ let(:vortex) { engine.create_vortex(vortex_type: :emotional) }
46
+
47
+ it 'adds a thought to the specified vortex' do
48
+ engine.inject_thought(vortex_id: vortex.vortex_id, content: 'hello')
49
+ expect(vortex.captured_thoughts.size).to eq(1)
50
+ end
51
+
52
+ it 'returns vortex_id and thought_id' do
53
+ result = engine.inject_thought(vortex_id: vortex.vortex_id, content: 'hello')
54
+ expect(result[:vortex_id]).to eq(vortex.vortex_id)
55
+ expect(result[:thought_id]).to match(/\A[0-9a-f-]{36}\z/)
56
+ end
57
+
58
+ it 'accepts domain and distance_from_core' do
59
+ engine.inject_thought(vortex_id: vortex.vortex_id, content: 'thought', domain: :emotional, distance_from_core: 0.5)
60
+ thought = vortex.captured_thoughts.first
61
+ expect(thought.domain).to eq(:emotional)
62
+ expect(thought.distance_from_core).to eq(0.5)
63
+ end
64
+
65
+ it 'raises ArgumentError for unknown vortex_id' do
66
+ expect { engine.inject_thought(vortex_id: 'nonexistent', content: 'x') }.to raise_error(ArgumentError)
67
+ end
68
+ end
69
+
70
+ describe '#tick_all!' do
71
+ before do
72
+ v = engine.create_vortex(vortex_type: :analytical)
73
+ engine.inject_thought(vortex_id: v.vortex_id, content: 'thought 1')
74
+ end
75
+
76
+ it 'returns ticked count' do
77
+ result = engine.tick_all!
78
+ expect(result[:ticked]).to eq(1)
79
+ end
80
+
81
+ it 'includes results per vortex' do
82
+ result = engine.tick_all!
83
+ expect(result[:results]).to be_an(Array)
84
+ expect(result[:results].first).to have_key(:vortex_id)
85
+ end
86
+
87
+ it 'spirals thoughts deeper' do
88
+ vortex = engine.vortices.first
89
+ depth_before = vortex.captured_thoughts.first.spiral_depth
90
+ engine.tick_all!
91
+ expect(vortex.captured_thoughts.first.spiral_depth).to be > depth_before
92
+ end
93
+
94
+ it 'accepts custom spiral_rate' do
95
+ vortex = engine.vortices.first
96
+ engine.tick_all!(spiral_rate: 0.3)
97
+ expect(vortex.captured_thoughts.first.spiral_depth).to be_within(0.001).of(0.3)
98
+ end
99
+ end
100
+
101
+ describe '#dissipate_all!' do
102
+ before do
103
+ engine.create_vortex(vortex_type: :analytical)
104
+ engine.create_vortex(vortex_type: :creative)
105
+ end
106
+
107
+ it 'reduces angular_velocity of all vortices' do
108
+ before_velocities = engine.vortices.map(&:angular_velocity)
109
+ engine.dissipate_all!
110
+ after_velocities = engine.vortices.map(&:angular_velocity)
111
+ expect(after_velocities.zip(before_velocities).all? { |a, b| a <= b }).to be true
112
+ end
113
+
114
+ it 'removes fully dissipated vortices' do
115
+ v = engine.create_vortex(vortex_type: :emotional, angular_velocity: 0.1)
116
+ 50.times { engine.dissipate_all!(rate: 0.3) }
117
+ expect(engine.find_vortex(v.vortex_id)).to be_nil
118
+ end
119
+
120
+ it 'returns dissipated and remaining counts' do
121
+ result = engine.dissipate_all!
122
+ expect(result).to have_key(:dissipated)
123
+ expect(result).to have_key(:remaining)
124
+ end
125
+ end
126
+
127
+ describe '#deepest_vortices' do
128
+ before do
129
+ engine.create_vortex(vortex_type: :analytical, depth: 0.9)
130
+ engine.create_vortex(vortex_type: :creative, depth: 0.5)
131
+ engine.create_vortex(vortex_type: :emotional, depth: 0.2)
132
+ end
133
+
134
+ it 'returns vortices sorted by depth descending' do
135
+ result = engine.deepest_vortices(limit: 3)
136
+ depths = result.map { |v| v[:depth] }
137
+ expect(depths).to eq(depths.sort.reverse)
138
+ end
139
+
140
+ it 'respects limit' do
141
+ result = engine.deepest_vortices(limit: 2)
142
+ expect(result.size).to eq(2)
143
+ end
144
+
145
+ it 'returns hashes' do
146
+ result = engine.deepest_vortices
147
+ expect(result).to all(be_a(Hash))
148
+ end
149
+ end
150
+
151
+ describe '#vortex_report' do
152
+ before do
153
+ v1 = engine.create_vortex(vortex_type: :analytical, angular_velocity: 0.9)
154
+ v2 = engine.create_vortex(vortex_type: :creative, angular_velocity: 0.1)
155
+ engine.inject_thought(vortex_id: v1.vortex_id, content: 'thought A')
156
+ engine.inject_thought(vortex_id: v2.vortex_id, content: 'thought B')
157
+ end
158
+
159
+ it 'returns total_vortices' do
160
+ expect(engine.vortex_report[:total_vortices]).to eq(2)
161
+ end
162
+
163
+ it 'returns powerful_count' do
164
+ expect(engine.vortex_report[:powerful_count]).to eq(1)
165
+ end
166
+
167
+ it 'returns calm_count' do
168
+ expect(engine.vortex_report[:calm_count]).to eq(1)
169
+ end
170
+
171
+ it 'returns total_thoughts' do
172
+ expect(engine.vortex_report[:total_thoughts]).to eq(2)
173
+ end
174
+
175
+ it 'returns core_thoughts count' do
176
+ expect(engine.vortex_report[:core_thoughts]).to be_a(Integer)
177
+ end
178
+
179
+ it 'returns deepest_vortices array' do
180
+ expect(engine.vortex_report[:deepest_vortices]).to be_an(Array)
181
+ end
182
+ end
183
+
184
+ describe '#find_vortex' do
185
+ it 'returns the matching vortex' do
186
+ vortex = engine.create_vortex(vortex_type: :analytical)
187
+ expect(engine.find_vortex(vortex.vortex_id)).to eq(vortex)
188
+ end
189
+
190
+ it 'returns nil for unknown id' do
191
+ expect(engine.find_vortex('nonexistent')).to be_nil
192
+ end
193
+ end
194
+
195
+ describe '#remove_vortex' do
196
+ it 'removes vortex by id' do
197
+ vortex = engine.create_vortex(vortex_type: :analytical)
198
+ engine.remove_vortex(vortex.vortex_id)
199
+ expect(engine.find_vortex(vortex.vortex_id)).to be_nil
200
+ end
201
+
202
+ it 'returns removed count' do
203
+ vortex = engine.create_vortex(vortex_type: :analytical)
204
+ result = engine.remove_vortex(vortex.vortex_id)
205
+ expect(result[:removed]).to eq(1)
206
+ end
207
+
208
+ it 'returns 0 for unknown id' do
209
+ result = engine.remove_vortex('nonexistent')
210
+ expect(result[:removed]).to eq(0)
211
+ end
212
+ end
213
+
214
+ describe '#clear' do
215
+ it 'removes all vortices' do
216
+ engine.create_vortex(vortex_type: :analytical)
217
+ engine.create_vortex(vortex_type: :creative)
218
+ engine.clear
219
+ expect(engine.vortices).to be_empty
220
+ end
221
+
222
+ it 'returns cleared: true' do
223
+ result = engine.clear
224
+ expect(result[:cleared]).to be true
225
+ end
226
+ end
227
+ end