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,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/defense/phantom/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Defense::Phantom::Runners::CognitivePhantom do
6
+ let(:client) { Legion::Extensions::Agentic::Defense::Phantom::Client.new }
7
+
8
+ describe '#register_removal' do
9
+ it 'returns success: true with valid params' do
10
+ result = client.register_removal(capability_name: 'lex-http', capability_domain: :network)
11
+ expect(result[:success]).to be true
12
+ end
13
+
14
+ it 'returns a phantom_id uuid' do
15
+ result = client.register_removal(capability_name: 'lex-http')
16
+ expect(result[:phantom_id]).to match(/\A[0-9a-f-]{36}\z/)
17
+ end
18
+
19
+ it 'returns capability_name in result' do
20
+ result = client.register_removal(capability_name: 'lex-redis')
21
+ expect(result[:capability_name]).to eq('lex-redis')
22
+ end
23
+
24
+ it 'returns state in result' do
25
+ result = client.register_removal(capability_name: 'lex-vault')
26
+ expect(Legion::Extensions::Agentic::Defense::Phantom::Helpers::Constants::PHANTOM_STATES).to include(result[:state])
27
+ end
28
+
29
+ it 'returns intensity in result' do
30
+ result = client.register_removal(capability_name: 'lex-consul')
31
+ expect(result[:intensity]).to be_a(Float)
32
+ expect(result[:intensity]).to be_between(0.0, 1.0)
33
+ end
34
+
35
+ it 'returns success: false for empty capability_name' do
36
+ result = client.register_removal(capability_name: '')
37
+ expect(result[:success]).to be false
38
+ expect(result[:error]).to be_a(String)
39
+ end
40
+
41
+ it 'uses injected engine when provided' do
42
+ eng = Legion::Extensions::Agentic::Defense::Phantom::Helpers::PhantomEngine.new
43
+ result = client.register_removal(capability_name: 'lex-ssh', engine: eng)
44
+ expect(result[:success]).to be true
45
+ expect(eng.all_phantoms.size).to eq(1)
46
+ end
47
+
48
+ it 'defaults capability_domain to :general' do
49
+ result = client.register_removal(capability_name: 'lex-smtp')
50
+ expect(result[:success]).to be true
51
+ end
52
+ end
53
+
54
+ describe '#process_stimulus' do
55
+ before { client.register_removal(capability_name: 'lex-http', capability_domain: :network) }
56
+
57
+ it 'returns success: true' do
58
+ result = client.process_stimulus(stimulus: 'http.get fired')
59
+ expect(result[:success]).to be true
60
+ end
61
+
62
+ it 'returns fired_count' do
63
+ result = client.process_stimulus(stimulus: 'event', domain: :any)
64
+ expect(result[:fired_count]).to be >= 1
65
+ end
66
+
67
+ it 'returns signals array' do
68
+ result = client.process_stimulus(stimulus: 'event', domain: :any)
69
+ expect(result[:signals]).to be_an(Array)
70
+ end
71
+
72
+ it 'signals contain expected keys' do
73
+ result = client.process_stimulus(stimulus: 'event', domain: :any)
74
+ expect(result[:signals].first).to include(:id, :phantom_limb_id, :trigger_type, :intensity_at_trigger)
75
+ end
76
+
77
+ it 'returns domain in result' do
78
+ result = client.process_stimulus(stimulus: 'event', domain: :network)
79
+ expect(result[:domain]).to eq(:network)
80
+ end
81
+
82
+ it 'returns success: false when stimulus is nil' do
83
+ result = client.process_stimulus(stimulus: nil)
84
+ expect(result[:success]).to be false
85
+ end
86
+
87
+ it 'uses injected engine' do
88
+ eng = Legion::Extensions::Agentic::Defense::Phantom::Helpers::PhantomEngine.new
89
+ eng.register_removal(capability_name: 'injected-cap')
90
+ result = client.process_stimulus(stimulus: 'event', domain: :any, engine: eng)
91
+ expect(result[:fired_count]).to eq(1)
92
+ end
93
+ end
94
+
95
+ describe '#acknowledge_phantom' do
96
+ let(:phantom_id) do
97
+ client.register_removal(capability_name: 'lex-github')[:phantom_id]
98
+ end
99
+
100
+ it 'returns success: true for valid phantom_id' do
101
+ result = client.acknowledge_phantom(phantom_id: phantom_id)
102
+ expect(result[:success]).to be true
103
+ end
104
+
105
+ it 'returns acknowledged: true for valid phantom_id' do
106
+ result = client.acknowledge_phantom(phantom_id: phantom_id)
107
+ expect(result[:acknowledged]).to be true
108
+ end
109
+
110
+ it 'returns success: false for unknown phantom_id' do
111
+ result = client.acknowledge_phantom(phantom_id: SecureRandom.uuid)
112
+ expect(result[:success]).to be false
113
+ end
114
+
115
+ it 'returns success: false for empty phantom_id' do
116
+ result = client.acknowledge_phantom(phantom_id: '')
117
+ expect(result[:success]).to be false
118
+ expect(result[:error]).to be_a(String)
119
+ end
120
+
121
+ it 'uses injected engine' do
122
+ eng = Legion::Extensions::Agentic::Defense::Phantom::Helpers::PhantomEngine.new
123
+ limb = eng.register_removal(capability_name: 'injected-cap')
124
+ result = client.acknowledge_phantom(phantom_id: limb.id, engine: eng)
125
+ expect(result[:acknowledged]).to be true
126
+ end
127
+ end
128
+
129
+ describe '#phantom_status' do
130
+ before do
131
+ client.register_removal(capability_name: 'lex-http')
132
+ client.register_removal(capability_name: 'lex-vault')
133
+ end
134
+
135
+ it 'returns success: true' do
136
+ expect(client.phantom_status[:success]).to be true
137
+ end
138
+
139
+ it 'returns total count' do
140
+ expect(client.phantom_status[:total]).to eq(2)
141
+ end
142
+
143
+ it 'returns active count' do
144
+ expect(client.phantom_status[:active]).to be >= 0
145
+ end
146
+
147
+ it 'returns by_state breakdown' do
148
+ expect(client.phantom_status[:by_state]).to be_a(Hash)
149
+ end
150
+
151
+ it 'returns total_activations' do
152
+ client.process_stimulus(stimulus: 'event', domain: :any)
153
+ expect(client.phantom_status[:total_activations]).to be >= 2
154
+ end
155
+
156
+ it 'uses injected engine' do
157
+ eng = Legion::Extensions::Agentic::Defense::Phantom::Helpers::PhantomEngine.new
158
+ eng.register_removal(capability_name: 'cap')
159
+ result = client.phantom_status(engine: eng)
160
+ expect(result[:total]).to eq(1)
161
+ end
162
+ end
163
+
164
+ describe '#decay_all' do
165
+ before { client.register_removal(capability_name: 'lex-redis') }
166
+
167
+ it 'returns success: true' do
168
+ expect(client.decay_all[:success]).to be true
169
+ end
170
+
171
+ it 'returns resolved_this_cycle count' do
172
+ expect(client.decay_all[:resolved_this_cycle]).to be >= 0
173
+ end
174
+
175
+ it 'includes total in result' do
176
+ expect(client.decay_all[:total]).to be >= 0
177
+ end
178
+
179
+ it 'decays phantom intensity' do
180
+ client.phantom_status[:by_state][:acute]
181
+ client.decay_all
182
+ after_report = client.phantom_status
183
+ expect(after_report[:total]).to be >= 0
184
+ end
185
+
186
+ it 'uses injected engine' do
187
+ eng = Legion::Extensions::Agentic::Defense::Phantom::Helpers::PhantomEngine.new
188
+ eng.register_removal(capability_name: 'cap')
189
+ result = client.decay_all(engine: eng)
190
+ expect(result[:success]).to be true
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Defense::Quicksand::Client do
4
+ subject(:client) { described_class.new }
5
+
6
+ it 'includes the runner module' do
7
+ expect(described_class.ancestors).to include(
8
+ Legion::Extensions::Agentic::Defense::Quicksand::Runners::CognitiveQuicksand
9
+ )
10
+ end
11
+
12
+ it 'responds to create_trap' do
13
+ expect(client).to respond_to(:create_trap)
14
+ end
15
+
16
+ it 'responds to struggle' do
17
+ expect(client).to respond_to(:struggle)
18
+ end
19
+
20
+ it 'responds to attempt_escape' do
21
+ expect(client).to respond_to(:attempt_escape)
22
+ end
23
+
24
+ it 'responds to quicksand_status' do
25
+ expect(client).to respond_to(:quicksand_status)
26
+ end
27
+
28
+ it 'can create and struggle through client' do
29
+ result = client.create_trap(trap_type: :overthinking, domain: :test, content: 'loop')
30
+ expect(result[:success]).to be true
31
+ trap_id = result[:trap][:id]
32
+ struggle_result = client.struggle(trap_id: trap_id)
33
+ expect(struggle_result[:success]).to be true
34
+ end
35
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Defense::Quicksand::Helpers::Constants do
4
+ described_class = Legion::Extensions::Agentic::Defense::Quicksand::Helpers::Constants
5
+
6
+ describe 'TRAP_TYPES' do
7
+ it 'contains expected types' do
8
+ expect(described_class::TRAP_TYPES).to eq(%i[overthinking rumination analysis_paralysis
9
+ perfectionism indecision])
10
+ end
11
+
12
+ it 'is frozen' do
13
+ expect(described_class::TRAP_TYPES).to be_frozen
14
+ end
15
+ end
16
+
17
+ describe 'STRUGGLE_MODES' do
18
+ it 'contains expected modes' do
19
+ expect(described_class::STRUGGLE_MODES).to eq(%i[thrash freeze sink float escape])
20
+ end
21
+ end
22
+
23
+ describe 'numeric constants' do
24
+ it 'defines MAX_TRAPS' do
25
+ expect(described_class::MAX_TRAPS).to eq(200)
26
+ end
27
+
28
+ it 'defines SINK_RATE' do
29
+ expect(described_class::SINK_RATE).to eq(0.08)
30
+ end
31
+
32
+ it 'defines STRUGGLE_PENALTY' do
33
+ expect(described_class::STRUGGLE_PENALTY).to eq(0.12)
34
+ end
35
+
36
+ it 'defines ESCAPE_THRESHOLD' do
37
+ expect(described_class::ESCAPE_THRESHOLD).to eq(0.3)
38
+ end
39
+ end
40
+
41
+ describe '.label_for' do
42
+ it 'returns :submerged for high depth' do
43
+ expect(described_class.label_for(described_class::DEPTH_LABELS, 0.9)).to eq(:submerged)
44
+ end
45
+
46
+ it 'returns :surface for low depth' do
47
+ expect(described_class.label_for(described_class::DEPTH_LABELS, 0.1)).to eq(:surface)
48
+ end
49
+
50
+ it 'returns :concrete for high viscosity' do
51
+ expect(described_class.label_for(described_class::VISCOSITY_LABELS, 0.9)).to eq(:concrete)
52
+ end
53
+
54
+ it 'returns :dry for low viscosity' do
55
+ expect(described_class.label_for(described_class::VISCOSITY_LABELS, 0.1)).to eq(:dry)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Defense::Quicksand::Helpers::Pit do
4
+ subject(:pit) { described_class.new }
5
+
6
+ describe '#initialize' do
7
+ it 'assigns a UUID id' do
8
+ expect(pit.id).to match(/\A[0-9a-f-]{36}\z/)
9
+ end
10
+
11
+ it 'defaults saturation to 0.5' do
12
+ expect(pit.saturation).to eq(0.5)
13
+ end
14
+
15
+ it 'defaults danger_level to 0.3' do
16
+ expect(pit.danger_level).to eq(0.3)
17
+ end
18
+
19
+ it 'starts with empty trap_ids' do
20
+ expect(pit.trap_ids).to be_empty
21
+ end
22
+ end
23
+
24
+ describe '#add_trap' do
25
+ it 'adds a trap id' do
26
+ expect(pit.add_trap('t-1')).to eq(:added)
27
+ expect(pit.trap_ids).to include('t-1')
28
+ end
29
+
30
+ it 'returns :already_present for duplicates' do
31
+ pit.add_trap('t-1')
32
+ expect(pit.add_trap('t-1')).to eq(:already_present)
33
+ end
34
+
35
+ it 'recalculates danger level' do
36
+ initial = pit.danger_level
37
+ 5.times { |i| pit.add_trap("t-#{i}") }
38
+ expect(pit.danger_level).not_to eq(initial)
39
+ end
40
+ end
41
+
42
+ describe '#remove_trap' do
43
+ it 'removes a trap id' do
44
+ pit.add_trap('t-1')
45
+ expect(pit.remove_trap('t-1')).to eq(:removed)
46
+ end
47
+
48
+ it 'returns :not_found for missing' do
49
+ expect(pit.remove_trap('nope')).to eq(:not_found)
50
+ end
51
+ end
52
+
53
+ describe '#saturate!' do
54
+ it 'increases saturation' do
55
+ initial = pit.saturation
56
+ pit.saturate!(rate: 0.1)
57
+ expect(pit.saturation).to eq((initial + 0.1).round(10))
58
+ end
59
+ end
60
+
61
+ describe '#drain!' do
62
+ it 'decreases saturation' do
63
+ initial = pit.saturation
64
+ pit.drain!(rate: 0.1)
65
+ expect(pit.saturation).to eq((initial - 0.1).round(10))
66
+ end
67
+ end
68
+
69
+ describe '#deadly?' do
70
+ it 'returns false at default' do
71
+ expect(pit).not_to be_deadly
72
+ end
73
+
74
+ it 'returns true when danger_level >= 0.8' do
75
+ pit.danger_level = 0.9
76
+ expect(pit).to be_deadly
77
+ end
78
+ end
79
+
80
+ describe '#safe?' do
81
+ it 'returns false at default' do
82
+ expect(pit).not_to be_safe
83
+ end
84
+
85
+ it 'returns true when danger_level < 0.2' do
86
+ pit.danger_level = 0.1
87
+ expect(pit).to be_safe
88
+ end
89
+ end
90
+
91
+ describe '#trap_count' do
92
+ it 'returns 0 initially' do
93
+ expect(pit.trap_count).to eq(0)
94
+ end
95
+ end
96
+
97
+ describe '#to_h' do
98
+ it 'includes all expected keys' do
99
+ expected = %i[id saturation danger_level trap_count deadly safe created_at]
100
+ expect(pit.to_h.keys).to match_array(expected)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Defense::Quicksand::Helpers::QuicksandEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ let(:default_attrs) { { trap_type: :overthinking, domain: :reasoning, content: 'loop' } }
7
+
8
+ describe '#create_trap' do
9
+ it 'creates and stores a trap' do
10
+ t = engine.create_trap(**default_attrs)
11
+ expect(t).to be_a(Legion::Extensions::Agentic::Defense::Quicksand::Helpers::Trap)
12
+ expect(engine.all_traps.size).to eq(1)
13
+ end
14
+
15
+ it 'raises when limit reached' do
16
+ stub_const('Legion::Extensions::Agentic::Defense::Quicksand::Helpers::Constants::MAX_TRAPS', 1)
17
+ engine.create_trap(**default_attrs)
18
+ expect do
19
+ engine.create_trap(trap_type: :rumination, domain: :t, content: 'x')
20
+ end.to raise_error(ArgumentError, /trap limit/)
21
+ end
22
+ end
23
+
24
+ describe '#create_pit' do
25
+ it 'creates and stores a pit' do
26
+ p = engine.create_pit
27
+ expect(p).to be_a(Legion::Extensions::Agentic::Defense::Quicksand::Helpers::Pit)
28
+ expect(engine.all_pits.size).to eq(1)
29
+ end
30
+ end
31
+
32
+ describe '#sink_trap' do
33
+ it 'increases trap depth' do
34
+ t = engine.create_trap(**default_attrs)
35
+ initial = t.depth
36
+ engine.sink_trap(trap_id: t.id)
37
+ expect(t.depth).to be > initial
38
+ end
39
+
40
+ it 'raises for unknown trap' do
41
+ expect do
42
+ engine.sink_trap(trap_id: 'bad')
43
+ end.to raise_error(ArgumentError, /trap not found/)
44
+ end
45
+ end
46
+
47
+ describe '#struggle' do
48
+ it 'returns trap data with depth and count' do
49
+ t = engine.create_trap(**default_attrs)
50
+ result = engine.struggle(trap_id: t.id)
51
+ expect(result[:struggle_count]).to eq(1)
52
+ expect(result[:depth]).to be > 0.3
53
+ end
54
+ end
55
+
56
+ describe '#calm' do
57
+ it 'decreases trap depth' do
58
+ t = engine.create_trap(**default_attrs)
59
+ initial = t.depth
60
+ engine.calm(trap_id: t.id)
61
+ expect(t.depth).to be < initial
62
+ end
63
+ end
64
+
65
+ describe '#attempt_escape' do
66
+ it 'escapes when shallow' do
67
+ t = engine.create_trap(**default_attrs, depth: 0.5)
68
+ result = engine.attempt_escape(trap_id: t.id)
69
+ expect(result[:result]).to eq(:escaped)
70
+ end
71
+
72
+ it 'fails when deep' do
73
+ t = engine.create_trap(**default_attrs, depth: 0.9)
74
+ result = engine.attempt_escape(trap_id: t.id)
75
+ expect(result[:result]).to eq(:too_deep)
76
+ end
77
+ end
78
+
79
+ describe '#add_trap_to_pit' do
80
+ it 'links trap to pit' do
81
+ t = engine.create_trap(**default_attrs)
82
+ p = engine.create_pit
83
+ expect(engine.add_trap_to_pit(trap_id: t.id, pit_id: p.id)).to eq(:added)
84
+ end
85
+ end
86
+
87
+ describe '#sink_all!' do
88
+ it 'sinks all traps' do
89
+ t1 = engine.create_trap(**default_attrs)
90
+ t2 = engine.create_trap(trap_type: :rumination, domain: :t, content: 'x')
91
+ engine.sink_all!
92
+ expect(t1.depth).to be > 0.3
93
+ expect(t2.depth).to be > 0.3
94
+ end
95
+ end
96
+
97
+ describe '#calm_all!' do
98
+ it 'calms all traps' do
99
+ t = engine.create_trap(**default_attrs, depth: 0.5)
100
+ engine.calm_all!
101
+ expect(t.depth).to be < 0.5
102
+ end
103
+ end
104
+
105
+ describe '#traps_by_type' do
106
+ it 'returns counts' do
107
+ engine.create_trap(**default_attrs)
108
+ engine.create_trap(trap_type: :rumination, domain: :t, content: 'x')
109
+ counts = engine.traps_by_type
110
+ expect(counts[:overthinking]).to eq(1)
111
+ expect(counts[:rumination]).to eq(1)
112
+ end
113
+ end
114
+
115
+ describe '#deepest' do
116
+ it 'returns traps sorted by depth descending' do
117
+ engine.create_trap(**default_attrs, depth: 0.2)
118
+ t2 = engine.create_trap(trap_type: :rumination, domain: :t, content: 'x', depth: 0.9)
119
+ expect(engine.deepest(limit: 1).first).to eq(t2)
120
+ end
121
+ end
122
+
123
+ describe '#submerged_traps' do
124
+ it 'returns only submerged' do
125
+ engine.create_trap(**default_attrs, depth: 0.9)
126
+ engine.create_trap(trap_type: :rumination, domain: :t, content: 'x', depth: 0.3)
127
+ expect(engine.submerged_traps.size).to eq(1)
128
+ end
129
+ end
130
+
131
+ describe '#stuck_traps' do
132
+ it 'returns stuck traps' do
133
+ engine.create_trap(**default_attrs, depth: 0.6, viscosity: 0.7)
134
+ engine.create_trap(trap_type: :rumination, domain: :t, content: 'x', depth: 0.1)
135
+ expect(engine.stuck_traps.size).to eq(1)
136
+ end
137
+ end
138
+
139
+ describe '#quicksand_report' do
140
+ it 'returns comprehensive hash' do
141
+ engine.create_trap(**default_attrs)
142
+ report = engine.quicksand_report
143
+ expect(report).to include(:total_traps, :total_pits, :by_type,
144
+ :submerged, :stuck, :avg_depth, :deadly_pits)
145
+ end
146
+
147
+ it 'handles empty engine' do
148
+ report = engine.quicksand_report
149
+ expect(report[:total_traps]).to eq(0)
150
+ expect(report[:avg_depth]).to eq(0.0)
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Defense::Quicksand::Helpers::Trap do
4
+ subject(:trap) do
5
+ described_class.new(trap_type: :overthinking, domain: :reasoning, content: 'recursive loop')
6
+ end
7
+
8
+ describe '#initialize' do
9
+ it 'assigns a UUID id' do
10
+ expect(trap.id).to match(/\A[0-9a-f-]{36}\z/)
11
+ end
12
+
13
+ it 'sets trap_type as symbol' do
14
+ expect(trap.trap_type).to eq(:overthinking)
15
+ end
16
+
17
+ it 'defaults depth to 0.3' do
18
+ expect(trap.depth).to eq(0.3)
19
+ end
20
+
21
+ it 'defaults viscosity to 0.5' do
22
+ expect(trap.viscosity).to eq(0.5)
23
+ end
24
+
25
+ it 'starts with 0 struggle_count' do
26
+ expect(trap.struggle_count).to eq(0)
27
+ end
28
+
29
+ it 'accepts custom depth' do
30
+ t = described_class.new(trap_type: :rumination, domain: :t, content: 'x', depth: 0.7)
31
+ expect(t.depth).to eq(0.7)
32
+ end
33
+
34
+ it 'clamps depth to 0..1' do
35
+ t = described_class.new(trap_type: :indecision, domain: :t, content: 'x', depth: 5.0)
36
+ expect(t.depth).to eq(1.0)
37
+ end
38
+
39
+ it 'raises on unknown trap type' do
40
+ expect do
41
+ described_class.new(trap_type: :happiness, domain: :t, content: 'x')
42
+ end.to raise_error(ArgumentError, /unknown trap type/)
43
+ end
44
+ end
45
+
46
+ describe '#sink!' do
47
+ it 'increases depth' do
48
+ initial = trap.depth
49
+ trap.sink!
50
+ expect(trap.depth).to eq((initial + 0.08).round(10))
51
+ end
52
+
53
+ it 'accepts custom rate' do
54
+ trap.sink!(rate: 0.2)
55
+ expect(trap.depth).to eq(0.5)
56
+ end
57
+
58
+ it 'clamps at 1.0' do
59
+ trap.depth = 0.95
60
+ trap.sink!(rate: 0.2)
61
+ expect(trap.depth).to eq(1.0)
62
+ end
63
+ end
64
+
65
+ describe '#struggle!' do
66
+ it 'increases depth based on viscosity' do
67
+ initial = trap.depth
68
+ trap.struggle!
69
+ penalty = 0.12 * 0.5
70
+ expect(trap.depth).to eq((initial + penalty).round(10))
71
+ end
72
+
73
+ it 'increments struggle_count' do
74
+ trap.struggle!
75
+ expect(trap.struggle_count).to eq(1)
76
+ end
77
+
78
+ it 'sinks faster with high viscosity' do
79
+ trap.viscosity = 0.9
80
+ initial = trap.depth
81
+ trap.struggle!
82
+ expect(trap.depth).to be > (initial + 0.1)
83
+ end
84
+ end
85
+
86
+ describe '#calm!' do
87
+ it 'decreases depth' do
88
+ initial = trap.depth
89
+ trap.calm!
90
+ expect(trap.depth).to eq((initial - 0.03).round(10))
91
+ end
92
+
93
+ it 'clamps at 0' do
94
+ trap.depth = 0.01
95
+ trap.calm!(rate: 0.1)
96
+ expect(trap.depth).to eq(0.0)
97
+ end
98
+ end
99
+
100
+ describe '#escape!' do
101
+ it 'returns :escaped when shallow enough' do
102
+ trap.depth = 0.5
103
+ expect(trap.escape!).to eq(:escaped)
104
+ expect(trap.depth).to eq(0.0)
105
+ end
106
+
107
+ it 'returns :too_deep when too deep' do
108
+ trap.depth = 0.8
109
+ expect(trap.escape!).to eq(:too_deep)
110
+ end
111
+ end
112
+
113
+ describe '#submerged?' do
114
+ it 'returns false at default depth' do
115
+ expect(trap).not_to be_submerged
116
+ end
117
+
118
+ it 'returns true at 0.8+' do
119
+ trap.depth = 0.85
120
+ expect(trap).to be_submerged
121
+ end
122
+ end
123
+
124
+ describe '#surface?' do
125
+ it 'returns false at default depth' do
126
+ expect(trap).not_to be_surface
127
+ end
128
+
129
+ it 'returns true below 0.2' do
130
+ trap.depth = 0.1
131
+ expect(trap).to be_surface
132
+ end
133
+ end
134
+
135
+ describe '#stuck?' do
136
+ it 'returns false at default' do
137
+ expect(trap).not_to be_stuck
138
+ end
139
+
140
+ it 'returns true when deep and viscous' do
141
+ trap.depth = 0.6
142
+ trap.viscosity = 0.7
143
+ expect(trap).to be_stuck
144
+ end
145
+ end
146
+
147
+ describe '#depth_label' do
148
+ it 'returns :ankle_deep at default' do
149
+ expect(trap.depth_label).to eq(:ankle_deep)
150
+ end
151
+ end
152
+
153
+ describe '#viscosity_label' do
154
+ it 'returns :moderate at default' do
155
+ expect(trap.viscosity_label).to eq(:moderate)
156
+ end
157
+ end
158
+
159
+ describe '#to_h' do
160
+ it 'includes all expected keys' do
161
+ expected = %i[id trap_type domain content depth viscosity depth_label
162
+ viscosity_label struggle_count submerged surface stuck created_at]
163
+ expect(trap.to_h.keys).to match_array(expected)
164
+ end
165
+ end
166
+ end