lex-agentic-defense 0.1.0 → 0.1.4

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 (21) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/README.md +45 -3
  4. data/lib/legion/extensions/agentic/defense/epistemic_vigilance/helpers/vigilance_engine.rb +2 -0
  5. data/lib/legion/extensions/agentic/defense/epistemic_vigilance/version.rb +1 -1
  6. data/lib/legion/extensions/agentic/defense/extinction/runners/extinction.rb +17 -7
  7. data/lib/legion/extensions/agentic/defense/friction/helpers/friction_engine.rb +14 -2
  8. data/lib/legion/extensions/agentic/defense/friction/version.rb +1 -1
  9. data/lib/legion/extensions/agentic/defense/immune_response/helpers/immune_engine.rb +4 -0
  10. data/lib/legion/extensions/agentic/defense/immune_response/version.rb +1 -1
  11. data/lib/legion/extensions/agentic/defense/immunology/helpers/immune_engine.rb +4 -0
  12. data/lib/legion/extensions/agentic/defense/immunology/version.rb +1 -1
  13. data/lib/legion/extensions/agentic/defense/version.rb +1 -1
  14. data/spec/legion/extensions/agentic/defense/epistemic_vigilance/helpers/vigilance_engine_spec.rb +14 -0
  15. data/spec/legion/extensions/agentic/defense/extinction/runners/extinction_spec.rb +20 -0
  16. data/spec/legion/extensions/agentic/defense/friction/helpers/friction_engine_spec.rb +82 -44
  17. data/spec/legion/extensions/agentic/defense/friction/runners/cognitive_friction_spec.rb +9 -9
  18. data/spec/legion/extensions/agentic/defense/immune_response/cognitive_immune_response_spec.rb +1 -1
  19. data/spec/legion/extensions/agentic/defense/immune_response/helpers/immune_engine_spec.rb +26 -0
  20. data/spec/legion/extensions/agentic/defense/immunology/helpers/immune_engine_spec.rb +26 -0
  21. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 38b6880ab2168a8ec8e0e037c8f031d175eb30312cea1b00981b7d5b67a631b1
4
- data.tar.gz: ee8f26503794b5f3168120b1bd9537f9fdcad7eedfb5dd0efab75b016ce55b59
3
+ metadata.gz: 3dabe10b53cfee2ee630d2f7094a78a0731f7a0f4038f218bf2ac177d409352b
4
+ data.tar.gz: 78a58c65ef5d6465e03322e6fe9031c333078c65b983bf0be11b07b2f27b3fd7
5
5
  SHA512:
6
- metadata.gz: 8cfe651310f156f79c8c1476e210fd17b90a2344d689d5b636edd94396d59353b42f8ae53e36f4e4e400f0c7584272dbc79ae6d0fa434fded2a8f2f3c4e4fcd7
7
- data.tar.gz: 929f72c0515d624586865faed6ed4ecd65cfa8f3c65849f66363539b1d7c66c2b65210c8cca8190f4dccf19eb4fe5fe4f9634e55a61d477e4662e9a841fa0eae
6
+ metadata.gz: 671ce401a9d8ba558d5ee77e8223b716115543d892944542155be97748052c0240830c535995c60251bd2d8fcb40abf16eeffe15f84bce38eae6bcd9eaabb493
7
+ data.tar.gz: 5c91978e36743338e7fa07bc290d8c26650016a9d6523ea1ffc7532a241a6a83617dc4a1b9f5483d0c8abc26111f02bfc19051fb954b79b639a9dc0bf26cb139
data/CHANGELOG.md CHANGED
@@ -2,6 +2,29 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.1.3] - 2026-03-21
6
+
7
+ ### Added
8
+ - Right-to-erasure propagation from extinction level 4 to Apollo knowledge store
9
+ - Apollo erasure wired in enforce_escalation_effects (delete non-confirmed, redact confirmed)
10
+
11
+ ### Changed
12
+ - enforce_escalation_effects restructured: DigitalWorker termination uses if-block instead of early return
13
+
14
+ ## [0.1.2] - 2026-03-18
15
+
16
+ ### Changed
17
+ - Enforce ANTIGEN_TYPES at ImmuneResponse::ImmuneEngine method boundaries: `register_antigen` and `create_antibody` return nil for invalid antigen_type values (v0.1.1)
18
+ - Enforce MANIPULATION_TACTICS at Immunology::ImmuneEngine method boundaries: `detect_threat` and `create_antibody` return nil for invalid tactic values (v0.1.1)
19
+ - Added 8 new enum validation specs (2 per enforced constant)
20
+
21
+ ## [0.1.1] - 2026-03-18
22
+
23
+ ### Changed
24
+ - Enforce STATE_TYPES at FrictionEngine method boundaries: `set_current_state`, `set_friction`, `get_friction`, `attempt_transition`, `force_transition` return nil for invalid state values
25
+ - Enforce CLAIM_VERDICTS at VigilanceEngine#adjudicate_claim: returns nil for invalid verdict values
26
+ - Updated specs to use valid STATE_TYPES values; added 8 new enum validation specs
27
+
5
28
  ## [0.1.0] - 2026-03-18
6
29
 
7
30
  ### Added
data/README.md CHANGED
@@ -1,13 +1,55 @@
1
1
  # lex-agentic-defense
2
2
 
3
- LEX agentic defense domain: threat detection, stress response, boundaries
3
+ Domain consolidation gem for cognitive defense, immunity, and error management. Bundles 15 source extensions into one loadable unit under `Legion::Extensions::Agentic::Defense`.
4
4
 
5
- ## Sub-modules
5
+ ## Overview
6
6
 
7
- *(populated as extensions are consolidated)*
7
+ **Gem**: `lex-agentic-defense`
8
+ **Version**: 0.1.2
9
+ **Namespace**: `Legion::Extensions::Agentic::Defense`
10
+
11
+ ## Sub-Modules
12
+
13
+ | Sub-Module | Source Gem | Purpose |
14
+ |---|---|---|
15
+ | `Defense::ImmuneResponse` | `lex-cognitive-immune-response` | Active defense responses to cognitive threats |
16
+ | `Defense::Immunology` | `lex-cognitive-immunology` | Immune system modeling for belief protection |
17
+ | `Defense::Erosion` | `lex-cognitive-erosion` | Gradual degradation of outdated beliefs |
18
+ | `Defense::Friction` | `lex-cognitive-friction` | Resistance to undesired belief or behavior change |
19
+ | `Defense::Quicksand` | `lex-cognitive-quicksand` | Entrapment patterns — stuck states |
20
+ | `Defense::Quicksilver` | `lex-cognitive-quicksilver` | Rapid adaptive response to threats |
21
+ | `Defense::Phantom` | `lex-cognitive-phantom` | Phantom cognitive states — residual patterns after removal |
22
+ | `Defense::EpistemicVigilance` | `lex-epistemic-vigilance` | Critical evaluation of incoming information, deception detection |
23
+ | `Defense::Bias` | `lex-bias` | Cognitive bias catalog and de-biasing strategies |
24
+ | `Defense::Confabulation` | `lex-confabulation` | False memory generation detection |
25
+ | `Defense::Dissonance` | `lex-dissonance` | Cognitive dissonance detection and reduction |
26
+ | `Defense::ErrorMonitoring` | `lex-error-monitoring` | ACC error monitoring — anterior cingulate analog |
27
+ | `Defense::Extinction` | `lex-extinction` | Four-level containment ladder with authority-gated escalation |
28
+ | `Defense::Avalanche` | `lex-cognitive-avalanche` | Cascading cognitive failure detection |
29
+ | `Defense::Whirlpool` | `lex-cognitive-whirlpool` | Circular/recursive thought pattern detection |
30
+
31
+ ## Actors
32
+
33
+ - `Defense::Bias::Actors::Update` — interval actor, updates bias calibration state
34
+ - `Defense::Confabulation::Actors::Decay` — interval actor, decays confabulation detections
35
+ - `Defense::EpistemicVigilance::Actors::Update` — interval actor, updates vigilance baseline
36
+ - `Defense::ErrorMonitoring::Actors::Tick` — interval actor, runs error monitoring tick
37
+ - `Defense::Extinction::Actors::ProtocolMonitor` — runs every 300s, monitors containment protocol state
8
38
 
9
39
  ## Installation
10
40
 
11
41
  ```ruby
12
42
  gem 'lex-agentic-defense'
13
43
  ```
44
+
45
+ ## Development
46
+
47
+ ```bash
48
+ bundle install
49
+ bundle exec rspec # 1706 examples, 0 failures
50
+ bundle exec rubocop # 0 offenses
51
+ ```
52
+
53
+ ## License
54
+
55
+ MIT
@@ -74,6 +74,8 @@ module Legion
74
74
  end
75
75
 
76
76
  def adjudicate_claim(claim_id:, verdict:)
77
+ return nil unless CLAIM_VERDICTS.include?(verdict.to_sym)
78
+
77
79
  claim = @claims[claim_id]
78
80
  source = claim && @sources[claim.source_id]
79
81
  return { error: :claim_not_found } unless claim
@@ -5,7 +5,7 @@ module Legion
5
5
  module Agentic
6
6
  module Defense
7
7
  module EpistemicVigilance
8
- VERSION = '0.1.0'
8
+ VERSION = '0.1.1'
9
9
  end
10
10
  end
11
11
  end
@@ -84,16 +84,26 @@ module Legion
84
84
  Legion::Logging.warn '[extinction] cryptographic erasure triggered'
85
85
  end
86
86
 
87
- return unless defined?(Legion::Data::Model::DigitalWorker)
87
+ if defined?(Legion::Data::Model::DigitalWorker)
88
+ begin
89
+ Legion::Data::Model::DigitalWorker.where(lifecycle_state: 'active').update(
90
+ lifecycle_state: 'terminated', updated_at: Time.now.utc
91
+ )
92
+ rescue StandardError
93
+ nil
94
+ end
95
+ Legion::Logging.warn '[extinction] all active workers terminated'
96
+ end
97
+
98
+ return unless defined?(Legion::Extensions::Apollo::Runners::Knowledge)
88
99
 
89
100
  begin
90
- Legion::Data::Model::DigitalWorker.where(lifecycle_state: 'active').update(
91
- lifecycle_state: 'terminated', updated_at: Time.now.utc
92
- )
93
- rescue StandardError
94
- nil
101
+ obj = Object.new.extend(Legion::Extensions::Apollo::Runners::Knowledge)
102
+ obj.handle_erasure_request(agent_id: 'system:extinction')
103
+ Legion::Logging.warn '[extinction] apollo erasure propagated'
104
+ rescue StandardError => e
105
+ Legion::Logging.error "[extinction] apollo erasure failed: #{e.message}"
95
106
  end
96
- Legion::Logging.warn '[extinction] all active workers terminated'
97
107
  end
98
108
 
99
109
  def emit_escalation_event(level, authority, reason)
@@ -16,24 +16,34 @@ module Legion
16
16
  end
17
17
 
18
18
  def set_current_state(state:)
19
+ return nil unless STATE_TYPES.include?(state.to_sym)
20
+
19
21
  @current_state = state.to_sym
20
22
  end
21
23
 
22
24
  attr_reader :current_state
23
25
 
24
26
  def set_friction(from_state:, to_state:, friction:)
27
+ return nil unless STATE_TYPES.include?(from_state.to_sym)
28
+ return nil unless STATE_TYPES.include?(to_state.to_sym)
29
+
25
30
  key = :"#{from_state}_to_#{to_state}"
26
31
  @friction_map[key] = friction.to_f.clamp(0.0, 1.0)
27
32
  end
28
33
 
29
34
  def get_friction(from_state:, to_state:)
35
+ return nil unless STATE_TYPES.include?(from_state.to_sym)
36
+ return nil unless STATE_TYPES.include?(to_state.to_sym)
37
+
30
38
  key = :"#{from_state}_to_#{to_state}"
31
39
  @friction_map.fetch(key, DEFAULT_FRICTION)
32
40
  end
33
41
 
34
42
  def attempt_transition(to_state:, force: 0.5)
43
+ return nil unless STATE_TYPES.include?(to_state.to_sym)
44
+
35
45
  prune_if_needed
36
- friction = get_friction(from_state: @current_state, to_state: to_state)
46
+ friction = get_friction(from_state: @current_state, to_state: to_state) || DEFAULT_FRICTION
37
47
  transition = StateTransition.new(
38
48
  from_state: @current_state,
39
49
  to_state: to_state,
@@ -46,8 +56,10 @@ module Legion
46
56
  end
47
57
 
48
58
  def force_transition(to_state:)
59
+ return nil unless STATE_TYPES.include?(to_state.to_sym)
60
+
49
61
  prune_if_needed
50
- friction = get_friction(from_state: @current_state, to_state: to_state)
62
+ friction = get_friction(from_state: @current_state, to_state: to_state) || DEFAULT_FRICTION
51
63
  transition = StateTransition.new(
52
64
  from_state: @current_state,
53
65
  to_state: to_state,
@@ -5,7 +5,7 @@ module Legion
5
5
  module Agentic
6
6
  module Defense
7
7
  module Friction
8
- VERSION = '0.1.0'
8
+ VERSION = '0.1.1'
9
9
  end
10
10
  end
11
11
  end
@@ -16,6 +16,8 @@ module Legion
16
16
  end
17
17
 
18
18
  def register_antigen(pattern:, antigen_type:, threat_level: DEFAULT_THREAT_LEVEL)
19
+ return nil unless ANTIGEN_TYPES.include?(antigen_type.to_sym)
20
+
19
21
  prune_antigens
20
22
  antigen = Antigen.new(
21
23
  pattern: pattern, antigen_type: antigen_type, threat_level: threat_level
@@ -38,6 +40,8 @@ module Legion
38
40
  end
39
41
 
40
42
  def create_antibody(antigen_type:, signature:, immunity_level: 0.3)
43
+ return nil unless ANTIGEN_TYPES.include?(antigen_type.to_sym)
44
+
41
45
  prune_antibodies
42
46
  antibody = Antibody.new(
43
47
  antigen_type: antigen_type, signature: signature, immunity_level: immunity_level
@@ -5,7 +5,7 @@ module Legion
5
5
  module Agentic
6
6
  module Defense
7
7
  module ImmuneResponse
8
- VERSION = '0.1.0'
8
+ VERSION = '0.1.1'
9
9
  end
10
10
  end
11
11
  end
@@ -17,6 +17,8 @@ module Legion
17
17
  end
18
18
 
19
19
  def detect_threat(source:, tactic:, content_hash:, threat_level: 0.5)
20
+ return nil unless Constants::MANIPULATION_TACTICS.include?(tactic.to_sym)
21
+
20
22
  prune_threats_if_full
21
23
 
22
24
  threat = Threat.new(
@@ -68,6 +70,8 @@ module Legion
68
70
  end
69
71
 
70
72
  def create_antibody(tactic:, pattern:, strength: 0.5)
73
+ return nil unless Constants::MANIPULATION_TACTICS.include?(tactic.to_sym)
74
+
71
75
  prune_antibodies_if_full
72
76
 
73
77
  ab = Antibody.new(tactic: tactic, pattern: pattern, strength: strength)
@@ -5,7 +5,7 @@ module Legion
5
5
  module Agentic
6
6
  module Defense
7
7
  module Immunology
8
- VERSION = '0.1.0'
8
+ VERSION = '0.1.1'
9
9
  end
10
10
  end
11
11
  end
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Agentic
6
6
  module Defense
7
- VERSION = '0.1.0'
7
+ VERSION = '0.1.4'
8
8
  end
9
9
  end
10
10
  end
@@ -149,6 +149,20 @@ RSpec.describe Legion::Extensions::Agentic::Defense::EpistemicVigilance::Helpers
149
149
  after = engine.source_reliability(source_id: source_id)[:reliability]
150
150
  expect(after).to be < before
151
151
  end
152
+
153
+ it 'returns nil for an invalid verdict' do
154
+ result = engine.adjudicate_claim(claim_id: claim_id, verdict: :bogus_verdict)
155
+ expect(result).to be_nil
156
+ end
157
+
158
+ it 'accepts all valid CLAIM_VERDICTS' do
159
+ verdicts = Legion::Extensions::Agentic::Defense::EpistemicVigilance::Helpers::Constants::CLAIM_VERDICTS
160
+ verdicts.each do |v|
161
+ cid = engine.submit_claim(content: "claim_#{v}", source_id: source_id, domain: :science)[:claim][:id]
162
+ result = engine.adjudicate_claim(claim_id: cid, verdict: v)
163
+ expect(result[:verdict]).to eq(v)
164
+ end
165
+ end
152
166
  end
153
167
 
154
168
  describe '#source_reliability' do
@@ -80,6 +80,26 @@ RSpec.describe Legion::Extensions::Agentic::Defense::Extinction::Runners::Extinc
80
80
  expect(pc_mod).to receive(:erase_all)
81
81
  client.escalate(level: 4, authority: :physical_keyholders, reason: 'final')
82
82
  end
83
+
84
+ it 'propagates Apollo erasure on level 4' do
85
+ events = Module.new { def self.emit(*, **); end }
86
+ stub_const('Legion::Events', events)
87
+ pc_mod = Module.new { def self.erase_all; end }
88
+ stub_const('Legion::Extensions::Privatecore::Runners::Privatecore', pc_mod)
89
+
90
+ apollo_runner = Module.new do
91
+ def handle_erasure_request(agent_id:, **)
92
+ { deleted: 1, redacted: 0, agent_id: agent_id }
93
+ end
94
+ end
95
+ stub_const('Legion::Extensions::Apollo::Runners::Knowledge', apollo_runner)
96
+
97
+ client.escalate(level: 1, authority: :governance_council, reason: 's1')
98
+ client.escalate(level: 2, authority: :governance_council, reason: 's2')
99
+ client.escalate(level: 3, authority: :council_plus_executive, reason: 's3')
100
+ result = client.escalate(level: 4, authority: :physical_keyholders, reason: 'final')
101
+ expect(result[:escalated]).to be true
102
+ end
83
103
  end
84
104
 
85
105
  describe '#monitor_protocol' do
@@ -10,92 +10,126 @@ RSpec.describe Legion::Extensions::Agentic::Defense::Friction::Helpers::Friction
10
10
  end
11
11
 
12
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)
13
+ it 'changes the current state to a valid STATE_TYPE' do
14
+ engine.set_current_state(state: :focus_mode)
15
+ expect(engine.current_state).to eq(:focus_mode)
16
16
  end
17
17
 
18
- it 'converts to symbol' do
19
- engine.set_current_state(state: 'thinking')
20
- expect(engine.current_state).to eq(:thinking)
18
+ it 'returns nil for an invalid state' do
19
+ result = engine.set_current_state(state: :invalid_state)
20
+ expect(result).to be_nil
21
+ expect(engine.current_state).to eq(:rest_mode)
22
+ end
23
+
24
+ it 'accepts all valid STATE_TYPES' do
25
+ described_class.include(Legion::Extensions::Agentic::Defense::Friction::Helpers::Constants)
26
+ Legion::Extensions::Agentic::Defense::Friction::Helpers::Constants::STATE_TYPES.each do |state|
27
+ eng = described_class.new
28
+ eng.set_current_state(state: state)
29
+ expect(eng.current_state).to eq(state)
30
+ end
21
31
  end
22
32
  end
23
33
 
24
34
  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)
35
+ it 'stores and retrieves friction for a valid path' do
36
+ engine.set_friction(from_state: :rest_mode, to_state: :focus_mode, friction: 0.7)
37
+ expect(engine.get_friction(from_state: :rest_mode, to_state: :focus_mode)).to eq(0.7)
28
38
  end
29
39
 
30
40
  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)
41
+ engine.set_friction(from_state: :rest_mode, to_state: :focus_mode, friction: 5.0)
42
+ expect(engine.get_friction(from_state: :rest_mode, to_state: :focus_mode)).to eq(1.0)
43
+ end
44
+
45
+ it 'returns nil when from_state is invalid' do
46
+ expect(engine.set_friction(from_state: :invalid, to_state: :focus_mode, friction: 0.5)).to be_nil
47
+ end
48
+
49
+ it 'returns nil when to_state is invalid' do
50
+ expect(engine.set_friction(from_state: :rest_mode, to_state: :invalid, friction: 0.5)).to be_nil
33
51
  end
34
52
 
35
- it 'returns default friction for unknown paths' do
53
+ it 'returns nil from get_friction when from_state is invalid' do
54
+ expect(engine.get_friction(from_state: :invalid, to_state: :focus_mode)).to be_nil
55
+ end
56
+
57
+ it 'returns default friction for unknown valid paths' do
36
58
  default = Legion::Extensions::Agentic::Defense::Friction::Helpers::Constants::DEFAULT_FRICTION
37
- expect(engine.get_friction(from_state: :x, to_state: :y)).to eq(default)
59
+ expect(engine.get_friction(from_state: :rest_mode, to_state: :focus_mode)).to eq(default)
38
60
  end
39
61
  end
40
62
 
41
63
  describe '#attempt_transition' do
42
64
  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)
65
+ engine.set_friction(from_state: :rest_mode, to_state: :focus_mode, friction: 0.3)
66
+ transition = engine.attempt_transition(to_state: :focus_mode, force: 0.8)
45
67
  expect(transition.completed?).to be true
46
- expect(engine.current_state).to eq(:active)
68
+ expect(engine.current_state).to eq(:focus_mode)
47
69
  end
48
70
 
49
71
  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)
72
+ engine.set_friction(from_state: :rest_mode, to_state: :focus_mode, friction: 0.9)
73
+ transition = engine.attempt_transition(to_state: :focus_mode, force: 0.1)
52
74
  expect(transition.completed?).to be false
53
75
  expect(engine.current_state).to eq(:rest_mode)
54
76
  end
55
77
 
56
78
  it 'records the transition in history' do
57
- engine.attempt_transition(to_state: :active, force: 0.8)
79
+ engine.attempt_transition(to_state: :focus_mode, force: 0.8)
58
80
  expect(engine.transition_history.size).to eq(1)
59
81
  end
82
+
83
+ it 'returns nil for invalid to_state' do
84
+ result = engine.attempt_transition(to_state: :invalid_state, force: 0.9)
85
+ expect(result).to be_nil
86
+ end
60
87
  end
61
88
 
62
89
  describe '#force_transition' do
63
90
  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)
91
+ engine.set_friction(from_state: :rest_mode, to_state: :vigilant_mode, friction: 1.0)
92
+ transition = engine.force_transition(to_state: :vigilant_mode)
66
93
  expect(transition.completed?).to be true
67
- expect(engine.current_state).to eq(:locked)
94
+ expect(engine.current_state).to eq(:vigilant_mode)
95
+ end
96
+
97
+ it 'returns nil for invalid to_state' do
98
+ result = engine.force_transition(to_state: :bogus_mode)
99
+ expect(result).to be_nil
68
100
  end
69
101
  end
70
102
 
71
103
  describe '#transition_history' do
72
104
  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)
105
+ engine.attempt_transition(to_state: :focus_mode, force: 0.9)
106
+ engine.attempt_transition(to_state: :analytical_mode, force: 0.9)
75
107
  history = engine.transition_history
76
108
  expect(history.first.created_at).to be <= history.last.created_at
77
109
  end
78
110
 
79
111
  it 'respects the limit' do
80
- 5.times { |i| engine.attempt_transition(to_state: :"s#{i}", force: 0.9) }
112
+ states = %i[focus_mode social_mode analytical_mode creative_mode vigilant_mode]
113
+ states.each { |s| engine.attempt_transition(to_state: s, force: 0.9) }
81
114
  expect(engine.transition_history(limit: 3).size).to eq(3)
82
115
  end
83
116
  end
84
117
 
85
118
  describe '#successful_transitions' do
86
119
  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)
120
+ engine.set_friction(from_state: :rest_mode, to_state: :focus_mode, friction: 0.3)
121
+ engine.attempt_transition(to_state: :focus_mode, force: 0.8)
122
+ engine.set_current_state(state: :focus_mode)
123
+ engine.set_friction(from_state: :focus_mode, to_state: :analytical_mode, friction: 0.9)
124
+ engine.attempt_transition(to_state: :analytical_mode, force: 0.1)
91
125
  expect(engine.successful_transitions.size).to eq(1)
92
126
  end
93
127
  end
94
128
 
95
129
  describe '#resisted_transitions' do
96
130
  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)
131
+ engine.set_friction(from_state: :rest_mode, to_state: :vigilant_mode, friction: 0.9)
132
+ engine.attempt_transition(to_state: :vigilant_mode, force: 0.1)
99
133
  expect(engine.resisted_transitions.size).to eq(1)
100
134
  end
101
135
  end
@@ -106,10 +140,11 @@ RSpec.describe Legion::Extensions::Agentic::Defense::Friction::Helpers::Friction
106
140
  end
107
141
 
108
142
  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)
143
+ engine.set_friction(from_state: :rest_mode, to_state: :focus_mode, friction: 0.3)
144
+ engine.attempt_transition(to_state: :focus_mode, force: 0.8)
145
+ engine.set_current_state(state: :focus_mode)
146
+ engine.set_friction(from_state: :focus_mode, to_state: :social_mode, friction: 0.9)
147
+ engine.attempt_transition(to_state: :social_mode, force: 0.1)
113
148
  expect(engine.success_rate).to eq(0.5)
114
149
  end
115
150
  end
@@ -120,26 +155,28 @@ RSpec.describe Legion::Extensions::Agentic::Defense::Friction::Helpers::Friction
120
155
  end
121
156
 
122
157
  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)
158
+ engine.set_friction(from_state: :rest_mode, to_state: :focus_mode, friction: 0.2)
159
+ engine.attempt_transition(to_state: :focus_mode, force: 0.9)
160
+ engine.set_current_state(state: :focus_mode)
161
+ engine.set_friction(from_state: :focus_mode, to_state: :social_mode, friction: 0.8)
162
+ engine.attempt_transition(to_state: :social_mode, force: 0.9)
127
163
  expect(engine.average_friction).to eq(0.5)
128
164
  end
129
165
  end
130
166
 
131
167
  describe '#highest_friction_paths' do
132
168
  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)
169
+ engine.set_friction(from_state: :rest_mode, to_state: :focus_mode, friction: 0.3)
170
+ engine.set_friction(from_state: :rest_mode, to_state: :social_mode, friction: 0.9)
171
+ engine.set_friction(from_state: :rest_mode, to_state: :analytical_mode, friction: 0.6)
136
172
  paths = engine.highest_friction_paths(limit: 3)
137
173
  expect(paths.first[:friction]).to eq(0.9)
138
174
  expect(paths.last[:friction]).to eq(0.3)
139
175
  end
140
176
 
141
177
  it 'respects the limit' do
142
- 5.times { |i| engine.set_friction(from_state: :"a#{i}", to_state: :"b#{i}", friction: i * 0.2) }
178
+ valid_states = %i[focus_mode social_mode analytical_mode creative_mode vigilant_mode]
179
+ valid_states.each { |s| engine.set_friction(from_state: :rest_mode, to_state: s, friction: 0.5) }
143
180
  expect(engine.highest_friction_paths(limit: 2).size).to eq(2)
144
181
  end
145
182
  end
@@ -168,7 +205,8 @@ RSpec.describe Legion::Extensions::Agentic::Defense::Friction::Helpers::Friction
168
205
  it 'prunes oldest transition when limit reached' do
169
206
  stub_const('Legion::Extensions::Agentic::Defense::Friction::Helpers::Constants::MAX_TRANSITIONS', 3)
170
207
  eng = described_class.new
171
- 4.times { |i| eng.attempt_transition(to_state: :"s#{i}", force: 0.9) }
208
+ states = %i[focus_mode social_mode analytical_mode creative_mode]
209
+ states.each { |s| eng.attempt_transition(to_state: s, force: 0.9) }
172
210
  expect(eng.transition_history.size).to eq(3)
173
211
  end
174
212
  end
@@ -5,15 +5,15 @@ RSpec.describe Legion::Extensions::Agentic::Defense::Friction::Runners::Cognitiv
5
5
 
6
6
  describe '#set_current_state' do
7
7
  it 'returns success with new state' do
8
- result = client.set_current_state(state: :active)
8
+ result = client.set_current_state(state: :focus_mode)
9
9
  expect(result[:success]).to be true
10
- expect(result[:state]).to eq(:active)
10
+ expect(result[:state]).to eq(:focus_mode)
11
11
  end
12
12
  end
13
13
 
14
14
  describe '#set_friction' do
15
15
  it 'returns success with friction details' do
16
- result = client.set_friction(from_state: :rest, to_state: :active, friction: 0.7)
16
+ result = client.set_friction(from_state: :rest_mode, to_state: :focus_mode, friction: 0.7)
17
17
  expect(result[:success]).to be true
18
18
  expect(result[:friction]).to eq(0.7)
19
19
  end
@@ -21,8 +21,8 @@ RSpec.describe Legion::Extensions::Agentic::Defense::Friction::Runners::Cognitiv
21
21
 
22
22
  describe '#get_friction' do
23
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)
24
+ client.set_friction(from_state: :rest_mode, to_state: :focus_mode, friction: 0.6)
25
+ result = client.get_friction(from_state: :rest_mode, to_state: :focus_mode)
26
26
  expect(result[:success]).to be true
27
27
  expect(result[:friction]).to eq(0.6)
28
28
  end
@@ -30,7 +30,7 @@ RSpec.describe Legion::Extensions::Agentic::Defense::Friction::Runners::Cognitiv
30
30
 
31
31
  describe '#attempt_transition' do
32
32
  it 'returns transition details' do
33
- result = client.attempt_transition(to_state: :active, force: 0.9)
33
+ result = client.attempt_transition(to_state: :focus_mode, force: 0.9)
34
34
  expect(result[:success]).to be true
35
35
  expect(result[:transition]).to be_a(Hash)
36
36
  expect(result[:current_state]).to be_a(Symbol)
@@ -39,7 +39,7 @@ RSpec.describe Legion::Extensions::Agentic::Defense::Friction::Runners::Cognitiv
39
39
 
40
40
  describe '#force_transition' do
41
41
  it 'always completes' do
42
- result = client.force_transition(to_state: :locked)
42
+ result = client.force_transition(to_state: :vigilant_mode)
43
43
  expect(result[:success]).to be true
44
44
  expect(result[:transition][:completed]).to be true
45
45
  end
@@ -47,7 +47,7 @@ RSpec.describe Legion::Extensions::Agentic::Defense::Friction::Runners::Cognitiv
47
47
 
48
48
  describe '#transition_history' do
49
49
  it 'returns history array' do
50
- client.attempt_transition(to_state: :a, force: 0.9)
50
+ client.attempt_transition(to_state: :focus_mode, force: 0.9)
51
51
  result = client.transition_history
52
52
  expect(result[:success]).to be true
53
53
  expect(result[:count]).to eq(1)
@@ -72,7 +72,7 @@ RSpec.describe Legion::Extensions::Agentic::Defense::Friction::Runners::Cognitiv
72
72
 
73
73
  describe '#highest_friction_paths' do
74
74
  it 'returns paths array' do
75
- client.set_friction(from_state: :a, to_state: :b, friction: 0.8)
75
+ client.set_friction(from_state: :rest_mode, to_state: :focus_mode, friction: 0.8)
76
76
  result = client.highest_friction_paths
77
77
  expect(result[:success]).to be true
78
78
  expect(result[:paths]).to be_a(Array)
@@ -2,6 +2,6 @@
2
2
 
3
3
  RSpec.describe Legion::Extensions::Agentic::Defense::ImmuneResponse do
4
4
  it 'has a version number' do
5
- expect(Legion::Extensions::Agentic::Defense::ImmuneResponse::VERSION).to eq('0.1.0')
5
+ expect(Legion::Extensions::Agentic::Defense::ImmuneResponse::VERSION).to eq('0.1.1')
6
6
  end
7
7
  end
@@ -15,6 +15,19 @@ RSpec.describe Legion::Extensions::Agentic::Defense::ImmuneResponse::Helpers::Im
15
15
  ag = engine.register_antigen(pattern: 'test', antigen_type: :data_poisoning)
16
16
  expect(engine.most_threatening.map(&:id)).to include(ag.id)
17
17
  end
18
+
19
+ it 'rejects invalid antigen_type' do
20
+ result = engine.register_antigen(pattern: 'test', antigen_type: :nonexistent_threat)
21
+ expect(result).to be_nil
22
+ end
23
+
24
+ it 'accepts all ANTIGEN_TYPES' do
25
+ constants = Legion::Extensions::Agentic::Defense::ImmuneResponse::Helpers::Constants::ANTIGEN_TYPES
26
+ constants.each do |val|
27
+ result = engine.register_antigen(pattern: 'test', antigen_type: val)
28
+ expect(result).not_to be_nil, "Expected #{val.inspect} to be accepted"
29
+ end
30
+ end
18
31
  end
19
32
 
20
33
  describe '#encounter' do
@@ -52,6 +65,19 @@ RSpec.describe Legion::Extensions::Agentic::Defense::ImmuneResponse::Helpers::Im
52
65
  ab = engine.create_antibody(antigen_type: :prompt_injection, signature: 'test')
53
66
  expect(ab).to be_a(Legion::Extensions::Agentic::Defense::ImmuneResponse::Helpers::Antibody)
54
67
  end
68
+
69
+ it 'rejects invalid antigen_type' do
70
+ result = engine.create_antibody(antigen_type: :nonexistent_threat, signature: 'test')
71
+ expect(result).to be_nil
72
+ end
73
+
74
+ it 'accepts all ANTIGEN_TYPES' do
75
+ constants = Legion::Extensions::Agentic::Defense::ImmuneResponse::Helpers::Constants::ANTIGEN_TYPES
76
+ constants.each do |val|
77
+ result = engine.create_antibody(antigen_type: val, signature: 'test')
78
+ expect(result).not_to be_nil, "Expected #{val.inspect} to be accepted"
79
+ end
80
+ end
55
81
  end
56
82
 
57
83
  describe '#vaccinate' do
@@ -36,6 +36,19 @@ RSpec.describe Legion::Extensions::Agentic::Defense::Immunology::Helpers::Immune
36
36
  engine.detect_threat(source: 's2', tactic: :strawman, content_hash: 'b')
37
37
  expect(engine.to_h[:threat_count]).to eq(2)
38
38
  end
39
+
40
+ it 'rejects invalid tactic' do
41
+ result = engine.detect_threat(source: 'user', tactic: :nonexistent_tactic, content_hash: 'h1')
42
+ expect(result).to be_nil
43
+ end
44
+
45
+ it 'accepts all MANIPULATION_TACTICS' do
46
+ constants = Legion::Extensions::Agentic::Defense::Immunology::Helpers::Constants::MANIPULATION_TACTICS
47
+ constants.each do |val|
48
+ result = engine.detect_threat(source: 'user', tactic: val, content_hash: "hash_#{val}")
49
+ expect(result).not_to be_nil, "Expected #{val.inspect} to be accepted"
50
+ end
51
+ end
39
52
  end
40
53
 
41
54
  describe '#quarantine_threat' do
@@ -114,6 +127,19 @@ RSpec.describe Legion::Extensions::Agentic::Defense::Immunology::Helpers::Immune
114
127
  ab = engine.create_antibody(tactic: :gaslighting, pattern: 'test', strength: 0.8)
115
128
  expect(ab.strength).to eq(0.8)
116
129
  end
130
+
131
+ it 'rejects invalid tactic' do
132
+ result = engine.create_antibody(tactic: :nonexistent_tactic, pattern: 'test')
133
+ expect(result).to be_nil
134
+ end
135
+
136
+ it 'accepts all MANIPULATION_TACTICS' do
137
+ constants = Legion::Extensions::Agentic::Defense::Immunology::Helpers::Constants::MANIPULATION_TACTICS
138
+ constants.each do |val|
139
+ result = engine.create_antibody(tactic: val, pattern: "pattern_#{val}")
140
+ expect(result).not_to be_nil, "Expected #{val.inspect} to be accepted"
141
+ end
142
+ end
117
143
  end
118
144
 
119
145
  describe '#scan_for_tactic' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-agentic-defense
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity