lex-node 0.2.3 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d0260fcda3e0a5469d3bfc8d7065d4870addf415732a693da58d979746dcf97b
4
- data.tar.gz: 67a9e3e080786935fea85006e9623cae1aff3a0995d9e507ba37eb723ffff48b
3
+ metadata.gz: a8bce7f974591cba97508bd1d4a9033315f4207050da3996f082438267c1de9c
4
+ data.tar.gz: 3af059ccb67edb50d52c749adbd8014cd29c9d75af4fa3b85c84c678a453301b
5
5
  SHA512:
6
- metadata.gz: 27d58631dc278eceb7988cc6125382e55babcac167e2df4a68b386d0b2ac47c8ece02b76f8333732e9d342659d2fc0cc159241fae86a7194a5490ec90be6d504
7
- data.tar.gz: 886335964005f1f868d644c0b084a1a2e82991a9714e00a25154551864c6cf6b55d4d2aaf2d66edd4c9b9313cc1650b07589345c20dbdcb71e95ee815b247384
6
+ metadata.gz: 5dd82b1f0b200c50a7d4b3cedc639e73ad47b27335a8405182b6f2c1a9f6372930c51d1b3c52bcd0403198593d6b22679e989e83f944e75b28a59d86a19ce339
7
+ data.tar.gz: 5a3c3d9ffdccca307b9e8981b73f18021eabdfb2ee44167231b75290d7c8dd33fd2474e08db6ccb377eede6fb634e5274de5c558fe77aa914a9818a96c88ebc9
data/.gitignore CHANGED
@@ -7,5 +7,7 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
 
10
+ Gemfile.lock
11
+
10
12
  # rspec failure tracking
11
13
  .rspec_status
data/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.1] - 2026-03-19
4
+
5
+ ### Fixed
6
+ - `push_key` actor referenced removed `Runners::Crypt` (consolidated into `Runners::Node` in v0.3.0); updated actor and spec to use `Runners::Node`
7
+
8
+ ## [0.3.0] - 2026-03-18
9
+
10
+ ### Fixed
11
+ - `update_public_key` changed from class method to instance method (was unreachable by AMQP dispatch)
12
+ - `request_cluster_secret` now uses correct message namespace
13
+ - Beat message uses `[:name]` instead of `[:hostname]` for node identity
14
+ - Boot time tracked as class constant (uptime_seconds was always ~0)
15
+ - Added `require 'base64'` for Ruby 3.4+ compatibility
16
+ - Public key encoding standardized to Base64 across all messages
17
+ - Transport settings access uses safe navigation (nil crash prevention)
18
+ - Beat actor uses symbol key for `beat_interval` setting
19
+ - VaultTokenRequest actor now has `use_runner? true` (was dead wiring)
20
+ - Request vault token message Base64-encodes public key
21
+
22
+ ### Changed
23
+ - Consolidated Runners::Crypt into Runners::Node (deleted runners/crypt.rb)
24
+ - Deleted data_test/ directory (broken MySQL-only migrations, zero consumers)
25
+ - Vault runners support multi-cluster token storage (backward-compatible)
26
+
27
+ ### Removed
28
+ - Duplicate push_public_key/receive_cluster_secret in Runners::Node (used Crypt versions)
29
+ - Unused `require 'socket'` in transport queue
30
+ - `|| nil` redundancies in push_cluster_secret message
31
+
3
32
  ## [0.2.3] - 2026-03-16
4
33
 
5
34
  ### 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.2.3
13
+ **Version**: 0.3.0
14
14
 
15
15
  ## Architecture
16
16
 
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Node identity and cluster management for [LegionIO](https://github.com/LegionIO/LegionIO). Handles heartbeat broadcasting, dynamic configuration distribution, cluster secret exchange, Vault token management, and RSA public key distribution between nodes.
4
4
 
5
- **Version**: 0.2.2
5
+ **Version**: 0.3.0
6
6
 
7
7
  This is a core LEX installed by default with LegionIO.
8
8
 
data/lex-node.gemspec CHANGED
@@ -25,4 +25,6 @@ Gem::Specification.new do |spec|
25
25
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
26
  end
27
27
  spec.require_paths = ['lib']
28
+
29
+ spec.add_dependency 'base64'
28
30
  end
@@ -26,7 +26,7 @@ module Legion
26
26
  end
27
27
 
28
28
  def time
29
- settings['beat_interval']
29
+ settings[:beat_interval]
30
30
  end
31
31
  end
32
32
  end
@@ -10,7 +10,7 @@ module Legion
10
10
  end
11
11
 
12
12
  def runner_class
13
- Legion::Extensions::Node::Runners::Crypt
13
+ Legion::Extensions::Node::Runners::Node
14
14
  end
15
15
 
16
16
  def disabled?
@@ -14,7 +14,7 @@ module Legion
14
14
  end
15
15
 
16
16
  def use_runner?
17
- false
17
+ true
18
18
  end
19
19
 
20
20
  def check_subtask?
@@ -8,9 +8,9 @@ module Legion
8
8
  include Legion::Extensions::Helpers::Transport
9
9
 
10
10
  def beat(status: 'active', **opts)
11
- log.debug 'sending hearbeat'
11
+ log.debug 'sending heartbeat'
12
12
  messages::Beat.new(status: status).publish
13
- { success: true, status: status, version: Legion::VERSION || nil, **opts }
13
+ { success: true, status: status, version: defined?(Legion::VERSION) ? Legion::VERSION : nil, **opts }
14
14
  end
15
15
 
16
16
  include Legion::Extensions::Helpers::Lex
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'base64'
4
+
3
5
  module Legion
4
6
  module Extensions
5
7
  module Node
@@ -60,9 +62,9 @@ module Legion
60
62
  def push_public_key(**_opts)
61
63
  log.debug 'push_public_key'
62
64
  message_hash = { function: 'update_public_key',
63
- public_key: Legion::Crypt.public_key.to_s,
65
+ public_key: Base64.encode64(Legion::Crypt.public_key),
64
66
  **Legion::Settings[:client] }
65
- Legion::Extensions::Node::Transport::Messages::PublicKey.new(**message_hash).publish
67
+ Legion::Extensions::Node::Transport::Messages::PublicKey.new(message_hash).publish
66
68
  {}
67
69
  end
68
70
 
@@ -72,6 +74,25 @@ module Legion
72
74
  {}
73
75
  end
74
76
 
77
+ def delete_public_key(name:, **_opts)
78
+ log.debug 'delete_public_key'
79
+ Legion::Settings[:cluster][:public_keys].delete(name)
80
+ {}
81
+ end
82
+
83
+ def request_public_keys(**_opts)
84
+ log.debug 'request_public_keys'
85
+ message_hash = { function: 'push_public_key' }
86
+ Legion::Extensions::Node::Transport::Messages::RequestPublicKeys.new(**message_hash).publish
87
+ {}
88
+ end
89
+
90
+ def request_cluster_secret(**_opts)
91
+ log.debug 'request_cluster_secret'
92
+ Legion::Extensions::Node::Transport::Messages::RequestClusterSecret.new.publish
93
+ {}
94
+ end
95
+
75
96
  def push_cluster_secret(public_key:, queue_name:, **_opts)
76
97
  log.debug 'push_cluster_secret'
77
98
  return {} unless Legion::Settings[:crypt][:cs_encrypt_ready]
@@ -86,9 +107,11 @@ module Legion
86
107
  {}
87
108
  end
88
109
 
89
- def receive_cluster_secret(message:, **_opts)
110
+ def receive_cluster_secret(message:, **opts)
90
111
  log.debug 'receive_cluster_secret'
91
112
  Legion::Settings[:crypt][:cluster_secret] = Legion::Crypt.decrypt_from_keypair(message: message)
113
+ Legion::Settings[:crypt][:encrypted_string] = opts[:encrypted_string]
114
+ Legion::Settings[:crypt][:validation_string] = opts[:validation_string]
92
115
  {}
93
116
  end
94
117
 
@@ -17,10 +17,18 @@ module Legion
17
17
  {}
18
18
  end
19
19
 
20
- def receive_vault_token(message:, **opts)
21
- return if Legion::Settings[:crypt][:vault][:connected]
20
+ def receive_vault_token(message: nil, token: nil, cluster_name: nil, **opts)
21
+ return { success: false, already_connected: true } if Legion::Settings[:crypt][:vault][:connected]
22
+
23
+ token ||= Legion::Crypt.decrypt_from_keypair(message: message)
24
+ clusters = Legion::Settings[:crypt][:vault][:clusters]
25
+ if cluster_name && clusters.is_a?(Hash) && clusters[cluster_name.to_sym]
26
+ clusters[cluster_name.to_sym][:token] = token
27
+ clusters[cluster_name.to_sym][:connected] = true
28
+ return { success: true }
29
+ end
22
30
 
23
- Legion::Settings[:crypt][:vault][:token] = Legion::Crypt.decrypt_from_keypair(message: message)
31
+ Legion::Settings[:crypt][:vault][:token] = token
24
32
  %i[protocol address port].each do |setting|
25
33
  next unless opts.key? setting
26
34
  next unless Legion::Settings[:crypt][:vault][setting].nil?
@@ -6,6 +6,7 @@ module Legion
6
6
  module Transport
7
7
  module Messages
8
8
  class Beat < Legion::Transport::Message
9
+ BOOT_TIME = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
9
10
  def routing_key
10
11
  'status'
11
12
  end
@@ -24,7 +25,7 @@ module Legion
24
25
 
25
26
  def message
26
27
  hash = {
27
- name: Legion::Settings[:client][:hostname],
28
+ name: Legion::Settings[:client][:name],
28
29
  pid: ::Process.pid,
29
30
  timestamp: Time.now,
30
31
  status: @options[:status].nil? ? 'healthy' : @options[:status]
@@ -72,11 +73,7 @@ module Legion
72
73
  end
73
74
 
74
75
  def uptime_seconds
75
- (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - boot_time).round(0)
76
- end
77
-
78
- def boot_time
79
- @boot_time ||= ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
76
+ (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - BOOT_TIME).round(0)
80
77
  end
81
78
 
82
79
  def collect_worker_ids
@@ -18,8 +18,8 @@ module Legion
18
18
  { function: 'receive_cluster_secret',
19
19
  runner_class: 'Legion::Extensions::Node::Runners::Crypt',
20
20
  message: @options[:message],
21
- validation_string: @options[:validation_string] || nil,
22
- encrypted_string: @options[:encrypted_string] || nil,
21
+ validation_string: @options[:validation_string],
22
+ encrypted_string: @options[:encrypted_string],
23
23
  public_key: Base64.encode64(Legion::Crypt.public_key) }
24
24
  end
25
25
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'base64'
4
+
3
5
  module Legion
4
6
  module Extensions
5
7
  module Node
@@ -15,7 +17,7 @@ module Legion
15
17
  function: 'push_vault_token',
16
18
  node_name: Legion::Settings[:client][:name],
17
19
  runner_class: 'Legion::Extensions::Node::Runners::Vault',
18
- public_key: Legion::Crypt.public_key
20
+ public_key: Base64.encode64(Legion::Crypt.public_key.to_s)
19
21
  }
20
22
  end
21
23
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'socket'
4
-
5
3
  module Legion
6
4
  module Extensions
7
5
  module Node
@@ -10,8 +10,8 @@ module Legion
10
10
 
11
11
  def self.additional_e_to_q
12
12
  array = [{ from: 'node', to: 'node', routing_key: "node.#{Legion::Settings[:client][:name]}" }]
13
- array.push(from: 'node', to: 'node', routing_key: 'node.data.#') if Legion::Settings[:data][:connected]
14
- array.push(from: 'node', to: 'node', routing_key: 'node.cache.#') if Legion::Settings[:cache][:connected]
13
+ array.push(from: 'node', to: 'node', routing_key: 'node.data.#') if Legion::Settings[:data]&.[](:connected) || false
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
16
  array
17
17
  end
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Node
6
- VERSION = '0.2.3'
6
+ VERSION = '0.3.1'
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,14 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-node
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
8
8
  bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies: []
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: base64
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
12
26
  description: This lex is responsible for sending heartbeats, allowing for dynamic
13
27
  config, cluster secret, etc
14
28
  email:
@@ -25,7 +39,6 @@ files:
25
39
  - CLAUDE.md
26
40
  - Dockerfile
27
41
  - Gemfile
28
- - Gemfile.lock
29
42
  - LICENSE
30
43
  - README.md
31
44
  - docker_deploy.rb
@@ -41,7 +54,6 @@ files:
41
54
  - lib/legion/extensions/node/data_test/migrations/003_legion_version_colume.rb
42
55
  - lib/legion/extensions/node/data_test/migrations/004_node_extensions.rb
43
56
  - lib/legion/extensions/node/runners/beat.rb
44
- - lib/legion/extensions/node/runners/crypt.rb
45
57
  - lib/legion/extensions/node/runners/node.rb
46
58
  - lib/legion/extensions/node/runners/vault.rb
47
59
  - lib/legion/extensions/node/transport.rb
data/Gemfile.lock DELETED
@@ -1,87 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- lex-node (0.2.3)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- addressable (2.8.9)
10
- public_suffix (>= 2.0.2, < 8.0)
11
- ast (2.4.3)
12
- bigdecimal (4.0.1)
13
- diff-lcs (1.6.2)
14
- docile (1.4.1)
15
- json (2.19.1)
16
- json-schema (6.2.0)
17
- addressable (~> 2.8)
18
- bigdecimal (>= 3.1, < 5)
19
- language_server-protocol (3.17.0.5)
20
- lint_roller (1.1.0)
21
- mcp (0.8.0)
22
- json-schema (>= 4.1)
23
- parallel (1.27.0)
24
- parser (3.3.10.2)
25
- ast (~> 2.4.1)
26
- racc
27
- prism (1.9.0)
28
- public_suffix (7.0.5)
29
- racc (1.8.1)
30
- rainbow (3.1.1)
31
- rake (13.3.1)
32
- regexp_parser (2.11.3)
33
- rspec (3.13.2)
34
- rspec-core (~> 3.13.0)
35
- rspec-expectations (~> 3.13.0)
36
- rspec-mocks (~> 3.13.0)
37
- rspec-core (3.13.6)
38
- rspec-support (~> 3.13.0)
39
- rspec-expectations (3.13.5)
40
- diff-lcs (>= 1.2.0, < 2.0)
41
- rspec-support (~> 3.13.0)
42
- rspec-mocks (3.13.8)
43
- diff-lcs (>= 1.2.0, < 2.0)
44
- rspec-support (~> 3.13.0)
45
- rspec-support (3.13.7)
46
- rspec_junit_formatter (0.6.0)
47
- rspec-core (>= 2, < 4, != 2.12.0)
48
- rubocop (1.85.1)
49
- json (~> 2.3)
50
- language_server-protocol (~> 3.17.0.2)
51
- lint_roller (~> 1.1.0)
52
- mcp (~> 0.6)
53
- parallel (~> 1.10)
54
- parser (>= 3.3.0.2)
55
- rainbow (>= 2.2.2, < 4.0)
56
- regexp_parser (>= 2.9.3, < 3.0)
57
- rubocop-ast (>= 1.49.0, < 2.0)
58
- ruby-progressbar (~> 1.7)
59
- unicode-display_width (>= 2.4.0, < 4.0)
60
- rubocop-ast (1.49.1)
61
- parser (>= 3.3.7.2)
62
- prism (~> 1.7)
63
- ruby-progressbar (1.13.0)
64
- simplecov (0.22.0)
65
- docile (~> 1.1)
66
- simplecov-html (~> 0.11)
67
- simplecov_json_formatter (~> 0.1)
68
- simplecov-html (0.13.2)
69
- simplecov_json_formatter (0.1.4)
70
- unicode-display_width (3.2.0)
71
- unicode-emoji (~> 4.1)
72
- unicode-emoji (4.2.0)
73
-
74
- PLATFORMS
75
- arm64-darwin-25
76
- ruby
77
-
78
- DEPENDENCIES
79
- lex-node!
80
- rake
81
- rspec
82
- rspec_junit_formatter
83
- rubocop
84
- simplecov
85
-
86
- BUNDLED WITH
87
- 2.6.9
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Legion
4
- module Extensions
5
- module Node
6
- module Runners
7
- module Crypt
8
- def push_public_key(**_opts)
9
- log.debug 'push_public_key'
10
- message_hash = { function: 'update_public_key',
11
- public_key: Base64.encode64(Legion::Crypt.public_key),
12
- **Legion::Settings[:client] }
13
- Legion::Extensions::Node::Transport::Messages::PublicKey.new(message_hash).publish
14
- {}
15
- end
16
-
17
- def self.update_public_key(name:, public_key:, **_opts)
18
- log.debug 'update_public_key'
19
- Legion::Settings[:cluster][:public_keys][name] = public_key
20
- {}
21
- end
22
-
23
- def delete_public_key(name:, **_opts)
24
- log.debug 'delete_public_key'
25
- Legion::Settings[:cluster][:public_keys].delete(name)
26
- {}
27
- end
28
-
29
- def request_public_keys(**_opts)
30
- log.debug 'request_public_keys'
31
- message_hash = { function: 'push_public_key' }
32
- Legion::Extensions::Node::Transport::Messages::RequestPublicKeys.new(**message_hash).publish
33
- {}
34
- end
35
-
36
- def request_cluster_secret(**_opts)
37
- log.debug 'request_cluster_secret'
38
- Legion::Transport::Messages::RequestClusterSecret.new.publish
39
- {}
40
- end
41
-
42
- def push_cluster_secret(public_key:, queue_name:, **_opts)
43
- log.debug 'push_cluster_secret'
44
- return {} unless Legion::Settings[:crypt][:cs_encrypt_ready]
45
-
46
- encrypted = Legion::Crypt.encrypt_from_keypair(pub_key: public_key,
47
- message: Legion::Settings[:crypt][:cluster_secret].to_s)
48
- legion = Legion::Crypt.encrypt('legion')
49
- Legion::Extensions::Node::Transport::Messages::PushClusterSecret.new(message: encrypted,
50
- queue_name: queue_name,
51
- validation_string: 'legion',
52
- encrypted_string: legion).publish
53
- {}
54
- end
55
-
56
- def receive_cluster_secret(message:, **opts)
57
- log.debug 'receive_cluster_secret'
58
- Legion::Settings[:crypt][:cluster_secret] = Legion::Crypt.decrypt_from_keypair(message)
59
- Legion::Settings[:crypt][:encrypted_string] = opts[:encrypted_string]
60
- Legion::Settings[:crypt][:validation_string] = opts[:validation_string]
61
- {}
62
- end
63
-
64
- include Legion::Extensions::Helpers::Lex
65
- end
66
- end
67
- end
68
- end
69
- end