lex-agentic-defense 0.1.9 → 0.1.11

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 438bf1f411bc8c25778ba2b7294eb8f9d2f5b0d917592e2840a8e3fc90456058
4
- data.tar.gz: d88fb5784d2d8c146f385d04f1b30c0c8347962161ce0f95d317bd2946c3d4c8
3
+ metadata.gz: 04de0f8db49067510bfca89be592029c22ee49b658a37594b833b8d0cf679e53
4
+ data.tar.gz: 8211375022a5be1d4a830faa5ca509b045ff1ac1a0b56e38fb4b6e4c19adfc87
5
5
  SHA512:
6
- metadata.gz: 6ea987da729a0f6da4eb313a8e7dbf9363c6bcbd5fdb165ba4849ec7f8a6a30e28e6dba79f1a26ea43709d95d756a61e365502d7d5625228ede0630b28ff9554
7
- data.tar.gz: df8661c7ddbe9d0ef6271308c2222671049fe8e5895e944fe6d7b1d5598b76c0513f80c6af6148588e8b02bc15a5d1ab0fde0b8b9b33c144368e4c862ab21827
6
+ metadata.gz: b02bc0f3c34e2278f72e74841db5a9bdec9e89ad67e250eafedff87347e59253ccbf963b5121d71814ef6d5100e7bc6ea28a464ee47de3c6b5a2b65f1850ec25
7
+ data.tar.gz: d73484ab315c373ae67a1d40460d90762b9d086a499ebef13b31bdbf0f9783ca1b3af299bb0b5f46cebe49c7e0b6ada489648ee7b2bf51e458c1a064c330cb99
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.11] - 2026-06-01
4
+ ### Fixed
5
+ - Extinction sub-module `VERSION` corrected from `0.2.0` to `0.1.0` — aligns with sibling sub-modules.
6
+ - `ProtocolState#load_from_local` no longer uses `.max()` to reconcile in-memory vs. DB level, preventing silent reversion of de-escalation on restart.
7
+ - `ProtocolState#save_to_local` now uses an atomic upsert (update-then-insert-if-zero) instead of a check-then-act pattern, eliminating a race condition.
8
+ - `ProtocolState#parse_history` now logs errors instead of silently returning an empty array.
9
+
10
+ ## [0.1.10] - 2026-05-07
11
+ ### Fixed
12
+ - Extinction protocol authority checks now accept string authorities from JSON/API callers.
13
+ - Extinction state persistence failures are logged and return false below level 4, while level 4 erasure state failures raise instead of being swallowed.
14
+ - Level 4 Apollo erasure propagation now prefers the Apollo client when available.
15
+
3
16
  ## [0.1.9] - 2026-04-22
4
17
  ### Fixed
5
18
  - Confabulation decay actor now calls `decay_claims` instead of read-only `confabulation_report`; added `decay_claims` method to age out stale unverified claims
@@ -21,6 +21,7 @@ module Legion
21
21
  end
22
22
 
23
23
  def escalate(level, authority:, reason:)
24
+ authority = authority.to_sym if authority.respond_to?(:to_sym)
24
25
  return :invalid_level unless Levels.valid_level?(level)
25
26
  return :already_at_or_above if level <= @current_level
26
27
  return :insufficient_authority unless authority == Levels.required_authority(level)
@@ -37,6 +38,7 @@ module Legion
37
38
  end
38
39
 
39
40
  def deescalate(target_level, authority:, reason:)
41
+ authority = authority.to_sym if authority.respond_to?(:to_sym)
40
42
  return :not_active unless @active
41
43
  return :invalid_target if target_level >= @current_level
42
44
  return :irreversible unless Levels.reversible?(@current_level)
@@ -63,23 +65,25 @@ module Legion
63
65
  end
64
66
 
65
67
  def save_to_local
66
- return unless defined?(Legion::Data::Local) && local_data_connected?
68
+ return unless local_persistence_connected?
67
69
 
68
- row = {
69
- id: 1,
70
+ payload = {
70
71
  current_level: @current_level,
71
72
  active: @active,
72
73
  history: ::JSON.dump(@history.map { |h| h.merge(at: h[:at].to_s) }),
73
74
  updated_at: Time.now.utc
74
75
  }
75
- db = local_data_connection
76
- if db[:extinction_state].where(id: 1).any?
77
- db[:extinction_state].where(id: 1).update(row.except(:id))
78
- else
79
- db[:extinction_state].insert(row)
80
- end
81
- rescue StandardError => _e
82
- nil
76
+ db = local_persistence_connection
77
+
78
+ # Atomic upsert — avoids check-then-act race condition
79
+ existing = db[:extinction_state].where(id: 1).update(payload)
80
+ db[:extinction_state].insert(id: 1, **payload) if existing.zero?
81
+ true
82
+ rescue StandardError => e
83
+ log.error("lex-extinction: save_to_local failed: #{e.message}")
84
+ raise if @current_level >= 4
85
+
86
+ false
83
87
  end
84
88
 
85
89
  private
@@ -89,17 +93,19 @@ module Legion
89
93
  end
90
94
 
91
95
  def load_from_local
92
- return unless defined?(Legion::Data::Local) && local_data_connected?
96
+ return unless local_persistence_connected?
93
97
 
94
- row = local_data_connection[:extinction_state].where(id: 1).first
98
+ row = local_persistence_connection[:extinction_state].where(id: 1).first
95
99
  return unless row
96
100
 
97
101
  db_level = row[:current_level].to_i
98
- @current_level = [db_level, @current_level].max
102
+ @current_level = db_level
99
103
  @active = [true, 1].include?(row[:active])
100
104
  @history = parse_history(row[:history])
101
- rescue StandardError => _e
102
- nil
105
+ true
106
+ rescue StandardError => e
107
+ log.error("lex-extinction: load_from_local failed: #{e.message}")
108
+ false
103
109
  end
104
110
 
105
111
  def parse_history(raw)
@@ -113,9 +119,28 @@ module Legion
113
119
  at: Time.parse(h[:at].to_s)
114
120
  )
115
121
  end
116
- rescue StandardError => _e
122
+ rescue StandardError => e
123
+ log.error("lex-extinction: parse_history failed: #{e.message}")
117
124
  []
118
125
  end
126
+
127
+ def local_persistence_connected?
128
+ local_data_connected?
129
+ rescue NoMethodError => e
130
+ log.debug("lex-extinction: local persistence availability unavailable: #{e.message}")
131
+ false
132
+ end
133
+
134
+ def local_persistence_connection
135
+ local_data_connection
136
+ rescue NoMethodError => e
137
+ log.debug("lex-extinction: local persistence connection unavailable: #{e.message}")
138
+ raise
139
+ end
140
+
141
+ def log
142
+ Legion::Logging
143
+ end
119
144
  end
120
145
  end
121
146
  end
@@ -103,14 +103,24 @@ module Legion
103
103
  end
104
104
  end
105
105
 
106
+ if defined?(Legion::Extensions::Apollo::Client)
107
+ begin
108
+ Legion::Extensions::Apollo::Client.new.handle_erasure_request(agent_id: 'system:extinction')
109
+ log.warn('[extinction] apollo erasure propagated')
110
+ return
111
+ rescue StandardError => e
112
+ log.error("[extinction] apollo erasure failed: #{e.message}")
113
+ end
114
+ end
115
+
106
116
  return unless defined?(Legion::Extensions::Apollo::Runners::Knowledge)
107
117
 
108
118
  begin
109
- obj = Object.new.extend(Legion::Extensions::Apollo::Runners::Knowledge)
110
- obj.handle_erasure_request(agent_id: 'system:extinction')
111
- log.warn('[extinction] apollo erasure propagated')
119
+ eraser = Object.new.tap { |o| o.extend(Legion::Extensions::Apollo::Runners::Knowledge) }
120
+ eraser.handle_erasure_request(agent_id: 'system:extinction')
121
+ log.warn('[extinction] apollo erasure propagated via runner fallback')
112
122
  rescue StandardError => e
113
- log.error("[extinction] apollo erasure failed: #{e.message}")
123
+ log.error("[extinction] apollo erasure fallback failed: #{e.message}")
114
124
  end
115
125
  end
116
126
 
@@ -5,7 +5,7 @@ module Legion
5
5
  module Agentic
6
6
  module Defense
7
7
  module Extinction
8
- VERSION = '0.2.0'
8
+ VERSION = '0.1.0'
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.9'
7
+ VERSION = '0.1.11'
8
8
  end
9
9
  end
10
10
  end
@@ -78,6 +78,13 @@ RSpec.describe Legion::Extensions::Agentic::Defense::Extinction::Helpers::Protoc
78
78
  end
79
79
 
80
80
  context 'with valid escalation' do
81
+ it 'accepts string authorities from JSON/API callers' do
82
+ result = state.escalate(1, authority: 'governance_council', reason: 'threat')
83
+
84
+ expect(result).to eq(:escalated)
85
+ expect(state.history.last[:authority]).to eq(:governance_council)
86
+ end
87
+
81
88
  it 'returns :escalated for level 1 with governance_council' do
82
89
  expect(state.escalate(1, authority: :governance_council, reason: 'threat')).to eq(:escalated)
83
90
  end
@@ -207,6 +214,11 @@ RSpec.describe Legion::Extensions::Agentic::Defense::Extinction::Helpers::Protoc
207
214
  result = state.deescalate(0, authority: :council_plus_executive, reason: 'test')
208
215
  expect(result).to eq(:deescalated)
209
216
  end
217
+
218
+ it 'accepts string authority for deescalation' do
219
+ result = state.deescalate(0, authority: 'council_plus_executive', reason: 'test')
220
+ expect(result).to eq(:deescalated)
221
+ end
210
222
  end
211
223
 
212
224
  context 'with valid de-escalation' do
@@ -288,4 +300,24 @@ RSpec.describe Legion::Extensions::Agentic::Defense::Extinction::Helpers::Protoc
288
300
  expect(state.to_h[:history_size]).to eq(1)
289
301
  end
290
302
  end
303
+
304
+ describe '#save_to_local' do
305
+ it 'logs and returns false when persistence fails below level 4' do
306
+ state.instance_variable_set(:@current_level, 2)
307
+ allow(state).to receive(:local_data_connected?).and_return(true)
308
+ allow(state).to receive(:local_data_connection).and_raise(StandardError, 'disk full')
309
+
310
+ expect(Legion::Logging).to receive(:error).with(/save_to_local failed/)
311
+ expect(state.save_to_local).to be false
312
+ end
313
+
314
+ it 'raises when persistence fails for level 4 erasure state' do
315
+ state.instance_variable_set(:@current_level, 4)
316
+ allow(state).to receive(:local_data_connected?).and_return(true)
317
+ allow(state).to receive(:local_data_connection).and_raise(StandardError, 'disk full')
318
+ allow(Legion::Logging).to receive(:error)
319
+
320
+ expect { state.save_to_local }.to raise_error(StandardError, /disk full/)
321
+ end
322
+ end
291
323
  end
@@ -100,6 +100,25 @@ RSpec.describe Legion::Extensions::Agentic::Defense::Extinction::Runners::Extinc
100
100
  result = client.escalate(level: 4, authority: :physical_keyholders, reason: 'final')
101
101
  expect(result[:escalated]).to be true
102
102
  end
103
+
104
+ it 'uses Apollo client when it is available for level 4 erasure' do
105
+ events = Module.new { def self.emit(*, **); end }
106
+ stub_const('Legion::Events', events)
107
+ pc_mod = Module.new { def self.erase_all; end }
108
+ stub_const('Legion::Extensions::Privatecore::Runners::Privatecore', pc_mod)
109
+
110
+ apollo_client = instance_double('Legion::Extensions::Apollo::Client')
111
+ apollo_class = class_double('Legion::Extensions::Apollo::Client', new: apollo_client)
112
+ stub_const('Legion::Extensions::Apollo::Client', apollo_class)
113
+ allow(apollo_client).to receive(:handle_erasure_request).and_return({ deleted: 1 })
114
+
115
+ client.escalate(level: 1, authority: :governance_council, reason: 's1')
116
+ client.escalate(level: 2, authority: :governance_council, reason: 's2')
117
+ client.escalate(level: 3, authority: :council_plus_executive, reason: 's3')
118
+ client.escalate(level: 4, authority: :physical_keyholders, reason: 'final')
119
+
120
+ expect(apollo_client).to have_received(:handle_erasure_request).with(agent_id: 'system:extinction')
121
+ end
103
122
  end
104
123
 
105
124
  describe '#monitor_protocol' 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.9
4
+ version: 0.1.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity