lex-node 0.3.4 → 0.3.6

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: f1ec7edd1a4b098a60538b0832c7b53f97149a26e805a6a391a724f070f1bfdb
4
- data.tar.gz: 4ecb9b8cd149e2472e78031558b2bf476b0cf44d18e8af31a97e867a0cff7219
3
+ metadata.gz: dc51c7fec2b526f23237c3c8da1c8457f7b10250ad7606a3d519049675025cc5
4
+ data.tar.gz: a0f8d664fdd16a3d16d64a948e3dfe2e8c27935251f1be5ffeef3bac21042220
5
5
  SHA512:
6
- metadata.gz: c1d8fa3687ff0498a7722f1a2905e660d88dba42f8eed4cfefc06ece9779d74f6cbe3490164c624909658d85a3187c289c6e721d68f9a7a49b3d0792cd02b9b1
7
- data.tar.gz: 7c880bcd2c21b10212ff8202fb63b7f6dbd339a17a0373c9c9b6c85e02af4c6b21127f4e0fb504c1599e384169dc49e139051af7bc28bfb097d4995bcd131d20
6
+ metadata.gz: fd71f5b8d3152d625af005aef5add980d90548d6299bf7f6afd93b69585941b69c37b29ecc882d9325a0075d0fc60b744c9080305e2375350714f3a8cfcb52ba
7
+ data.tar.gz: c127f5a0b891e70df65b4639588a628596c8207ec31a21529bee8f39117c4cbf36b4d202d27b2619224e815035291bd8c8b81015f1a369071ff1041a8339d770
@@ -0,0 +1,7 @@
1
+ # Auto-generated from team-config.yml
2
+ # Team: extensions
3
+ #
4
+ # To apply: scripts/apply-codeowners.sh lex-node
5
+
6
+ * @LegionIO/maintainers
7
+ * @LegionIO/extensions
@@ -0,0 +1,18 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ day: monday
8
+ open-pull-requests-limit: 5
9
+ labels:
10
+ - "type:dependencies"
11
+ - package-ecosystem: github-actions
12
+ directory: /
13
+ schedule:
14
+ interval: weekly
15
+ day: monday
16
+ open-pull-requests-limit: 5
17
+ labels:
18
+ - "type:dependencies"
@@ -10,8 +10,8 @@ jobs:
10
10
  ci:
11
11
  uses: LegionIO/.github/.github/workflows/ci.yml@main
12
12
 
13
- lint:
14
- uses: LegionIO/.github/.github/workflows/lint-patterns.yml@main
13
+ excluded-files:
14
+ uses: LegionIO/.github/.github/workflows/excluded-files.yml@main
15
15
 
16
16
  security:
17
17
  uses: LegionIO/.github/.github/workflows/security-scan.yml@main
@@ -27,8 +27,8 @@ jobs:
27
27
  uses: LegionIO/.github/.github/workflows/stale.yml@main
28
28
 
29
29
  release:
30
- needs: [ci, lint]
30
+ needs: [ci, excluded-files]
31
31
  if: github.event_name == 'push' && github.ref == 'refs/heads/main'
32
32
  uses: LegionIO/.github/.github/workflows/release.yml@main
33
33
  secrets:
34
- rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
34
+ rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
data/.rubocop.yml CHANGED
@@ -1,50 +1,2 @@
1
- AllCops:
2
- TargetRubyVersion: 3.4
3
- NewCops: enable
4
- SuggestExtensions: false
5
-
6
- Layout/LineLength:
7
- Max: 160
8
-
9
- Layout/SpaceAroundEqualsInParameterDefault:
10
- EnforcedStyle: space
11
-
12
- Layout/HashAlignment:
13
- EnforcedHashRocketStyle: table
14
- EnforcedColonStyle: table
15
-
16
- Metrics/MethodLength:
17
- Max: 50
18
-
19
- Metrics/ClassLength:
20
- Max: 1500
21
-
22
- Metrics/ModuleLength:
23
- Max: 1500
24
-
25
- Metrics/BlockLength:
26
- Max: 40
27
- Exclude:
28
- - 'spec/**/*'
29
-
30
- Metrics/AbcSize:
31
- Max: 60
32
-
33
- Metrics/CyclomaticComplexity:
34
- Max: 15
35
-
36
- Metrics/PerceivedComplexity:
37
- Max: 17
38
-
39
- Style/Documentation:
40
- Enabled: false
41
-
42
- Style/SymbolArray:
43
- Enabled: true
44
-
45
- Style/FrozenStringLiteralComment:
46
- Enabled: true
47
- EnforcedStyle: always
48
-
49
- Naming/FileName:
50
- Enabled: false
1
+ inherit_gem:
2
+ rubocop-legion: config/lex.yml
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.6] - 2026-03-31
4
+
5
+ ### Added
6
+ - `legion.cluster.control` topic exchange for cluster-wide broadcast (durable, topic type)
7
+ - Per-node auto-delete queue `legion.cluster.control.<name>` bound to the exchange on boot
8
+ - `ClusterSettings` message: publishes settings changes with configurable routing key to the cluster control exchange
9
+ - `ClusterKillswitch` message: publishes emergency extension block via routing key `settings.extensions.blocked`
10
+ - `ClusterControl` subscription actor: subscribes to the per-node cluster control queue, routes to existing runners by routing key
11
+ - `Runners::Node#broadcast_settings(settings:, routing_key: 'settings', restart: false)`: sends settings to all nodes via the cluster control exchange
12
+ - `Runners::Node#killswitch(extension:)`: blocks an extension cluster-wide with immediate reload
13
+ - 51 new specs covering all new components (162 total, 0 failures)
14
+
15
+ ## [0.3.5] - 2026-03-30
16
+
17
+ ### Changed
18
+ - update to rubocop-legion 0.1.7, resolve all offenses
19
+
3
20
  ## [Unreleased]
4
21
 
5
22
  ### Added
data/CLAUDE.md CHANGED
@@ -10,7 +10,7 @@ Core Legion Extension responsible for node identity within a LegionIO cluster. H
10
10
 
11
11
  **GitHub**: https://github.com/LegionIO/lex-node
12
12
  **License**: MIT
13
- **Version**: 0.3.2
13
+ **Version**: 0.3.4
14
14
 
15
15
  ## Architecture
16
16
 
@@ -57,7 +57,9 @@ Legion::Extensions::Node
57
57
  ├── 001_nodes_table # Core nodes table
58
58
  ├── 002_node_history_table # Node activity history
59
59
  ├── 003_legion_version_colume # Legion version column (note: typo in filename)
60
- └── 004_node_extensions # Installed extensions per node
60
+ ├── 004_node_extensions # Installed extensions per node
61
+ ├── 005_add_slow_query_indexes # Composite index on (status, active, updated)
62
+ └── 006_add_nodes_created_index # Indexes on created, updated, composite (status, active, created)
61
63
  ```
62
64
 
63
65
  ## Key Files
data/Gemfile CHANGED
@@ -8,5 +8,6 @@ group :test do
8
8
  gem 'rspec'
9
9
  gem 'rspec_junit_formatter'
10
10
  gem 'rubocop'
11
+ gem 'rubocop-legion', '~> 0.1', require: false
11
12
  gem 'simplecov'
12
13
  end
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Node
6
6
  module Actor
7
- class Beat < Legion::Extensions::Actors::Every
7
+ class Beat < Legion::Extensions::Actors::Every # rubocop:disable Legion/Extension/EveryActorRequiresTime
8
8
  def runner_function
9
9
  'beat'
10
10
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Node
6
+ module Actor
7
+ class ClusterControl < Legion::Extensions::Actors::Subscription
8
+ def runner_class
9
+ Legion::Extensions::Node::Runners::Node
10
+ end
11
+
12
+ def queue_class
13
+ Legion::Extensions::Node::Transport::Queues::ClusterControl
14
+ end
15
+
16
+ def disabled?
17
+ false
18
+ end
19
+
20
+ def use_runner?
21
+ true
22
+ end
23
+
24
+ def check_subtask?
25
+ false
26
+ end
27
+
28
+ def generate_task?
29
+ false
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -20,7 +20,7 @@ module Legion
20
20
  shovel_links: fetch_shovel_links(http, settings)
21
21
  }
22
22
  rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, SocketError, Net::OpenTimeout, Net::ReadTimeout => e
23
- log_warn("RabbitMQ management API unreachable: #{e.message}")
23
+ log.warn("RabbitMQ management API unreachable: #{e.message}")
24
24
  { node_count: unreachable, quorum_leaders: unreachable, shovel_links: unreachable }
25
25
  end
26
26
 
@@ -66,7 +66,7 @@ module Legion
66
66
  (running == total ? 'ok' : 'warn')
67
67
  end
68
68
  { status: status, total: total, running: running }
69
- rescue StandardError
69
+ rescue StandardError => _e
70
70
  { status: 'ok', total: 0, running: 0 }
71
71
  end
72
72
 
@@ -89,7 +89,7 @@ module Legion
89
89
  return nil unless response.code.start_with?('2')
90
90
 
91
91
  ::JSON.parse(response.body)
92
- rescue StandardError
92
+ rescue StandardError => _e
93
93
  nil
94
94
  end
95
95
 
@@ -115,11 +115,7 @@ module Legion
115
115
  end
116
116
 
117
117
  def log_warn(msg)
118
- if defined?(Legion::Logging)
119
- Legion::Logging.warn { msg }
120
- elsif defined?(Legion::Transport) && Legion::Transport.respond_to?(:logger)
121
- Legion::Transport.logger.warn(msg)
122
- end
118
+ log.warn(msg)
123
119
  end
124
120
  end
125
121
  end
@@ -120,6 +120,30 @@ module Legion
120
120
  public_key: public_key)
121
121
  end
122
122
 
123
+ def broadcast_settings(settings:, routing_key: 'settings', restart: false, **_opts)
124
+ log.debug "broadcast_settings: routing_key=#{routing_key} keys=#{settings.keys.join(', ')}"
125
+ Legion::Extensions::Node::Transport::Messages::ClusterSettings.new(
126
+ settings: settings,
127
+ routing_key: routing_key,
128
+ restart: restart
129
+ ).publish
130
+ {}
131
+ rescue StandardError => e
132
+ log.error "broadcast_settings failed: #{e.message}"
133
+ {}
134
+ end
135
+
136
+ def killswitch(extension:, **_opts)
137
+ log.debug "killswitch: blocking extension #{extension} cluster-wide"
138
+ Legion::Extensions::Node::Transport::Messages::ClusterKillswitch.new(
139
+ extension: extension.to_s.delete_prefix('lex-')
140
+ ).publish
141
+ {}
142
+ rescue StandardError => e
143
+ log.error "killswitch failed: #{e.message}"
144
+ {}
145
+ end
146
+
123
147
  private
124
148
 
125
149
  def publish_update_result(action:, status:, detail: nil, error: nil)
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Node
6
+ module Transport
7
+ module Exchanges
8
+ class ClusterControl
9
+ EXCHANGE_NAME = 'legion.cluster.control'
10
+ EXCHANGE_TYPE = :topic
11
+ EXCHANGE_OPTIONS = { durable: true }.freeze
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -63,7 +63,7 @@ module Legion
63
63
  else
64
64
  File.read("/proc/#{::Process.pid}/statm").split[1].to_i * (4096.0 / 1_048_576)
65
65
  end
66
- rescue StandardError
66
+ rescue StandardError => _e
67
67
  0.0
68
68
  end
69
69
 
@@ -89,7 +89,7 @@ module Legion
89
89
  return [] unless defined?(Legion::DigitalWorker)
90
90
 
91
91
  Legion::DigitalWorker.active_local_ids
92
- rescue StandardError
92
+ rescue StandardError => _e
93
93
  []
94
94
  end
95
95
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Node
6
+ module Transport
7
+ module Messages
8
+ class ClusterKillswitch < Legion::Transport::Message
9
+ ROUTING_KEY = 'settings.extensions.blocked'
10
+
11
+ def routing_key
12
+ ROUTING_KEY
13
+ end
14
+
15
+ def exchange
16
+ Legion::Extensions::Node::Transport::Exchanges::ClusterControl
17
+ end
18
+
19
+ def type
20
+ 'task'
21
+ end
22
+
23
+ def encrypt?
24
+ false
25
+ end
26
+
27
+ def message
28
+ {
29
+ function: 'update_settings',
30
+ settings: { extensions: { blocked: [@options[:extension]] } },
31
+ restart: true
32
+ }
33
+ end
34
+
35
+ def validate
36
+ raise 'extension must be a String' unless @options[:extension].is_a?(String)
37
+
38
+ @valid = true
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Node
6
+ module Transport
7
+ module Messages
8
+ class ClusterSettings < Legion::Transport::Message
9
+ def routing_key
10
+ @options.fetch(:routing_key, 'settings')
11
+ end
12
+
13
+ def exchange
14
+ Legion::Extensions::Node::Transport::Exchanges::ClusterControl
15
+ end
16
+
17
+ def type
18
+ 'task'
19
+ end
20
+
21
+ def encrypt?
22
+ false
23
+ end
24
+
25
+ def message
26
+ {
27
+ function: 'update_settings',
28
+ settings: @options[:settings],
29
+ restart: @options.fetch(:restart, false)
30
+ }
31
+ end
32
+
33
+ def validate
34
+ raise 'settings must be a Hash' unless @options[:settings].is_a?(Hash)
35
+
36
+ @valid = true
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Node
6
+ module Transport
7
+ module Queues
8
+ class ClusterControl < Legion::Transport::Queue
9
+ def queue_name
10
+ "legion.cluster.control.#{Legion::Settings[:client][:name]}"
11
+ end
12
+
13
+ def queue_options
14
+ { durable: false, exclusive: false, auto_delete: true,
15
+ arguments: { 'x-queue-type': 'classic' } }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -13,6 +13,7 @@ module Legion
13
13
  array.push(from: 'node', to: 'node', routing_key: 'node.data.#') if Legion::Settings[:data]&.[](:connected) || false
14
14
  array.push(from: 'node', to: 'node', routing_key: 'node.cache.#') if Legion::Settings[:cache]&.[](:connected) || false
15
15
  array.push(from: 'node', to: 'node', routing_key: 'node.crypt.#')
16
+ array.push(from: 'cluster_control', to: 'cluster_control', routing_key: '#')
16
17
  array
17
18
  end
18
19
  end
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Node
6
- VERSION = '0.3.4'
6
+ VERSION = '0.3.6'
7
7
  end
8
8
  end
9
9
  end
@@ -6,7 +6,7 @@ require 'legion/extensions/node/helpers/rabbitmq'
6
6
  module Legion
7
7
  module Extensions
8
8
  module Node
9
- extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
9
+ extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core, false
10
10
  end
11
11
  end
12
12
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-node
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.3.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -129,6 +129,8 @@ executables: []
129
129
  extensions: []
130
130
  extra_rdoc_files: []
131
131
  files:
132
+ - ".github/CODEOWNERS"
133
+ - ".github/dependabot.yml"
132
134
  - ".github/workflows/ci.yml"
133
135
  - ".gitignore"
134
136
  - ".rspec"
@@ -143,6 +145,7 @@ files:
143
145
  - lex-node.gemspec
144
146
  - lib/legion/extensions/node.rb
145
147
  - lib/legion/extensions/node/actors/beat.rb
148
+ - lib/legion/extensions/node/actors/cluster_control.rb
146
149
  - lib/legion/extensions/node/actors/crypt.rb
147
150
  - lib/legion/extensions/node/actors/push_key.rb
148
151
  - lib/legion/extensions/node/actors/vault.rb
@@ -158,8 +161,11 @@ files:
158
161
  - lib/legion/extensions/node/runners/node.rb
159
162
  - lib/legion/extensions/node/runners/vault.rb
160
163
  - lib/legion/extensions/node/transport.rb
164
+ - lib/legion/extensions/node/transport/exchanges/cluster_control.rb
161
165
  - lib/legion/extensions/node/transport/exchanges/node.rb
162
166
  - lib/legion/extensions/node/transport/messages/beat.rb
167
+ - lib/legion/extensions/node/transport/messages/cluster_killswitch.rb
168
+ - lib/legion/extensions/node/transport/messages/cluster_settings.rb
163
169
  - lib/legion/extensions/node/transport/messages/public_key.rb
164
170
  - lib/legion/extensions/node/transport/messages/push_cluster_secret.rb
165
171
  - lib/legion/extensions/node/transport/messages/push_vault_token.rb
@@ -167,6 +173,7 @@ files:
167
173
  - lib/legion/extensions/node/transport/messages/request_public_keys.rb
168
174
  - lib/legion/extensions/node/transport/messages/request_vault_token.rb
169
175
  - lib/legion/extensions/node/transport/messages/update_result.rb
176
+ - lib/legion/extensions/node/transport/queues/cluster_control.rb
170
177
  - lib/legion/extensions/node/transport/queues/crypt.rb
171
178
  - lib/legion/extensions/node/transport/queues/health.rb
172
179
  - lib/legion/extensions/node/transport/queues/node.rb