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 +4 -4
- data/CHANGELOG.md +6 -0
- data/docs/plans/2026-03-18-core-lex-uplift-design.md +252 -0
- data/docs/plans/2026-03-18-core-lex-uplift-implementation.md +1816 -0
- data/legionio.gemspec +1 -1
- data/lib/legion/version.rb +1 -1
- metadata +3 -3
- data/exe/legion-tty +0 -27
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3fe88835ee309cdcd0e6028c5347a7b5be018a8697f14e96bfba294d821ac9b0
|
|
4
|
+
data.tar.gz: 44e770db00a040967b0bc2a6000615cd081a9c42d8c3f68289e30e0baf1f46f0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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)
|