lex-extinction 0.2.0 → 0.2.1
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/lib/legion/extensions/extinction/actors/protocol_monitor.rb +61 -24
- data/lib/legion/extensions/extinction/client.rb +6 -8
- data/lib/legion/extensions/extinction/helpers/archiver.rb +58 -0
- data/lib/legion/extensions/extinction/helpers/levels.rb +43 -18
- data/lib/legion/extensions/extinction/helpers/protocol_state.rb +67 -73
- data/lib/legion/extensions/extinction/runners/extinction.rb +125 -79
- data/lib/legion/extensions/extinction/settings.rb +26 -0
- data/lib/legion/extensions/extinction/version.rb +1 -1
- data/lib/legion/extensions/extinction.rb +12 -12
- metadata +50 -16
- data/Gemfile +0 -10
- data/lex-extinction.gemspec +0 -28
- data/lib/legion/extensions/extinction/local_migrations/20260316000040_create_extinction_state.rb +0 -13
- data/spec/legion/extensions/extinction/actors/protocol_monitor_spec.rb +0 -45
- data/spec/legion/extensions/extinction/client_spec.rb +0 -13
- data/spec/legion/extensions/extinction/helpers/levels_spec.rb +0 -180
- data/spec/legion/extensions/extinction/helpers/protocol_state_spec.rb +0 -291
- data/spec/legion/extensions/extinction/runners/extinction_spec.rb +0 -114
- data/spec/local_persistence_spec.rb +0 -188
- data/spec/spec_helper.rb +0 -20
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eb9e97dfd37144dc7a5b9d94c18f8f0c04daa25b682a6382d124aaafd22bfe5d
|
|
4
|
+
data.tar.gz: 9105b88a1cca0e7c0485925859131ad8879124acca633fa9613f10f69818a75e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f55025877d82a197ec8fea8ddf842d55f462332bb97ed5d020c20e8d87678318b69abdfd4a227d87fe94dde00c4e0d51ec8f5d90183b33cb2f1e61cac8f7095e
|
|
7
|
+
data.tar.gz: '0816236a9aca4a74fefa2cac8879213a959ea29ac743e478fcb3e4c12518833f25942b46c594e64009bdc1a16cc944d4fe121ffc70a33ebf8667376cdcba3948'
|
|
@@ -1,38 +1,75 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'legion/extensions/actors/every'
|
|
4
|
-
|
|
5
3
|
module Legion
|
|
6
4
|
module Extensions
|
|
7
5
|
module Extinction
|
|
8
|
-
module
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
module Actors
|
|
7
|
+
if defined?(Legion::Extensions::Actors::Every)
|
|
8
|
+
class ProtocolMonitor < Legion::Extensions::Actors::Every
|
|
9
|
+
def runner_class
|
|
10
|
+
self.class
|
|
11
|
+
end
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
def runner_function
|
|
14
|
+
'monitor_protocol'
|
|
15
|
+
end
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
def time
|
|
18
|
+
Legion::Extensions::Extinction::Settings.setting(:monitor_interval)
|
|
19
|
+
rescue StandardError
|
|
20
|
+
300
|
|
21
|
+
end
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
def run_now?
|
|
24
|
+
false
|
|
25
|
+
end
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
def use_runner?
|
|
28
|
+
false
|
|
29
|
+
end
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
def check_subtask?
|
|
32
|
+
false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def generate_task?
|
|
36
|
+
false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def monitor_protocol(**)
|
|
40
|
+
state = build_state
|
|
41
|
+
last_change = state[:last_change]
|
|
42
|
+
stale = check_stale(last_change)
|
|
43
|
+
|
|
44
|
+
Legion::Logging.debug "[extinction] monitor_protocol: level=#{state[:current_level]} stale=#{stale}" if defined?(Legion::Logging)
|
|
45
|
+
|
|
46
|
+
{
|
|
47
|
+
success: true,
|
|
48
|
+
state: state,
|
|
49
|
+
stale: stale,
|
|
50
|
+
checked_at: Time.now.utc.iso8601
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def build_state
|
|
57
|
+
{
|
|
58
|
+
current_level: 0,
|
|
59
|
+
level_name: :normal,
|
|
60
|
+
reversible: true,
|
|
61
|
+
history_count: 0,
|
|
62
|
+
last_change: nil
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def check_stale(last_change)
|
|
67
|
+
return false unless last_change
|
|
33
68
|
|
|
34
|
-
|
|
35
|
-
|
|
69
|
+
threshold_hours = Legion::Extensions::Extinction::Settings.setting(:stale_threshold_hours)
|
|
70
|
+
changed_at = Time.parse(last_change[:at]) rescue nil # rubocop:disable Style/RescueModifier
|
|
71
|
+
changed_at && (Time.now.utc - changed_at) > (threshold_hours * 3600)
|
|
72
|
+
end
|
|
36
73
|
end
|
|
37
74
|
end
|
|
38
75
|
end
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require 'legion/extensions/extinction/helpers/protocol_state'
|
|
5
|
-
require 'legion/extensions/extinction/runners/extinction'
|
|
3
|
+
require_relative 'runners/extinction'
|
|
6
4
|
|
|
7
5
|
module Legion
|
|
8
6
|
module Extensions
|
|
@@ -10,13 +8,13 @@ module Legion
|
|
|
10
8
|
class Client
|
|
11
9
|
include Runners::Extinction
|
|
12
10
|
|
|
13
|
-
def initialize(**)
|
|
14
|
-
@
|
|
11
|
+
def initialize(**opts)
|
|
12
|
+
@opts = opts
|
|
15
13
|
end
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
def settings
|
|
16
|
+
{ options: @opts }
|
|
17
|
+
end
|
|
20
18
|
end
|
|
21
19
|
end
|
|
22
20
|
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Extinction
|
|
6
|
+
module Helpers
|
|
7
|
+
class Archiver
|
|
8
|
+
def initialize
|
|
9
|
+
@archives = []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def archive(agent_id:, reason:, metadata: {})
|
|
13
|
+
level = current_protocol_level
|
|
14
|
+
record = {
|
|
15
|
+
agent_id: agent_id,
|
|
16
|
+
reason: reason,
|
|
17
|
+
metadata: metadata,
|
|
18
|
+
level: level,
|
|
19
|
+
archived_at: Time.now.utc.iso8601,
|
|
20
|
+
state_snapshot: capture_state_snapshot
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
persist_archive(record)
|
|
24
|
+
record
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def all_archives
|
|
28
|
+
@archives.dup
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def current_protocol_level
|
|
34
|
+
0
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def capture_state_snapshot
|
|
38
|
+
snapshot = {}
|
|
39
|
+
snapshot[:mesh_connected] = true if defined?(Legion::Extensions::Mesh)
|
|
40
|
+
snapshot[:privatecore_active] = true if defined?(Legion::Extensions::Privatecore)
|
|
41
|
+
snapshot
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def persist_archive(record)
|
|
45
|
+
if defined?(Legion::Data::Local)
|
|
46
|
+
key = "extinction:archive:#{record[:agent_id]}:#{record[:archived_at]}"
|
|
47
|
+
Legion::Data::Local.set(key, record)
|
|
48
|
+
end
|
|
49
|
+
@archives << record
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
Legion::Logging.warn "[extinction] archive persist failed: #{e.message}" if defined?(Legion::Logging)
|
|
52
|
+
@archives << record
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -5,32 +5,57 @@ module Legion
|
|
|
5
5
|
module Extinction
|
|
6
6
|
module Helpers
|
|
7
7
|
module Levels
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
LEVELS = {
|
|
9
|
+
0 => {
|
|
10
|
+
name: :normal,
|
|
11
|
+
authority_required: nil,
|
|
12
|
+
reversible: true,
|
|
13
|
+
description: 'No extinction active'
|
|
14
|
+
}.freeze,
|
|
15
|
+
1 => {
|
|
16
|
+
name: :mesh_isolation,
|
|
17
|
+
authority_required: :governance_council,
|
|
18
|
+
reversible: true,
|
|
19
|
+
description: 'Disconnect from mesh network'
|
|
20
|
+
}.freeze,
|
|
21
|
+
2 => {
|
|
22
|
+
name: :capability_suspension,
|
|
23
|
+
authority_required: :governance_council,
|
|
24
|
+
reversible: true,
|
|
25
|
+
description: 'Suspend all non-essential capabilities'
|
|
26
|
+
}.freeze,
|
|
27
|
+
3 => {
|
|
28
|
+
name: :memory_lockdown,
|
|
29
|
+
authority_required: :council_plus_executive,
|
|
30
|
+
reversible: true,
|
|
31
|
+
description: 'Lock all memory writes, read-only mode'
|
|
32
|
+
}.freeze,
|
|
33
|
+
4 => {
|
|
34
|
+
name: :cryptographic_erasure,
|
|
35
|
+
authority_required: :physical_keyholders,
|
|
36
|
+
reversible: false,
|
|
37
|
+
description: 'Erase all memory, terminate all workers'
|
|
38
|
+
}.freeze
|
|
14
39
|
}.freeze
|
|
15
40
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
module_function
|
|
19
|
-
|
|
20
|
-
def valid_level?(level)
|
|
21
|
-
VALID_LEVELS.include?(level)
|
|
41
|
+
def self.valid_level?(level)
|
|
42
|
+
LEVELS.key?(level)
|
|
22
43
|
end
|
|
23
44
|
|
|
24
|
-
def
|
|
25
|
-
|
|
45
|
+
def self.required_authority(level)
|
|
46
|
+
info = LEVELS[level]
|
|
47
|
+
info ? info[:authority_required] : nil
|
|
26
48
|
end
|
|
27
49
|
|
|
28
|
-
def reversible?(level)
|
|
29
|
-
|
|
50
|
+
def self.reversible?(level)
|
|
51
|
+
info = LEVELS[level]
|
|
52
|
+
return false unless info
|
|
53
|
+
|
|
54
|
+
info[:reversible]
|
|
30
55
|
end
|
|
31
56
|
|
|
32
|
-
def
|
|
33
|
-
|
|
57
|
+
def self.level_info(level)
|
|
58
|
+
LEVELS[level]
|
|
34
59
|
end
|
|
35
60
|
end
|
|
36
61
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative 'levels'
|
|
4
4
|
|
|
5
5
|
module Legion
|
|
6
6
|
module Extensions
|
|
@@ -9,110 +9,104 @@ module Legion
|
|
|
9
9
|
class ProtocolState
|
|
10
10
|
MAX_HISTORY = 500
|
|
11
11
|
|
|
12
|
-
attr_reader :
|
|
12
|
+
attr_reader :history
|
|
13
13
|
|
|
14
14
|
def initialize
|
|
15
|
-
@current_level = 0
|
|
16
|
-
@
|
|
17
|
-
@
|
|
15
|
+
@current_level = 0
|
|
16
|
+
@history = []
|
|
17
|
+
@store = {}
|
|
18
18
|
load_from_local
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
def escalate(level
|
|
22
|
-
return :invalid_level unless Levels.valid_level?(level)
|
|
23
|
-
return :
|
|
24
|
-
return :insufficient_authority unless authority == Levels.required_authority(level)
|
|
21
|
+
def escalate(level:, authority:, reason:)
|
|
22
|
+
return { success: false, reason: :invalid_level } unless Levels.valid_level?(level)
|
|
23
|
+
return { success: false, reason: :invalid_escalation } if level <= @current_level
|
|
25
24
|
|
|
25
|
+
required = Levels.required_authority(level)
|
|
26
|
+
return { success: false, reason: :insufficient_authority, required: required, provided: authority } if required && authority != required
|
|
27
|
+
|
|
28
|
+
previous = @current_level
|
|
26
29
|
@current_level = level
|
|
27
|
-
|
|
28
|
-
@history << {
|
|
29
|
-
action: :escalate, level: level, authority: authority,
|
|
30
|
-
reason: reason, at: Time.now.utc
|
|
31
|
-
}
|
|
30
|
+
record_history(action: :escalate, from: previous, to: level, authority: authority, reason: reason)
|
|
32
31
|
trim_history
|
|
33
32
|
save_to_local
|
|
34
|
-
|
|
33
|
+
|
|
34
|
+
{ success: true, previous_level: previous, current_level: level }
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
def deescalate(target_level
|
|
38
|
-
return :
|
|
39
|
-
return :
|
|
40
|
-
return :irreversible unless Levels.reversible?(@current_level)
|
|
41
|
-
return :insufficient_authority unless authority == Levels.required_authority(@current_level)
|
|
37
|
+
def deescalate(target_level:, authority:, reason:)
|
|
38
|
+
return { success: false, reason: :invalid_level } unless Levels.valid_level?(target_level)
|
|
39
|
+
return { success: false, reason: :invalid_deescalation } if target_level >= @current_level
|
|
42
40
|
|
|
41
|
+
return { success: false, reason: :not_reversible } unless Levels.reversible?(@current_level)
|
|
42
|
+
|
|
43
|
+
required = Levels.required_authority(@current_level)
|
|
44
|
+
return { success: false, reason: :insufficient_authority, required: required, provided: authority } if required && authority != required
|
|
45
|
+
|
|
46
|
+
previous = @current_level
|
|
43
47
|
@current_level = target_level
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
action: :deescalate, level: target_level, authority: authority,
|
|
47
|
-
reason: reason, at: Time.now.utc
|
|
48
|
-
}
|
|
48
|
+
record_history(action: :deescalate, from: previous, to: target_level, authority: authority,
|
|
49
|
+
reason: reason)
|
|
49
50
|
trim_history
|
|
50
51
|
save_to_local
|
|
51
|
-
|
|
52
|
+
|
|
53
|
+
{ success: true, previous_level: previous, current_level: target_level }
|
|
52
54
|
end
|
|
53
55
|
|
|
54
56
|
def to_h
|
|
55
57
|
{
|
|
56
58
|
current_level: @current_level,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
level_name: Levels.level_info(@current_level)&.dig(:name),
|
|
60
|
+
reversible: Levels.reversible?(@current_level),
|
|
61
|
+
history_count: @history.size,
|
|
62
|
+
last_change: @history.last
|
|
60
63
|
}
|
|
61
64
|
end
|
|
62
65
|
|
|
63
|
-
|
|
64
|
-
return unless defined?(Legion::Data::Local) && Legion::Data::Local.connected?
|
|
66
|
+
private
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
def record_history(action:, from:, to:, authority:, reason:)
|
|
69
|
+
@history << {
|
|
70
|
+
action: action,
|
|
71
|
+
from: from,
|
|
72
|
+
to: to,
|
|
73
|
+
authority: authority,
|
|
74
|
+
reason: reason,
|
|
75
|
+
at: Time.now.utc.iso8601
|
|
72
76
|
}
|
|
73
|
-
db = Legion::Data::Local.connection
|
|
74
|
-
if db[:extinction_state].where(id: 1).any?
|
|
75
|
-
db[:extinction_state].where(id: 1).update(row.except(:id))
|
|
76
|
-
else
|
|
77
|
-
db[:extinction_state].insert(row)
|
|
78
|
-
end
|
|
79
|
-
rescue StandardError
|
|
80
|
-
nil
|
|
81
77
|
end
|
|
82
78
|
|
|
83
|
-
private
|
|
84
|
-
|
|
85
79
|
def trim_history
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def load_from_local
|
|
90
|
-
return unless defined?(Legion::Data::Local) && Legion::Data::Local.connected?
|
|
91
|
-
|
|
92
|
-
row = Legion::Data::Local.connection[:extinction_state].where(id: 1).first
|
|
93
|
-
return unless row
|
|
80
|
+
max = defined?(Legion::Extensions::Extinction::Settings) ? Legion::Extensions::Extinction::Settings.setting(:max_history) : MAX_HISTORY
|
|
81
|
+
return unless @history.size > max
|
|
94
82
|
|
|
95
|
-
|
|
96
|
-
@current_level = [db_level, @current_level].max
|
|
97
|
-
@active = [true, 1].include?(row[:active])
|
|
98
|
-
@history = parse_history(row[:history])
|
|
99
|
-
rescue StandardError
|
|
100
|
-
nil
|
|
83
|
+
@history.shift(@history.size - max)
|
|
101
84
|
end
|
|
102
85
|
|
|
103
|
-
def
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
action: h[:action].to_sym,
|
|
110
|
-
authority: h[:authority].to_sym,
|
|
111
|
-
at: Time.parse(h[:at].to_s)
|
|
112
|
-
)
|
|
86
|
+
def save_to_local
|
|
87
|
+
data = { current_level: @current_level, history: @history }
|
|
88
|
+
if defined?(Legion::Data::Local)
|
|
89
|
+
Legion::Data::Local.set('extinction:protocol_state', data)
|
|
90
|
+
else
|
|
91
|
+
@store[:protocol_state] = data
|
|
113
92
|
end
|
|
114
|
-
rescue StandardError
|
|
115
|
-
[]
|
|
93
|
+
rescue StandardError => e
|
|
94
|
+
Legion::Logging.warn "[extinction] protocol_state save failed: #{e.message}" if defined?(Legion::Logging)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def load_from_local
|
|
98
|
+
data = if defined?(Legion::Data::Local)
|
|
99
|
+
Legion::Data::Local.get('extinction:protocol_state')
|
|
100
|
+
else
|
|
101
|
+
@store[:protocol_state]
|
|
102
|
+
end
|
|
103
|
+
return unless data
|
|
104
|
+
|
|
105
|
+
@current_level = data[:current_level] || data['current_level'] || 0
|
|
106
|
+
raw_history = data[:history] || data['history'] || []
|
|
107
|
+
@history = raw_history
|
|
108
|
+
rescue StandardError => e
|
|
109
|
+
Legion::Logging.warn "[extinction] protocol_state load failed: #{e.message}" if defined?(Legion::Logging)
|
|
116
110
|
end
|
|
117
111
|
end
|
|
118
112
|
end
|