legionio 1.4.109 → 1.4.111
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/.rubocop.yml +1 -0
- data/CHANGELOG.md +15 -0
- data/CODEOWNERS +1 -0
- data/Gemfile +2 -0
- data/lib/legion/cli/failover_command.rb +123 -0
- data/lib/legion/cli.rb +4 -0
- data/lib/legion/extensions/actors/subscription.rb +21 -0
- data/lib/legion/region/failover.rb +48 -0
- data/lib/legion/region.rb +91 -0
- data/lib/legion/sandbox.rb +26 -5
- data/lib/legion/service.rb +22 -0
- data/lib/legion/version.rb +1 -1
- data/lib/legion.rb +2 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fa14ee5aba79a712b64bd58ff53a40076fd8c4d3f84d6b58757f393175aba7f2
|
|
4
|
+
data.tar.gz: 572ed5d1c54e429da795341b6aa725c9187c06e4dca4445eff530d59f3324f5e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3eb840d78c0218c25882e8f3e757164ca7463b73f246aec0ac41a99c3c54ab3e644c5f59ee0fd695c0ab31818043f976f765c1316930a4ae86985179f92b69ba
|
|
7
|
+
data.tar.gz: 338a32c3e8e50b6f4113144b90daf0508a95032ddf3c393317ef1f431671db2d1caf32caf94c5e870181c5cf05e6a483a329bfdb636eacd2b6a4ce646ab34a8b
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Legion Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.111] - 2026-03-21
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Register logging hooks in boot sequence: fatal/error/warn published to `legion.logging` RMQ exchange
|
|
7
|
+
- Routing key pattern: `legion.<source>.<level>` (e.g., `legion.core.fatal`, `legion.lex-slack.error`)
|
|
8
|
+
- `Legion::Region` module: cloud metadata detection (AWS IMDSv2, Azure IMDS), region affinity routing
|
|
9
|
+
- `Legion::Region::Failover`: promote regions with replication lag checks, --dry-run, --force
|
|
10
|
+
- `legion failover` CLI: promote and status subcommands for region failover management
|
|
11
|
+
|
|
12
|
+
## [1.4.110] - 2026-03-21
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- Domain restrictions in extension Sandbox (allowed_domains on Policy, domain_allowed? check)
|
|
16
|
+
- Sandbox.allowed? class method for combined capability + domain checks
|
|
17
|
+
|
|
3
18
|
## [1.4.109] - 2026-03-21
|
|
4
19
|
|
|
5
20
|
### Added
|
data/CODEOWNERS
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* @Esity
|
data/Gemfile
CHANGED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
require 'legion/cli/output'
|
|
5
|
+
require 'legion/cli/connection'
|
|
6
|
+
|
|
7
|
+
module Legion
|
|
8
|
+
module CLI
|
|
9
|
+
class Failover < Thor
|
|
10
|
+
namespace 'failover'
|
|
11
|
+
|
|
12
|
+
class_option :json, type: :boolean, default: false, desc: 'Output as JSON'
|
|
13
|
+
class_option :no_color, type: :boolean, default: false, desc: 'Disable color output'
|
|
14
|
+
|
|
15
|
+
desc 'promote', 'Promote a region to primary'
|
|
16
|
+
option :region, type: :string, required: true, desc: 'Target region to promote'
|
|
17
|
+
option :dry_run, type: :boolean, default: false, desc: 'Show replication lag without promoting'
|
|
18
|
+
option :force, type: :boolean, default: false, desc: 'Force promotion even if lag exceeds threshold'
|
|
19
|
+
def promote
|
|
20
|
+
out = formatter
|
|
21
|
+
ensure_settings
|
|
22
|
+
|
|
23
|
+
target = options[:region]
|
|
24
|
+
require 'legion/region/failover'
|
|
25
|
+
|
|
26
|
+
if options[:dry_run]
|
|
27
|
+
run_dry_run(out, target)
|
|
28
|
+
else
|
|
29
|
+
run_promote(out, target)
|
|
30
|
+
end
|
|
31
|
+
rescue Legion::Region::Failover::UnknownRegionError => e
|
|
32
|
+
out.error(e.message)
|
|
33
|
+
raise SystemExit, 1
|
|
34
|
+
rescue Legion::Region::Failover::LagTooHighError => e
|
|
35
|
+
if options[:force]
|
|
36
|
+
out.warn("#{e.message} — forcing promotion")
|
|
37
|
+
force_promote(out, target)
|
|
38
|
+
else
|
|
39
|
+
out.error("#{e.message}. Use --force to override.")
|
|
40
|
+
raise SystemExit, 1
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
desc 'status', 'Show current region configuration'
|
|
45
|
+
def status
|
|
46
|
+
out = formatter
|
|
47
|
+
ensure_settings
|
|
48
|
+
|
|
49
|
+
region_config = Legion::Settings[:region] || {}
|
|
50
|
+
if options[:json]
|
|
51
|
+
out.json(region_config)
|
|
52
|
+
else
|
|
53
|
+
out.header('Region Configuration')
|
|
54
|
+
out.detail({
|
|
55
|
+
current: region_config[:current] || '(not set)',
|
|
56
|
+
primary: region_config[:primary] || '(not set)',
|
|
57
|
+
failover: region_config[:failover] || '(not set)',
|
|
58
|
+
peers: (region_config[:peers] || []).join(', ').then { |s| s.empty? ? '(none)' : s },
|
|
59
|
+
default_affinity: region_config[:default_affinity] || 'prefer_local'
|
|
60
|
+
})
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
no_commands do
|
|
65
|
+
def formatter
|
|
66
|
+
@formatter ||= Output::Formatter.new(
|
|
67
|
+
json: options[:json],
|
|
68
|
+
color: !options[:no_color]
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def ensure_settings
|
|
75
|
+
Connection.ensure_settings
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def run_dry_run(out, target)
|
|
79
|
+
Legion::Region::Failover.validate_target!(target)
|
|
80
|
+
lag = Legion::Region::Failover.replication_lag
|
|
81
|
+
|
|
82
|
+
if options[:json]
|
|
83
|
+
out.json({ target: target, lag_seconds: lag, dry_run: true })
|
|
84
|
+
else
|
|
85
|
+
out.header('Failover Dry Run')
|
|
86
|
+
lag_str = lag ? "#{lag.round(1)}s" : '(unavailable — no DB connection)'
|
|
87
|
+
out.detail({ target: target, replication_lag: lag_str })
|
|
88
|
+
if lag && lag > Legion::Region::Failover::MAX_LAG_SECONDS
|
|
89
|
+
out.warn("Lag exceeds #{Legion::Region::Failover::MAX_LAG_SECONDS}s threshold")
|
|
90
|
+
else
|
|
91
|
+
out.success('Lag within acceptable range')
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def run_promote(out, target)
|
|
97
|
+
result = Legion::Region::Failover.promote!(region: target)
|
|
98
|
+
if options[:json]
|
|
99
|
+
out.json(result)
|
|
100
|
+
else
|
|
101
|
+
out.success("Region promoted: #{result[:previous]} -> #{result[:promoted]}")
|
|
102
|
+
lag_str = result[:lag_seconds] ? "#{result[:lag_seconds].round(1)}s" : '(unavailable)'
|
|
103
|
+
out.detail({ promoted: result[:promoted], previous: result[:previous], replication_lag: lag_str })
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def force_promote(out, target)
|
|
108
|
+
previous = Legion::Settings.dig(:region, :primary)
|
|
109
|
+
lag = Legion::Region::Failover.replication_lag
|
|
110
|
+
Legion::Settings[:region][:primary] = target
|
|
111
|
+
Legion::Events.emit('region.failover', from: previous, to: target) if defined?(Legion::Events)
|
|
112
|
+
|
|
113
|
+
result = { promoted: target, previous: previous, lag_seconds: lag, forced: true }
|
|
114
|
+
if options[:json]
|
|
115
|
+
out.json(result)
|
|
116
|
+
else
|
|
117
|
+
out.success("Region force-promoted: #{previous} -> #{target}")
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
data/lib/legion/cli.rb
CHANGED
|
@@ -54,6 +54,7 @@ module Legion
|
|
|
54
54
|
autoload :Payroll, 'legion/cli/payroll_command'
|
|
55
55
|
autoload :Interactive, 'legion/cli/interactive'
|
|
56
56
|
autoload :Docs, 'legion/cli/docs_command'
|
|
57
|
+
autoload :Failover, 'legion/cli/failover_command'
|
|
57
58
|
|
|
58
59
|
class Main < Thor
|
|
59
60
|
def self.exit_on_failure?
|
|
@@ -282,6 +283,9 @@ module Legion
|
|
|
282
283
|
desc 'docs SUBCOMMAND', 'Documentation site generator'
|
|
283
284
|
subcommand 'docs', Legion::CLI::Docs
|
|
284
285
|
|
|
286
|
+
desc 'failover SUBCOMMAND', 'Region failover management'
|
|
287
|
+
subcommand 'failover', Legion::CLI::Failover
|
|
288
|
+
|
|
285
289
|
desc 'tree', 'Print a tree of all available commands'
|
|
286
290
|
def tree
|
|
287
291
|
legion_print_command_tree(self.class, 'legion', '')
|
|
@@ -112,6 +112,19 @@ module Legion
|
|
|
112
112
|
|
|
113
113
|
message = process_message(payload, metadata, delivery_info)
|
|
114
114
|
fn = find_function(message)
|
|
115
|
+
|
|
116
|
+
affinity_result = check_region_affinity(message)
|
|
117
|
+
if affinity_result == :reject
|
|
118
|
+
Legion::Logging.warn "Rejecting message: region affinity mismatch (region=#{message[:region]}, affinity=#{message[:region_affinity]})"
|
|
119
|
+
@queue.reject(delivery_info.delivery_tag) if manual_ack
|
|
120
|
+
next
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
if affinity_result == :remote
|
|
124
|
+
Legion::Logging.debug 'Processing remote-region message ' \
|
|
125
|
+
"(region=#{message[:region]}, affinity=#{message[:region_affinity]})"
|
|
126
|
+
end
|
|
127
|
+
|
|
115
128
|
if use_runner?
|
|
116
129
|
dispatch_runner(message, runner_class, fn, check_subtask?, generate_task?)
|
|
117
130
|
else
|
|
@@ -131,6 +144,14 @@ module Legion
|
|
|
131
144
|
|
|
132
145
|
private
|
|
133
146
|
|
|
147
|
+
def check_region_affinity(message)
|
|
148
|
+
return :local unless defined?(Legion::Region)
|
|
149
|
+
|
|
150
|
+
region = message[:region]
|
|
151
|
+
affinity = message[:region_affinity]
|
|
152
|
+
Legion::Region.affinity_for(region, affinity)
|
|
153
|
+
end
|
|
154
|
+
|
|
134
155
|
def dispatch_runner(message, runner_cls, function, check_subtask, generate_task)
|
|
135
156
|
run_block = lambda {
|
|
136
157
|
Legion::Runner.run(**message,
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Region
|
|
5
|
+
module Failover
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
MAX_LAG_SECONDS = 30
|
|
9
|
+
|
|
10
|
+
def promote!(region:)
|
|
11
|
+
validate_target!(region)
|
|
12
|
+
|
|
13
|
+
lag = replication_lag
|
|
14
|
+
raise LagTooHighError, "replication lag #{lag.round(1)}s exceeds #{MAX_LAG_SECONDS}s threshold" if lag && lag > MAX_LAG_SECONDS
|
|
15
|
+
|
|
16
|
+
previous = Legion::Settings.dig(:region, :primary)
|
|
17
|
+
Legion::Settings[:region][:primary] = region
|
|
18
|
+
Legion::Events.emit('region.failover', from: previous, to: region) if defined?(Legion::Events)
|
|
19
|
+
|
|
20
|
+
{ promoted: region, previous: previous, lag_seconds: lag }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def replication_lag
|
|
24
|
+
return nil unless defined?(Legion::Data) && Legion::Data.respond_to?(:connection) && Legion::Data.connection
|
|
25
|
+
|
|
26
|
+
row = Legion::Data.connection.fetch(
|
|
27
|
+
'SELECT EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp())) AS lag'
|
|
28
|
+
).first
|
|
29
|
+
row[:lag]&.to_f
|
|
30
|
+
rescue StandardError
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def validate_target!(region)
|
|
35
|
+
peers = Legion::Settings.dig(:region, :peers) || []
|
|
36
|
+
failover = Legion::Settings.dig(:region, :failover)
|
|
37
|
+
known = (peers + [failover].compact).uniq
|
|
38
|
+
|
|
39
|
+
return if known.include?(region)
|
|
40
|
+
|
|
41
|
+
raise UnknownRegionError, "'#{region}' is not a known peer or failover region (known: #{known.join(', ')})"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class LagTooHighError < StandardError; end
|
|
45
|
+
class UnknownRegionError < StandardError; end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Region
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def current
|
|
10
|
+
setting = defined?(Legion::Settings) ? Legion::Settings.dig(:region, :current) : nil
|
|
11
|
+
setting || detect_from_metadata
|
|
12
|
+
rescue StandardError
|
|
13
|
+
nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def local?(target_region)
|
|
17
|
+
target_region.nil? || target_region == current
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def affinity_for(message_region, affinity)
|
|
21
|
+
return :local if local?(message_region) || affinity == 'any'
|
|
22
|
+
return :remote if affinity == 'prefer_local'
|
|
23
|
+
return :reject if affinity == 'require_local'
|
|
24
|
+
|
|
25
|
+
:local
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def primary
|
|
29
|
+
return nil unless defined?(Legion::Settings)
|
|
30
|
+
|
|
31
|
+
Legion::Settings.dig(:region, :primary)
|
|
32
|
+
rescue StandardError
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def failover
|
|
37
|
+
return nil unless defined?(Legion::Settings)
|
|
38
|
+
|
|
39
|
+
Legion::Settings.dig(:region, :failover)
|
|
40
|
+
rescue StandardError
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def peers
|
|
45
|
+
return [] unless defined?(Legion::Settings)
|
|
46
|
+
|
|
47
|
+
Legion::Settings.dig(:region, :peers) || []
|
|
48
|
+
rescue StandardError
|
|
49
|
+
[]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def detect_from_metadata
|
|
53
|
+
detect_aws_region || detect_azure_region
|
|
54
|
+
rescue StandardError
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def detect_aws_region
|
|
59
|
+
uri = URI('http://169.254.169.254/latest/meta-data/placement/region')
|
|
60
|
+
token_uri = URI('http://169.254.169.254/latest/api/token')
|
|
61
|
+
|
|
62
|
+
token = Net::HTTP.start(token_uri.host, token_uri.port, open_timeout: 1, read_timeout: 1) do |http|
|
|
63
|
+
req = Net::HTTP::Put.new(token_uri)
|
|
64
|
+
req['X-aws-ec2-metadata-token-ttl-seconds'] = '21600'
|
|
65
|
+
http.request(req).body
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
Net::HTTP.start(uri.host, uri.port, open_timeout: 1, read_timeout: 1) do |http|
|
|
69
|
+
req = Net::HTTP::Get.new(uri)
|
|
70
|
+
req['X-aws-ec2-metadata-token'] = token
|
|
71
|
+
response = http.request(req)
|
|
72
|
+
response.is_a?(Net::HTTPSuccess) ? response.body.strip : nil
|
|
73
|
+
end
|
|
74
|
+
rescue StandardError
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def detect_azure_region
|
|
79
|
+
uri = URI('http://169.254.169.254/metadata/instance/compute/location?api-version=2021-02-01&format=text')
|
|
80
|
+
|
|
81
|
+
Net::HTTP.start(uri.host, uri.port, open_timeout: 1, read_timeout: 1) do |http|
|
|
82
|
+
req = Net::HTTP::Get.new(uri)
|
|
83
|
+
req['Metadata'] = 'true'
|
|
84
|
+
response = http.request(req)
|
|
85
|
+
response.is_a?(Net::HTTPSuccess) ? response.body.strip : nil
|
|
86
|
+
end
|
|
87
|
+
rescue StandardError
|
|
88
|
+
nil
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
data/lib/legion/sandbox.rb
CHANGED
|
@@ -12,23 +12,31 @@ module Legion
|
|
|
12
12
|
transport:publish transport:subscribe
|
|
13
13
|
].freeze
|
|
14
14
|
|
|
15
|
-
attr_reader :extension_name, :capabilities
|
|
15
|
+
attr_reader :extension_name, :capabilities, :allowed_domains
|
|
16
16
|
|
|
17
|
-
def initialize(extension_name:, capabilities: [])
|
|
17
|
+
def initialize(extension_name:, capabilities: [], allowed_domains: nil)
|
|
18
18
|
@extension_name = extension_name
|
|
19
19
|
@capabilities = capabilities.select { |c| CAPABILITIES.include?(c) }.freeze
|
|
20
|
+
@allowed_domains = allowed_domains&.map(&:to_s)&.freeze
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
def allowed?(capability)
|
|
23
24
|
capabilities.include?(capability.to_s)
|
|
24
25
|
end
|
|
26
|
+
|
|
27
|
+
def domain_allowed?(agent_domain)
|
|
28
|
+
return true if allowed_domains.nil? || allowed_domains.empty?
|
|
29
|
+
|
|
30
|
+
allowed_domains.include?(agent_domain.to_s)
|
|
31
|
+
end
|
|
25
32
|
end
|
|
26
33
|
|
|
27
34
|
class << self
|
|
28
|
-
def register_policy(extension_name, capabilities:)
|
|
35
|
+
def register_policy(extension_name, capabilities:, allowed_domains: nil)
|
|
29
36
|
policies[extension_name] = Policy.new(
|
|
30
|
-
extension_name:
|
|
31
|
-
capabilities:
|
|
37
|
+
extension_name: extension_name,
|
|
38
|
+
capabilities: capabilities,
|
|
39
|
+
allowed_domains: allowed_domains
|
|
32
40
|
)
|
|
33
41
|
end
|
|
34
42
|
|
|
@@ -45,6 +53,19 @@ module Legion
|
|
|
45
53
|
true
|
|
46
54
|
end
|
|
47
55
|
|
|
56
|
+
def allowed?(extension_name: nil, gem_name: nil, capability: nil, agent_domain: nil)
|
|
57
|
+
ext = extension_name || gem_name
|
|
58
|
+
return true unless enforcement_enabled?
|
|
59
|
+
|
|
60
|
+
policy = policy_for(ext)
|
|
61
|
+
|
|
62
|
+
return false if capability && !policy.allowed?(capability)
|
|
63
|
+
|
|
64
|
+
return false if agent_domain && !policy.domain_allowed?(agent_domain)
|
|
65
|
+
|
|
66
|
+
true
|
|
67
|
+
end
|
|
68
|
+
|
|
48
69
|
def enforcement_enabled?
|
|
49
70
|
return false unless defined?(Legion::Settings)
|
|
50
71
|
|
data/lib/legion/service.rb
CHANGED
|
@@ -45,6 +45,7 @@ module Legion
|
|
|
45
45
|
if transport
|
|
46
46
|
setup_transport
|
|
47
47
|
Legion::Readiness.mark_ready(:transport)
|
|
48
|
+
register_logging_hooks
|
|
48
49
|
end
|
|
49
50
|
|
|
50
51
|
if cache
|
|
@@ -250,6 +251,27 @@ module Legion
|
|
|
250
251
|
Legion::Transport::Connection.setup
|
|
251
252
|
end
|
|
252
253
|
|
|
254
|
+
def register_logging_hooks
|
|
255
|
+
return unless Legion::Transport::Connection.session_open?
|
|
256
|
+
|
|
257
|
+
exchange = Legion::Transport::Exchanges::Logging.new
|
|
258
|
+
|
|
259
|
+
%i[fatal error warn].each do |level|
|
|
260
|
+
Legion::Logging.send(:"on_#{level}") do |event|
|
|
261
|
+
next unless Legion::Transport::Connection.session_open?
|
|
262
|
+
|
|
263
|
+
source = event[:lex] || 'core'
|
|
264
|
+
routing_key = "legion.#{source}.#{level}"
|
|
265
|
+
exchange.publish(Legion::JSON.dump(event), routing_key: routing_key)
|
|
266
|
+
rescue StandardError
|
|
267
|
+
nil
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
Legion::Logging.enable_hooks!
|
|
272
|
+
Legion::Logging.info('Logging hooks registered for RMQ publishing')
|
|
273
|
+
end
|
|
274
|
+
|
|
253
275
|
def setup_alerts
|
|
254
276
|
enabled = begin
|
|
255
277
|
Legion::Settings[:alerts][:enabled]
|
data/lib/legion/version.rb
CHANGED
data/lib/legion.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: legionio
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.4.
|
|
4
|
+
version: 1.4.111
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -335,6 +335,7 @@ files:
|
|
|
335
335
|
- ".rubocop.yml"
|
|
336
336
|
- CHANGELOG.md
|
|
337
337
|
- CLAUDE.md
|
|
338
|
+
- CODEOWNERS
|
|
338
339
|
- Dockerfile
|
|
339
340
|
- Gemfile
|
|
340
341
|
- LICENSE
|
|
@@ -498,6 +499,7 @@ files:
|
|
|
498
499
|
- lib/legion/cli/doctor_command.rb
|
|
499
500
|
- lib/legion/cli/error.rb
|
|
500
501
|
- lib/legion/cli/eval_command.rb
|
|
502
|
+
- lib/legion/cli/failover_command.rb
|
|
501
503
|
- lib/legion/cli/function.rb
|
|
502
504
|
- lib/legion/cli/gaia_command.rb
|
|
503
505
|
- lib/legion/cli/generate_command.rb
|
|
@@ -653,6 +655,8 @@ files:
|
|
|
653
655
|
- lib/legion/process.rb
|
|
654
656
|
- lib/legion/process_role.rb
|
|
655
657
|
- lib/legion/readiness.rb
|
|
658
|
+
- lib/legion/region.rb
|
|
659
|
+
- lib/legion/region/failover.rb
|
|
656
660
|
- lib/legion/registry.rb
|
|
657
661
|
- lib/legion/registry/governance.rb
|
|
658
662
|
- lib/legion/registry/persistence.rb
|