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 +4 -4
- data/CHANGELOG.md +13 -0
- data/lib/legion/extensions/agentic/defense/extinction/helpers/protocol_state.rb +42 -17
- data/lib/legion/extensions/agentic/defense/extinction/runners/extinction.rb +14 -4
- data/lib/legion/extensions/agentic/defense/extinction/version.rb +1 -1
- data/lib/legion/extensions/agentic/defense/version.rb +1 -1
- data/spec/legion/extensions/agentic/defense/extinction/helpers/protocol_state_spec.rb +32 -0
- data/spec/legion/extensions/agentic/defense/extinction/runners/extinction_spec.rb +19 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 04de0f8db49067510bfca89be592029c22ee49b658a37594b833b8d0cf679e53
|
|
4
|
+
data.tar.gz: 8211375022a5be1d4a830faa5ca509b045ff1ac1a0b56e38fb4b6e4c19adfc87
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
68
|
+
return unless local_persistence_connected?
|
|
67
69
|
|
|
68
|
-
|
|
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 =
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
rescue StandardError =>
|
|
82
|
-
|
|
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
|
|
96
|
+
return unless local_persistence_connected?
|
|
93
97
|
|
|
94
|
-
row =
|
|
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 =
|
|
102
|
+
@current_level = db_level
|
|
99
103
|
@active = [true, 1].include?(row[:active])
|
|
100
104
|
@history = parse_history(row[:history])
|
|
101
|
-
|
|
102
|
-
|
|
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 =>
|
|
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
|
-
|
|
110
|
-
|
|
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
|
|
|
@@ -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
|