legionio 1.7.24 → 1.7.25
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 +7 -0
- data/lib/legion/api/default_settings.rb +29 -1
- data/lib/legion/api/helpers.rb +14 -1
- data/lib/legion/api/llm.rb +8 -1
- data/lib/legion/api/middleware/request_logger.rb +19 -0
- data/lib/legion/api.rb +2 -0
- data/lib/legion/identity/request.rb +18 -2
- data/lib/legion/service.rb +31 -11
- data/lib/legion/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d65ff77ca667361b558a97d1da1c3d5d2b1090bb4aad5559532406cd18e781a7
|
|
4
|
+
data.tar.gz: 7583822cc4cf804703b8a733be36bb711d1cae70724a49dc98cafea6583268ab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 06f7712d8c9c6e944890ba2c55efbdf3463a6c39e906017cede6d3401215de9144e7c916c39c8b981e8f6ea6646a5439bcc10a399443fe7642b3ab06e3ef2c7a
|
|
7
|
+
data.tar.gz: 5e5819fc7973e9cb6de4b64e88c40b74d033fd666c4b9585726a3b1e87b9468f6cf4759fed226a9c3a7bc755a10b9267403dcd001d2c10f08b7aaf36e6264812
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Legion Changelog
|
|
2
2
|
|
|
3
|
+
## [1.7.25] - 2026-04-06
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Wire Format Phase 3 Group 2: `Identity::Request::SOURCE_NORMALIZATION` constant — maps middleware-emitted source values (`:api_key`, `:local`, `:jwt`, `:kerberos`, `:system`) to canonical credential enum at `from_auth_context` construction time
|
|
7
|
+
- Wire Format Phase 3 Group 2: `response_meta` in `API::Helpers` now includes `caller` block (`canonical_name`, `kind`, `source`) when the request is authenticated and `env['legion.principal']` is set by `Identity::Middleware`
|
|
8
|
+
- Wire Format Phase 3 Group 2: `POST /api/llm/inference` wires `to_caller_hash` from the authenticated principal into the pipeline `caller:` field, replacing the hardcoded `{ type: :user, credential: :api }` fallback
|
|
9
|
+
|
|
3
10
|
## [1.7.24] - 2026-04-06
|
|
4
11
|
|
|
5
12
|
### Fixed
|
|
@@ -13,7 +13,8 @@ module Legion
|
|
|
13
13
|
puma: puma_defaults,
|
|
14
14
|
bind_retries: 3,
|
|
15
15
|
bind_retry_wait: 2,
|
|
16
|
-
tls: tls_defaults
|
|
16
|
+
tls: tls_defaults,
|
|
17
|
+
elastic_apm: elastic_apm_defaults
|
|
17
18
|
}
|
|
18
19
|
end
|
|
19
20
|
|
|
@@ -31,6 +32,33 @@ module Legion
|
|
|
31
32
|
enabled: false
|
|
32
33
|
}
|
|
33
34
|
end
|
|
35
|
+
|
|
36
|
+
def self.elastic_apm_defaults
|
|
37
|
+
{
|
|
38
|
+
enabled: false,
|
|
39
|
+
server_url: 'http://localhost:8200',
|
|
40
|
+
api_key: nil,
|
|
41
|
+
secret_token: nil,
|
|
42
|
+
api_buffer_size: 256,
|
|
43
|
+
api_request_size: '750kb',
|
|
44
|
+
api_request_time: '10s',
|
|
45
|
+
capture_body: 'off',
|
|
46
|
+
capture_headers: true,
|
|
47
|
+
capture_env: true,
|
|
48
|
+
disable_send: false,
|
|
49
|
+
environment: nil,
|
|
50
|
+
hostname: nil,
|
|
51
|
+
ignore_url_patterns: %w[/api/health /api/ready],
|
|
52
|
+
pool_size: 1,
|
|
53
|
+
service_name: 'LegionIO',
|
|
54
|
+
service_node_name: nil,
|
|
55
|
+
service_version: nil,
|
|
56
|
+
sample_rate: 1.0,
|
|
57
|
+
verify_server_cert: true,
|
|
58
|
+
central_config: true,
|
|
59
|
+
span_frames_min_duration: '5ms'
|
|
60
|
+
}
|
|
61
|
+
end
|
|
34
62
|
end
|
|
35
63
|
end
|
|
36
64
|
end
|
data/lib/legion/api/helpers.rb
CHANGED
|
@@ -173,10 +173,23 @@ module Legion
|
|
|
173
173
|
private
|
|
174
174
|
|
|
175
175
|
def response_meta
|
|
176
|
-
{
|
|
176
|
+
meta = {
|
|
177
177
|
timestamp: Time.now.utc.iso8601,
|
|
178
178
|
node: Legion::Settings[:client][:name]
|
|
179
179
|
}
|
|
180
|
+
|
|
181
|
+
if authenticated? && defined?(Legion::Identity::Request)
|
|
182
|
+
req = env['legion.principal']
|
|
183
|
+
if req
|
|
184
|
+
meta[:caller] = {
|
|
185
|
+
canonical_name: req.canonical_name,
|
|
186
|
+
kind: req.kind,
|
|
187
|
+
source: req.source
|
|
188
|
+
}
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
meta
|
|
180
193
|
end
|
|
181
194
|
|
|
182
195
|
def page_limit
|
data/lib/legion/api/llm.rb
CHANGED
|
@@ -280,12 +280,19 @@ module Legion
|
|
|
280
280
|
require 'legion/llm/pipeline/request' unless defined?(Legion::LLM::Pipeline::Request)
|
|
281
281
|
require 'legion/llm/pipeline/executor' unless defined?(Legion::LLM::Pipeline::Executor)
|
|
282
282
|
|
|
283
|
+
principal = defined?(Legion::Identity::Request) && env['legion.principal']
|
|
284
|
+
caller_ctx = if principal
|
|
285
|
+
principal.to_caller_hash
|
|
286
|
+
else
|
|
287
|
+
{ requested_by: { identity: caller_identity, type: :user, credential: :api } }
|
|
288
|
+
end
|
|
289
|
+
|
|
283
290
|
req = Legion::LLM::Pipeline::Request.build(
|
|
284
291
|
messages: messages,
|
|
285
292
|
system: body[:system],
|
|
286
293
|
routing: { provider: provider, model: model },
|
|
287
294
|
tools: tool_classes,
|
|
288
|
-
caller:
|
|
295
|
+
caller: caller_ctx,
|
|
289
296
|
conversation_id: body[:conversation_id],
|
|
290
297
|
metadata: { requested_tools: requested_tools },
|
|
291
298
|
stream: streaming,
|
|
@@ -49,6 +49,25 @@ module Legion
|
|
|
49
49
|
parts << "query=#{query}" if query
|
|
50
50
|
parts.join(' ')
|
|
51
51
|
end
|
|
52
|
+
|
|
53
|
+
def peek_body(env)
|
|
54
|
+
input = env['rack.input']
|
|
55
|
+
return '-' unless input.respond_to?(:read) && input.respond_to?(:rewind)
|
|
56
|
+
|
|
57
|
+
begin
|
|
58
|
+
input.rewind
|
|
59
|
+
raw = input.read(1024)
|
|
60
|
+
raw.to_s.gsub(/\s+/, ' ')[0, 512]
|
|
61
|
+
rescue StandardError
|
|
62
|
+
'-'
|
|
63
|
+
ensure
|
|
64
|
+
begin
|
|
65
|
+
input.rewind
|
|
66
|
+
rescue StandardError
|
|
67
|
+
nil
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
52
71
|
end
|
|
53
72
|
end
|
|
54
73
|
end
|
data/lib/legion/api.rb
CHANGED
|
@@ -222,5 +222,7 @@ module Legion
|
|
|
222
222
|
|
|
223
223
|
use Legion::API::Middleware::RequestLogger
|
|
224
224
|
use Legion::Rbac::Middleware if defined?(Legion::Rbac::Middleware)
|
|
225
|
+
use ElasticAPM::Middleware if defined?(ElasticAPM::Middleware) &&
|
|
226
|
+
Legion::Settings.dig(:api, :elastic_apm, :enabled)
|
|
225
227
|
end
|
|
226
228
|
end
|
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
module Legion
|
|
4
4
|
module Identity
|
|
5
5
|
class Request
|
|
6
|
+
# Maps middleware-emitted source values to the canonical credential enum.
|
|
7
|
+
# :local is emitted by Middleware#system_principal for unauthenticated loopback
|
|
8
|
+
# requests and must normalize to :system to maintain audit trail consistency.
|
|
9
|
+
# :jwt is intentionally kept distinct — JWT is the transport, not the provider.
|
|
10
|
+
# Entra-specific identification requires issuer inspection (Phase 7 concern).
|
|
11
|
+
SOURCE_NORMALIZATION = {
|
|
12
|
+
api_key: :api,
|
|
13
|
+
jwt: :jwt,
|
|
14
|
+
kerberos: :kerberos,
|
|
15
|
+
local: :system,
|
|
16
|
+
system: :system
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
6
19
|
attr_reader :principal_id, :canonical_name, :kind, :groups, :source, :metadata
|
|
7
20
|
|
|
8
21
|
def initialize(principal_id:, canonical_name:, kind:, groups: [], source: nil, metadata: {}) # rubocop:disable Metrics/ParameterLists
|
|
@@ -10,7 +23,7 @@ module Legion
|
|
|
10
23
|
@canonical_name = canonical_name
|
|
11
24
|
@kind = kind
|
|
12
25
|
@groups = groups.freeze
|
|
13
|
-
@source = source
|
|
26
|
+
@source = SOURCE_NORMALIZATION.fetch(source&.to_sym, source)
|
|
14
27
|
@metadata = metadata.freeze
|
|
15
28
|
freeze
|
|
16
29
|
end
|
|
@@ -23,16 +36,19 @@ module Legion
|
|
|
23
36
|
|
|
24
37
|
# Builds a Request from a parsed auth claims hash with symbol keys:
|
|
25
38
|
# { sub:, name:, preferred_username:, kind:, groups:, source: }
|
|
39
|
+
# The source value is normalized via SOURCE_NORMALIZATION at construction time.
|
|
26
40
|
def self.from_auth_context(claims_hash)
|
|
27
41
|
raw_name = claims_hash[:name] || claims_hash[:preferred_username] || ''
|
|
28
42
|
canonical = raw_name.to_s.strip.downcase.gsub('.', '-')
|
|
43
|
+
raw_source = claims_hash[:source]&.to_sym
|
|
44
|
+
normalized_source = SOURCE_NORMALIZATION.fetch(raw_source, raw_source)
|
|
29
45
|
|
|
30
46
|
new(
|
|
31
47
|
principal_id: claims_hash[:sub],
|
|
32
48
|
canonical_name: canonical,
|
|
33
49
|
kind: claims_hash[:kind] || :human,
|
|
34
50
|
groups: claims_hash[:groups] || [],
|
|
35
|
-
source:
|
|
51
|
+
source: normalized_source
|
|
36
52
|
)
|
|
37
53
|
end
|
|
38
54
|
|
data/lib/legion/service.rb
CHANGED
|
@@ -327,21 +327,12 @@ module Legion
|
|
|
327
327
|
end
|
|
328
328
|
|
|
329
329
|
def setup_apm
|
|
330
|
-
apm_settings = Legion::Settings
|
|
330
|
+
apm_settings = Legion::Settings.dig(:api, :elastic_apm) || {}
|
|
331
331
|
return unless apm_settings[:enabled]
|
|
332
332
|
|
|
333
333
|
require 'elastic-apm'
|
|
334
334
|
|
|
335
|
-
config =
|
|
336
|
-
service_name: apm_settings[:service_name] || "legion-#{Legion::Settings[:client][:name]}",
|
|
337
|
-
server_url: apm_settings[:server_url] || 'http://localhost:8200',
|
|
338
|
-
environment: apm_settings[:environment] || Legion::Settings[:environment] || 'development',
|
|
339
|
-
secret_token: apm_settings[:secret_token],
|
|
340
|
-
api_key: apm_settings[:api_key],
|
|
341
|
-
log_level: apm_settings[:log_level]&.to_sym || Logger::WARN,
|
|
342
|
-
transaction_sample_rate: apm_settings[:sample_rate] || 1.0
|
|
343
|
-
}.compact
|
|
344
|
-
|
|
335
|
+
config = build_apm_config(apm_settings)
|
|
345
336
|
ElasticAPM.start(**config)
|
|
346
337
|
@apm_running = true
|
|
347
338
|
log.info "Elastic APM started: server=#{config[:server_url]} service=#{config[:service_name]}"
|
|
@@ -1145,6 +1136,35 @@ module Legion
|
|
|
1145
1136
|
}.compact
|
|
1146
1137
|
end
|
|
1147
1138
|
|
|
1139
|
+
def build_apm_config(apm)
|
|
1140
|
+
{
|
|
1141
|
+
server_url: apm[:server_url] || 'http://localhost:8200',
|
|
1142
|
+
api_key: apm[:api_key],
|
|
1143
|
+
secret_token: apm[:secret_token],
|
|
1144
|
+
api_buffer_size: apm[:api_buffer_size] || 256,
|
|
1145
|
+
api_request_size: apm[:api_request_size] || '750kb',
|
|
1146
|
+
api_request_time: apm[:api_request_time] || '10s',
|
|
1147
|
+
capture_body: apm.fetch(:capture_body, 'off'),
|
|
1148
|
+
capture_headers: apm.fetch(:capture_headers, true),
|
|
1149
|
+
capture_env: apm.fetch(:capture_env, true),
|
|
1150
|
+
disable_send: apm.fetch(:disable_send, false),
|
|
1151
|
+
environment: apm[:environment] || Legion::Settings[:environment] || 'development',
|
|
1152
|
+
framework_name: 'LegionIO',
|
|
1153
|
+
framework_version: Legion::VERSION,
|
|
1154
|
+
hostname: apm[:hostname] || Legion::Settings[:client][:name],
|
|
1155
|
+
ignore_url_patterns: apm[:ignore_url_patterns] || %w[/api/health /api/ready],
|
|
1156
|
+
logger: Legion::Logging.log,
|
|
1157
|
+
pool_size: apm[:pool_size] || 1,
|
|
1158
|
+
service_name: apm[:service_name] || 'LegionIO',
|
|
1159
|
+
service_node_name: apm[:service_node_name] || Legion::Settings[:client][:name],
|
|
1160
|
+
service_version: apm[:service_version] || Legion::VERSION,
|
|
1161
|
+
transaction_sample_rate: apm[:sample_rate] || 1.0,
|
|
1162
|
+
verify_server_cert: apm.fetch(:verify_server_cert, true),
|
|
1163
|
+
central_config: apm.fetch(:central_config, true),
|
|
1164
|
+
span_frames_min_duration: apm[:span_frames_min_duration]
|
|
1165
|
+
}.compact
|
|
1166
|
+
end
|
|
1167
|
+
|
|
1148
1168
|
def ssl_server_settings(tls_cfg, bind, port)
|
|
1149
1169
|
return {} unless tls_cfg
|
|
1150
1170
|
|
data/lib/legion/version.rb
CHANGED