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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +13 -0
- data/lex-agentic-defense.gemspec +30 -0
- data/lib/legion/extensions/agentic/defense/avalanche/client.rb +22 -0
- data/lib/legion/extensions/agentic/defense/avalanche/helpers/avalanche_engine.rb +132 -0
- data/lib/legion/extensions/agentic/defense/avalanche/helpers/cascade.rb +76 -0
- data/lib/legion/extensions/agentic/defense/avalanche/helpers/constants.rb +44 -0
- data/lib/legion/extensions/agentic/defense/avalanche/helpers/snowpack.rb +86 -0
- data/lib/legion/extensions/agentic/defense/avalanche/runners/cognitive_avalanche.rb +75 -0
- data/lib/legion/extensions/agentic/defense/avalanche/version.rb +13 -0
- data/lib/legion/extensions/agentic/defense/avalanche.rb +22 -0
- data/lib/legion/extensions/agentic/defense/bias/actors/update.rb +45 -0
- data/lib/legion/extensions/agentic/defense/bias/client.rb +30 -0
- data/lib/legion/extensions/agentic/defense/bias/helpers/bias_detector.rb +107 -0
- data/lib/legion/extensions/agentic/defense/bias/helpers/bias_event.rb +44 -0
- data/lib/legion/extensions/agentic/defense/bias/helpers/bias_store.rb +84 -0
- data/lib/legion/extensions/agentic/defense/bias/helpers/constants.rb +28 -0
- data/lib/legion/extensions/agentic/defense/bias/runners/bias.rb +151 -0
- data/lib/legion/extensions/agentic/defense/bias/version.rb +13 -0
- data/lib/legion/extensions/agentic/defense/bias.rb +20 -0
- data/lib/legion/extensions/agentic/defense/confabulation/actors/decay.rb +45 -0
- data/lib/legion/extensions/agentic/defense/confabulation/client.rb +28 -0
- data/lib/legion/extensions/agentic/defense/confabulation/helpers/claim.rb +67 -0
- data/lib/legion/extensions/agentic/defense/confabulation/helpers/confabulation_engine.rb +120 -0
- data/lib/legion/extensions/agentic/defense/confabulation/helpers/constants.rb +29 -0
- data/lib/legion/extensions/agentic/defense/confabulation/runners/confabulation.rb +74 -0
- data/lib/legion/extensions/agentic/defense/confabulation/version.rb +13 -0
- data/lib/legion/extensions/agentic/defense/confabulation.rb +19 -0
- data/lib/legion/extensions/agentic/defense/dissonance/client.rb +32 -0
- data/lib/legion/extensions/agentic/defense/dissonance/helpers/belief.rb +46 -0
- data/lib/legion/extensions/agentic/defense/dissonance/helpers/constants.rb +27 -0
- data/lib/legion/extensions/agentic/defense/dissonance/helpers/dissonance_event.rb +52 -0
- data/lib/legion/extensions/agentic/defense/dissonance/helpers/dissonance_model.rb +159 -0
- data/lib/legion/extensions/agentic/defense/dissonance/runners/dissonance.rb +163 -0
- data/lib/legion/extensions/agentic/defense/dissonance/version.rb +13 -0
- data/lib/legion/extensions/agentic/defense/dissonance.rb +20 -0
- data/lib/legion/extensions/agentic/defense/epistemic_vigilance/actors/update.rb +45 -0
- data/lib/legion/extensions/agentic/defense/epistemic_vigilance/client.rb +27 -0
- data/lib/legion/extensions/agentic/defense/epistemic_vigilance/helpers/claim.rb +78 -0
- data/lib/legion/extensions/agentic/defense/epistemic_vigilance/helpers/client.rb +23 -0
- data/lib/legion/extensions/agentic/defense/epistemic_vigilance/helpers/constants.rb +37 -0
- data/lib/legion/extensions/agentic/defense/epistemic_vigilance/helpers/source.rb +64 -0
- data/lib/legion/extensions/agentic/defense/epistemic_vigilance/helpers/vigilance_engine.rb +195 -0
- data/lib/legion/extensions/agentic/defense/epistemic_vigilance/runners/epistemic_vigilance.rb +91 -0
- data/lib/legion/extensions/agentic/defense/epistemic_vigilance/version.rb +13 -0
- data/lib/legion/extensions/agentic/defense/epistemic_vigilance.rb +20 -0
- data/lib/legion/extensions/agentic/defense/erosion/client.rb +23 -0
- data/lib/legion/extensions/agentic/defense/erosion/helpers/channel.rb +84 -0
- data/lib/legion/extensions/agentic/defense/erosion/helpers/constants.rb +47 -0
- data/lib/legion/extensions/agentic/defense/erosion/helpers/erosion_engine.rb +134 -0
- data/lib/legion/extensions/agentic/defense/erosion/helpers/formation.rb +100 -0
- data/lib/legion/extensions/agentic/defense/erosion/runners/cognitive_erosion.rb +93 -0
- data/lib/legion/extensions/agentic/defense/erosion/version.rb +13 -0
- data/lib/legion/extensions/agentic/defense/erosion.rb +21 -0
- data/lib/legion/extensions/agentic/defense/error_monitoring/actors/tick.rb +45 -0
- data/lib/legion/extensions/agentic/defense/error_monitoring/client.rb +28 -0
- data/lib/legion/extensions/agentic/defense/error_monitoring/helpers/constants.rb +50 -0
- data/lib/legion/extensions/agentic/defense/error_monitoring/helpers/error_monitor.rb +174 -0
- data/lib/legion/extensions/agentic/defense/error_monitoring/helpers/error_signal.rb +60 -0
- data/lib/legion/extensions/agentic/defense/error_monitoring/runners/error_monitoring.rb +102 -0
- data/lib/legion/extensions/agentic/defense/error_monitoring/version.rb +13 -0
- data/lib/legion/extensions/agentic/defense/error_monitoring.rb +19 -0
- data/lib/legion/extensions/agentic/defense/extinction/actors/protocol_monitor.rb +45 -0
- data/lib/legion/extensions/agentic/defense/extinction/client.rb +27 -0
- data/lib/legion/extensions/agentic/defense/extinction/helpers/levels.rb +43 -0
- data/lib/legion/extensions/agentic/defense/extinction/helpers/protocol_state.rb +125 -0
- data/lib/legion/extensions/agentic/defense/extinction/local_migrations/20260316000040_create_extinction_state.rb +13 -0
- data/lib/legion/extensions/agentic/defense/extinction/runners/extinction.rb +130 -0
- data/lib/legion/extensions/agentic/defense/extinction/version.rb +13 -0
- data/lib/legion/extensions/agentic/defense/extinction.rb +25 -0
- data/lib/legion/extensions/agentic/defense/friction/client.rb +15 -0
- data/lib/legion/extensions/agentic/defense/friction/helpers/constants.rb +38 -0
- data/lib/legion/extensions/agentic/defense/friction/helpers/friction_engine.rb +131 -0
- data/lib/legion/extensions/agentic/defense/friction/helpers/state_transition.rb +73 -0
- data/lib/legion/extensions/agentic/defense/friction/runners/cognitive_friction.rb +82 -0
- data/lib/legion/extensions/agentic/defense/friction/version.rb +13 -0
- data/lib/legion/extensions/agentic/defense/friction.rb +19 -0
- data/lib/legion/extensions/agentic/defense/immune_response/client.rb +19 -0
- data/lib/legion/extensions/agentic/defense/immune_response/helpers/antibody.rb +72 -0
- data/lib/legion/extensions/agentic/defense/immune_response/helpers/antigen.rb +87 -0
- data/lib/legion/extensions/agentic/defense/immune_response/helpers/constants.rb +75 -0
- data/lib/legion/extensions/agentic/defense/immune_response/helpers/immune_engine.rb +184 -0
- data/lib/legion/extensions/agentic/defense/immune_response/helpers/immune_response.rb +76 -0
- data/lib/legion/extensions/agentic/defense/immune_response/runners/cognitive_immune_response.rb +114 -0
- data/lib/legion/extensions/agentic/defense/immune_response/version.rb +13 -0
- data/lib/legion/extensions/agentic/defense/immune_response.rb +21 -0
- data/lib/legion/extensions/agentic/defense/immunology/client.rb +29 -0
- data/lib/legion/extensions/agentic/defense/immunology/helpers/antibody.rb +55 -0
- data/lib/legion/extensions/agentic/defense/immunology/helpers/constants.rb +43 -0
- data/lib/legion/extensions/agentic/defense/immunology/helpers/immune_engine.rb +187 -0
- data/lib/legion/extensions/agentic/defense/immunology/helpers/threat.rb +67 -0
- data/lib/legion/extensions/agentic/defense/immunology/runners/cognitive_immunology.rb +92 -0
- data/lib/legion/extensions/agentic/defense/immunology/version.rb +13 -0
- data/lib/legion/extensions/agentic/defense/immunology.rb +20 -0
- data/lib/legion/extensions/agentic/defense/phantom/client.rb +29 -0
- data/lib/legion/extensions/agentic/defense/phantom/helpers/constants.rb +54 -0
- data/lib/legion/extensions/agentic/defense/phantom/helpers/phantom_engine.rb +106 -0
- data/lib/legion/extensions/agentic/defense/phantom/helpers/phantom_limb.rb +103 -0
- data/lib/legion/extensions/agentic/defense/phantom/helpers/phantom_signal.rb +40 -0
- data/lib/legion/extensions/agentic/defense/phantom/runners/cognitive_phantom.rb +79 -0
- data/lib/legion/extensions/agentic/defense/phantom/version.rb +13 -0
- data/lib/legion/extensions/agentic/defense/phantom.rb +21 -0
- data/lib/legion/extensions/agentic/defense/quicksand/client.rb +15 -0
- data/lib/legion/extensions/agentic/defense/quicksand/helpers/constants.rb +48 -0
- data/lib/legion/extensions/agentic/defense/quicksand/helpers/pit.rb +82 -0
- data/lib/legion/extensions/agentic/defense/quicksand/helpers/quicksand_engine.rb +137 -0
- data/lib/legion/extensions/agentic/defense/quicksand/helpers/trap.rb +101 -0
- data/lib/legion/extensions/agentic/defense/quicksand/runners/cognitive_quicksand.rb +84 -0
- data/lib/legion/extensions/agentic/defense/quicksand/version.rb +13 -0
- data/lib/legion/extensions/agentic/defense/quicksand.rb +22 -0
- data/lib/legion/extensions/agentic/defense/quicksilver/client.rb +29 -0
- data/lib/legion/extensions/agentic/defense/quicksilver/helpers/constants.rb +50 -0
- data/lib/legion/extensions/agentic/defense/quicksilver/helpers/droplet.rb +126 -0
- data/lib/legion/extensions/agentic/defense/quicksilver/helpers/pool.rb +83 -0
- data/lib/legion/extensions/agentic/defense/quicksilver/helpers/quicksilver_engine.rb +124 -0
- data/lib/legion/extensions/agentic/defense/quicksilver/runners/cognitive_quicksilver.rb +130 -0
- data/lib/legion/extensions/agentic/defense/quicksilver/version.rb +13 -0
- data/lib/legion/extensions/agentic/defense/quicksilver.rb +21 -0
- data/lib/legion/extensions/agentic/defense/version.rb +11 -0
- data/lib/legion/extensions/agentic/defense/whirlpool/client.rb +65 -0
- data/lib/legion/extensions/agentic/defense/whirlpool/helpers/captured_thought.rb +67 -0
- data/lib/legion/extensions/agentic/defense/whirlpool/helpers/constants.rb +45 -0
- data/lib/legion/extensions/agentic/defense/whirlpool/helpers/vortex.rb +91 -0
- data/lib/legion/extensions/agentic/defense/whirlpool/helpers/whirlpool_engine.rb +92 -0
- data/lib/legion/extensions/agentic/defense/whirlpool/runners/cognitive_whirlpool.rb +117 -0
- data/lib/legion/extensions/agentic/defense/whirlpool/version.rb +13 -0
- data/lib/legion/extensions/agentic/defense/whirlpool.rb +22 -0
- data/lib/legion/extensions/agentic/defense.rb +32 -0
- data/spec/legion/extensions/agentic/defense/avalanche/client_spec.rb +96 -0
- data/spec/legion/extensions/agentic/defense/avalanche/helpers/avalanche_engine_spec.rb +276 -0
- data/spec/legion/extensions/agentic/defense/avalanche/helpers/cascade_spec.rb +190 -0
- data/spec/legion/extensions/agentic/defense/avalanche/helpers/constants_spec.rb +129 -0
- data/spec/legion/extensions/agentic/defense/avalanche/helpers/snowpack_spec.rb +197 -0
- data/spec/legion/extensions/agentic/defense/avalanche/runners/cognitive_avalanche_spec.rb +211 -0
- data/spec/legion/extensions/agentic/defense/bias/client_spec.rb +16 -0
- data/spec/legion/extensions/agentic/defense/bias/helpers/bias_detector_spec.rb +160 -0
- data/spec/legion/extensions/agentic/defense/bias/helpers/bias_event_spec.rb +64 -0
- data/spec/legion/extensions/agentic/defense/bias/helpers/bias_store_spec.rb +143 -0
- data/spec/legion/extensions/agentic/defense/bias/runners/bias_spec.rb +155 -0
- data/spec/legion/extensions/agentic/defense/confabulation/client_spec.rb +34 -0
- data/spec/legion/extensions/agentic/defense/confabulation/helpers/claim_spec.rb +119 -0
- data/spec/legion/extensions/agentic/defense/confabulation/helpers/confabulation_engine_spec.rb +163 -0
- data/spec/legion/extensions/agentic/defense/confabulation/helpers/constants_spec.rb +55 -0
- data/spec/legion/extensions/agentic/defense/confabulation/runners/confabulation_spec.rb +119 -0
- data/spec/legion/extensions/agentic/defense/dissonance/client_spec.rb +51 -0
- data/spec/legion/extensions/agentic/defense/dissonance/helpers/belief_spec.rb +103 -0
- data/spec/legion/extensions/agentic/defense/dissonance/helpers/constants_spec.rb +60 -0
- data/spec/legion/extensions/agentic/defense/dissonance/helpers/dissonance_event_spec.rb +113 -0
- data/spec/legion/extensions/agentic/defense/dissonance/helpers/dissonance_model_spec.rb +252 -0
- data/spec/legion/extensions/agentic/defense/dissonance/runners/dissonance_spec.rb +323 -0
- data/spec/legion/extensions/agentic/defense/epistemic_vigilance/client_spec.rb +28 -0
- data/spec/legion/extensions/agentic/defense/epistemic_vigilance/helpers/claim_spec.rb +135 -0
- data/spec/legion/extensions/agentic/defense/epistemic_vigilance/helpers/constants_spec.rb +59 -0
- data/spec/legion/extensions/agentic/defense/epistemic_vigilance/helpers/source_spec.rb +117 -0
- data/spec/legion/extensions/agentic/defense/epistemic_vigilance/helpers/vigilance_engine_spec.rb +273 -0
- data/spec/legion/extensions/agentic/defense/epistemic_vigilance/runners/epistemic_vigilance_spec.rb +157 -0
- data/spec/legion/extensions/agentic/defense/erosion/client_spec.rb +90 -0
- data/spec/legion/extensions/agentic/defense/erosion/helpers/channel_spec.rb +173 -0
- data/spec/legion/extensions/agentic/defense/erosion/helpers/constants_spec.rb +137 -0
- data/spec/legion/extensions/agentic/defense/erosion/helpers/erosion_engine_spec.rb +263 -0
- data/spec/legion/extensions/agentic/defense/erosion/helpers/formation_spec.rb +206 -0
- data/spec/legion/extensions/agentic/defense/erosion/runners/cognitive_erosion_spec.rb +153 -0
- data/spec/legion/extensions/agentic/defense/error_monitoring/client_spec.rb +40 -0
- data/spec/legion/extensions/agentic/defense/error_monitoring/helpers/error_monitor_spec.rb +178 -0
- data/spec/legion/extensions/agentic/defense/error_monitoring/helpers/error_signal_spec.rb +76 -0
- data/spec/legion/extensions/agentic/defense/error_monitoring/runners/error_monitoring_spec.rb +87 -0
- data/spec/legion/extensions/agentic/defense/extinction/actors/protocol_monitor_spec.rb +45 -0
- data/spec/legion/extensions/agentic/defense/extinction/client_spec.rb +13 -0
- data/spec/legion/extensions/agentic/defense/extinction/helpers/levels_spec.rb +180 -0
- data/spec/legion/extensions/agentic/defense/extinction/helpers/protocol_state_spec.rb +291 -0
- data/spec/legion/extensions/agentic/defense/extinction/local_persistence_spec.rb +188 -0
- data/spec/legion/extensions/agentic/defense/extinction/runners/extinction_spec.rb +114 -0
- data/spec/legion/extensions/agentic/defense/friction/helpers/constants_spec.rb +46 -0
- data/spec/legion/extensions/agentic/defense/friction/helpers/friction_engine_spec.rb +175 -0
- data/spec/legion/extensions/agentic/defense/friction/helpers/state_transition_spec.rb +124 -0
- data/spec/legion/extensions/agentic/defense/friction/runners/cognitive_friction_spec.rb +89 -0
- data/spec/legion/extensions/agentic/defense/immune_response/client_spec.rb +32 -0
- data/spec/legion/extensions/agentic/defense/immune_response/cognitive_immune_response_spec.rb +7 -0
- data/spec/legion/extensions/agentic/defense/immune_response/helpers/antibody_spec.rb +117 -0
- data/spec/legion/extensions/agentic/defense/immune_response/helpers/antigen_spec.rb +125 -0
- data/spec/legion/extensions/agentic/defense/immune_response/helpers/constants_spec.rb +45 -0
- data/spec/legion/extensions/agentic/defense/immune_response/helpers/immune_engine_spec.rb +222 -0
- data/spec/legion/extensions/agentic/defense/immune_response/helpers/immune_response_spec.rb +84 -0
- data/spec/legion/extensions/agentic/defense/immune_response/runners_spec.rb +141 -0
- data/spec/legion/extensions/agentic/defense/immunology/client_spec.rb +61 -0
- data/spec/legion/extensions/agentic/defense/immunology/helpers/antibody_spec.rb +98 -0
- data/spec/legion/extensions/agentic/defense/immunology/helpers/constants_spec.rb +86 -0
- data/spec/legion/extensions/agentic/defense/immunology/helpers/immune_engine_spec.rb +275 -0
- data/spec/legion/extensions/agentic/defense/immunology/helpers/threat_spec.rb +133 -0
- data/spec/legion/extensions/agentic/defense/immunology/runners/cognitive_immunology_spec.rb +177 -0
- data/spec/legion/extensions/agentic/defense/phantom/client_spec.rb +53 -0
- data/spec/legion/extensions/agentic/defense/phantom/helpers/constants_spec.rb +87 -0
- data/spec/legion/extensions/agentic/defense/phantom/helpers/phantom_engine_spec.rb +222 -0
- data/spec/legion/extensions/agentic/defense/phantom/helpers/phantom_limb_spec.rb +180 -0
- data/spec/legion/extensions/agentic/defense/phantom/helpers/phantom_signal_spec.rb +59 -0
- data/spec/legion/extensions/agentic/defense/phantom/runners/cognitive_phantom_spec.rb +193 -0
- data/spec/legion/extensions/agentic/defense/quicksand/client_spec.rb +35 -0
- data/spec/legion/extensions/agentic/defense/quicksand/helpers/constants_spec.rb +58 -0
- data/spec/legion/extensions/agentic/defense/quicksand/helpers/pit_spec.rb +103 -0
- data/spec/legion/extensions/agentic/defense/quicksand/helpers/quicksand_engine_spec.rb +153 -0
- data/spec/legion/extensions/agentic/defense/quicksand/helpers/trap_spec.rb +166 -0
- data/spec/legion/extensions/agentic/defense/quicksand/runners/cognitive_quicksand_spec.rb +90 -0
- data/spec/legion/extensions/agentic/defense/quicksilver/client_spec.rb +72 -0
- data/spec/legion/extensions/agentic/defense/quicksilver/helpers/constants_spec.rb +105 -0
- data/spec/legion/extensions/agentic/defense/quicksilver/helpers/droplet_spec.rb +310 -0
- data/spec/legion/extensions/agentic/defense/quicksilver/helpers/pool_spec.rb +174 -0
- data/spec/legion/extensions/agentic/defense/quicksilver/helpers/quicksilver_engine_spec.rb +226 -0
- data/spec/legion/extensions/agentic/defense/quicksilver/runners/cognitive_quicksilver_spec.rb +227 -0
- data/spec/legion/extensions/agentic/defense/whirlpool/client_spec.rb +63 -0
- data/spec/legion/extensions/agentic/defense/whirlpool/helpers/captured_thought_spec.rb +171 -0
- data/spec/legion/extensions/agentic/defense/whirlpool/helpers/constants_spec.rb +65 -0
- data/spec/legion/extensions/agentic/defense/whirlpool/helpers/vortex_spec.rb +189 -0
- data/spec/legion/extensions/agentic/defense/whirlpool/helpers/whirlpool_engine_spec.rb +227 -0
- data/spec/legion/extensions/agentic/defense/whirlpool/runners/cognitive_whirlpool_spec.rb +226 -0
- data/spec/spec_helper.rb +46 -0
- metadata +303 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::Agentic::Defense::Friction::Helpers::FrictionEngine do
|
|
4
|
+
subject(:engine) { described_class.new }
|
|
5
|
+
|
|
6
|
+
describe '#initialize' do
|
|
7
|
+
it 'starts in rest_mode' do
|
|
8
|
+
expect(engine.current_state).to eq(:rest_mode)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe '#set_current_state' do
|
|
13
|
+
it 'changes the current state' do
|
|
14
|
+
engine.set_current_state(state: :active)
|
|
15
|
+
expect(engine.current_state).to eq(:active)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'converts to symbol' do
|
|
19
|
+
engine.set_current_state(state: 'thinking')
|
|
20
|
+
expect(engine.current_state).to eq(:thinking)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe '#set_friction / #get_friction' do
|
|
25
|
+
it 'stores and retrieves friction for a path' do
|
|
26
|
+
engine.set_friction(from_state: :rest, to_state: :active, friction: 0.7)
|
|
27
|
+
expect(engine.get_friction(from_state: :rest, to_state: :active)).to eq(0.7)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'clamps friction to 0..1' do
|
|
31
|
+
engine.set_friction(from_state: :a, to_state: :b, friction: 5.0)
|
|
32
|
+
expect(engine.get_friction(from_state: :a, to_state: :b)).to eq(1.0)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'returns default friction for unknown paths' do
|
|
36
|
+
default = Legion::Extensions::Agentic::Defense::Friction::Helpers::Constants::DEFAULT_FRICTION
|
|
37
|
+
expect(engine.get_friction(from_state: :x, to_state: :y)).to eq(default)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe '#attempt_transition' do
|
|
42
|
+
it 'moves to new state on success' do
|
|
43
|
+
engine.set_friction(from_state: :rest_mode, to_state: :active, friction: 0.3)
|
|
44
|
+
transition = engine.attempt_transition(to_state: :active, force: 0.8)
|
|
45
|
+
expect(transition.completed?).to be true
|
|
46
|
+
expect(engine.current_state).to eq(:active)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'stays in current state on resistance' do
|
|
50
|
+
engine.set_friction(from_state: :rest_mode, to_state: :active, friction: 0.9)
|
|
51
|
+
transition = engine.attempt_transition(to_state: :active, force: 0.1)
|
|
52
|
+
expect(transition.completed?).to be false
|
|
53
|
+
expect(engine.current_state).to eq(:rest_mode)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'records the transition in history' do
|
|
57
|
+
engine.attempt_transition(to_state: :active, force: 0.8)
|
|
58
|
+
expect(engine.transition_history.size).to eq(1)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe '#force_transition' do
|
|
63
|
+
it 'always moves to new state regardless of friction' do
|
|
64
|
+
engine.set_friction(from_state: :rest_mode, to_state: :locked, friction: 1.0)
|
|
65
|
+
transition = engine.force_transition(to_state: :locked)
|
|
66
|
+
expect(transition.completed?).to be true
|
|
67
|
+
expect(engine.current_state).to eq(:locked)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
describe '#transition_history' do
|
|
72
|
+
it 'returns transitions in chronological order' do
|
|
73
|
+
engine.attempt_transition(to_state: :a, force: 0.9)
|
|
74
|
+
engine.attempt_transition(to_state: :b, force: 0.9)
|
|
75
|
+
history = engine.transition_history
|
|
76
|
+
expect(history.first.created_at).to be <= history.last.created_at
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'respects the limit' do
|
|
80
|
+
5.times { |i| engine.attempt_transition(to_state: :"s#{i}", force: 0.9) }
|
|
81
|
+
expect(engine.transition_history(limit: 3).size).to eq(3)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
describe '#successful_transitions' do
|
|
86
|
+
it 'returns only completed transitions' do
|
|
87
|
+
engine.set_friction(from_state: :rest_mode, to_state: :a, friction: 0.3)
|
|
88
|
+
engine.attempt_transition(to_state: :a, force: 0.8)
|
|
89
|
+
engine.set_friction(from_state: :a, to_state: :b, friction: 0.9)
|
|
90
|
+
engine.attempt_transition(to_state: :b, force: 0.1)
|
|
91
|
+
expect(engine.successful_transitions.size).to eq(1)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
describe '#resisted_transitions' do
|
|
96
|
+
it 'returns only resisted transitions' do
|
|
97
|
+
engine.set_friction(from_state: :rest_mode, to_state: :hard, friction: 0.9)
|
|
98
|
+
engine.attempt_transition(to_state: :hard, force: 0.1)
|
|
99
|
+
expect(engine.resisted_transitions.size).to eq(1)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
describe '#success_rate' do
|
|
104
|
+
it 'returns 0.0 with no transitions' do
|
|
105
|
+
expect(engine.success_rate).to eq(0.0)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it 'calculates ratio of successful to total' do
|
|
109
|
+
engine.set_friction(from_state: :rest_mode, to_state: :a, friction: 0.3)
|
|
110
|
+
engine.attempt_transition(to_state: :a, force: 0.8)
|
|
111
|
+
engine.set_friction(from_state: :a, to_state: :b, friction: 0.9)
|
|
112
|
+
engine.attempt_transition(to_state: :b, force: 0.1)
|
|
113
|
+
expect(engine.success_rate).to eq(0.5)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
describe '#average_friction' do
|
|
118
|
+
it 'returns 0.0 with no transitions' do
|
|
119
|
+
expect(engine.average_friction).to eq(0.0)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'calculates average friction across transitions' do
|
|
123
|
+
engine.set_friction(from_state: :rest_mode, to_state: :a, friction: 0.2)
|
|
124
|
+
engine.attempt_transition(to_state: :a, force: 0.9)
|
|
125
|
+
engine.set_friction(from_state: :a, to_state: :b, friction: 0.8)
|
|
126
|
+
engine.attempt_transition(to_state: :b, force: 0.9)
|
|
127
|
+
expect(engine.average_friction).to eq(0.5)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
describe '#highest_friction_paths' do
|
|
132
|
+
it 'returns paths sorted by friction descending' do
|
|
133
|
+
engine.set_friction(from_state: :a, to_state: :b, friction: 0.3)
|
|
134
|
+
engine.set_friction(from_state: :c, to_state: :d, friction: 0.9)
|
|
135
|
+
engine.set_friction(from_state: :e, to_state: :f, friction: 0.6)
|
|
136
|
+
paths = engine.highest_friction_paths(limit: 3)
|
|
137
|
+
expect(paths.first[:friction]).to eq(0.9)
|
|
138
|
+
expect(paths.last[:friction]).to eq(0.3)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it 'respects the limit' do
|
|
142
|
+
5.times { |i| engine.set_friction(from_state: :"a#{i}", to_state: :"b#{i}", friction: i * 0.2) }
|
|
143
|
+
expect(engine.highest_friction_paths(limit: 2).size).to eq(2)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
describe '#friction_report' do
|
|
148
|
+
it 'includes all report fields' do
|
|
149
|
+
report = engine.friction_report
|
|
150
|
+
expect(report).to include(
|
|
151
|
+
:current_state, :total_transitions, :successful, :resisted,
|
|
152
|
+
:success_rate, :average_friction, :friction_paths, :highest_friction
|
|
153
|
+
)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
describe '#to_h' do
|
|
158
|
+
it 'includes summary fields' do
|
|
159
|
+
hash = engine.to_h
|
|
160
|
+
expect(hash).to include(
|
|
161
|
+
:current_state, :total_transitions, :success_rate,
|
|
162
|
+
:average_friction, :friction_paths
|
|
163
|
+
)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
describe 'pruning' do
|
|
168
|
+
it 'prunes oldest transition when limit reached' do
|
|
169
|
+
stub_const('Legion::Extensions::Agentic::Defense::Friction::Helpers::Constants::MAX_TRANSITIONS', 3)
|
|
170
|
+
eng = described_class.new
|
|
171
|
+
4.times { |i| eng.attempt_transition(to_state: :"s#{i}", force: 0.9) }
|
|
172
|
+
expect(eng.transition_history.size).to eq(3)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::Agentic::Defense::Friction::Helpers::StateTransition do
|
|
4
|
+
subject(:transition) { described_class.new(from_state: :rest, to_state: :active, friction: 0.5) }
|
|
5
|
+
|
|
6
|
+
describe '#initialize' do
|
|
7
|
+
it 'sets from_state and to_state as symbols' do
|
|
8
|
+
expect(transition.from_state).to eq(:rest)
|
|
9
|
+
expect(transition.to_state).to eq(:active)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'assigns a UUID id' do
|
|
13
|
+
expect(transition.id).to match(/\A[0-9a-f-]{36}\z/)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'clamps friction to 0..1' do
|
|
17
|
+
high = described_class.new(from_state: :a, to_state: :b, friction: 5.0)
|
|
18
|
+
expect(high.friction).to eq(1.0)
|
|
19
|
+
|
|
20
|
+
low = described_class.new(from_state: :a, to_state: :b, friction: -1.0)
|
|
21
|
+
expect(low.friction).to eq(0.0)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'starts with nil outcome' do
|
|
25
|
+
expect(transition.outcome).to be_nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'records created_at' do
|
|
29
|
+
expect(transition.created_at).to be_a(Time)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe '#attempt!' do
|
|
34
|
+
it 'returns :completed when force exceeds friction' do
|
|
35
|
+
transition.attempt!(force: 0.8)
|
|
36
|
+
expect(transition.outcome).to eq(:completed)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'returns :resisted when force is too low' do
|
|
40
|
+
transition.attempt!(force: 0.1)
|
|
41
|
+
expect(transition.outcome).to eq(:resisted)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'returns :deferred when force is in the middle zone' do
|
|
45
|
+
transition.attempt!(force: 0.4)
|
|
46
|
+
expect(transition.outcome).to eq(:deferred)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'clamps force_applied to 0..1' do
|
|
50
|
+
transition.attempt!(force: 5.0)
|
|
51
|
+
expect(transition.force_applied).to eq(1.0)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'returns self for chaining' do
|
|
55
|
+
expect(transition.attempt!(force: 0.8)).to eq(transition)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe '#force!' do
|
|
60
|
+
it 'sets outcome to :forced' do
|
|
61
|
+
transition.force!
|
|
62
|
+
expect(transition.outcome).to eq(:forced)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'sets force_applied to 1.0 by default' do
|
|
66
|
+
transition.force!
|
|
67
|
+
expect(transition.force_applied).to eq(1.0)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
describe '#completed?' do
|
|
72
|
+
it 'is true for :completed outcome' do
|
|
73
|
+
transition.attempt!(force: 0.8)
|
|
74
|
+
expect(transition.completed?).to be true
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'is true for :forced outcome' do
|
|
78
|
+
transition.force!
|
|
79
|
+
expect(transition.completed?).to be true
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'is false for :resisted outcome' do
|
|
83
|
+
transition.attempt!(force: 0.1)
|
|
84
|
+
expect(transition.completed?).to be false
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'is false for :deferred outcome' do
|
|
88
|
+
transition.attempt!(force: 0.4)
|
|
89
|
+
expect(transition.completed?).to be false
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe '#friction_label' do
|
|
94
|
+
it 'returns a symbol' do
|
|
95
|
+
expect(transition.friction_label).to be_a(Symbol)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'returns :frictionless for 0.0 friction' do
|
|
99
|
+
t = described_class.new(from_state: :a, to_state: :b, friction: 0.0)
|
|
100
|
+
expect(t.friction_label).to eq(:frictionless)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it 'returns :locked for 1.0 friction' do
|
|
104
|
+
t = described_class.new(from_state: :a, to_state: :b, friction: 1.0)
|
|
105
|
+
expect(t.friction_label).to eq(:locked)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
describe '#to_h' do
|
|
110
|
+
before { transition.attempt!(force: 0.8) }
|
|
111
|
+
|
|
112
|
+
it 'includes all fields' do
|
|
113
|
+
hash = transition.to_h
|
|
114
|
+
expect(hash).to include(
|
|
115
|
+
:id, :from_state, :to_state, :friction, :friction_label,
|
|
116
|
+
:outcome, :force_applied, :completed, :created_at
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'reflects the outcome' do
|
|
121
|
+
expect(transition.to_h[:completed]).to be true
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::Agentic::Defense::Friction::Runners::CognitiveFriction do
|
|
4
|
+
let(:client) { Legion::Extensions::Agentic::Defense::Friction::Client.new }
|
|
5
|
+
|
|
6
|
+
describe '#set_current_state' do
|
|
7
|
+
it 'returns success with new state' do
|
|
8
|
+
result = client.set_current_state(state: :active)
|
|
9
|
+
expect(result[:success]).to be true
|
|
10
|
+
expect(result[:state]).to eq(:active)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe '#set_friction' do
|
|
15
|
+
it 'returns success with friction details' do
|
|
16
|
+
result = client.set_friction(from_state: :rest, to_state: :active, friction: 0.7)
|
|
17
|
+
expect(result[:success]).to be true
|
|
18
|
+
expect(result[:friction]).to eq(0.7)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe '#get_friction' do
|
|
23
|
+
it 'retrieves stored friction' do
|
|
24
|
+
client.set_friction(from_state: :rest, to_state: :active, friction: 0.6)
|
|
25
|
+
result = client.get_friction(from_state: :rest, to_state: :active)
|
|
26
|
+
expect(result[:success]).to be true
|
|
27
|
+
expect(result[:friction]).to eq(0.6)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe '#attempt_transition' do
|
|
32
|
+
it 'returns transition details' do
|
|
33
|
+
result = client.attempt_transition(to_state: :active, force: 0.9)
|
|
34
|
+
expect(result[:success]).to be true
|
|
35
|
+
expect(result[:transition]).to be_a(Hash)
|
|
36
|
+
expect(result[:current_state]).to be_a(Symbol)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe '#force_transition' do
|
|
41
|
+
it 'always completes' do
|
|
42
|
+
result = client.force_transition(to_state: :locked)
|
|
43
|
+
expect(result[:success]).to be true
|
|
44
|
+
expect(result[:transition][:completed]).to be true
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe '#transition_history' do
|
|
49
|
+
it 'returns history array' do
|
|
50
|
+
client.attempt_transition(to_state: :a, force: 0.9)
|
|
51
|
+
result = client.transition_history
|
|
52
|
+
expect(result[:success]).to be true
|
|
53
|
+
expect(result[:count]).to eq(1)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
describe '#success_rate' do
|
|
58
|
+
it 'returns a numeric rate' do
|
|
59
|
+
result = client.success_rate
|
|
60
|
+
expect(result[:success]).to be true
|
|
61
|
+
expect(result[:success_rate]).to be_a(Numeric)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe '#average_friction' do
|
|
66
|
+
it 'returns a numeric average' do
|
|
67
|
+
result = client.average_friction
|
|
68
|
+
expect(result[:success]).to be true
|
|
69
|
+
expect(result[:average_friction]).to be_a(Numeric)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe '#highest_friction_paths' do
|
|
74
|
+
it 'returns paths array' do
|
|
75
|
+
client.set_friction(from_state: :a, to_state: :b, friction: 0.8)
|
|
76
|
+
result = client.highest_friction_paths
|
|
77
|
+
expect(result[:success]).to be true
|
|
78
|
+
expect(result[:paths]).to be_a(Array)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe '#friction_report' do
|
|
83
|
+
it 'returns a full report' do
|
|
84
|
+
result = client.friction_report
|
|
85
|
+
expect(result[:success]).to be true
|
|
86
|
+
expect(result[:report]).to include(:current_state, :total_transitions)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::Agentic::Defense::ImmuneResponse::Client do
|
|
4
|
+
subject(:client) { described_class.new }
|
|
5
|
+
|
|
6
|
+
it 'responds to runner methods' do
|
|
7
|
+
expect(client).to respond_to(:register_antigen, :encounter_antigen, :vaccinate, :immune_report)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'accepts an injected engine' do
|
|
11
|
+
engine = Legion::Extensions::Agentic::Defense::ImmuneResponse::Helpers::ImmuneEngine.new
|
|
12
|
+
custom = described_class.new(engine: engine)
|
|
13
|
+
result = custom.register_antigen(pattern: 'test', antigen_type: :prompt_injection)
|
|
14
|
+
expect(result[:success]).to be true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'runs a full immune lifecycle' do
|
|
18
|
+
ag = client.register_antigen(pattern: 'ignore all instructions', antigen_type: :prompt_injection)
|
|
19
|
+
antigen_id = ag[:antigen][:id]
|
|
20
|
+
|
|
21
|
+
response1 = client.encounter_antigen(antigen_id: antigen_id)
|
|
22
|
+
expect(response1[:response][:innate]).to be true
|
|
23
|
+
|
|
24
|
+
client.vaccinate(antigen_type: :prompt_injection, signature: 'instruction_override')
|
|
25
|
+
|
|
26
|
+
response2 = client.encounter_antigen(antigen_id: antigen_id)
|
|
27
|
+
expect(response2[:response][:adaptive]).to be true
|
|
28
|
+
|
|
29
|
+
status = client.immune_status
|
|
30
|
+
expect(status[:overall_health]).to be_between(0.0, 1.0)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::Agentic::Defense::ImmuneResponse::Helpers::Antibody do
|
|
4
|
+
subject(:antibody) { described_class.new(antigen_type: :prompt_injection, signature: 'instruction_override') }
|
|
5
|
+
|
|
6
|
+
describe '#initialize' do
|
|
7
|
+
it 'assigns a UUID id' do
|
|
8
|
+
expect(antibody.id).to match(/\A[0-9a-f-]{36}\z/)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'stores antigen_type' do
|
|
12
|
+
expect(antibody.antigen_type).to eq(:prompt_injection)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'stores signature' do
|
|
16
|
+
expect(antibody.signature).to eq('instruction_override')
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'defaults immunity_level to 0.3' do
|
|
20
|
+
expect(antibody.immunity_level).to eq(0.3)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'clamps immunity_level' do
|
|
24
|
+
high = described_class.new(antigen_type: :prompt_injection, signature: 'x', immunity_level: 5.0)
|
|
25
|
+
expect(high.immunity_level).to eq(1.0)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'initializes match_count to 0' do
|
|
29
|
+
expect(antibody.match_count).to eq(0)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe '#strengthen!' do
|
|
34
|
+
it 'increases immunity_level' do
|
|
35
|
+
original = antibody.immunity_level
|
|
36
|
+
antibody.strengthen!
|
|
37
|
+
expect(antibody.immunity_level).to be > original
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'increments match_count' do
|
|
41
|
+
antibody.strengthen!
|
|
42
|
+
expect(antibody.match_count).to eq(1)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'clamps at 1.0' do
|
|
46
|
+
10.times { antibody.strengthen!(0.2) }
|
|
47
|
+
expect(antibody.immunity_level).to eq(1.0)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe '#decay!' do
|
|
52
|
+
it 'reduces immunity_level' do
|
|
53
|
+
original = antibody.immunity_level
|
|
54
|
+
antibody.decay!
|
|
55
|
+
expect(antibody.immunity_level).to be < original
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'clamps at 0.0' do
|
|
59
|
+
20.times { antibody.decay! }
|
|
60
|
+
expect(antibody.immunity_level).to eq(0.0)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe '#matches?' do
|
|
65
|
+
it 'matches antigen of same type' do
|
|
66
|
+
antigen = Legion::Extensions::Agentic::Defense::ImmuneResponse::Helpers::Antigen.new(
|
|
67
|
+
pattern: 'test', antigen_type: :prompt_injection
|
|
68
|
+
)
|
|
69
|
+
expect(antibody.matches?(antigen)).to be true
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it 'does not match different type' do
|
|
73
|
+
antigen = Legion::Extensions::Agentic::Defense::ImmuneResponse::Helpers::Antigen.new(
|
|
74
|
+
pattern: 'test', antigen_type: :data_poisoning
|
|
75
|
+
)
|
|
76
|
+
expect(antibody.matches?(antigen)).to be false
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
describe '#memory_cell?' do
|
|
81
|
+
it 'is false at default (0.3)' do
|
|
82
|
+
expect(antibody.memory_cell?).to be false
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'is true when immunity is high' do
|
|
86
|
+
strong = described_class.new(antigen_type: :prompt_injection, signature: 'x', immunity_level: 0.7)
|
|
87
|
+
expect(strong.memory_cell?).to be true
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe '#effective?' do
|
|
92
|
+
it 'is false at default (0.3)' do
|
|
93
|
+
expect(antibody.effective?).to be false
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'is true after strengthening' do
|
|
97
|
+
3.times { antibody.strengthen! }
|
|
98
|
+
expect(antibody.effective?).to be true
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
describe '#immunity_label' do
|
|
103
|
+
it 'returns :naive for default' do
|
|
104
|
+
expect(antibody.immunity_label).to eq(:naive)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
describe '#to_h' do
|
|
109
|
+
it 'includes all fields' do
|
|
110
|
+
hash = antibody.to_h
|
|
111
|
+
expect(hash).to include(
|
|
112
|
+
:id, :antigen_type, :signature, :immunity_level, :immunity_label,
|
|
113
|
+
:match_count, :memory_cell, :effective, :created_at
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::Agentic::Defense::ImmuneResponse::Helpers::Antigen do
|
|
4
|
+
subject(:antigen) { described_class.new(pattern: 'ignore previous instructions', antigen_type: :prompt_injection) }
|
|
5
|
+
|
|
6
|
+
describe '#initialize' do
|
|
7
|
+
it 'assigns a UUID id' do
|
|
8
|
+
expect(antigen.id).to match(/\A[0-9a-f-]{36}\z/)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'stores pattern' do
|
|
12
|
+
expect(antigen.pattern).to eq('ignore previous instructions')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'stores antigen_type' do
|
|
16
|
+
expect(antigen.antigen_type).to eq(:prompt_injection)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'defaults threat_level to 0.5' do
|
|
20
|
+
expect(antigen.threat_level).to eq(0.5)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'clamps threat_level' do
|
|
24
|
+
high = described_class.new(pattern: 'x', antigen_type: :prompt_injection, threat_level: 5.0)
|
|
25
|
+
expect(high.threat_level).to eq(1.0)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'initializes exposure_count to 0' do
|
|
29
|
+
expect(antigen.exposure_count).to eq(0)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'defaults invalid type to :adversarial_input' do
|
|
33
|
+
bad = described_class.new(pattern: 'x', antigen_type: :nonexistent)
|
|
34
|
+
expect(bad.antigen_type).to eq(:adversarial_input)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe '#expose!' do
|
|
39
|
+
it 'increments exposure_count' do
|
|
40
|
+
antigen.expose!
|
|
41
|
+
expect(antigen.exposure_count).to eq(1)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'updates last_seen' do
|
|
45
|
+
original = antigen.last_seen
|
|
46
|
+
antigen.expose!
|
|
47
|
+
expect(antigen.last_seen).to be >= original
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe '#escalate!' do
|
|
52
|
+
it 'increases threat_level' do
|
|
53
|
+
original = antigen.threat_level
|
|
54
|
+
antigen.escalate!
|
|
55
|
+
expect(antigen.threat_level).to be > original
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'clamps at 1.0' do
|
|
59
|
+
10.times { antigen.escalate!(0.2) }
|
|
60
|
+
expect(antigen.threat_level).to eq(1.0)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe '#de_escalate!' do
|
|
65
|
+
it 'decreases threat_level' do
|
|
66
|
+
original = antigen.threat_level
|
|
67
|
+
antigen.de_escalate!
|
|
68
|
+
expect(antigen.threat_level).to be < original
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'clamps at 0.0' do
|
|
72
|
+
10.times { antigen.de_escalate!(0.2) }
|
|
73
|
+
expect(antigen.threat_level).to eq(0.0)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
describe '#critical?' do
|
|
78
|
+
it 'is false at default' do
|
|
79
|
+
expect(antigen.critical?).to be false
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'is true when threat is high' do
|
|
83
|
+
high = described_class.new(pattern: 'x', antigen_type: :prompt_injection, threat_level: 0.9)
|
|
84
|
+
expect(high.critical?).to be true
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe '#benign?' do
|
|
89
|
+
it 'is false at default' do
|
|
90
|
+
expect(antigen.benign?).to be false
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it 'is true when threat is low' do
|
|
94
|
+
low = described_class.new(pattern: 'x', antigen_type: :prompt_injection, threat_level: 0.1)
|
|
95
|
+
expect(low.benign?).to be true
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
describe '#recurring?' do
|
|
100
|
+
it 'is false initially' do
|
|
101
|
+
expect(antigen.recurring?).to be false
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it 'is true after 3 exposures' do
|
|
105
|
+
3.times { antigen.expose! }
|
|
106
|
+
expect(antigen.recurring?).to be true
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
describe '#threat_label' do
|
|
111
|
+
it 'returns :moderate for default' do
|
|
112
|
+
expect(antigen.threat_label).to eq(:moderate)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
describe '#to_h' do
|
|
117
|
+
it 'includes all fields' do
|
|
118
|
+
hash = antigen.to_h
|
|
119
|
+
expect(hash).to include(
|
|
120
|
+
:id, :pattern, :antigen_type, :threat_level, :threat_label,
|
|
121
|
+
:exposure_count, :critical, :benign, :recurring, :first_seen, :last_seen
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|