legionio 1.4.63 → 1.4.64

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: 577e7caee9c14f38ce12e36d35f8a92fe4d66d33c7d62dbde89c8581575dc961
4
- data.tar.gz: 0165ac8ccd3b32b48cce36a7188669ceb4254b344be99243a1e08fca77235a9f
3
+ metadata.gz: 3fe88835ee309cdcd0e6028c5347a7b5be018a8697f14e96bfba294d821ac9b0
4
+ data.tar.gz: 44e770db00a040967b0bc2a6000615cd081a9c42d8c3f68289e30e0baf1f46f0
5
5
  SHA512:
6
- metadata.gz: 8192110eaf2b6f11cc38852448feade2999655706c657f2ed0ff78cd8fe698994c78443f90444d4ea0510060d66d6f679ebd6f58a9f9b46d42ef66352413434f
7
- data.tar.gz: e807e335d274d228fac57bd6761380af2508f5c98cb40b8d4285ae9da9740cd10b1b592b3d3cad21e6a961f9f8cda05196fb5a35dee0a0ae18d6a6b969ddeb7f
6
+ metadata.gz: bbb73700901c2804ceb8200e5154e484899a217ebb26a122ed29d00c0505947fac537473e5540720f3b0a492fb2826881027f02860ef6a9a1d341583dc701cb0
7
+ data.tar.gz: 5400b10d5097542980cbce00011fb3d3447dc2913671b741b19c95f29f68fb323b9d6688fdab739e26277641d27612b33026c8320a0631932e548e5e49425300
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Legion Changelog
2
2
 
3
+ ## [1.4.64] - 2026-03-18
4
+
5
+ ### Fixed
6
+ - Remove legacy `exe/legion-tty` from legionio gem (conflicts with legion-tty gem executable)
7
+ - Explicitly list executables as `legion` and `legionio` in gemspec instead of glob pattern
8
+
3
9
  ## [1.4.63] - 2026-03-18
4
10
 
5
11
  ### Added
@@ -0,0 +1,252 @@
1
+ # Core LEX Uplift Design
2
+
3
+ ## Problem / Motivation
4
+
5
+ The 5 core operational extensions (lex-tasker, lex-scheduler, lex-node, lex-health, lex-lex) have accumulated bugs, dead code, MySQL-only SQL, and low spec coverage since their initial implementation. A bottom-up audit found **~50 bugs** across the 5 extensions, including:
6
+
7
+ - **Critical runtime crashes**: NameError, NoMethodError, TypeError on common code paths
8
+ - **SQL injection risk**: string interpolation in raw SQL queries
9
+ - **Cross-DB failures**: MySQL-only DDL and query syntax that breaks on PostgreSQL/SQLite
10
+ - **Dead code**: entire runner modules with no actor wiring, unreachable class methods
11
+ - **Architecture gaps**: missing subscription actors, broken model definitions, incorrect Sequel patterns
12
+
13
+ Meanwhile, lex-conditioner (0.3.0, 140 specs, 99% coverage) and lex-transformer (0.2.0, 86 specs, 96% coverage) demonstrate the quality bar these extensions should meet: standalone Clients where useful, high spec coverage, cross-DB compatibility, and clean code.
14
+
15
+ ## Goal
16
+
17
+ Uplift all 5 core extensions to conditioner/transformer quality parity:
18
+ - Fix all identified bugs
19
+ - Add standalone Clients where useful (lex-tasker, lex-scheduler)
20
+ - Achieve 90%+ spec coverage
21
+ - Clean up dead code, duplicate helpers, broken migrations
22
+ - Ensure cross-DB compatibility (SQLite, PostgreSQL, MySQL)
23
+
24
+ ## Approach
25
+
26
+ **Option B (chosen): Full uplift to conditioner/transformer parity** — bug fixes + standalone Clients + 90%+ spec coverage + cleanup for all 5 extensions. This was chosen over Option A (bugs-only) because many bugs are intertwined with structural issues that require cleanup to fix properly.
27
+
28
+ ## Design Decisions
29
+
30
+ 1. **lex-scheduler mode runners (ModeScheduler, ModeTransition, EmergencyPromotion)**: **Remove**. Dead code with no actor wiring, broken dependencies (Legion::Events doesn't exist), implicit undeclared dependency chains. YAGNI — if HA scheduling is needed later, it would be redesigned against the current architecture.
31
+
32
+ 2. **lex-node Runners::Crypt**: **Consolidate into Runners::Node**. The split was premature — no separate actor wiring exists. Merge the 2-3 working methods, delete the rest. Also delete `data_test/` directory (4 broken migrations, zero consumers).
33
+
34
+ 3. **Standalone Clients**: **lex-tasker and lex-scheduler only**. The other three (lex-health, lex-lex, lex-node) are infrastructure plumbing — no use case for calling them outside the message bus.
35
+
36
+ 4. **Multi-cluster Vault compatibility (lex-node)**: Per the `2026-03-18-config-import-vault-multicluster` design, `Legion::Crypt` now supports multi-cluster Vault. lex-node's vault runners must handle both legacy single-cluster and new multi-cluster token storage paths.
37
+
38
+ ---
39
+
40
+ ## Extension 1: lex-tasker (0.2.3 -> 0.3.0)
41
+
42
+ ### Bug Fixes (15 items)
43
+
44
+ | # | File | Bug | Fix |
45
+ |---|------|-----|-----|
46
+ | 1 | `runners/check_subtask.rb` | `extend FindSubtask` — instance calls unreachable | `include FindSubtask` |
47
+ | 2 | `runners/fetch_delayed.rb` | `extend FetchDelayed` — same issue | `include FetchDelayed` |
48
+ | 3 | `runners/log.rb:14` | `payload[:node_id]` — NameError | `opts[:node_id]` |
49
+ | 4 | `runners/log.rb:16` | `Node.where(opts[:name])` — bare string | `Node.where(name: opts[:name])` |
50
+ | 5 | `runners/log.rb:17` | `runner.values.nil?` — NoMethodError when runner nil | `runner.nil?` |
51
+ | 6 | `runners/log.rb:47` | `TaskLog.all.delete` — Array#delete no-op | `TaskLog.dataset.delete` |
52
+ | 7 | `runners/task_manager.rb:13` | `dataset.where(status:)` result discarded | Reassign `dataset =` |
53
+ | 8 | `runners/task_manager.rb:11` | MySQL `DATE_SUB(SYSDATE(), ...)` | `Sequel.lit('created <= ?', Time.now - (age * 86_400))` |
54
+ | 9 | `runners/updater.rb` | Missing `return` on early exit | Add `return` before `update_hash.none?` |
55
+ | 10 | `runners/updater.rb:14` | `log.unknown task.class` debug artifact | Remove |
56
+ | 11 | `runners/check_subtask.rb` | `relationship[:delay].zero?` nil crash | `relationship[:delay].to_i.zero?` |
57
+ | 12 | `runners/check_subtask.rb` | `task_hash = relationship` cache mutation | `task_hash = relationship.dup` |
58
+ | 13 | `runners/check_subtask.rb` | `opts[:result]` vs `opts[:results]` fan-out asymmetry | Check both keys |
59
+ | 14 | `helpers/*` | SQL string interpolation (injection risk) | `Sequel.lit('... = ?', value)` |
60
+ | 15 | `helpers/*` | Backtick quoting, `legion.` prefix, `CONCAT()` | Sequel DSL |
61
+
62
+ ### Cleanup
63
+
64
+ - Delete `helpers/base.rb` (empty stub, never included)
65
+ - Deduplicate `find_trigger`/`find_subtasks` into single shared helper module
66
+ - Remove commented-out `Legion::Runner::Status` reference
67
+ - Remove duplicate `data_required?` instance method from entry point
68
+ - Implement `expire_queued` or delete it (total no-op stub)
69
+ - Fix `fetch_delayed` queue TTL from 1ms to 1000ms
70
+ - Fix `task[:task_delay]` missing from SELECT in `find_delayed`
71
+ - Remove `check_subtask? true` / `generate_task? true` from TaskManager actor
72
+
73
+ ### Standalone Client
74
+
75
+ `Legion::Extensions::Tasker::Client.new` wraps `check_subtasks`, `find_trigger`, `find_subtasks` for programmatic use outside AMQP. Accepts `data_model:` injection for testing.
76
+
77
+ ### Spec Coverage Target
78
+
79
+ 75 existing -> ~140+ specs, target 90%+
80
+
81
+ New specs needed:
82
+ - Runners: `check_subtasks`, `dispatch_task`, `send_task`, `insert_task`, `purge_old`, `expire_queued`, `add_log` (all branches), `update_status` (empty hash path)
83
+ - Helpers: `find_trigger`, `find_subtasks`, `find_delayed` with cross-DB stubs
84
+ - Actors: all 3 actors
85
+ - Client suite
86
+ - Edge cases: nil delay, nil function, nil runner, cache mutation
87
+
88
+ ---
89
+
90
+ ## Extension 2: lex-scheduler (0.2.0 -> 0.3.0)
91
+
92
+ ### Bug Fixes (10 items)
93
+
94
+ | # | File | Bug | Fix |
95
+ |---|------|-----|-----|
96
+ | 1 | `migrations/001` + `002` | Raw MySQL DDL | Rewrite as Sequel DSL |
97
+ | 2 | `migrations/005` | Column type `File` | `String, text: true` |
98
+ | 3 | `data/models/schedule_log.rb` | Defines `class Schedule` (wrong name) | `class ScheduleLog` |
99
+ | 4 | `transport/queues/schedule.rb` | `x-message-ttl: 5` (5ms) | `5000` (5s) |
100
+ | 5 | `runners/schedule.rb` | `last_run` nil crash | Nil guard, default to epoch |
101
+ | 6 | `runners/schedule.rb` | `function` nil crash | Nil guard on lookup |
102
+ | 7 | `runners/schedule.rb` | Dead cron guard `Time.now < previous_time` | Remove (always false) |
103
+ | 8 | `messages/send_task.rb` | `function.values[:name]` nil crash | Nil guard on chain |
104
+ | 9 | `messages/refresh.rb` | Dead `message_example` from lex-node | Delete method |
105
+ | 10 | `runners/schedule.rb` | ScheduleLog never written | Add creation after dispatch |
106
+
107
+ ### Removal
108
+
109
+ - Delete `runners/mode_scheduler.rb`, `runners/mode_transition.rb`, `runners/emergency_promotion.rb`
110
+ - Delete associated specs
111
+ - Dead code: no actor wiring, `Legion::Events` doesn't exist, implicit undeclared dependency chain
112
+
113
+ ### Cleanup
114
+
115
+ - Remove duplicate `data_required?` instance method
116
+ - Remove unused `payload` local var in `send_task` no-transform path
117
+ - Remove duplicate `scheduler_spec.rb`
118
+
119
+ ### Standalone Client
120
+
121
+ `Legion::Extensions::Scheduler::Client.new` wraps `schedule_tasks` (list due schedules), `send_task` (dispatch one). Constructor accepts `fugit:` override for testing cron parsing.
122
+
123
+ ### Spec Coverage Target
124
+
125
+ 39 existing -> ~100+ specs, target 90%+
126
+
127
+ New specs: cron happy-path dispatch, `last_run: nil`, nil function, bad cron string, interval schedules, Schedule/ScheduleLog model CRUD, message validation/routing, actors, Client suite, cross-DB migration verification.
128
+
129
+ ---
130
+
131
+ ## Extension 3: lex-node (0.2.3 -> 0.3.0)
132
+
133
+ ### Bug Fixes (11 items)
134
+
135
+ | # | File | Bug | Fix |
136
+ |---|------|-----|-----|
137
+ | 1 | `runners/crypt.rb:17` | `def self.update_public_key` — class method unreachable | Remove `self.` |
138
+ | 2 | `runners/crypt.rb:38` | Wrong namespace `Legion::Transport::Messages::RequestClusterSecret` | Use extension's own namespace |
139
+ | 3 | `messages/beat.rb` | `[:hostname]` vs `[:name]` | Use `[:name]` |
140
+ | 4 | `messages/beat.rb` | `@boot_time` per-instance (uptime always ~0) | Class-level `BOOT_TIME` constant |
141
+ | 5 | `messages/request_vault_token.rb` | Public key sent raw (not Base64) | `Base64.encode64()` |
142
+ | 6 | `runners/node.rb:63` | `public_key.to_s` gives PEM format | `Base64.encode64(...)` |
143
+ | 7 | `transport/transport.rb` | `Settings[:data][:connected]` nil crash | Safe navigation `&.[]` |
144
+ | 8 | `runners/beat.rb:13` | `Legion::VERSION \|\| nil` doesn't guard | `defined?` check |
145
+ | 9 | `actors/beat.rb` | `settings['beat_interval']` string key | `settings[:beat_interval]` |
146
+ | 10 | 3 files | Missing `require 'base64'` | Add require |
147
+ | 11 | `runners/beat.rb` | "hearbeat" typo | Fix |
148
+
149
+ ### Consolidation
150
+
151
+ - Merge useful methods from `Runners::Crypt` into `Runners::Node`: `push_public_key`, `request_cluster_secret`, `push_cluster_secret`, `receive_cluster_secret`
152
+ - Delete `runners/crypt.rb` entirely
153
+ - Delete `data_test/` directory (4 broken migrations, zero consumers)
154
+ - Deduplicate divergent implementations
155
+
156
+ ### Multi-Cluster Vault Compatibility
157
+
158
+ Per the `2026-03-18-config-import-vault-multicluster` design:
159
+ - `Runners::Vault#receive_vault_token` — if `clusters.any?`, store token in cluster entry
160
+ - `Runners::Vault#push_vault_token` — iterate `connected_clusters` when multi-cluster active
161
+ - `Runners::Vault#request_token` — check `connected_clusters` in addition to legacy path
162
+ - Fix `actors/vault_token_request.rb` — set `use_runner? true`
163
+
164
+ ### Cleanup
165
+
166
+ - Delete unused `require 'socket'` in queues/node.rb
167
+ - Remove `|| nil` redundancies
168
+ - Remove duplicate node_spec.rb
169
+ - Fix exchange references to use extension's own exchange class
170
+ - Update README and gemspec
171
+
172
+ ### Spec Coverage Target
173
+
174
+ 61 existing -> ~120+ specs, target 90%+
175
+
176
+ New specs: all consolidated Node methods, vault runners (single + multi-cluster), all 5 actors, all 8 message classes, transport bindings, edge cases.
177
+
178
+ ---
179
+
180
+ ## Extension 4: lex-health (0.1.8 -> 0.2.0)
181
+
182
+ ### Bug Fixes (7 items)
183
+
184
+ | # | File | Bug | Fix |
185
+ |---|------|-----|-----|
186
+ | 1 | `runners/health.rb:27,39` | `active: 1` (integer) on TrueClass column | `active: true` |
187
+ | 2 | `messages/watchdog.rb` | Routing key `'health'` doesn't match queue `node.health` | `'node.health'` |
188
+ | 3 | `runners/health.rb` | Missing `require 'time'` | Add require |
189
+ | 4 | `runners/health.rb:19` | Nil `updated` before time comparison | Nil guard |
190
+ | 5 | `runners/health.rb:47` | TOCTOU race on concurrent insert | `insert_conflict` or rescue |
191
+ | 6 | `runners/health.rb` | `delete(node_id:)` no nil guard | `Node[node_id]&.delete` |
192
+ | 7 | `runners/watchdog.rb` | `mark_workers_offline` doesn't clear `health_node` | Add `health_node: nil` |
193
+
194
+ ### Cleanup
195
+
196
+ - Remove duplicate `data_required?` instance method
197
+ - Remove dead `runner_function` from Watchdog actor
198
+ - Fix spec ordering: `create_table` -> `create_table?`
199
+ - Normalize `respond_to?(:log)` -> `respond_to?(:log, true)`
200
+
201
+ ### Spec Coverage Target
202
+
203
+ 21 existing -> ~70+ specs, target 90%+
204
+
205
+ New specs: `update` (existing node path), `insert` (all kwargs), `delete` (found + not found), timestamp guard, watchdog `expire` variants, `mark_workers_offline` clears `health_node`, actors, message validation/routing, concurrent insert race, PostgreSQL boolean.
206
+
207
+ ---
208
+
209
+ ## Extension 5: lex-lex (0.2.1 -> 0.3.0)
210
+
211
+ ### Bug Fixes (4 items)
212
+
213
+ | # | File | Bug | Fix |
214
+ |---|------|-----|-----|
215
+ | 1 | `lex.rb` | `def data_required?` instance method (Core's `false` wins) | `def self.data_required?` |
216
+ | 2 | `runners/sync.rb` | `updated` counter incremented even when no write | Only increment on actual DB write |
217
+ | 3 | `runners/sync.rb` | `active: true` forced on every sync | Respect existing `active` value |
218
+ | 4 | `runners/register.rb` | No nil guard on `extension_id` after soft failure | Add guard |
219
+
220
+ ### Cleanup
221
+
222
+ - Fix `sync.rb` to reconcile runners and functions (not just extensions)
223
+ - Remove `update` variable shadowing in Extension, Runner modules
224
+ - No standalone Client (infrastructure sink)
225
+
226
+ ### Spec Coverage Target
227
+
228
+ 55 existing -> ~90+ specs, target 90%+
229
+
230
+ New specs: entry point `data_required?`, Sync actor, Extension.get(namespace:), Function.build_args nil-name edge case, Function.update drops name silently, Sync with matching namespace, Register.save mid-loop failure, runner/function reconciliation.
231
+
232
+ ---
233
+
234
+ ## Cross-Cutting Concerns
235
+
236
+ - All entry points: remove duplicate `data_required?` instance methods
237
+ - All raw SQL: convert to Sequel DSL or `Sequel.lit` with parameterized placeholders
238
+ - All migrations: rewrite MySQL-only DDL as Sequel `create_table` blocks
239
+ - All specs: fix load-order fragility with `create_table?` (idempotent)
240
+ - Version bumps: tasker 0.3.0, scheduler 0.3.0, node 0.3.0, health 0.2.0, lex 0.3.0
241
+
242
+ ## Execution Order
243
+
244
+ Recommended: **lex-lex first** (simplest, fewest dependencies), then **lex-health**, then **lex-node** (needs multi-cluster vault awareness), then **lex-scheduler**, then **lex-tasker** (most complex, most bugs).
245
+
246
+ ## Not Included
247
+
248
+ - New features beyond what exists (no new runners, no new actor types)
249
+ - lex-node HA mode scheduling (removed, YAGNI)
250
+ - lex-scheduler mode transitions (removed, YAGNI)
251
+ - Runtime dependency declarations in gemspecs (these extensions run inside the LegionIO bundle)
252
+ - Subscription actor for lex-lex Register.save (requires framework-level wiring discussion)