legionio 1.9.23 → 1.9.28

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: e6be612db3e6de32b7c4baf82b6a38aac19a0a0bb53d198f736ae4d2d638829d
4
- data.tar.gz: bf294cc20dbb5701b7bd4f5fd474dbc27d6eae37d2c084cf7b06ec2cbb99f2dc
3
+ metadata.gz: 166725a37cf7c1d479070df9c3a19a53ec6fa915e1fe29b9e881358136392dc5
4
+ data.tar.gz: '09fe43d7158d9fea1439f820c231efeb25a3a1766b3c988411504f28cdbabf1d'
5
5
  SHA512:
6
- metadata.gz: 3968a2056a7f9c5c2bdbaeff4934165c0a0abd47bde56202f91befeac65c3bd5e38e321de7df3056f2fb71ff66016a89fc2b1803288b598da8fbdaedcc628e77
7
- data.tar.gz: cc371efeb8f0ad2b32bccfe8140a3ebba0310bded8718c5736c9338fbf088f407d27f58a27d05ad2f6d371b5be45d3d5d50dfddbb4a5d4edb9888a44430a5d9d
6
+ metadata.gz: 424b1f6992f9bce5ae26fa0f09c986ee9bf961a9beccf64d3498f3db651747bbcab3ef8bb6ee25f45cdfc6c1df9e0ddd031132a253b334edeba1a63b2a1532cd
7
+ data.tar.gz: 2462fd9c2277a81b381f4b91c3674930606e6c80460d708c36d252a7a2a7e1475f9b397daa3d55c2c8b17b1c33dbb50eb2ceec7acd0dd3abfe59376d4913ef0a
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --exclude-pattern spec/live/**/*_spec.rb
data/CHANGELOG.md CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.9.28] - 2026-05-08
6
+
7
+ ### Fixed
8
+ - Task outcome observation now ignores internal runner completions without task ids, preventing periodic mesh gossip ticks from feeding meta-learning and Apollo ingestion.
9
+ - Identity resolver database persistence now targets the current identity provider, principal, identity, and audit log schema.
10
+
11
+ ## [1.9.27] - 2026-05-08
12
+
13
+ ### Fixed
14
+ - Preserve omitted `/api/llm/inference` client tool definitions as absent instead of `tools: []`, allowing legion-llm registry and trigger-based tool injection to run for normal API requests.
15
+ - Added an opt-in live daemon integration spec suite that uses explicit Faraday test dependencies and its own isolated RSpec helper.
16
+
17
+ ## [1.9.26] - 2026-05-07
18
+
19
+ ### Fixed
20
+ - Use the local `Legion::Identity::Process` identity for unauthenticated loopback API principals even when the process is only fallback-bound, avoiding generic `system:system` attribution in downstream LLM audit flows.
21
+
22
+ ## [1.9.25] - 2026-05-07
23
+
24
+ ### Fixed
25
+ - Updated identity model references in `identity_audit.rb` and `identity/broker.rb` to use the portable namespace (`Identity::AuditLog`, `Identity::Principal`, `Identity::GroupMembership`) after legacy top-level identity models were removed from legion-data.
26
+
27
+ ## [1.9.24] - 2026-05-07
28
+
29
+ ### Changed
30
+ - Removed deprecated direct AI provider extensions (`lex-azure-ai`, `lex-bedrock`, `lex-claude`, `lex-foundry`, `lex-gemini`, `lex-ollama`, `lex-openai`) from the extension catalog; use their `lex-llm-*` counterparts instead.
31
+
5
32
  ## [1.9.23] - 2026-05-07
6
33
 
7
34
  ### Fixed
data/CLAUDE.md CHANGED
@@ -1,805 +1,98 @@
1
- # LegionIO: Async Job Engine and Task Framework
1
+ # LegionIO
2
2
 
3
- **Repository Level 3 Documentation**
4
- - **Parent**: `../CLAUDE.md`
5
-
6
- ## Purpose
7
-
8
- The primary gem for the LegionIO framework. An extensible async job engine for scheduling tasks, creating relationships between services, and running them concurrently via RabbitMQ. Orchestrates all `legion-*` gems and loads Legion Extensions (LEXs).
3
+ Primary gem. Orchestrates all `legion-*` gems and loads LEX extensions.
9
4
 
10
5
  **GitHub**: https://github.com/LegionIO/LegionIO
11
- **Gem**: `legionio`
12
- **Version**: 1.8.12
13
- **License**: Apache-2.0
14
- **Docker**: `legionio/legion`
15
- **Ruby**: >= 3.4
6
+ **Gem**: `legionio` | **Ruby**: >= 3.4
16
7
 
17
8
  ## Binary Split
18
9
 
19
10
  | Binary | Purpose |
20
11
  |--------|---------|
21
- | `legion` | Interactive TTY shell + dev-workflow commands (chat, commit, review, plan, memory, init) |
22
- | `legionio` | Daemon lifecycle + all operational commands (start, stop, lex, task, config, mcp, etc.) |
23
-
24
- `legion` with no args launches the TTY interactive shell. With args, it routes to dev-workflow subcommands.
25
- `legionio` is the full operational CLI — all 40+ subcommands.
26
-
27
- ## Architecture
28
-
29
- ### Boot Sequence (exe/legion)
30
-
31
- Before any Legion code loads, `exe/legion` applies three performance optimizations:
32
-
33
- 1. **YJIT** — `RubyVM::YJIT.enable` for 15-30% runtime throughput (guarded with `if defined?`)
34
- 2. **GC tuning** — pre-allocates 600k heap slots, raises malloc limits (all `||=` so ENV overrides are respected)
35
- 3. **bootsnap** — caches YARV bytecodes and `$LOAD_PATH` resolution at `~/.legionio/cache/bootsnap/`
36
-
37
- ### Startup Sequence
38
-
39
- ```
40
- Legion.start
41
- └── Legion::Service.new
42
- ├── 1. setup_logging (legion-logging)
43
- ├── 2. setup_settings (legion-settings, loads /etc/legionio, ~/legionio, ./settings)
44
- ├── 3. Legion::Crypt.start (legion-crypt, Vault connection)
45
- ├── 4. setup_transport (legion-transport, RabbitMQ connection)
46
- ├── 5. require legion-cache
47
- ├── 6. setup_data (legion-data, MySQL/SQLite + migrations, optional)
48
- ├── 7. setup_rbac (legion-rbac, optional)
49
- ├── 8. setup_llm (legion-llm, AI provider setup + routing, optional)
50
- ├── 9. setup_apollo (legion-apollo, shared + local knowledge store, optional)
51
- ├── 10. setup_gaia (legion-gaia, cognitive coordination layer, optional)
52
- ├── 11. setup_telemetry (OpenTelemetry, optional)
53
- ├── 12. setup_supervision (process supervision)
54
- ├── 13. load_extensions (multi-phase: phase 0 (identity providers) loads and hooks actors first, then phase 1 (everything else))
55
- ├── 14. Legion::Crypt.cs (distribute cluster secret)
56
- └── 15. setup_api (start Sinatra/Puma on port 4567)
57
- ```
58
-
59
- Each phase calls `Legion::Readiness.mark_ready(:component)`. All phases are individually toggleable via `Service.new(transport: false, ...)`.
60
-
61
- Extension loading is multi-phase and parallel: `hook_extensions` calls `group_by_phase` to partition discovered extensions by phase number (from the category registry), then iterates phases sequentially. Phase 0 contains identity providers (`lex-identity-*` gems, category `:identity`, tier 0); phase 1 contains all other extensions. Within each phase, extensions are `require`d and `autobuild` runs concurrently on a `Concurrent::FixedThreadPool(min(count, extensions.parallel_pool_size))`, collecting actors into a thread-safe `Concurrent::Array` of `@pending_actors`. Pool size defaults to 24, configurable via `Legion::Settings[:extensions][:parallel_pool_size]`. After each phase's extensions are loaded, `hook_phase_actors` starts AMQP subscriptions, timers, and other actor types for that phase sequentially — ensuring identity providers are fully running before any other extension boots. Catalog transitions (`transition(:running)` and `flush_persisted_transitions`) happen after all phases complete. Thread safety relies on ThreadLocal AMQP channels, per-extension Settings keys, and sequential post-processing of Catalog transitions and Registry writes.
62
-
63
- ### Reload Sequence
64
-
65
- `Legion.reload` shuts down all subsystems in reverse order, waits for them to drain, then re-runs setup from settings onward. Extensions and API are re-loaded fresh.
66
-
67
- ### Module Structure
68
-
69
- ```
70
- Legion (lib/legion.rb)
71
- ├── Service # Orchestrator: initializes all modules, manages lifecycle
72
- │ # Entry points: Legion.start, .shutdown, .reload
73
- ├── Process # Daemonization: PID management, signal traps (SIGINT=quit), main loop
74
- ├── Readiness # Startup readiness tracking
75
- │ # COMPONENTS: settings, crypt, transport, cache, data, gaia, extensions, api
76
- │ # Readiness.ready? checks all; /api/ready returns JSON status
77
- ├── Events # In-process pub/sub event bus
78
- │ # Events.on(name) / .emit(name, **payload) / .once / .off
79
- │ # Wildcard '*' listener supported
80
- │ # Lifecycle: service.ready, service.shutting_down, service.shutdown
81
- │ # Extension: extension.loaded
82
- │ # Runner: ingress.received
83
- ├── Ingress # Universal entry point for runner invocation
84
- │ # Sources: amqp, http, cli, api — all normalize through here
85
- │ # Ingress.run(payload:, runner_class:, function:, source:)
86
- │ # Ingress.normalize returns message hash without executing
87
- ├── Extensions # LEX discovery, loading, and lifecycle management
88
- │ ├── Core # Mixin: data_required?, cache_required?, crypt_required?, mcp_tools?, mcp_tools_deferred?, etc.
89
- │ ├── Actors/ # Actor execution modes
90
- │ │ ├── Base # Base actor class
91
- │ │ ├── Every # Run at interval (timer)
92
- │ │ ├── Loop # Continuous loop
93
- │ │ ├── Once # Run once at startup
94
- │ │ ├── Poll # Polling actor
95
- │ │ ├── Subscription # AMQP subscription (FixedThreadPool per worker count)
96
- │ │ └── Nothing # No-op actor
97
- │ ├── Builders/ # Build actors and runners from LEX definitions
98
- │ │ ├── Actors # Build actors from extension definitions
99
- │ │ ├── Runners # Build runners from extension definitions; exposes `runner_modules` accessor for Discovery
100
- │ │ ├── Helpers # Builder utilities
101
- │ │ ├── Hooks # Webhook hook system builder
102
- │ │ └── Routes # Auto-route builder: introspects runners, registers POST /api/extensions/* routes
103
- │ ├── Helpers/ # Helper mixins for extensions
104
- │ │ ├── Base # Base helper mixin
105
- │ │ ├── Core # Core helper mixin
106
- │ │ ├── Cache # Cache access helper
107
- │ │ ├── Data # Database access helper
108
- │ │ ├── Logger # Logging helper
109
- │ │ ├── Transport # AMQP transport helper
110
- │ │ ├── Task # Task management helper (generate_task_id)
111
- │ │ └── Lex # LEX metadata helper
112
- │ ├── Data/ # Extension data layer
113
- │ │ ├── Migrator # Extension-specific migrations
114
- │ │ └── Model # Extension-specific models
115
- │ ├── Hooks/
116
- │ │ └── Base # Webhook hook system base class
117
- │ └── Transport # Extension transport setup
118
-
119
- ├── API (Sinatra) # Full REST API under /api/ prefix, served by Puma
120
- │ ├── Helpers # json_response, json_collection, json_error, pagination, redact_hash
121
- │ │ # parse_request_body, paginate dataset
122
- │ ├── Routes/
123
- │ │ ├── Tasks # CRUD + trigger via Ingress, task logs
124
- │ │ ├── Extensions # Nested: extensions/runners/functions + invoke
125
- │ │ ├── Nodes # List/show nodes (filterable by active/status)
126
- │ │ ├── Schedules # CRUD for lex-scheduler schedules + logs
127
- │ │ ├── Relationships # CRUD (backed by legion-data migration 013)
128
- │ │ ├── Chains # Stub (501) - no data model yet
129
- │ │ ├── Settings # Read/write settings with redaction + readonly guards
130
- │ │ ├── Events # SSE stream (sinatra stream) + ring buffer polling fallback
131
- │ │ ├── Transport # Connection status, exchanges, queues, publish
132
- │ │ ├── Hooks # List + trigger registered extension hooks
133
- │ │ ├── LexDispatch # Dispatch: `POST /api/extensions/:lex/:type/:component/:method` + discovery GET
134
- │ │ ├── Workers # Digital worker lifecycle (`/api/workers/*`) + team routes (`/api/teams/*`)
135
- │ │ ├── Coldstart # `POST /api/coldstart/ingest` — trigger lex-coldstart ingest from API
136
- │ │ ├── Capacity # Aggregate, forecast, per-worker capacity endpoints
137
- │ │ ├── Tenants # Tenant listing, provisioning, suspension, quota
138
- │ │ ├── Audit # Audit log query: list, show, count, export
139
- │ │ ├── Rbac # RBAC: role listing, permission grants, access checks
140
- │ │ ├── Webhooks # Webhook subscription CRUD + delivery status
141
- │ │ └── Validators # Request body schema validation helpers
142
- │ ├── Middleware/
143
- │ │ ├── Auth # JWT Bearer auth middleware (real validation, skip paths for health/ready)
144
- │ │ ├── Tenant # Tenant extraction from JWT/header, sets TenantContext
145
- │ │ ├── ApiVersion # `/api/v1/` rewrite, Deprecation/Sunset headers
146
- │ │ ├── BodyLimit # Request body size limit (1MB max, returns 413)
147
- │ │ └── RateLimit # Sliding-window rate limiting with per-IP/agent/tenant tiers
148
- │ └── router # Class-level Router: extension_names, find_extension_route, registered_routes
149
- │ # Populated by Builders::Routes during autobuild via LexDispatch
150
-
151
- ├── MCP (legion-mcp gem) # Extracted to standalone gem — see legion-mcp/CLAUDE.md
152
- │ └── (tools, 2 resources, TierRouter, PatternStore, ContextGuard, Observer, EmbeddingIndex)
153
-
154
- ├── Tools # Canonical tool layer — replaces Extensions::Capability and Catalog::Registry
155
- │ ├── Base # Base class for all framework tools (Do, Status, Config are built-in statics)
156
- │ ├── Registry # always/deferred classification for all tools; replaces Catalog::Registry
157
- │ │ # Extensions declare tools via `mcp_tools?` / `mcp_tools_deferred?` DSL on Core
158
- │ ├── Discovery # Auto-discovers tools from extension runner modules at boot
159
- │ │ # `runner_modules` accessor on Builders::Runners feeds Discovery
160
- │ │ # `loaded_extension_modules` on Extensions exposes the full set
161
- │ └── EmbeddingCache # 5-tier persistent embedding cache:
162
- │ # L0 in-memory hash → L1 Cache::Local → L2 Cache → L3 Data::Local → L4 Data
163
-
164
- ├── DigitalWorker # Digital worker platform (AI-as-labor governance)
165
- │ ├── Lifecycle # Worker state machine (active/paused/retired/terminated)
166
- │ ├── Registry # In-process worker registry
167
- │ ├── RiskTier # AIRB risk tier classification + governance constraints
168
- │ └── ValueMetrics # Token/cost/latency value tracking
169
-
170
- ├── Graph # Task relationship visualization
171
- │ ├── Builder # Builds adjacency graph from relationships table (chain/worker filtering)
172
- │ └── Exporter # Renders to Mermaid and DOT (Graphviz) formats
173
-
174
- ├── TraceSearch # Natural language trace search via LLM structured output
175
- │ # Translates NL queries to safe JSON filter DSL (column allowlist)
176
- │ # Uses Legion::LLM.structured for JSON extraction
177
-
178
- ├── Runner # Task execution engine
179
- │ ├── Log # Task logging
180
- │ └── Status # Task status tracking
181
-
182
- ├── Supervision # Process supervision
183
- ├── Lex # Legacy LEX gem discovery (see Extensions for current code)
184
-
185
- └── CLI (Thor) # Unified CLI: exe/legion -> Legion::CLI::Main
186
- ├── Output::Formatter # color tables, JSON mode, status indicators, ANSI stripping
187
- ├── Theme # Purple palette, orbital ASCII banner, branded CLI output
188
- ├── Connection # Lazy connection manager (ensure_settings, ensure_transport, etc.)
189
- ├── Error # CLI-specific error class
190
- ├── Start # `legion start` - daemon boot via Legion::Process
191
- ├── Status # `legion status` - probes API or shows static info
192
- ├── Check # `legion check` - smoke-test subsystems, 3 depth levels
193
- ├── Lex # `legion lex` - list, info, create, enable, disable, exec/invoke_ext + LexGenerator
194
- ├── Task # `legion task` - list, show, logs, trigger (mapped as run), purge
195
- ├── Chain # `legion chain` - list, create, delete
196
- ├── Config # `legion config` - show (redacted), path, validate, scaffold
197
- ├── ConfigScaffold # `legion config scaffold` - generates starter JSON config files
198
- ├── Generate # `legion generate` - runner, actor, exchange, queue, message
199
- ├── Mcp # `legion mcp` - stdio (default) or HTTP transport
200
- ├── Worker # `legion worker` - digital worker lifecycle management
201
- ├── Coldstart # `legion coldstart` - ingest CLAUDE.md/MEMORY.md into lex-memory
202
- ├── Chat # `legion chat` - interactive AI REPL + headless prompt mode
203
- │ ├── Session # Multi-turn chat session with streaming
204
- │ ├── SessionStore # Persistent session save/load/list/resume/fork
205
- │ ├── Permissions # Tool permission model (interactive/auto_approve/read_only)
206
- │ ├── ToolRegistry # Chat tool discovery and registration (40 built-in tools + extension tools)
207
- │ ├── ExtensionTool # permission_tier DSL module for LEX chat tools (:read/:write/:shell)
208
- │ ├── ExtensionToolLoader # Lazy discovery of tools/ directories from loaded extensions
209
- │ ├── Context # Project awareness (git, language, instructions, extra dirs)
210
- │ ├── MarkdownRenderer # Terminal markdown rendering with syntax highlighting
211
- │ ├── WebFetch # /fetch slash command for web page context injection
212
- │ ├── WebSearch # DuckDuckGo HTML scraping search engine
213
- │ ├── Checkpoint # File edit checkpointing with /rewind undo
214
- │ ├── MemoryStore # Persistent memory (project + global scopes, markdown files)
215
- │ ├── Subagent # Background subagent spawning via headless subprocess
216
- │ ├── AgentRegistry # Custom agent definitions from .legion/agents/ (JSON/YAML)
217
- │ ├── AgentDelegator # @name at-mention parsing and agent dispatch
218
- │ ├── ChatLogger # Chat-specific logging
219
- │ └── Tools/ # Built-in tools: read_file, write_file, edit_file,
220
- │ # search_files, search_content, run_command,
221
- │ # save_memory, search_memory, web_search, spawn_agent,
222
- │ # search_traces, query_knowledge, ingest_knowledge,
223
- │ # consolidate_memory, relate_knowledge, knowledge_maintenance,
224
- │ # knowledge_stats, summarize_traces, list_extensions,
225
- │ # manage_tasks, system_status, view_events
226
- ├── Memory # `legion memory` - persistent memory CLI (list/add/forget/search)
227
- ├── Plan # `legion plan` - read-only exploration mode
228
- ├── Swarm # `legion swarm` - multi-agent workflow orchestration
229
- ├── Commit # `legion commit` - AI-generated commit messages via LLM
230
- ├── Pr # `legion pr` - AI-generated PR title and description via LLM
231
- ├── Review # `legion review` - AI code review with severity levels
232
- ├── Gaia # `legion gaia` - Gaia status
233
- ├── Llm # `legion llm` - LLM subsystem status and provider health
234
- ├── Detect # `legion detect scan` - scan environment and recommend extensions
235
- ├── Observe # `legion observe stats` - MCP tool usage statistics from Observer
236
- ├── Tty # `legion tty interactive` - launch rich terminal UI (legion-tty)
237
- ├── Graph # `legion graph show` - task relationship graph (mermaid/dot)
238
- ├── Trace # `legion trace search` - NL trace search via LLM
239
- ├── Dashboard # `legion dashboard` - TUI operational dashboard with auto-refresh
240
- │ ├── DataFetcher # Polls REST API for workers, health, events
241
- │ └── Renderer # Terminal-based dashboard rendering
242
- ├── Cost # `legion cost` - cost summary, worker, team, top, budget, export
243
- │ └── DataClient # API client for cost data aggregation
244
- ├── Skill # `legion skill` - list, show, create, run skill files
245
- ├── Audit # `legion audit` - query audit log (list, show, count, export)
246
- ├── Rbac # `legion rbac` - role management, permission grants, access check
247
- ├── Init # `legion init` - interactive project setup wizard
248
- │ ├── ConfigGenerator # Generates starter config files from templates
249
- │ └── EnvironmentDetector # Detects runtime environment (Docker, CI, services)
250
- ├── Marketplace # `legion marketplace` - extension marketplace (search, install, publish)
251
- ├── Notebook # `legion notebook` - interactive task notebook REPL
252
- ├── Update # `legion update` - self-update via Homebrew or gem
253
- ├── Schedule # `legion schedule` - schedule list/show/add/remove/logs
254
- └── Completion # `legion completion` - bash/zsh tab completion scripts
255
- ```
256
-
257
- ### Extension Discovery
12
+ | `legion` | Interactive TTY shell + dev-workflow (chat, commit, review, plan, memory) |
13
+ | `legionio` | Daemon lifecycle + operational commands (start, stop, lex, task, config, mcp) |
258
14
 
259
- `Legion::Extensions.find_extensions` discovers lex-* gems via `Bundler.load.specs` (when running under Bundler) or falls back to `Gem::Specification.all_names`. It also processes `Legion::Settings[:extensions]` for explicitly configured extensions, attempting `Gem.install` for missing ones if `auto_install` is enabled.
15
+ ## Boot Sequence
260
16
 
261
- **Category registry**: Extensions are classified by `categorize_and_order` using `default_category_registry`. Each category has a `type` (`:list` or `:prefix`), `tier` (load order within a phase), and `phase`:
262
-
263
- | Category | Type | Tier | Phase | Matches |
264
- |----------|------|------|-------|---------|
265
- | `identity` | prefix | 0 | 0 | `lex-identity-*` gems |
266
- | `core` | list | 1 | 1 | explicitly listed core extensions |
267
- | `ai` | list | 2 | 1 | explicitly listed AI provider extensions |
268
- | `gaia` | list | 3 | 1 | explicitly listed GAIA extensions |
269
- | `agentic` | prefix | 4 | 1 | `lex-agentic-*` gems |
270
-
271
- **Role-based filtering**: After discovery, `apply_role_filter` prunes extensions based on `Legion::Settings[:role][:profile]`:
272
-
273
- | Profile | What loads |
274
- |---------|-----------|
275
- | `nil` (default) | Everything — no filtering |
276
- | `:core` | 14 core operational extensions only |
277
- | `:cognitive` | core + all agentic extensions |
278
- | `:service` | core + service + other integrations |
279
- | `:dev` | core + AI + essential agentic (~20 extensions) |
280
- | `:custom` | only what's listed in `role[:extensions]` |
281
-
282
- Configure via settings JSON: `{"role": {"profile": "dev"}}`
283
-
284
- Loader checks per extension:
285
- - `data_required?` — skipped if legion-data not connected
286
- - `cache_required?` — skipped if legion-cache not connected
287
- - `crypt_required?` — skipped if cluster secret not available
288
- - `vault_required?` — skipped if Vault not connected
289
- - `llm_required?` — skipped if legion-llm not connected
290
-
291
- After loading, each extension calls `autobuild` then publishes a `LexRegister` message to RabbitMQ to persist runners in the database.
292
-
293
- ### CLI Details
17
+ `exe/legion` applies: YJIT, GC tuning (600k heap slots), bootsnap cache.
294
18
 
295
19
  ```
296
- legion
297
- version # Component versions + installed extension count
298
- start [-d] [-p PID] [-l LOG] [-t SECS] [--log-level info] [--http-port PORT]
299
- stop [-p PID] [--signal INT]
300
- status
301
- check [--extensions] [--full] # exit code 0/1
302
-
303
- lex
304
- list [-a]
305
- info <name>
306
- create <name>
307
- enable <name>
308
- disable <name>
309
-
310
- task
311
- list [-n 20] [-s status] [-e extension]
312
- show <id>
313
- logs <id> [-n 50]
314
- run <ext.runner.func> [key:val ...] # 'run' is mapped to trigger method
315
- purge [--days 7] [-y]
316
-
317
- chain
318
- list [-n 20]
319
- create <name>
320
- delete <id> [-y]
321
-
322
- config
323
- show [-s section]
324
- path
325
- validate
326
- scaffold [--dir ./settings] [--only transport,data,...] [--full] [--force]
327
-
328
- generate (alias: g)
329
- runner <name> [--functions x]
330
- actor <name> [--type sub]
331
- exchange <name>
332
- queue <name>
333
- message <name>
334
- tool <name>
335
-
336
- mcp
337
- stdio # default
338
- http [--port 9393] [--host localhost]
339
-
340
- worker
341
- list [-s status] [-t risk_tier]
342
- show <id>
343
- create <name> --entra_app_id ID --owner_msid EMAIL --extension NAME [--team T] [--client_secret S]
344
- pause <id>
345
- activate <id>
346
- retire <id>
347
- terminate <id>
348
- costs [--days 30]
349
-
350
- coldstart
351
- ingest <path> # file or directory, parses CLAUDE.md / MEMORY.md
352
- preview <path> # dry-run, shows traces without storing
353
- status
354
-
355
- chat # interactive AI REPL (requires legion-llm)
356
- prompt <text> # headless single-prompt mode (also accepts stdin pipe)
357
- [--model MODEL] [--provider PROVIDER]
358
- [--no_markdown] [--incognito]
359
- [--max_budget_usd N] [--auto_approve / -y]
360
- [--add_dir DIR ...] [--personality STYLE]
361
- [--continue / -c] [--resume NAME] [--fork NAME]
362
- # Slash commands:
363
- # /help, /quit, /cost, /status, /clear, /new
364
- # /save NAME, /load NAME, /sessions, /compact
365
- # /fetch URL, /search QUERY, /diff, /copy
366
- # /rewind [N|FILE], /memory [add TEXT]
367
- # /agent TASK, /agents, /plan, /swarm NAME
368
- # /review [SCOPE], /permissions [MODE], /personality STYLE
369
- # /model X, /edit (open $EDITOR)
370
- # /commit, /workers, /dream
371
- # Bang commands: !<shell command> (quick shell exec with context injection)
372
- # At-mentions: @agent_name <task> (delegate to custom agent)
373
-
374
- memory # persistent memory management
375
- list [--global]
376
- add TEXT [--global]
377
- forget INDEX [--global]
378
- search QUERY
379
- clear [--global] [-y]
380
-
381
- plan # read-only exploration mode (no writes/edits/shell)
382
- [--model MODEL] [--provider PROVIDER]
383
- # Slash commands: /save (writes plan to docs/work/planning/), /help, /quit
384
-
385
- swarm # multi-agent workflow orchestration
386
- start NAME # run a workflow from .legion/swarms/NAME.json
387
- list # list available workflows
388
- show NAME # show workflow details
389
- [--model MODEL]
390
-
391
- commit # AI-generated commit message via LLM
392
- [--model MODEL] [--provider PROVIDER]
393
-
394
- pr # AI-generated PR title + description via LLM
395
- [--model MODEL] [--provider PROVIDER]
396
- [--base BRANCH] [--draft]
397
-
398
- review [FILES...] # AI code review with severity levels
399
- [--model MODEL] [--provider PROVIDER]
400
- [--diff] # review staged/unstaged diff instead of files
401
-
402
- gaia
403
- status # show Gaia system status
404
-
405
- schedule
406
- list
407
- show <id>
408
- add <name> <cron> <runner>
409
- remove <id>
410
- logs <id>
411
-
412
- completion
413
- bash # output bash completion script
414
- zsh # output zsh completion script
415
- install # print installation instructions
416
-
417
- openapi
418
- generate [-o FILE] # output OpenAPI 3.1.0 spec JSON
419
- routes # list all API routes with HTTP method + summary
420
-
421
- doctor [--fix] [--json] # diagnose environment, suggest/apply fixes
422
- # checks: Ruby, bundle, config, RabbitMQ, DB, cache, Vault,
423
- # extensions, PID files, permissions
424
- # exit 0=all pass, 1=any fail
425
-
426
- telemetry
427
- stats [SESSION_ID] # aggregate or per-session telemetry stats
428
- ingest PATH # manually ingest a session log file
429
-
430
- graph
431
- show [--chain ID] [--worker ID] # display task relationship graph
432
- [--format mermaid|dot] [--output FILE] [--limit N]
433
-
434
- trace
435
- search QUERY [--limit N] # natural language trace search via LLM
436
-
437
- dashboard
438
- start [--url URL] [--refresh N] # TUI operational dashboard with auto-refresh
439
-
440
- cost
441
- summary # overall cost summary (today/week/month)
442
- worker <id> # per-worker cost breakdown
443
- team <name> # per-team cost attribution
444
- top [--limit 10] # top cost consumers
445
- budget # budget status
446
- export [--format csv|json] # export cost data
447
-
448
- skill
449
- list # list discovered skills
450
- show <name> # display skill definition
451
- create <name> # scaffold new skill file
452
- run <name> [args] # run skill outside of chat
453
-
454
- audit
455
- list [--entity TYPE] [--action ACT] [--limit N]
456
- show <id>
457
- count [--entity TYPE] [--since TIME]
458
- export [--format json|csv]
459
-
460
- rbac
461
- roles # list roles
462
- grants <identity> # list grants for identity
463
- check <identity> <resource> <action> # check access
464
-
465
- init # interactive project setup wizard
466
- [--dir PATH] [--template NAME]
467
-
468
- marketplace
469
- search QUERY # search extension marketplace
470
- install NAME # install extension
471
- publish # publish current extension
472
-
473
- notebook # interactive task notebook REPL
474
-
475
- update # self-update via Homebrew or gem
476
-
477
- auth
478
- teams [--tenant-id ID] [--client-id ID] # browser OAuth flow for Microsoft Teams
20
+ Legion.start → Legion::Service.new
21
+ 1. setup_logging
22
+ 2. setup_settings
23
+ 3. Legion::Crypt.start
24
+ 4. setup_transport (RabbitMQ)
25
+ 5. require legion-cache
26
+ 6. setup_data (optional)
27
+ 7. setup_rbac (optional)
28
+ 8. setup_llm (optional)
29
+ 9. setup_apollo (optional)
30
+ 10. setup_gaia (optional)
31
+ 11. setup_telemetry (optional)
32
+ 12. setup_supervision
33
+ 13. load_extensions (multi-phase: phase 0 identity, phase 1 everything else, parallel)
34
+ 14. Legion::Crypt.cs (distribute cluster secret)
35
+ 15. setup_api (Sinatra/Puma on port 4567)
479
36
  ```
480
37
 
481
- **CLI design rules:**
482
- - Thor 1.5+ reserves `run` as a method name - use `map 'run' => :trigger` in Task subcommand
483
- - `::Process` must be explicit inside `Legion::` namespace (resolves to `Legion::Process` otherwise)
484
- - `Connection` is a module with class-level `ensure_*` methods, not instance-based
485
- - All commands support `--json` and `--no-color` at the class_option level
486
- - `::JSON` must be explicit inside `Legion::` namespace (resolves to `Legion::JSON` otherwise) — affects `pretty_generate` in config scaffold
38
+ Extension loading: phase 0 = `lex-identity-*` (sequential), phase 1 = everything else on `Concurrent::FixedThreadPool(24)`. After all phases: catalog transitions + registry writes.
487
39
 
488
- ### API Design
40
+ ## Extension Discovery
489
41
 
490
- - Base class: `Legion::API < Sinatra::Base`
491
- - All routes registered via `register Routes::ModuleName`
492
- - Requires `set :host_authorization, permitted: :any` (Sinatra 4.0+, else all requests get 403)
493
- - Response format: `{ data: ..., meta: { timestamp:, node: } }`
494
- - Error format: `{ error: { code:, message: }, meta: { timestamp:, node: } }`
495
- - `Legion::JSON.dump` takes exactly 1 positional arg — wrap kwargs in explicit `{}`
496
- - `Legion::JSON.load` returns symbol keys
497
- - Settings write: `Legion::Settings.loader.settings[:key] = value`
498
- - `Legion::Settings.loader.to_hash` for full settings hash
42
+ `find_extensions` discovers `lex-*` gems via Bundler or `Gem::Specification`. Category registry determines load phase and tier. Extensions declare requirements via `data_required?`, `cache_required?`, `crypt_required?`, `vault_required?`, `llm_required?` — skipped if dependency unavailable.
499
43
 
500
- ### MCP Design
44
+ Role profiles filter extensions: `nil` (all), `:core` (14), `:cognitive` (core + agentic), `:service` (core + integrations), `:dev` (core + AI + essential agentic), `:custom` (explicit list).
501
45
 
502
- Extracted to the `legion-mcp` gem (v0.7.3). See `legion-mcp/CLAUDE.md` for full architecture.
46
+ ## CLI Design Rules
503
47
 
504
- - `Legion::MCP.server` is memoized singleton call `Legion::MCP.reset!` in tests
505
- - Tool naming: `legion.snake_case_name` (dot namespace, not slash)
506
- - Tier 0 routing: PatternStore + TierRouter + ContextGuard for LLM-free cached responses
48
+ - Thor 1.5+ reserves `run` use `map 'run' => :trigger` in Task subcommand
49
+ - `::Process` must be explicit (resolves to `Legion::Process` otherwise)
50
+ - `::JSON` must be explicit (resolves to `Legion::JSON` otherwise)
51
+ - All commands support `--json` and `--no-color` at class_option level
52
+ - `Connection` module has class-level `ensure_*` methods, not instance-based
507
53
 
508
- ### Lite Mode
54
+ ## API Design
509
55
 
510
- `LEGION_MODE=lite` (or `--lite` CLI flag, or `:lite` ProcessRole) launches LegionIO without RabbitMQ, Redis, or Memcached:
56
+ - `Legion::API < Sinatra::Base` with `set :host_authorization, permitted: :any`
57
+ - Response: `{ data: ..., meta: { timestamp:, node: } }`
58
+ - Error: `{ error: { code:, message: }, meta: ... }`
59
+ - `Legion::JSON.dump` — 1 positional arg, wrap kwargs in `{}`
60
+ - `Legion::JSON.load` — returns symbol keys
511
61
 
512
- - `legion-transport` activates the `InProcess` adapter (stub Session/Channel/Exchange/Queue/Consumer that delegate to `Transport::Local` in-memory pub/sub)
513
- - `legion-cache` activates the `Memory` adapter (pure in-memory cache with TTL expiry and Mutex synchronization)
514
- - Useful for single-machine development, CI, and testing without infrastructure dependencies
515
- - Detection: `Connection.lite_mode?` checks `TYPE == 'local'`; cache checks `LEGION_MODE=lite` env var
62
+ ## Module Structure (Key Parts)
516
63
 
517
- ### `legion do`
518
-
519
- Natural-language intent router at the CLI level:
520
-
521
- ```bash
522
- legion do "list all running tasks"
523
- legion do "start the email extension"
524
- ```
525
-
526
- Resolves free-text intent to Capability Registry entries. If the daemon is running, delegates to the MCP `legion.do` tool (Tier 0 fast path). If no daemon, runs in-process. Returns the runner's response.
527
-
528
- ### `legion mind-growth`
529
-
530
- CLI for the autonomous cognitive architecture expansion system (`lex-mind-growth`). 10 subcommands:
531
-
532
- ```bash
533
- legion mind-growth status # current growth cycle state
534
- legion mind-growth analyze # gap analysis against 5 reference models
535
- legion mind-growth propose # propose a new concept
536
- legion mind-growth evaluate <id> # evaluate a proposal
537
- legion mind-growth build <id> # run staged build pipeline
538
- legion mind-growth list # list proposals
539
- legion mind-growth approve <id> # manually approve
540
- legion mind-growth reject <id> # manually reject
541
- legion mind-growth profile # cognitive profile across all models
542
- legion mind-growth health # extension fitness validation
543
- ```
544
-
545
- Requires `lex-mind-growth` to be loaded. Also exposes 6 MCP tools in the `legion.mind_growth_*` namespace via `legion-mcp`.
546
-
547
- ## Dependencies
548
-
549
- ### Runtime Gems
550
- | Gem | Purpose |
551
- |-----|---------|
552
- | `legion-cache` (>= 0.3) | Caching (Redis/Memcached) |
553
- | `legion-crypt` (>= 0.3) | Encryption, Vault, JWT |
554
- | `legion-json` (>= 1.2) | JSON serialization (multi_json wrapper) |
555
- | `legion-logging` (>= 0.3) | Logging |
556
- | `legion-settings` (>= 0.3) | Configuration + schema validation |
557
- | `legion-transport` (>= 1.2) | RabbitMQ AMQP messaging |
558
- | `lex-node` | Node identity extension |
559
- | `concurrent-ruby` + `ext` (>= 1.2) | Thread pool, concurrency primitives |
560
- | `daemons` (>= 1.4) | Process daemonization |
561
- | `bootsnap` (>= 1.18) | YARV bytecode + load-path caching |
562
- | `oj` (>= 3.16) | Fast JSON (C extension) |
563
- | `puma` (>= 6.0) | HTTP server for API |
564
- | `rackup` (>= 2.0) | Rack server launcher for MCP HTTP transport |
565
- | `legion-mcp` (>= 0.5) | MCP server + Tier 0 routing (extracted gem) |
566
- | `reline` (>= 0.5) | Interactive line editing for chat REPL |
567
- | `rouge` (>= 4.0) | Syntax highlighting for chat markdown rendering |
568
- | `tty-spinner` (~> 0.9) | Spinner animation for CLI loading states |
569
- | `sinatra` (>= 4.0) | HTTP API framework |
570
- | `thor` (>= 1.3) | CLI framework |
571
-
572
- ### Optional at Runtime (loaded dynamically)
573
- | Gem | Purpose |
574
- |-----|---------|
575
- | `legion-data` | MySQL/SQLite persistence (tasks, extensions, scheduling) |
576
- | `legion-llm` | LLM integration (Bedrock, Anthropic, OpenAI, Gemini, Ollama) |
577
-
578
- ### Dev Dependencies
579
64
  ```
580
- rack-test, rake, rspec, rubocop, rubocop-rspec, simplecov
65
+ Legion
66
+ ├── Service # Lifecycle orchestrator
67
+ ├── Process # PID, signals, daemonization
68
+ ├── Readiness # Component readiness tracking
69
+ ├── Events # In-process pub/sub (on/emit/once/off, wildcard *)
70
+ ├── Ingress # Universal runner entry point (normalize + run)
71
+ ├── Extensions # Discovery, loading, actors, builders, helpers
72
+ │ ├── Core # Mixin: requirement flags, autobuild
73
+ │ ├── Actors/ # Every, Loop, Once, Poll, Subscription, Nothing
74
+ │ └── Builders/ # Actors, Runners, Helpers, Hooks, Routes
75
+ ├── Tools # Registry (always/deferred), Discovery, EmbeddingCache
76
+ ├── API # Sinatra routes, middleware (Auth, Tenant, RateLimit, BodyLimit)
77
+ ├── DigitalWorker # AI-as-labor: Lifecycle, Registry, RiskTier, ValueMetrics
78
+ ├── CLI # Thor commands (40+ subcommands)
79
+ │ └── Chat # Interactive AI REPL (sessions, tools, memory, agents, skills)
80
+ └── Graph # Task relationship visualization (Mermaid/DOT)
581
81
  ```
582
82
 
583
- ## File Map
83
+ ## Lite Mode
584
84
 
585
- | Path | Purpose |
586
- |------|---------|
587
- | `lib/legion.rb` | Entry point: `Legion.start`, `.shutdown`, `.reload` |
588
- | `lib/legion/version.rb` | `Legion::VERSION` constant |
589
- | `lib/legion/service.rb` | Module orchestrator, startup + shutdown + reload sequences |
590
- | `lib/legion/process.rb` | Daemon lifecycle: PID management, daemonize, signal traps, main loop |
591
- | `lib/legion/readiness.rb` | Component readiness tracking (COMPONENTS constant, `ready?`, `to_h`) |
592
- | `lib/legion/events.rb` | In-process pub/sub: `on`, `emit`, `once`, `off`, wildcard `*` |
593
- | `lib/legion/ingress.rb` | Universal runner invocation: `normalize`, `run` |
594
- | `lib/legion/extensions.rb` | LEX discovery, loading, actor hooking, shutdown; exposes `loaded_extension_modules` for Tools::Discovery |
595
- | `lib/legion/extensions/core.rb` | Extension mixin (requirement flags, autobuild) |
596
- | `lib/legion/extensions/actors/` | Actor types: base, every, loop, once, poll, subscription, nothing, defaults |
597
- | `lib/legion/extensions/builders/` | Build actors, runners, helpers, hooks, routes from definitions |
598
- | `lib/legion/extensions/helpers/` | Mixins: base, core, cache, data, logger, transport, task, lex |
599
- | `lib/legion/extensions/data/` | Extension-level migrator and model |
600
- | `lib/legion/extensions/hooks/base.rb` | Webhook hook base class |
601
- | `lib/legion/extensions/transport.rb` | Extension transport setup |
602
- | `lib/legion/graph/builder.rb` | Graph builder: adjacency list from relationships table with chain/worker filtering |
603
- | `lib/legion/graph/exporter.rb` | Graph exporter: renders to Mermaid (`graph TD`) and DOT (Graphviz `digraph`) formats |
604
- | `lib/legion/trace_search.rb` | NL trace search: LLM structured output to JSON filter DSL with column allowlist |
605
- | `lib/legion/guardrails.rb` | Input validation guardrails for runner payloads |
606
- | `lib/legion/isolation.rb` | Process isolation for untrusted extension execution |
607
- | `lib/legion/sandbox.rb` | Sandboxed execution environment for extensions |
608
- | `lib/legion/context.rb` | Thread-local execution context (request tracing, tenant) |
609
- | `lib/legion/catalog.rb` | Extension catalog: registry of available extensions with metadata (Catalog::Registry removed — replaced by Tools::Registry) |
610
- | `lib/legion/tools.rb` | Tools module entry point |
611
- | `lib/legion/tools/base.rb` | Tools::Base — canonical base class for all tools |
612
- | `lib/legion/tools/registry.rb` | Tools::Registry — always/deferred classification, replaces Catalog::Registry |
613
- | `lib/legion/tools/discovery.rb` | Tools::Discovery — auto-discovers tools from extension runner_modules at boot |
614
- | `lib/legion/tools/embedding_cache.rb` | Tools::EmbeddingCache — 5-tier persistent embedding cache (L0–L4) |
615
- | `lib/legion/registry.rb` | Extension registry with security scanning |
616
- | `lib/legion/registry/security_scanner.rb` | Gem security scanner (CVE checks, signature verification) |
617
- | `lib/legion/webhooks.rb` | Webhook delivery system: HTTP POST with retry, HMAC signing |
618
- | `lib/legion/runner.rb` | Task execution engine |
619
- | `lib/legion/runner/log.rb` | Task logging |
620
- | `lib/legion/runner/status.rb` | Task status tracking |
621
- | `lib/legion/supervision.rb` | Process supervision |
622
- | `lib/legion/lex.rb` | Legacy `Legion::Cli::LexBuilder` (preserved, not used by new CLI) |
623
- | **API** | |
624
- | `lib/legion/api.rb` | Sinatra base app, health/ready routes, error handlers, hook registry |
625
- | `lib/legion/api/helpers.rb` | json_response, json_collection, json_error, pagination, redact_hash |
626
- | `lib/legion/api/tasks.rb` | Tasks: list, create (via Ingress), show, delete, logs |
627
- | `lib/legion/api/extensions.rb` | Extensions: nested REST (extensions/runners/functions + invoke) |
628
- | `lib/legion/api/nodes.rb` | Nodes: list (filterable), show |
629
- | `lib/legion/api/schedules.rb` | Schedules: CRUD + logs (requires lex-scheduler) |
630
- | `lib/legion/api/relationships.rb` | Relationships: CRUD (backed by legion-data migration 013) |
631
- | `lib/legion/api/chains.rb` | Chains: stub (501, no data model yet) |
632
- | `lib/legion/api/settings.rb` | Settings: read/write with redaction + readonly guards |
633
- | `lib/legion/api/events.rb` | Events: SSE stream + polling fallback (ring buffer) |
634
- | `lib/legion/api/transport.rb` | Transport: status, exchanges, queues, publish |
635
- | `lib/legion/api/lex_dispatch.rb` | LexDispatch: `POST /api/extensions/:lex/:type/:component/:method` dispatch + `GET` discovery; remote AMQP forwarding, hook-aware routing via `Routes::LexDispatch` |
636
- | `lib/legion/api/workers.rb` | Workers + Teams: digital worker lifecycle REST endpoints (`/api/workers/*`) and team cost endpoints (`/api/teams/*`) |
637
- | `lib/legion/api/coldstart.rb` | Coldstart: `POST /api/coldstart/ingest` — triggers lex-coldstart ingest runner (requires lex-coldstart + lex-memory) |
638
- | `lib/legion/api/gaia.rb` | Gaia: system status endpoints |
639
- | `lib/legion/api/token.rb` | Token: JWT token issuance endpoint |
640
- | `lib/legion/api/openapi.rb` | OpenAPI: `Legion::API::OpenAPI.spec` / `.to_json`; also served at `GET /api/openapi.json` |
641
- | `lib/legion/api/capacity.rb` | Capacity: aggregate, forecast, and per-worker capacity endpoints |
642
- | `lib/legion/api/tenants.rb` | Tenants: listing, provisioning, suspension, quota check |
643
- | `lib/legion/api/catalog.rb` | Catalog: extension catalog with metadata endpoints |
644
- | `lib/legion/api/llm.rb` | LLM: provider status and routing configuration endpoints |
645
- | `lib/legion/api/audit.rb` | Audit: list, show, count, export audit log entries |
646
- | `lib/legion/api/auth.rb` | Auth: combined token exchange endpoint (`POST /api/auth/token` — JWKS verify + RBAC claims mapper) |
647
- | `lib/legion/api/auth_human.rb` | Auth: human user authentication endpoints |
648
- | `lib/legion/api/auth_worker.rb` | Auth: digital worker authentication endpoints |
649
- | `lib/legion/api/rbac.rb` | RBAC: role listing, permission grants, access checks |
650
- | `lib/legion/api/validators.rb` | Request validators: schema validation helpers for API inputs |
651
- | `lib/legion/api/webhooks.rb` | Webhooks: CRUD for webhook subscriptions + delivery status |
652
- | `lib/legion/audit.rb` | Audit logging: AMQP publish + query layer (recent_for, count_for, resources_for, recent) backed by AuditLog model |
653
- | `lib/legion/audit/hash_chain.rb` | Tamper-evident hash chain for audit entries |
654
- | `lib/legion/audit/siem_export.rb` | SIEM export: format audit entries for Splunk/ELK ingestion |
655
- | `lib/legion/alerts.rb` | Configurable alerting rules engine: pattern matching, count conditions, cooldown dedup |
656
- | `lib/legion/telemetry.rb` | Opt-in OpenTelemetry tracing: `with_span` wrapper, `sanitize_attributes`, `record_exception` |
657
- | `lib/legion/metrics.rb` | Opt-in Prometheus metrics: event-driven counters, pull-based gauges, `prometheus-client` guarded |
658
- | `lib/legion/api/metrics.rb` | `GET /metrics` Prometheus text-format endpoint with gauge refresh |
659
- | `lib/legion/api/stats.rb` | `GET /api/stats` comprehensive daemon runtime stats (extensions, gaia, transport, cache, llm, data, api) |
660
- | `lib/legion/chat/notification_queue.rb` | Thread-safe priority queue for background notifications (critical/info/debug) |
661
- | `lib/legion/chat/notification_bridge.rb` | Event-driven bridge: matches Legion events to chat notifications via fnmatch patterns |
662
- | `lib/legion/api/middleware/auth.rb` | Auth: JWT Bearer auth middleware (real token validation, skip paths for health/ready) |
663
- | `lib/legion/api/middleware/api_version.rb` | ApiVersion: rewrites `/api/v1/` to `/api/`, adds Deprecation/Sunset headers on unversioned paths |
664
- | `lib/legion/api/middleware/body_limit.rb` | BodyLimit: request body size limit (1MB max, returns 413) |
665
- | `lib/legion/api/middleware/rate_limit.rb` | RateLimit: sliding-window rate limiting with per-IP/agent/tenant tiers |
666
- | `lib/legion/api/middleware/tenant.rb` | Tenant: extracts tenant_id from JWT/header, sets TenantContext per request |
667
- | `lib/legion/tenant_context.rb` | Thread-local tenant context propagation (set, clear, with block) |
668
- | `lib/legion/tenants.rb` | Tenant CRUD, suspension, quota enforcement |
669
- | `lib/legion/capacity/model.rb` | Workforce capacity calculation (throughput, utilization, forecast, per-worker) |
670
- | **MCP** (extracted to `legion-mcp` gem) | |
671
- | `lib/legion/digital_worker.rb` | DigitalWorker module entry point |
672
- | `lib/legion/digital_worker/lifecycle.rb` | Worker state machine |
673
- | `lib/legion/digital_worker/registry.rb` | In-process worker registry |
674
- | `lib/legion/digital_worker/risk_tier.rb` | AIRB risk tier + governance constraints |
675
- | `lib/legion/digital_worker/value_metrics.rb` | Token/cost/latency tracking |
676
- | **CLI v2** | |
677
- | `lib/legion/cli.rb` | `Legion::CLI::Main` Thor app, global flags, version, start/stop/status/check |
678
- | `lib/legion/cli/output.rb` | `Output::Formatter`: color, tables, JSON mode, ANSI stripping |
679
- | `lib/legion/cli/connection.rb` | Lazy connection manager (`ensure_settings`, `ensure_transport`, etc.) |
680
- | `lib/legion/cli/error.rb` | `CLI::Error` exception class |
681
- | `lib/legion/cli/start.rb` | `legion start` — boots Legion::Process |
682
- | `lib/legion/cli/status.rb` | `legion status` — probes API or returns static info |
683
- | `lib/legion/cli/check_command.rb` | `legion check` — 3-level smoke test, exit code 0/1 |
684
- | `lib/legion/cli/lex_command.rb` | `legion lex` subcommands + LexGenerator scaffolding + `invoke_ext`/`exec` dispatch via LexCliManifest |
685
- | `lib/legion/cli/lex_cli_manifest.rb` | JSON manifest cache for LEX CLI commands (alias resolution, staleness check) |
686
- | `lib/legion/cli/task_command.rb` | `legion task` subcommands (list, show, logs, trigger/run, purge) |
687
- | `lib/legion/cli/chain_command.rb` | `legion chain` subcommands (list, create, delete) |
688
- | `lib/legion/cli/config_command.rb` | `legion config` subcommands (show, path, validate, scaffold) |
689
- | `lib/legion/cli/config_scaffold.rb` | `legion config scaffold` — generates starter JSON config files per subsystem |
690
- | `lib/legion/cli/generate_command.rb` | `legion generate` subcommands (runner, actor, exchange, queue, message) |
691
- | `lib/legion/cli/mcp_command.rb` | `legion mcp` subcommand (stdio + HTTP transports) |
692
- | `lib/legion/cli/worker_command.rb` | `legion worker` subcommands (list, show, create, pause, retire, terminate, activate, costs) |
693
- | `lib/legion/cli/coldstart_command.rb` | `legion coldstart` subcommands (ingest, preview, status) |
694
- | `lib/legion/cli/chat_command.rb` | `legion chat` — interactive AI REPL + headless prompt mode |
695
- | `lib/legion/cli/chat/session.rb` | Chat session: multi-turn conversation, streaming, tool use |
696
- | `lib/legion/cli/chat/session_store.rb` | Session persistence: save, load, list, resume, fork |
697
- | `lib/legion/cli/chat/permissions.rb` | Tool permission model (interactive/auto_approve/read_only) |
698
- | `lib/legion/cli/chat/tool_registry.rb` | Chat tool discovery and registration (40 tools) |
699
- | `lib/legion/cli/chat/extension_tool.rb` | permission_tier DSL module for extension chat tools |
700
- | `lib/legion/cli/chat/extension_tool_loader.rb` | Lazy discovery engine: scans loaded extensions for tools/ directories |
701
- | `lib/legion/cli/chat/context.rb` | Project awareness: git info, language detection, instructions, extra dirs |
702
- | `lib/legion/cli/chat/markdown_renderer.rb` | Terminal markdown rendering with Rouge syntax highlighting |
703
- | `lib/legion/cli/chat/web_fetch.rb` | `/fetch` slash command: fetches web page, extracts text for context |
704
- | `lib/legion/cli/chat/web_search.rb` | DuckDuckGo HTML scraping search (parse results, extract URLs, auto-fetch) |
705
- | `lib/legion/cli/chat/checkpoint.rb` | File edit checkpointing: save prior state, rewind (N steps, per-file) |
706
- | `lib/legion/cli/chat/memory_store.rb` | Persistent memory: project (`.legion/memory.md`) + global (`~/.legion/memory/`) |
707
- | `lib/legion/cli/chat/subagent.rb` | Background subagent spawning via `Open3.capture3` to `legion chat prompt` |
708
- | `lib/legion/cli/chat/agent_registry.rb` | Custom agent definitions from `.legion/agents/*.json` and `.yaml` |
709
- | `lib/legion/cli/chat/agent_delegator.rb` | `@name` at-mention parsing and dispatch via Subagent |
710
- | `lib/legion/cli/chat/chat_logger.rb` | Chat-specific logging |
711
- | `lib/legion/cli/chat/context_manager.rb` | Context window management: dedup, compression, summarization strategies |
712
- | `lib/legion/cli/chat/progress_bar.rb` | Progress bar rendering for long operations |
713
- | `lib/legion/cli/chat/status_indicator.rb` | Status indicator (spinner, checkmark, cross) |
714
- | `lib/legion/cli/chat/team.rb` | Multi-user team support for chat sessions |
715
- | `lib/legion/cli/chat/tools/` | 40 built-in tools: read_file, write_file, edit_file, search_files, search_content, run_command, save_memory, search_memory, web_search, spawn_agent, search_traces, query_knowledge, ingest_knowledge, consolidate_memory, relate_knowledge, knowledge_maintenance, knowledge_stats, summarize_traces, list_extensions, manage_tasks, system_status, view_events, cost_summary, reflect, manage_schedules, worker_status, detect_anomalies, view_trends, trigger_dream, generate_insights, budget_status, provider_health, model_comparison, shadow_eval_status, entity_extract, arbitrage_status, escalation_status, graph_explore, scheduling_status, memory_status |
716
- | `lib/legion/chat/skills.rb` | Skill discovery: parses `.legion/skills/` and `~/.legionio/skills/` YAML frontmatter files |
717
- | `lib/legion/cli/graph_command.rb` | `legion graph` subcommands (show with --format mermaid\|dot, --chain, --output) |
718
- | `lib/legion/cli/trace_command.rb` | `legion trace search` — NL trace search via LLM |
719
- | `lib/legion/cli/dashboard_command.rb` | `legion dashboard` — TUI operational dashboard |
720
- | `lib/legion/cli/dashboard/data_fetcher.rb` | Dashboard API poller: workers, health, events |
721
- | `lib/legion/cli/dashboard/renderer.rb` | Dashboard terminal renderer with sections |
722
- | `lib/legion/cli/cost_command.rb` | `legion cost` — cost summary, worker, team, top, budget, export |
723
- | `lib/legion/cli/cost/data_client.rb` | Cost data aggregation API client |
724
- | `lib/legion/cli/skill_command.rb` | `legion skill` — list, show, create, run skill files |
725
- | `lib/legion/cli/audit_command.rb` | `legion audit` — query audit log (list, show, count, export) |
726
- | `lib/legion/cli/rbac_command.rb` | `legion rbac` — role management, permission grants, access checks |
727
- | `lib/legion/cli/init_command.rb` | `legion init` — interactive project setup wizard |
728
- | `lib/legion/cli/init/config_generator.rb` | Config file generation from templates |
729
- | `lib/legion/cli/init/environment_detector.rb` | Runtime environment detection (Docker, CI, services) |
730
- | `lib/legion/cli/marketplace_command.rb` | `legion marketplace` — extension search, install, publish |
731
- | `lib/legion/cli/notebook_command.rb` | `legion notebook` — interactive task notebook REPL |
732
- | `lib/legion/cli/update_command.rb` | `legion update` — self-update via Homebrew or gem |
733
- | `lib/legion/cli/lex_templates.rb` | LEX scaffold templates for generator |
734
- | `lib/legion/cli/version.rb` | CLI version display helper |
735
- | `lib/legion/docs/site_generator.rb` | Static documentation site generator |
736
- | `lib/legion/cli/memory_command.rb` | `legion memory` subcommands (list, add, forget, search, clear) |
737
- | `lib/legion/cli/plan_command.rb` | `legion plan` — read-only exploration mode with /save to docs/work/planning/ |
738
- | `lib/legion/cli/swarm_command.rb` | `legion swarm` — multi-agent workflow orchestration from `.legion/swarms/` |
739
- | `lib/legion/cli/commit_command.rb` | `legion commit` — AI-generated commit messages via LLM |
740
- | `lib/legion/cli/pr_command.rb` | `legion pr` — AI-generated PR title + description via LLM |
741
- | `lib/legion/cli/review_command.rb` | `legion review` — AI code review with severity levels (CRITICAL/WARNING/SUGGESTION/NOTE) |
742
- | `lib/legion/cli/gaia_command.rb` | `legion gaia` subcommands (status) |
743
- | `lib/legion/cli/llm_command.rb` | `legion llm` subcommands (status) — LLM subsystem status and provider health |
744
- | `lib/legion/cli/detect_command.rb` | `legion detect scan` — scan environment and recommend extensions |
745
- | `lib/legion/cli/observe_command.rb` | `legion observe stats` — MCP tool usage statistics from Observer |
746
- | `lib/legion/cli/tty_command.rb` | `legion tty interactive` — launch rich terminal UI (legion-tty interactive shell) |
747
- | `lib/legion/cli/interactive.rb` | `Interactive` Thor class — shared CLI module for `legion` binary entry point |
748
- | `lib/legion/cli/config_import.rb` | `legion config import` — import config from external sources |
749
- | `lib/legion/cli/schedule_command.rb` | `legion schedule` subcommands (list, show, add, remove, logs) |
750
- | `lib/legion/cli/completion_command.rb` | `legion completion` subcommands (bash, zsh, install) |
751
- | `lib/legion/cli/openapi_command.rb` | `legion openapi` subcommands (generate, routes); also `GET /api/openapi.json` endpoint |
752
- | `lib/legion/cli/doctor_command.rb` | `legion doctor` — 11-check environment diagnosis; `Doctor::Result` value object with status/message/prescription/auto_fixable |
753
- | `lib/legion/cli/doctor/` | Individual check modules: ruby_version, bundle, config, rabbitmq, database, cache, vault, extensions, pid, permissions, plus result.rb |
754
- | `lib/legion/cli/telemetry_command.rb` | `legion telemetry` subcommands (stats, ingest) — session log analytics |
755
- | `lib/legion/cli/auth_command.rb` | `legion auth` subcommands (teams) — delegated OAuth browser flow for external services |
756
- | `lib/legion/cli/admin_command.rb` | `legion admin` subcommands (purge-topology) — ops tooling for v2.0 AMQP topology cleanup |
757
- | `completions/legion.bash` | Bash tab completion script |
758
- | `completions/_legion` | Zsh tab completion script |
759
- | `lib/legion/cli/theme.rb` | Purple palette, orbital ASCII banner, branded CLI output |
760
- | **Legacy CLI (preserved, not loaded by new CLI)** | |
761
- | `lib/legion/cli/task.rb` | Old task commands |
762
- | `lib/legion/cli/trigger.rb` | Old trigger command |
763
- | `lib/legion/cli/chain.rb` | Old chain commands |
764
- | `lib/legion/cli/cohort.rb` | Old cohort commands |
765
- | `lib/legion/cli/function.rb` | Old function commands |
766
- | `lib/legion/cli/relationship.rb` | Old relationship commands |
767
- | `lib/legion/cli/lex/` | Old LEX sub-generators + ERB templates (still used by LexGenerator) |
768
- | **Executables** | |
769
- | `exe/legion` | Executable: YJIT, GC tuning, bootsnap, then `Legion::CLI::Main.start(ARGV)` |
770
- | `Dockerfile` | Docker build |
771
- | `docker_deploy.rb` | Build + push Docker image |
772
- | **Specs** | |
773
- | `spec/spec_helper.rb` | RSpec configuration |
774
-
775
- ## Known Stubs / TODO
776
-
777
- | Area | Status |
778
- |------|--------|
779
- | `API::Routes::Relationships` | Fully implemented (backed by legion-data migration 013) |
780
- | `API::Routes::Chains` | 501 stub - no data model |
781
- | `API::Middleware::Auth` | JWT Bearer auth middleware — real token validation and API key (`X-API-Key` header) auth both implemented |
782
- | `legion-data` chains/relationships models | Not yet implemented |
783
-
784
- ## Rubocop Notes
785
-
786
- - `.rubocop.yml` excludes `spec/**/*`, `legionio.gemspec`, `chat_command.rb`, `plan_command.rb`, `swarm_command.rb`, and `schedule_command.rb` from `Metrics/BlockLength`
787
- - `chat_command.rb` also excluded from `Metrics/AbcSize`, `Metrics/MethodLength`, and `Metrics/CyclomaticComplexity` (large REPL loop + slash command dispatch)
788
- - Hash alignment: `table` style enforced for both rocket and colon
789
- - `Naming/PredicateMethod` disabled
85
+ `LEGION_MODE=lite` `InProcess` transport adapter + `Memory` cache adapter. No RabbitMQ/Redis needed.
790
86
 
791
87
  ## Development
792
88
 
793
89
  ```bash
794
- bundle install
795
- bundle exec rspec # ~3500+ examples, 0 failures
90
+ bundle exec rspec # ~3500+ examples
796
91
  bundle exec rubocop # 0 offenses
797
92
  ```
798
93
 
799
- **Always run a full `bundle exec rspec` and `bundle exec rubocop -A` and fix all errors before committing.**
800
-
801
- Specs use `rack-test` for API testing. `Legion::JSON.load` returns symbol keys — use `body[:data]` not `body['data']` in specs.
94
+ Always run both before committing. Specs use `rack-test`. `Legion::JSON.load` returns symbol keys.
802
95
 
803
- ---
96
+ ## Rubocop
804
97
 
805
- **Maintained By**: Matthew Iverson (@Esity)
98
+ `.rubocop.yml` excludes `spec/**/*` from `Metrics/BlockLength`. `chat_command.rb` excluded from most Metrics cops. Hash alignment: `table` style.
data/Gemfile CHANGED
@@ -61,6 +61,8 @@ gem 'kramdown', '>= 2.0'
61
61
  gem 'mysql2'
62
62
 
63
63
  group :test do
64
+ gem 'faraday'
65
+ gem 'faraday-net_http'
64
66
  gem 'graphql'
65
67
  gem 'lex-codegen'
66
68
  gem 'lex-eval'
@@ -9,13 +9,13 @@ module Legion
9
9
 
10
10
  app.get '/api/identity/audit' do
11
11
  require_data!
12
- halt 503, json_error('unavailable', 'identity audit log not available') unless defined?(Legion::Data::Model::IdentityAuditLog)
12
+ halt 503, json_error('unavailable', 'identity audit log not available') unless defined?(Legion::Data::Model::Identity::AuditLog)
13
13
 
14
- dataset = Legion::Data::Model::IdentityAuditLog.dataset
14
+ dataset = Legion::Data::Model::Identity::AuditLog.dataset
15
15
 
16
16
  principal = params[:principal]
17
- if principal && defined?(Legion::Data::Model::Principal)
18
- principal_record = Legion::Data::Model::Principal.where(canonical_name: principal).first
17
+ if principal && defined?(Legion::Data::Model::Identity::Principal)
18
+ principal_record = Legion::Data::Model::Identity::Principal.where(canonical_name: principal).first
19
19
  halt 404, json_error('not_found', "principal '#{principal}' not found") unless principal_record
20
20
  dataset = dataset.where(principal_id: principal_record.id)
21
21
  end
@@ -213,7 +213,8 @@ module Legion
213
213
  validate_required!(body, :messages)
214
214
 
215
215
  messages = body[:messages]
216
- tools = body[:tools] || []
216
+ tools_present = body.key?(:tools)
217
+ tools = tools_present ? Array(body[:tools]) : []
217
218
  model = body[:model]
218
219
  provider = body[:provider]
219
220
  requested_tools = body[:requested_tools] || []
@@ -267,17 +268,19 @@ module Legion
267
268
  end
268
269
 
269
270
  caller_metadata = body[:metadata].is_a?(Hash) ? body[:metadata] : {}
270
- req = Legion::LLM::Inference::Request.build(
271
+ request_args = {
271
272
  messages: messages,
272
273
  system: body[:system],
273
274
  routing: { provider: provider, model: model },
274
- tools: tool_classes,
275
275
  caller: caller_ctx,
276
276
  conversation_id: body[:conversation_id],
277
277
  metadata: caller_metadata.merge(requested_tools: requested_tools),
278
278
  stream: streaming,
279
279
  cache: { strategy: :default, cacheable: true }
280
- )
280
+ }
281
+ request_args[:tools] = tool_classes if tools_present
282
+
283
+ req = Legion::LLM::Inference::Request.build(**request_args)
281
284
  executor = Legion::LLM::Inference::Executor.new(req)
282
285
 
283
286
  if streaming
@@ -27,13 +27,6 @@ module Legion
27
27
  { name: 'lex-transformer', category: 'core', description: 'Task chain transformation' },
28
28
  { name: 'lex-webhook', category: 'core', description: 'Inbound webhook receiver' },
29
29
  # ai
30
- { name: 'lex-azure-ai', category: 'ai', description: 'Azure OpenAI provider integration' },
31
- { name: 'lex-bedrock', category: 'ai', description: 'AWS Bedrock LLM provider integration' },
32
- { name: 'lex-claude', category: 'ai', description: 'Anthropic Claude provider integration' },
33
- { name: 'lex-foundry', category: 'ai', description: 'Azure AI Foundry provider integration' },
34
- { name: 'lex-gemini', category: 'ai', description: 'Google Gemini provider integration' },
35
- { name: 'lex-ollama', category: 'ai', description: 'Ollama local LLM provider integration' },
36
- { name: 'lex-openai', category: 'ai', description: 'OpenAI provider integration' },
37
30
  { name: 'lex-xai', category: 'ai', description: 'xAI Grok provider integration' },
38
31
  { name: 'lex-llm', category: 'ai', description: 'Common LLM provider base and routing metadata' },
39
32
  { name: 'lex-llm-anthropic', category: 'ai', description: 'Anthropic LLM provider integration' },
@@ -275,7 +275,7 @@ module Legion
275
275
  return [] unless defined?(Legion::Data) && Legion::Data.respond_to?(:connected?) && Legion::Data.connected?
276
276
 
277
277
  model = begin
278
- Legion::Data::Model::IdentityGroupMembership
278
+ Legion::Data::Model::Identity::GroupMembership
279
279
  rescue StandardError
280
280
  nil
281
281
  end
@@ -115,23 +115,41 @@ module Legion
115
115
  end
116
116
 
117
117
  def system_principal
118
- canonical = if defined?(Legion::Identity::Process) && Legion::Identity::Process.resolved?
119
- Legion::Identity::Process.canonical_name
120
- else
121
- 'system'
122
- end
118
+ attrs = system_identity_attributes
123
119
 
124
- if @system_principal&.canonical_name != canonical
120
+ if @system_principal&.canonical_name != attrs[:canonical_name] ||
121
+ @system_principal&.kind != attrs[:kind] ||
122
+ @system_principal&.source != Identity::Request::SOURCE_NORMALIZATION.fetch(attrs[:source], attrs[:source])
125
123
  @system_principal = Identity::Request.new(
126
- principal_id: "system:#{canonical}",
127
- canonical_name: canonical,
128
- kind: :service,
124
+ principal_id: "system:#{attrs[:canonical_name]}",
125
+ canonical_name: attrs[:canonical_name],
126
+ kind: attrs[:kind],
129
127
  groups: [],
130
- source: :local
128
+ source: attrs[:source]
131
129
  )
132
130
  end
133
131
  @system_principal
134
132
  end
133
+
134
+ def system_identity_attributes
135
+ process = defined?(Legion::Identity::Process) ? Legion::Identity::Process : nil
136
+ canonical = process_value(process, :canonical_name)
137
+ canonical = 'system' if canonical.nil? || canonical.to_s.empty?
138
+
139
+ {
140
+ canonical_name: canonical.to_s,
141
+ kind: process_value(process, :kind) || :service,
142
+ source: process_value(process, :source) || :local
143
+ }
144
+ end
145
+
146
+ def process_value(process, method_name)
147
+ return nil unless process.respond_to?(method_name)
148
+
149
+ process.public_send(method_name)
150
+ rescue StandardError
151
+ nil
152
+ end
135
153
  end
136
154
  end
137
155
  end
@@ -316,57 +316,74 @@ module Legion
316
316
  @resolved.make_true
317
317
  end
318
318
 
319
- def persist_to_db(composite) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
319
+ def persist_to_db(composite) # rubocop:disable Metrics/MethodLength
320
320
  return unless defined?(Legion::Data) && Legion::Data.respond_to?(:connected?) && Legion::Data.connected?
321
- return unless defined?(Legion::Data::Connection) &&
322
- Legion::Data::Connection.respond_to?(:adapter) &&
323
- Legion::Data::Connection.adapter == :postgres
324
321
 
325
- # upsert identity_providers
326
- composite[:providers]&.each do |name, info|
327
- Legion::Data.db[:identity_providers].insert_conflict(
322
+ now = Time.now.utc
323
+ db = Legion::Data.db
324
+
325
+ composite[:providers]&.each_key do |name|
326
+ db[:identity_providers].insert_conflict(
328
327
  target: :name,
329
- update: { status: info[:status].to_s, trust_level: info[:trust]&.to_s, last_seen_at: Time.now }
330
- ).insert(name: name.to_s, status: info[:status].to_s, trust_level: info[:trust]&.to_s, last_seen_at: Time.now)
328
+ update: { updated_at: now }
329
+ ).insert(
330
+ uuid: SecureRandom.uuid,
331
+ name: name.to_s,
332
+ provider_type: 'authenticate',
333
+ facing: 'both',
334
+ source: 'resolver',
335
+ enabled: true,
336
+ created_at: now,
337
+ updated_at: now
338
+ )
331
339
  end
332
340
 
333
- # upsert principals
334
- Legion::Data.db[:principals].insert_conflict(
335
- target: :canonical_name,
336
- update: { kind: composite[:kind].to_s, updated_at: Time.now }
341
+ db[:identity_principals].insert_conflict(
342
+ target: %i[canonical_name kind],
343
+ update: { last_seen_at: now, updated_at: now }
337
344
  ).insert(
345
+ uuid: SecureRandom.uuid,
338
346
  canonical_name: composite[:canonical_name],
339
347
  kind: composite[:kind].to_s,
340
- created_at: Time.now,
341
- updated_at: Time.now
348
+ active: true,
349
+ last_seen_at: now,
350
+ created_at: now,
351
+ updated_at: now
342
352
  )
343
353
 
344
- principal_row = Legion::Data.db[:principals].where(canonical_name: composite[:canonical_name]).first
354
+ principal_row = db[:identity_principals].where(
355
+ canonical_name: composite[:canonical_name], kind: composite[:kind].to_s
356
+ ).first
345
357
  principal_id = principal_row[:id] if principal_row
346
358
 
347
- # upsert identities per provider alias
348
359
  composite[:aliases]&.each do |provider_name, identities|
360
+ provider_row = db[:identity_providers].where(name: provider_name.to_s).first
361
+ next unless provider_row
362
+
349
363
  Array(identities).each do |ident|
350
- Legion::Data.db[:identities].insert_conflict(
351
- target: %i[principal_id provider_name provider_identity],
352
- update: { updated_at: Time.now }
364
+ db[:identities].insert_conflict(
365
+ target: %i[principal_id provider_id provider_identity_key],
366
+ update: { last_authenticated_at: now, updated_at: now }
353
367
  ).insert(
354
- principal_id: principal_id,
355
- provider_name: provider_name.to_s,
356
- provider_identity: ident,
357
- created_at: Time.now,
358
- updated_at: Time.now
368
+ uuid: SecureRandom.uuid,
369
+ principal_id: principal_id,
370
+ provider_id: provider_row[:id],
371
+ provider_identity_key: ident,
372
+ active: true,
373
+ last_authenticated_at: now,
374
+ created_at: now,
375
+ updated_at: now
359
376
  )
360
377
  end
361
378
  end
362
379
 
363
- # insert audit log
364
- Legion::Data.db[:identity_audit_log].insert(
365
- principal_id: principal_id,
366
- event_type: 'identity.resolved',
367
- provider_name: composite[:source].to_s,
368
- trust_level: composite[:trust]&.to_s,
369
- detail: Legion::JSON.dump(
380
+ db[:identity_audit_log].insert(
381
+ uuid: SecureRandom.uuid,
382
+ principal_id: principal_id,
383
+ event_type: 'identity.resolved',
384
+ provider_name: composite[:source].to_s,
385
+ trust_level: composite[:trust]&.to_s,
386
+ detail_payload: Legion::JSON.dump(
370
387
  {
371
388
  source: composite[:source],
372
389
  trust: composite[:trust],
@@ -374,9 +391,9 @@ module Legion
374
391
  session_id: @session_id
375
392
  }
376
393
  ),
377
- node_id: composite[:node_id],
378
- session_id: @session_id,
379
- created_at: Time.now
394
+ node_ref: composite[:node_id],
395
+ session_ref: @session_id,
396
+ created_at: now
380
397
  )
381
398
  rescue StandardError => e
382
399
  log_warn("DB persistence failed: #{e.message}")
@@ -406,18 +423,15 @@ module Legion
406
423
  end
407
424
 
408
425
  return unless defined?(Legion::Data) && Legion::Data.respond_to?(:connected?) && Legion::Data.connected?
409
- return unless defined?(Legion::Data::Connection) &&
410
- Legion::Data::Connection.respond_to?(:adapter) &&
411
- Legion::Data::Connection.adapter == :postgres
412
426
 
413
- # Audit the canonical change
414
- old_row = Legion::Data.db[:principals].where(canonical_name: old_canonical).first
427
+ old_row = Legion::Data.db[:identity_principals].where(canonical_name: old_canonical).first
415
428
  Legion::Data.db[:identity_audit_log].insert(
416
- principal_id: old_row&.dig(:id),
417
- event_type: 'identity.canonical_changed',
418
- provider_name: '',
419
- detail: Legion::JSON.dump({ old: old_canonical, new: new_canonical }),
420
- created_at: Time.now
429
+ uuid: SecureRandom.uuid,
430
+ principal_id: old_row&.dig(:id),
431
+ event_type: 'identity.canonical_changed',
432
+ provider_name: 'resolver',
433
+ detail_payload: Legion::JSON.dump({ old: old_canonical, new: new_canonical }),
434
+ created_at: Time.now
421
435
  )
422
436
  rescue StandardError => e
423
437
  log_warn("canonical change handling failed: #{e.message}")
@@ -34,8 +34,10 @@ module Legion
34
34
  private
35
35
 
36
36
  def handle_outcome(payload, success:)
37
- runner_class = payload[:runner_class].to_s
38
- function = payload[:function].to_s
37
+ return unless observable_outcome?(payload)
38
+
39
+ runner_class = outcome_value(payload, :runner_class).to_s
40
+ function = outcome_value(payload, :function).to_s
39
41
  domain = derive_domain(runner_class)
40
42
 
41
43
  record_learning(domain: domain, success: success)
@@ -52,6 +54,18 @@ module Legion
52
54
  last.gsub(/([A-Z])/, '_\1').delete_prefix('_').downcase
53
55
  end
54
56
 
57
+ def observable_outcome?(payload)
58
+ !outcome_value(payload, :task_id).to_s.strip.empty? &&
59
+ !outcome_value(payload, :runner_class).to_s.strip.empty? &&
60
+ !outcome_value(payload, :function).to_s.strip.empty?
61
+ end
62
+
63
+ def outcome_value(payload, key)
64
+ return unless payload.respond_to?(:[])
65
+
66
+ payload[key] || payload[key.to_s]
67
+ end
68
+
55
69
  def record_learning(domain:, success:)
56
70
  client = meta_learning_client
57
71
  return unless client
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Legion
4
- VERSION = '1.9.23'
4
+ VERSION = '1.9.28'
5
5
  end
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.9.23
4
+ version: 1.9.28
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -405,6 +405,7 @@ files:
405
405
  - ".github/workflows/ci.yml"
406
406
  - ".github/workflows/publish-homebrew.yml"
407
407
  - ".gitignore"
408
+ - ".rspec"
408
409
  - ".rubocop.yml"
409
410
  - AGENTS.md
410
411
  - CHANGELOG.md