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,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/defense/phantom/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Defense::Phantom::Client do
6
+ subject(:client) { described_class.new }
7
+
8
+ it 'includes the CognitivePhantom runner' do
9
+ expect(client).to respond_to(:register_removal)
10
+ expect(client).to respond_to(:process_stimulus)
11
+ expect(client).to respond_to(:acknowledge_phantom)
12
+ expect(client).to respond_to(:phantom_status)
13
+ expect(client).to respond_to(:decay_all)
14
+ end
15
+
16
+ it 'can be instantiated with keyword splat' do
17
+ expect { described_class.new(foo: :bar) }.not_to raise_error
18
+ end
19
+
20
+ describe 'full lifecycle' do
21
+ it 'registers, triggers, acknowledges, and decays a phantom' do
22
+ reg = client.register_removal(capability_name: 'lex-ssh', capability_domain: :remote)
23
+ expect(reg[:success]).to be true
24
+
25
+ stim = client.process_stimulus(stimulus: 'ssh connect attempt', domain: :any)
26
+ expect(stim[:fired_count]).to be >= 1
27
+
28
+ ack = client.acknowledge_phantom(phantom_id: reg[:phantom_id])
29
+ expect(ack[:acknowledged]).to be true
30
+
31
+ decay = client.decay_all
32
+ expect(decay[:success]).to be true
33
+ end
34
+
35
+ it 'tracks multiple phantom removals independently' do
36
+ client.register_removal(capability_name: 'lex-http')
37
+ client.register_removal(capability_name: 'lex-redis')
38
+ client.register_removal(capability_name: 'lex-vault')
39
+
40
+ status = client.phantom_status
41
+ expect(status[:total]).to eq(3)
42
+ expect(status[:active]).to eq(3)
43
+ end
44
+
45
+ it 'resolves phantom after many acknowledge calls' do
46
+ reg = client.register_removal(capability_name: 'short-lived-cap')
47
+ 50.times { client.acknowledge_phantom(phantom_id: reg[:phantom_id]) }
48
+ status = client.phantom_status
49
+ resolved = status[:by_state][:resolved]
50
+ expect(resolved).to be >= 1
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Defense::Phantom::Helpers::Constants do
4
+ describe 'constants' do
5
+ it 'defines MAX_PHANTOMS as 100' do
6
+ expect(described_class::MAX_PHANTOMS).to eq(100)
7
+ end
8
+
9
+ it 'defines INITIAL_INTENSITY as 0.8' do
10
+ expect(described_class::INITIAL_INTENSITY).to eq(0.8)
11
+ end
12
+
13
+ it 'defines INTENSITY_DECAY as 0.05' do
14
+ expect(described_class::INTENSITY_DECAY).to eq(0.05)
15
+ end
16
+
17
+ it 'defines MIN_INTENSITY as 0.01' do
18
+ expect(described_class::MIN_INTENSITY).to eq(0.01)
19
+ end
20
+
21
+ it 'defines four PHANTOM_STATES' do
22
+ expect(described_class::PHANTOM_STATES).to eq(%i[acute adapting residual resolved])
23
+ end
24
+
25
+ it 'defines four TRIGGER_TYPES' do
26
+ expect(described_class::TRIGGER_TYPES).to eq(%i[stimulus_match contextual_association temporal_pattern habitual])
27
+ end
28
+
29
+ it 'PHANTOM_STATES is frozen' do
30
+ expect(described_class::PHANTOM_STATES).to be_frozen
31
+ end
32
+
33
+ it 'TRIGGER_TYPES is frozen' do
34
+ expect(described_class::TRIGGER_TYPES).to be_frozen
35
+ end
36
+ end
37
+
38
+ describe '.label_for' do
39
+ it 'returns label for :acute' do
40
+ label = described_class.label_for(:acute)
41
+ expect(label).to be_a(String)
42
+ expect(label).not_to be_empty
43
+ end
44
+
45
+ it 'returns label for :adapting' do
46
+ label = described_class.label_for(:adapting)
47
+ expect(label).to be_a(String)
48
+ end
49
+
50
+ it 'returns label for :residual' do
51
+ label = described_class.label_for(:residual)
52
+ expect(label).to be_a(String)
53
+ end
54
+
55
+ it 'returns label for :resolved' do
56
+ label = described_class.label_for(:resolved)
57
+ expect(label).to be_a(String)
58
+ end
59
+
60
+ it 'returns unknown label for unrecognized state' do
61
+ label = described_class.label_for(:nonexistent)
62
+ expect(label).to include('Unknown')
63
+ end
64
+ end
65
+
66
+ describe '.state_for' do
67
+ it 'returns :acute for intensity >= 0.6' do
68
+ expect(described_class.state_for(0.8)).to eq(:acute)
69
+ expect(described_class.state_for(0.6)).to eq(:acute)
70
+ end
71
+
72
+ it 'returns :adapting for intensity in [0.3, 0.6)' do
73
+ expect(described_class.state_for(0.5)).to eq(:adapting)
74
+ expect(described_class.state_for(0.3)).to eq(:adapting)
75
+ end
76
+
77
+ it 'returns :residual for intensity in (MIN_INTENSITY, 0.3)' do
78
+ expect(described_class.state_for(0.15)).to eq(:residual)
79
+ expect(described_class.state_for(0.02)).to eq(:residual)
80
+ end
81
+
82
+ it 'returns :resolved for intensity at or below MIN_INTENSITY' do
83
+ expect(described_class.state_for(0.01)).to eq(:resolved)
84
+ expect(described_class.state_for(0.0)).to eq(:resolved)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Defense::Phantom::Helpers::PhantomEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ describe '#register_removal' do
7
+ it 'returns a PhantomLimb' do
8
+ limb = engine.register_removal(capability_name: 'lex-http', capability_domain: :network)
9
+ expect(limb).to be_a(Legion::Extensions::Agentic::Defense::Phantom::Helpers::PhantomLimb)
10
+ end
11
+
12
+ it 'assigns a uuid id' do
13
+ limb = engine.register_removal(capability_name: 'lex-redis')
14
+ expect(limb.id).to match(/\A[0-9a-f-]{36}\z/)
15
+ end
16
+
17
+ it 'defaults capability_domain to :general' do
18
+ limb = engine.register_removal(capability_name: 'lex-vault')
19
+ expect(limb.capability_domain).to eq(:general)
20
+ end
21
+
22
+ it 'adds limb to all_phantoms' do
23
+ engine.register_removal(capability_name: 'lex-http')
24
+ expect(engine.all_phantoms.size).to eq(1)
25
+ end
26
+
27
+ it 'returns nil when MAX_PHANTOMS is reached' do
28
+ stub_const('Legion::Extensions::Agentic::Defense::Phantom::Helpers::Constants::MAX_PHANTOMS', 2)
29
+ engine.register_removal(capability_name: 'cap1')
30
+ engine.register_removal(capability_name: 'cap2')
31
+ result = engine.register_removal(capability_name: 'cap3')
32
+ expect(result).to be_nil
33
+ end
34
+
35
+ it 'allows up to MAX_PHANTOMS registrations' do
36
+ stub_const('Legion::Extensions::Agentic::Defense::Phantom::Helpers::Constants::MAX_PHANTOMS', 3)
37
+ 3.times { |i| engine.register_removal(capability_name: "cap_#{i}") }
38
+ expect(engine.all_phantoms.size).to eq(3)
39
+ end
40
+ end
41
+
42
+ describe '#process_stimulus' do
43
+ before do
44
+ engine.register_removal(capability_name: 'lex-http', capability_domain: :network)
45
+ engine.register_removal(capability_name: 'lex-redis', capability_domain: :cache)
46
+ end
47
+
48
+ it 'returns an array of PhantomSignals' do
49
+ signals = engine.process_stimulus(stimulus: 'test', domain: :any)
50
+ expect(signals).to all(be_a(Legion::Extensions::Agentic::Defense::Phantom::Helpers::PhantomSignal))
51
+ end
52
+
53
+ it 'fires all active phantoms when domain is :any' do
54
+ signals = engine.process_stimulus(stimulus: 'event', domain: :any)
55
+ expect(signals.size).to eq(2)
56
+ end
57
+
58
+ it 'only fires phantoms matching domain' do
59
+ signals = engine.process_stimulus(stimulus: 'cache miss', domain: :cache)
60
+ expect(signals.size).to eq(1)
61
+ expect(signals.first.phantom_limb_id).to be_a(String)
62
+ end
63
+
64
+ it 'returns empty array when no matching domain' do
65
+ signals = engine.process_stimulus(stimulus: 'event', domain: :storage)
66
+ expect(signals).to be_empty
67
+ end
68
+
69
+ it 'does not fire resolved phantoms' do
70
+ limb = engine.all_phantoms.first
71
+ 160.times { limb.decay! }
72
+ signals = engine.process_stimulus(stimulus: 'lex-http request', domain: :network)
73
+ expect(signals).to be_empty
74
+ end
75
+ end
76
+
77
+ describe '#decay_all!' do
78
+ before { engine.register_removal(capability_name: 'lex-http') }
79
+
80
+ it 'reduces intensity of all active phantoms' do
81
+ before_intensity = engine.active_phantoms.first.intensity
82
+ engine.decay_all!
83
+ expect(engine.active_phantoms.first.intensity).to be < before_intensity
84
+ end
85
+
86
+ it 'returns count of newly resolved phantoms' do
87
+ stub_const('Legion::Extensions::Agentic::Defense::Phantom::Helpers::Constants::INITIAL_INTENSITY', Legion::Extensions::Agentic::Defense::Phantom::Helpers::Constants::MIN_INTENSITY)
88
+ engine2 = described_class.new
89
+ engine2.register_removal(capability_name: 'cap_at_min')
90
+ count = engine2.decay_all!
91
+ expect(count).to be >= 0
92
+ end
93
+ end
94
+
95
+ describe '#acknowledge' do
96
+ let!(:limb) { engine.register_removal(capability_name: 'lex-vault') }
97
+
98
+ it 'returns acknowledged: true for valid phantom_id' do
99
+ result = engine.acknowledge(phantom_id: limb.id)
100
+ expect(result[:acknowledged]).to be true
101
+ end
102
+
103
+ it 'returns phantom_id in result' do
104
+ result = engine.acknowledge(phantom_id: limb.id)
105
+ expect(result[:phantom_id]).to eq(limb.id)
106
+ end
107
+
108
+ it 'returns state in result' do
109
+ result = engine.acknowledge(phantom_id: limb.id)
110
+ expect(Legion::Extensions::Agentic::Defense::Phantom::Helpers::Constants::PHANTOM_STATES).to include(result[:state])
111
+ end
112
+
113
+ it 'accelerates decay on acknowledgment' do
114
+ before_intensity = limb.intensity
115
+ engine.acknowledge(phantom_id: limb.id)
116
+ expect(limb.intensity).to be < before_intensity
117
+ end
118
+
119
+ it 'returns acknowledged: false for unknown phantom_id' do
120
+ result = engine.acknowledge(phantom_id: 'nonexistent-uuid')
121
+ expect(result[:acknowledged]).to be false
122
+ expect(result[:reason]).to eq(:not_found)
123
+ end
124
+ end
125
+
126
+ describe '#all_phantoms' do
127
+ it 'returns all registered phantoms including resolved' do
128
+ engine.register_removal(capability_name: 'cap1')
129
+ engine.register_removal(capability_name: 'cap2')
130
+ expect(engine.all_phantoms.size).to eq(2)
131
+ end
132
+ end
133
+
134
+ describe '#active_phantoms' do
135
+ it 'excludes resolved phantoms' do
136
+ engine.register_removal(capability_name: 'cap1')
137
+ limb = engine.register_removal(capability_name: 'cap2')
138
+ 160.times { limb.decay! }
139
+ expect(engine.active_phantoms.size).to eq(1)
140
+ end
141
+ end
142
+
143
+ describe '#phantom_activity_report' do
144
+ before do
145
+ engine.register_removal(capability_name: 'cap1', capability_domain: :network)
146
+ engine.register_removal(capability_name: 'cap2', capability_domain: :cache)
147
+ end
148
+
149
+ it 'returns total count' do
150
+ expect(engine.phantom_activity_report[:total]).to eq(2)
151
+ end
152
+
153
+ it 'returns active count' do
154
+ expect(engine.phantom_activity_report[:active]).to eq(2)
155
+ end
156
+
157
+ it 'returns by_state breakdown' do
158
+ report = engine.phantom_activity_report
159
+ expect(report[:by_state]).to be_a(Hash)
160
+ expect(report[:by_state].keys).to match_array(%i[acute adapting residual resolved])
161
+ end
162
+
163
+ it 'returns total_activations' do
164
+ engine.process_stimulus(stimulus: 'event', domain: :any)
165
+ expect(engine.phantom_activity_report[:total_activations]).to eq(2)
166
+ end
167
+ end
168
+
169
+ describe '#most_persistent' do
170
+ before do
171
+ 3.times do |i|
172
+ limb = engine.register_removal(capability_name: "cap_#{i}")
173
+ i.times { limb.trigger!("stimulus_#{i}") }
174
+ end
175
+ end
176
+
177
+ it 'returns active phantoms sorted by activation_count descending' do
178
+ result = engine.most_persistent(limit: 3)
179
+ counts = result.map(&:activation_count)
180
+ expect(counts).to eq(counts.sort.reverse)
181
+ end
182
+
183
+ it 'respects limit' do
184
+ result = engine.most_persistent(limit: 2)
185
+ expect(result.size).to be <= 2
186
+ end
187
+ end
188
+
189
+ describe '#recently_triggered' do
190
+ before do
191
+ 2.times do |i|
192
+ limb = engine.register_removal(capability_name: "cap_#{i}")
193
+ limb.trigger!("stimulus_#{i}")
194
+ end
195
+ end
196
+
197
+ it 'returns recently triggered phantoms' do
198
+ result = engine.recently_triggered(limit: 5)
199
+ expect(result).not_to be_empty
200
+ result.each { |p| expect(p.last_triggered).not_to be_nil }
201
+ end
202
+
203
+ it 'respects limit' do
204
+ result = engine.recently_triggered(limit: 1)
205
+ expect(result.size).to be <= 1
206
+ end
207
+ end
208
+
209
+ describe '#resolve_check!' do
210
+ it 'returns count of resolved phantoms' do
211
+ limb = engine.register_removal(capability_name: 'cap_near_zero')
212
+ 160.times { limb.decay! }
213
+ count = engine.resolve_check!
214
+ expect(count).to be >= 1
215
+ end
216
+
217
+ it 'returns 0 when no phantoms are resolved' do
218
+ engine.register_removal(capability_name: 'fresh_cap')
219
+ expect(engine.resolve_check!).to eq(0)
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Defense::Phantom::Helpers::PhantomLimb do
4
+ subject(:limb) do
5
+ described_class.new(capability_name: 'lex-http', capability_domain: :network)
6
+ end
7
+
8
+ describe '#initialize' do
9
+ it 'assigns a uuid id' do
10
+ expect(limb.id).to match(/\A[0-9a-f-]{36}\z/)
11
+ end
12
+
13
+ it 'stores capability_name' do
14
+ expect(limb.capability_name).to eq('lex-http')
15
+ end
16
+
17
+ it 'stores capability_domain' do
18
+ expect(limb.capability_domain).to eq(:network)
19
+ end
20
+
21
+ it 'sets removed_at to now' do
22
+ expect(limb.removed_at).to be_a(Time)
23
+ end
24
+
25
+ it 'starts at INITIAL_INTENSITY' do
26
+ expect(limb.intensity).to be_within(0.001).of(Legion::Extensions::Agentic::Defense::Phantom::Helpers::Constants::INITIAL_INTENSITY)
27
+ end
28
+
29
+ it 'starts with zero activation_count' do
30
+ expect(limb.activation_count).to eq(0)
31
+ end
32
+
33
+ it 'has nil last_triggered initially' do
34
+ expect(limb.last_triggered).to be_nil
35
+ end
36
+
37
+ it 'has empty trigger_history' do
38
+ expect(limb.trigger_history).to be_empty
39
+ end
40
+ end
41
+
42
+ describe '#state' do
43
+ it 'returns :acute at initial intensity' do
44
+ expect(limb.state).to eq(:acute)
45
+ end
46
+
47
+ it 'transitions to :adapting as intensity drops' do
48
+ 7.times { limb.decay! }
49
+ expect(%i[adapting acute residual]).to include(limb.state)
50
+ end
51
+ end
52
+
53
+ describe '#resolved?' do
54
+ it 'returns false at initial intensity' do
55
+ expect(limb.resolved?).to be false
56
+ end
57
+
58
+ it 'returns true when intensity is at MIN_INTENSITY' do
59
+ 160.times { limb.decay! }
60
+ expect(limb.resolved?).to be true
61
+ end
62
+ end
63
+
64
+ describe '#trigger!' do
65
+ it 'returns a PhantomSignal' do
66
+ result = limb.trigger!('http.get request')
67
+ expect(result).to be_a(Legion::Extensions::Agentic::Defense::Phantom::Helpers::PhantomSignal)
68
+ end
69
+
70
+ it 'increments activation_count' do
71
+ limb.trigger!('test')
72
+ expect(limb.activation_count).to eq(1)
73
+ end
74
+
75
+ it 'sets last_triggered' do
76
+ limb.trigger!('test')
77
+ expect(limb.last_triggered).to be_a(Time)
78
+ end
79
+
80
+ it 'adds signal to trigger_history' do
81
+ limb.trigger!('test')
82
+ expect(limb.trigger_history.size).to eq(1)
83
+ end
84
+
85
+ it 'caps trigger_history at 50 entries' do
86
+ 55.times { |i| limb.trigger!("stimulus_#{i}") }
87
+ expect(limb.trigger_history.size).to eq(50)
88
+ end
89
+
90
+ it 'returns false when resolved' do
91
+ 160.times { limb.decay! }
92
+ expect(limb.trigger!('test')).to be false
93
+ end
94
+
95
+ it 'classifies trigger as :stimulus_match when stimulus contains capability name' do
96
+ signal = limb.trigger!('lex-http request fired')
97
+ expect(signal.trigger_type).to eq(:stimulus_match)
98
+ end
99
+
100
+ it 'classifies as :contextual_association for a fresh limb with generic stimulus' do
101
+ fresh = described_class.new(capability_name: 'lex-redis', capability_domain: :cache)
102
+ signal = fresh.trigger!('some unrelated event')
103
+ expect(signal.trigger_type).to eq(:contextual_association)
104
+ end
105
+
106
+ it 'classifies repeated rapid activations as :temporal_pattern' do
107
+ 2.times { limb.trigger!('something') }
108
+ signal = limb.trigger!('something')
109
+ expect(signal.trigger_type).to eq(:temporal_pattern)
110
+ end
111
+
112
+ it 'classifies as :habitual when activation_count > 10 and last trigger was long ago' do
113
+ fresh = described_class.new(capability_name: 'lex-vault', capability_domain: :secrets)
114
+ 11.times { fresh.trigger!('some event') }
115
+ fresh.instance_variable_set(:@last_triggered, Time.now.utc - 120)
116
+ signal = fresh.trigger!('some event')
117
+ expect(signal.trigger_type).to eq(:habitual)
118
+ end
119
+
120
+ it 'classifies signal with a valid trigger type' do
121
+ fresh = described_class.new(capability_name: 'lex-vault', capability_domain: :secrets)
122
+ 15.times { fresh.trigger!('some event') }
123
+ signal = fresh.trigger!('some event')
124
+ expect(Legion::Extensions::Agentic::Defense::Phantom::Helpers::Constants::TRIGGER_TYPES).to include(signal.trigger_type)
125
+ end
126
+ end
127
+
128
+ describe '#decay!' do
129
+ it 'reduces intensity by INTENSITY_DECAY' do
130
+ before = limb.intensity
131
+ limb.decay!
132
+ expect(limb.intensity).to be_within(0.001).of(before - Legion::Extensions::Agentic::Defense::Phantom::Helpers::Constants::INTENSITY_DECAY)
133
+ end
134
+
135
+ it 'does not drop below MIN_INTENSITY' do
136
+ 200.times { limb.decay! }
137
+ expect(limb.intensity).to eq(Legion::Extensions::Agentic::Defense::Phantom::Helpers::Constants::MIN_INTENSITY)
138
+ end
139
+
140
+ it 'does nothing when already resolved' do
141
+ 160.times { limb.decay! }
142
+ resolved_intensity = limb.intensity
143
+ limb.decay!
144
+ expect(limb.intensity).to eq(resolved_intensity)
145
+ end
146
+ end
147
+
148
+ describe '#adapt!' do
149
+ it 'reduces intensity faster than decay!' do
150
+ limb2 = described_class.new(capability_name: 'lex-http', capability_domain: :network)
151
+ limb.adapt!
152
+ limb2.decay!
153
+ expect(limb.intensity).to be < limb2.intensity
154
+ end
155
+
156
+ it 'does not drop below MIN_INTENSITY' do
157
+ 100.times { limb.adapt! }
158
+ expect(limb.intensity).to eq(Legion::Extensions::Agentic::Defense::Phantom::Helpers::Constants::MIN_INTENSITY)
159
+ end
160
+
161
+ it 'does nothing when already resolved' do
162
+ 100.times { limb.adapt! }
163
+ resolved_intensity = limb.intensity
164
+ limb.adapt!
165
+ expect(limb.intensity).to eq(resolved_intensity)
166
+ end
167
+ end
168
+
169
+ describe '#to_h' do
170
+ it 'returns a hash with expected keys' do
171
+ h = limb.to_h
172
+ expect(h).to include(:id, :capability_name, :capability_domain, :removed_at, :intensity, :activation_count, :state, :resolved)
173
+ end
174
+
175
+ it 'reflects resolved status' do
176
+ 160.times { limb.decay! }
177
+ expect(limb.to_h[:resolved]).to be true
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Defense::Phantom::Helpers::PhantomSignal do
4
+ let(:phantom_id) { SecureRandom.uuid }
5
+
6
+ subject(:signal) do
7
+ described_class.new(
8
+ phantom_limb_id: phantom_id,
9
+ stimulus: 'http.get request',
10
+ trigger_type: :stimulus_match,
11
+ intensity_at_trigger: 0.75
12
+ )
13
+ end
14
+
15
+ describe '#initialize' do
16
+ it 'assigns a uuid id' do
17
+ expect(signal.id).to match(/\A[0-9a-f-]{36}\z/)
18
+ end
19
+
20
+ it 'stores phantom_limb_id' do
21
+ expect(signal.phantom_limb_id).to eq(phantom_id)
22
+ end
23
+
24
+ it 'stores stimulus' do
25
+ expect(signal.stimulus).to eq('http.get request')
26
+ end
27
+
28
+ it 'stores trigger_type' do
29
+ expect(signal.trigger_type).to eq(:stimulus_match)
30
+ end
31
+
32
+ it 'stores intensity_at_trigger' do
33
+ expect(signal.intensity_at_trigger).to be_within(0.001).of(0.75)
34
+ end
35
+
36
+ it 'clamps intensity_at_trigger to [0, 1]' do
37
+ over = described_class.new(phantom_limb_id: phantom_id, stimulus: 's', trigger_type: :habitual, intensity_at_trigger: 2.0)
38
+ under = described_class.new(phantom_limb_id: phantom_id, stimulus: 's', trigger_type: :habitual, intensity_at_trigger: -0.5)
39
+ expect(over.intensity_at_trigger).to eq(1.0)
40
+ expect(under.intensity_at_trigger).to eq(0.0)
41
+ end
42
+
43
+ it 'sets timestamp to current utc time' do
44
+ expect(signal.timestamp).to be_a(Time)
45
+ end
46
+ end
47
+
48
+ describe '#to_h' do
49
+ it 'returns a hash with expected keys' do
50
+ h = signal.to_h
51
+ expect(h).to include(:id, :phantom_limb_id, :stimulus, :trigger_type, :intensity_at_trigger, :timestamp)
52
+ end
53
+
54
+ it 'intensity_at_trigger is rounded to 10 decimal places' do
55
+ h = signal.to_h
56
+ expect(h[:intensity_at_trigger]).to eq(signal.intensity_at_trigger.round(10))
57
+ end
58
+ end
59
+ end