lex-llm 0.7.3 → 0.8.0
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 +106 -0
- data/RULES.md +97 -0
- data/lib/legion/extensions/llm/auto_registration.rb +4 -13
- data/lib/legion/extensions/llm/canonical/chunk.rb +106 -92
- data/lib/legion/extensions/llm/canonical/content_block.rb +66 -75
- data/lib/legion/extensions/llm/canonical/message.rb +52 -67
- data/lib/legion/extensions/llm/canonical/params.rb +59 -33
- data/lib/legion/extensions/llm/canonical/request.rb +67 -54
- data/lib/legion/extensions/llm/canonical/response.rb +61 -76
- data/lib/legion/extensions/llm/canonical/strict.rb +105 -0
- data/lib/legion/extensions/llm/canonical/thinking.rb +99 -51
- data/lib/legion/extensions/llm/canonical/tool_call.rb +42 -51
- data/lib/legion/extensions/llm/canonical/tool_definition.rb +56 -46
- data/lib/legion/extensions/llm/canonical/tool_schema.rb +15 -22
- data/lib/legion/extensions/llm/canonical/usage.rb +47 -40
- data/lib/legion/extensions/llm/canonical.rb +6 -5
- data/lib/legion/extensions/llm/configuration.rb +40 -10
- data/lib/legion/extensions/llm/connection.rb +8 -30
- data/lib/legion/extensions/llm/credential_sources.rb +32 -49
- data/lib/legion/extensions/llm/discovery/actor.rb +92 -0
- data/lib/legion/extensions/llm/discovery/pipeline.rb +604 -0
- data/lib/legion/extensions/llm/error.rb +0 -14
- data/lib/legion/extensions/llm/fleet/contract_error.rb +15 -0
- data/lib/legion/extensions/llm/fleet/envelope_validation.rb +7 -6
- data/lib/legion/extensions/llm/fleet/fleet_envelope.rb +66 -0
- data/lib/legion/extensions/llm/fleet/protocol.rb +18 -5
- data/lib/legion/extensions/llm/fleet/provider_responder.rb +58 -153
- data/lib/legion/extensions/llm/fleet/token_validator.rb +15 -21
- data/lib/legion/extensions/llm/fleet/worker_execution.rb +107 -150
- data/lib/legion/extensions/llm/inventory/errors.rb +0 -1
- data/lib/legion/extensions/llm/inventory/evidence.rb +1 -1
- data/lib/legion/extensions/llm/inventory/identity.rb +55 -39
- data/lib/legion/extensions/llm/inventory/probe_token.rb +9 -12
- data/lib/legion/extensions/llm/inventory/publisher.rb +15 -53
- data/lib/legion/extensions/llm/inventory/records.rb +82 -99
- data/lib/legion/extensions/llm/inventory/registry.rb +54 -46
- data/lib/legion/extensions/llm/inventory/snapshot.rb +7 -21
- data/lib/legion/extensions/llm/inventory/weight_reconciler.rb +249 -0
- data/lib/legion/extensions/llm/inventory/weight_schema.rb +146 -0
- data/lib/legion/extensions/llm/provider/open_ai_compatible.rb +172 -182
- data/lib/legion/extensions/llm/provider.rb +192 -315
- data/lib/legion/extensions/llm/provider_contract.rb +25 -8
- data/lib/legion/extensions/llm/provider_settings.rb +5 -26
- data/lib/legion/extensions/llm/responses/thinking_extractor.rb +8 -1
- data/lib/legion/extensions/llm/responses/tool_arguments.rb +34 -0
- data/lib/legion/extensions/llm/routing/provider_outcome.rb +25 -0
- data/lib/legion/extensions/llm/routing/records.rb +34 -17
- data/lib/legion/extensions/llm/stream_accumulator.rb +186 -270
- data/lib/legion/extensions/llm/streaming.rb +50 -35
- data/lib/legion/extensions/llm/taxonomies.rb +29 -17
- data/lib/legion/extensions/llm/transport/fleet_lane.rb +8 -10
- data/lib/legion/extensions/llm/transport/messages/fleet_error.rb +3 -2
- data/lib/legion/extensions/llm/transport/messages/fleet_request.rb +6 -8
- data/lib/legion/extensions/llm/transport/messages/fleet_response.rb +10 -9
- data/lib/legion/extensions/llm/utils.rb +23 -5
- data/lib/legion/extensions/llm/version.rb +1 -1
- data/lib/legion/extensions/llm.rb +10 -98
- data/spec/legion/extensions/llm/auto_registration_spec.rb +4 -9
- data/spec/legion/extensions/llm/canonical/chunk_spec.rb +66 -252
- data/spec/legion/extensions/llm/canonical/content_block_spec.rb +52 -197
- data/spec/legion/extensions/llm/canonical/message_spec.rb +89 -204
- data/spec/legion/extensions/llm/canonical/params_spec.rb +57 -136
- data/spec/legion/extensions/llm/canonical/request_spec.rb +81 -143
- data/spec/legion/extensions/llm/canonical/response_spec.rb +68 -204
- data/spec/legion/extensions/llm/canonical/thinking_spec.rb +68 -148
- data/spec/legion/extensions/llm/canonical/tool_call_spec.rb +59 -162
- data/spec/legion/extensions/llm/canonical/tool_definition_spec.rb +55 -191
- data/spec/legion/extensions/llm/canonical/tool_schema_spec.rb +26 -67
- data/spec/legion/extensions/llm/canonical/usage_spec.rb +46 -155
- data/spec/legion/extensions/llm/configuration_spec.rb +31 -5
- data/spec/legion/extensions/llm/conformance/canonical_type_examples.rb +106 -0
- data/spec/legion/extensions/llm/conformance/conformance.rb +10 -2
- data/spec/legion/extensions/llm/conformance/provider_translator_examples.rb +1 -1
- data/spec/legion/extensions/llm/conformance/ssot_contract_conformance_spec.rb +130 -0
- data/spec/legion/extensions/llm/conformance/ssot_contract_examples.rb +507 -0
- data/spec/legion/extensions/llm/conformance/ssot_provider_examples.rb +11 -10
- data/spec/legion/extensions/llm/credential_sources_spec.rb +12 -13
- data/spec/legion/extensions/llm/error_spec.rb +2 -12
- data/spec/legion/extensions/llm/fleet/exact_offering_spec.rb +59 -45
- data/spec/legion/extensions/llm/fleet/provider_responder_spec.rb +178 -67
- data/spec/legion/extensions/llm/fleet/token_validator_spec.rb +7 -2
- data/spec/legion/extensions/llm/fleet/worker_execution_spec.rb +101 -62
- data/spec/legion/extensions/llm/fleet_messages_spec.rb +118 -123
- data/spec/legion/extensions/llm/inventory/boot_spec.rb +4 -4
- data/spec/legion/extensions/llm/inventory/identity_spec.rb +127 -110
- data/spec/legion/extensions/llm/inventory/probe_token_spec.rb +4 -4
- data/spec/legion/extensions/llm/inventory/publisher_spec.rb +8 -61
- data/spec/legion/extensions/llm/inventory/records_spec.rb +129 -41
- data/spec/legion/extensions/llm/inventory/registry_activation_spec.rb +55 -4
- data/spec/legion/extensions/llm/inventory/registry_replacement_spec.rb +5 -4
- data/spec/legion/extensions/llm/inventory/snapshot_spec.rb +11 -5
- data/spec/legion/extensions/llm/inventory/weight_reconciler_spec.rb +312 -0
- data/spec/legion/extensions/llm/inventory/weight_schema_spec.rb +141 -0
- data/spec/legion/extensions/llm/provider/open_ai_compatible_spec.rb +105 -68
- data/spec/legion/extensions/llm/provider/open_ai_compatible_tool_calls_array_spec.rb +7 -31
- data/spec/legion/extensions/llm/provider_contract_spec.rb +10 -15
- data/spec/legion/extensions/llm/provider_spec.rb +98 -78
- data/spec/legion/extensions/llm/routing/records_spec.rb +38 -5
- data/spec/legion/extensions/llm/stream_accumulator_spec.rb +174 -144
- data/spec/legion/extensions/llm/streaming_spec.rb +27 -0
- data/spec/legion/extensions/llm/taxonomies_spec.rb +43 -16
- data/spec/legion/extensions/llm/transport/fleet_lane_spec.rb +1 -1
- data/spec/legion/extensions/llm/utils_spec.rb +26 -7
- data/spec/legion/extensions/llm_base_contract_spec.rb +55 -90
- data/spec/legion/extensions/llm_extension_spec.rb +5 -5
- data/spec/support/fake_llm_provider.rb +45 -39
- data/spec/support/fake_ssot_harness.rb +7 -2
- data/spec/support/ssot_registry_helpers.rb +3 -2
- metadata +15 -54
- data/lib/legion/extensions/llm/agent.rb +0 -366
- data/lib/legion/extensions/llm/aliases.json +0 -436
- data/lib/legion/extensions/llm/aliases.rb +0 -67
- data/lib/legion/extensions/llm/attachment.rb +0 -229
- data/lib/legion/extensions/llm/chat.rb +0 -354
- data/lib/legion/extensions/llm/chunk.rb +0 -10
- data/lib/legion/extensions/llm/content.rb +0 -81
- data/lib/legion/extensions/llm/context.rb +0 -33
- data/lib/legion/extensions/llm/embedding.rb +0 -33
- data/lib/legion/extensions/llm/image.rb +0 -109
- data/lib/legion/extensions/llm/inventory/capabilities.rb +0 -40
- data/lib/legion/extensions/llm/inventory/scoped_refresher.rb +0 -311
- data/lib/legion/extensions/llm/message.rb +0 -118
- data/lib/legion/extensions/llm/mime_type.rb +0 -75
- data/lib/legion/extensions/llm/model/info.rb +0 -286
- data/lib/legion/extensions/llm/model/modalities.rb +0 -26
- data/lib/legion/extensions/llm/model/pricing.rb +0 -52
- data/lib/legion/extensions/llm/model/pricing_category.rb +0 -50
- data/lib/legion/extensions/llm/model/pricing_tier.rb +0 -37
- data/lib/legion/extensions/llm/model.rb +0 -11
- data/lib/legion/extensions/llm/models.json +0 -57313
- data/lib/legion/extensions/llm/models.rb +0 -530
- data/lib/legion/extensions/llm/models_schema.json +0 -168
- data/lib/legion/extensions/llm/moderation.rb +0 -60
- data/lib/legion/extensions/llm/registry_event_builder.rb +0 -141
- data/lib/legion/extensions/llm/registry_publisher.rb +0 -107
- data/lib/legion/extensions/llm/responses/chat_response.rb +0 -43
- data/lib/legion/extensions/llm/responses/embedding_response.rb +0 -38
- data/lib/legion/extensions/llm/responses/stream_chunk.rb +0 -43
- data/lib/legion/extensions/llm/routing/lane_key.rb +0 -66
- data/lib/legion/extensions/llm/routing/model_offering.rb +0 -241
- data/lib/legion/extensions/llm/routing/offering_registry.rb +0 -101
- data/lib/legion/extensions/llm/routing/registry_event.rb +0 -167
- data/lib/legion/extensions/llm/thinking.rb +0 -53
- data/lib/legion/extensions/llm/tokens.rb +0 -51
- data/lib/legion/extensions/llm/tool_call.rb +0 -34
- data/lib/legion/extensions/llm/transcription.rb +0 -39
- data/lib/legion/extensions/llm/transport/messages/registry_event.rb +0 -44
- data/spec/legion/extensions/llm/agent_spec.rb +0 -179
- data/spec/legion/extensions/llm/attachment_spec.rb +0 -25
- data/spec/legion/extensions/llm/conformance/fixtures/ssot_identity_vectors.json +0 -84
- data/spec/legion/extensions/llm/context_spec.rb +0 -127
- data/spec/legion/extensions/llm/inventory/capabilities_spec.rb +0 -43
- data/spec/legion/extensions/llm/inventory/scoped_refresher_spec.rb +0 -337
- data/spec/legion/extensions/llm/message_spec.rb +0 -64
- data/spec/legion/extensions/llm/model/info_spec.rb +0 -222
- data/spec/legion/extensions/llm/models_spec.rb +0 -104
- data/spec/legion/extensions/llm/registry_event_builder_spec.rb +0 -68
- data/spec/legion/extensions/llm/registry_publisher_spec.rb +0 -22
- data/spec/legion/extensions/llm/responses/response_objects_spec.rb +0 -75
- data/spec/legion/extensions/llm/routing/model_offering_spec.rb +0 -281
- data/spec/legion/extensions/llm/routing/offering_registry_spec.rb +0 -50
- data/spec/legion/extensions/llm/routing/registry_event_spec.rb +0 -120
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# 09 §1 — canonical type conformance: the T-groups as shared examples.
|
|
4
|
+
#
|
|
5
|
+
# The including spec supplies:
|
|
6
|
+
# let(:type_class) — the Canonical type
|
|
7
|
+
# let(:type_source) — a Hash exercising the members (from_hash input)
|
|
8
|
+
# optionally:
|
|
9
|
+
# let(:auto_generated_members) — member names filled by the factory when
|
|
10
|
+
# absent from the source (id, timestamp), excluded from member-equality
|
|
11
|
+
# checks.
|
|
12
|
+
#
|
|
13
|
+
# Type-specific laws (T4 member survival, T5 enum law, T7 member normalization
|
|
14
|
+
# for the type's normalizable members) are asserted in each type spec.
|
|
15
|
+
|
|
16
|
+
RSpec.shared_examples 'a canonical type' do
|
|
17
|
+
let(:type) { type_class }
|
|
18
|
+
let(:built) { type.from_hash(type_source) }
|
|
19
|
+
|
|
20
|
+
describe 'T1 — strict input' do
|
|
21
|
+
it 'raises a typed ArgumentError on nil (no nil factory)' do
|
|
22
|
+
expect { type.from_hash(nil) }
|
|
23
|
+
.to raise_error(ArgumentError, /expected Hash, got NilClass/)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'raises a typed ArgumentError naming the offending class on wrong-class input' do
|
|
27
|
+
expect { type.from_hash('not a hash') }
|
|
28
|
+
.to raise_error(ArgumentError, /expected Hash, got String/)
|
|
29
|
+
expect { type.from_hash(42) }
|
|
30
|
+
.to raise_error(ArgumentError, /expected Hash, got Integer/)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'never returns nil from from_hash (valid instances or typed raises only)' do
|
|
34
|
+
expect(type.from_hash(type_source)).not_to be_nil
|
|
35
|
+
result =
|
|
36
|
+
begin
|
|
37
|
+
type.from_hash({})
|
|
38
|
+
rescue ArgumentError
|
|
39
|
+
:typed_raise
|
|
40
|
+
end
|
|
41
|
+
expect(result).not_to be_nil
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe 'T2 — unknown-key policy (04 L5)' do
|
|
46
|
+
it 'folds unknown keys into metadata verbatim — no drops, no raises' do
|
|
47
|
+
source = type_source.merge(fleet_unknown_key: 'kept', other_unknown: [1, 2])
|
|
48
|
+
instance = type.from_hash(source)
|
|
49
|
+
|
|
50
|
+
expect(instance.metadata).to include(fleet_unknown_key: 'kept', other_unknown: [1, 2])
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'round-trips the folded unknowns' do
|
|
54
|
+
source = type_source.merge(fleet_unknown_key: 'kept')
|
|
55
|
+
instance = type.from_hash(source)
|
|
56
|
+
expect(type.from_hash(instance.to_h).metadata).to include(fleet_unknown_key: 'kept')
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe 'T3 — round-trip identity (04 L7)' do
|
|
61
|
+
it 'build → to_h → from_hash → Data-equal' do
|
|
62
|
+
expect(type.from_hash(built.to_h)).to eq(built)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'to_h is a plain Hash with symbol keys (no member lost, no key invented)' do
|
|
66
|
+
hash = built.to_h
|
|
67
|
+
expect(hash).to be_a(Hash)
|
|
68
|
+
expect(hash.keys).to all(be_a(Symbol))
|
|
69
|
+
checkable = (type.members.map(&:to_sym) - auto_generated_members)
|
|
70
|
+
checkable.each do |member|
|
|
71
|
+
value = built.public_send(member)
|
|
72
|
+
next if value.nil?
|
|
73
|
+
|
|
74
|
+
expect(hash.key?(member)).to be(true)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
describe 'T6 — serialization uniformity (04 L8)' do
|
|
80
|
+
it 'as_json == to_h and to_json == to_h.to_json' do
|
|
81
|
+
expect(built.as_json).to eq(built.to_h)
|
|
82
|
+
expect(built.to_json).to eq(built.to_h.to_json)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# H1: .new is the single strict constructor — it runs the same member
|
|
87
|
+
# contract as the factories. Poison that passes an is_a? check is not
|
|
88
|
+
# constructible through ANY public path.
|
|
89
|
+
describe 'T8 — strict .new (H1: one validated constructor)' do
|
|
90
|
+
let(:member_kwargs) { type_source.transform_keys(&:to_sym) }
|
|
91
|
+
|
|
92
|
+
it 'rejects unknown members with a typed ArgumentError (no silent drops)' do
|
|
93
|
+
expect { type_class.new(**member_kwargs, not_a_member: true) }
|
|
94
|
+
.to raise_error(ArgumentError, /not_a_member/)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it 'rejects a non-Hash metadata member' do
|
|
98
|
+
expect { type_class.new(**member_kwargs, metadata: 'not-a-hash') }
|
|
99
|
+
.to raise_error(ArgumentError, /metadata expected Hash, got String/)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'constructs valid data (the factories and .new share one contract)' do
|
|
103
|
+
expect { type_class.new(**member_kwargs) }.not_to raise_error
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -5,10 +5,17 @@
|
|
|
5
5
|
# Ship location: spec/legion/extensions/llm/conformance/
|
|
6
6
|
# Module: Canonical::Conformance
|
|
7
7
|
#
|
|
8
|
-
# Consumer pattern (in provider gem spec_helper)
|
|
8
|
+
# Consumer pattern (in provider gem spec_helper) — load the kit's example
|
|
9
|
+
# modules BY EXPLICIT NAME. Do NOT glob the kit dir: it also ships lex-llm's
|
|
10
|
+
# own self-test specs (echo_translator_spec, ssot_*_conformance_spec) plus
|
|
11
|
+
# their support files, which LoadError outside this repo.
|
|
9
12
|
# kit = File.join(Gem.loaded_specs['lex-llm'].full_gem_path,
|
|
10
13
|
# 'spec/legion/extensions/llm/conformance')
|
|
11
|
-
#
|
|
14
|
+
# %w[conformance.rb canonical_type_examples.rb client_translator_examples.rb
|
|
15
|
+
# provider_translator_examples.rb provider_tool_rendering_examples.rb
|
|
16
|
+
# ssot_contract_examples.rb ssot_provider_examples.rb].each do |f|
|
|
17
|
+
# require File.join(kit, f)
|
|
18
|
+
# end
|
|
12
19
|
#
|
|
13
20
|
# Then in specs:
|
|
14
21
|
# it_behaves_like 'a canonical provider translator', described_class
|
|
@@ -47,5 +54,6 @@ module Canonical
|
|
|
47
54
|
end
|
|
48
55
|
end
|
|
49
56
|
|
|
57
|
+
require_relative 'canonical_type_examples'
|
|
50
58
|
require_relative 'provider_translator_examples'
|
|
51
59
|
require_relative 'client_translator_examples'
|
|
@@ -298,7 +298,7 @@ RSpec.shared_examples 'a canonical provider translator' do |translator_class|
|
|
|
298
298
|
it 'preserves tool call identity across chunks' do
|
|
299
299
|
tool_chunks = chunks.select { |c| c['type'] == 'tool_call_delta' }
|
|
300
300
|
parsed_chunks = tool_chunks.map { |c| translator.parse_chunk(c) }
|
|
301
|
-
ids = parsed_chunks.map { |c| c.tool_call&.id }
|
|
301
|
+
ids = parsed_chunks.map { |c| c.tool_call.is_a?(Hash) ? c.tool_call[:id] : c.tool_call&.id }
|
|
302
302
|
expect(ids.uniq.length).to eq(1)
|
|
303
303
|
end
|
|
304
304
|
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'legion/extensions/llm/inventory/registry'
|
|
5
|
+
require 'support/ssot_registry_helpers'
|
|
6
|
+
require_relative 'conformance'
|
|
7
|
+
require_relative 'ssot_contract_examples'
|
|
8
|
+
|
|
9
|
+
# Kit self-test: the B/F/R shared examples run against a real Provider-derived
|
|
10
|
+
# callable, the real Registry, and the real fleet responder/worker — proving
|
|
11
|
+
# the examples provider gems will load are executable and meaningful.
|
|
12
|
+
# rubocop:disable RSpec/DescribeClass, RSpec/MultipleMemoizedHelpers -- kit host
|
|
13
|
+
RSpec.describe 'SSOT v4 contract conformance kit' do
|
|
14
|
+
include SsotRegistryHelpers
|
|
15
|
+
|
|
16
|
+
let(:registry) { Legion::Extensions::Llm::Inventory::Registry }
|
|
17
|
+
let(:family) { :fake_llm }
|
|
18
|
+
let(:key) do
|
|
19
|
+
Legion::Extensions::Llm::Inventory::Identity::InstanceKey.new(provider_family: family, instance_id: 'primary')
|
|
20
|
+
end
|
|
21
|
+
let(:coordinator) { probe_coordinator(key) }
|
|
22
|
+
let(:activated) { true }
|
|
23
|
+
|
|
24
|
+
# A real 0.8.0 callable: Provider-derived, canonical in and out, no HTTP.
|
|
25
|
+
let(:callable) do
|
|
26
|
+
Class.new(Legion::Extensions::Llm::Provider) do
|
|
27
|
+
def self.slug = 'kit'
|
|
28
|
+
def self.configuration_requirements = []
|
|
29
|
+
def api_base = 'https://kit.invalid'
|
|
30
|
+
|
|
31
|
+
def chat(messages, model:, tools: nil, **_rest)
|
|
32
|
+
enforce_canonical_messages!(messages)
|
|
33
|
+
enforce_canonical_tools!(tools)
|
|
34
|
+
Legion::Extensions::Llm::Canonical::Response.build(text: 'ok', model: model, stop_reason: :end_turn)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# rubocop:disable Lint/UnusedMethodArgument -- model: mirrors the 0.8.0 callable contract
|
|
38
|
+
def stream_chat(messages, model: nil, tools: nil, **_contract, &block)
|
|
39
|
+
enforce_canonical_messages!(messages)
|
|
40
|
+
enforce_canonical_tools!(tools)
|
|
41
|
+
block&.call(Legion::Extensions::Llm::Canonical::Chunk.text_delta(delta: 'ok', request_id: nil))
|
|
42
|
+
block&.call(Legion::Extensions::Llm::Canonical::Chunk.done(request_id: nil, stop_reason: :end_turn,
|
|
43
|
+
usage: Legion::Extensions::Llm::Canonical::Usage.build(input_tokens: 1)))
|
|
44
|
+
nil
|
|
45
|
+
end
|
|
46
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
|
47
|
+
|
|
48
|
+
def count_tokens(messages:, **_contract)
|
|
49
|
+
enforce_canonical_messages!(messages)
|
|
50
|
+
1
|
|
51
|
+
end
|
|
52
|
+
end.new(Legion::Extensions::Llm.config)
|
|
53
|
+
end
|
|
54
|
+
let(:instance_key) { key }
|
|
55
|
+
let(:lane_id) { registry.snapshot.lanes_for(instance_key: key).first.lane_id }
|
|
56
|
+
# The fleet claim field keeps the name offering_id (D4 protocol continuity);
|
|
57
|
+
# its value is the lane's 5 tuple.
|
|
58
|
+
let(:offering_id) { lane_id }
|
|
59
|
+
let(:callable_handle) { registry.snapshot.instance(instance_key: key).callable_handle }
|
|
60
|
+
let(:operation_evidence_map) { operation_evidence }
|
|
61
|
+
let(:unknown_value) do
|
|
62
|
+
Legion::Extensions::Llm::Inventory::ValueEvidence.new(status: :unknown, source: :absent)
|
|
63
|
+
end
|
|
64
|
+
let(:exact_envelope_data) do
|
|
65
|
+
{
|
|
66
|
+
request_id: 'req-1', correlation_id: 'corr-1', idempotency_key: 'idem-1',
|
|
67
|
+
operation: 'chat', provider: family.to_s, provider_instance: 'primary', model: 'gemma4',
|
|
68
|
+
params: { messages: [{ role: 'user', content: 'hello' }] },
|
|
69
|
+
reply_to: 'reply.1', message_context: {}, caller: 'e2e', trace_context: {},
|
|
70
|
+
signed_token: 'unsigned', timeout_seconds: 30,
|
|
71
|
+
expires_at: (Time.now.utc + 120).iso8601,
|
|
72
|
+
protocol_version: Legion::Extensions::Llm::Fleet::Protocol::VERSION,
|
|
73
|
+
execution_contract: Legion::Extensions::Llm::Fleet::Protocol::EXACT_EXECUTION_CONTRACT,
|
|
74
|
+
offering_id: offering_id
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
let(:valid_request_options) do
|
|
78
|
+
{
|
|
79
|
+
routing_key: 'llm.fleet.inference.gemma4.ctx8192',
|
|
80
|
+
request_id: 'req-1', correlation_id: 'corr-1', idempotency_key: 'idem-1',
|
|
81
|
+
operation: :chat, provider: family.to_s, provider_instance: 'primary', model: 'gemma4',
|
|
82
|
+
params: { messages: [{ role: 'user', content: 'hello' }] },
|
|
83
|
+
reply_to: 'reply.1', message_context: {}, caller: 'e2e', trace_context: {},
|
|
84
|
+
signed_token: 'signed.jwt', timeout_seconds: 30, expires_at: '2026-08-20T12:00:00Z',
|
|
85
|
+
protocol_version: Legion::Extensions::Llm::Fleet::Protocol::VERSION,
|
|
86
|
+
execution_contract: Legion::Extensions::Llm::Fleet::Protocol::EXACT_EXECUTION_CONTRACT,
|
|
87
|
+
offering_id: offering_id
|
|
88
|
+
}
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
before do
|
|
92
|
+
registry.reset!
|
|
93
|
+
claim_and_activate(
|
|
94
|
+
key: key, callable: callable, coordinator: coordinator, model: 'gemma4',
|
|
95
|
+
weight_inputs: { tier: 100, provider: 50, instance: 2, model_or_offering: 1 },
|
|
96
|
+
base_weight: 10_000
|
|
97
|
+
)
|
|
98
|
+
allow(Legion::Extensions::Llm::Fleet::Settings).to receive(:value).and_call_original
|
|
99
|
+
allow(Legion::Extensions::Llm::Fleet::Settings).to receive(:value)
|
|
100
|
+
.with(:fleet, :auth, :require_signed_token, default: true).and_return(false)
|
|
101
|
+
allow(Legion::Extensions::Llm::Fleet::Settings).to receive(:value)
|
|
102
|
+
.with(:fleet, :responder, :require_idempotency, default: nil).and_return(false)
|
|
103
|
+
Legion::Extensions::Llm::Fleet::WorkerExecution.reset_idempotency_cache!
|
|
104
|
+
Legion::Extensions::Llm::Fleet::TokenValidator.reset_replay_cache!
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it_behaves_like 'B1 — central canonical enforcement (08 F2)'
|
|
108
|
+
it_behaves_like 'B1b — central canonical tool enforcement (H3)'
|
|
109
|
+
it_behaves_like 'B2 — canonical outputs (05 O5, 08 R2)'
|
|
110
|
+
it_behaves_like 'B3 — operation preservation (PR #189 defect class)'
|
|
111
|
+
it_behaves_like 'B4 — no model re-derivation (PR #45 law)'
|
|
112
|
+
it_behaves_like 'B5 — no weight recomputation (PR #47 defect class)'
|
|
113
|
+
it_behaves_like 'B6 — zero-weight disable (U4)'
|
|
114
|
+
it_behaves_like 'B7 — health authority (RULES 9)'
|
|
115
|
+
it_behaves_like 'B8 — exact execution never downgrades (06 P2/W1)'
|
|
116
|
+
it_behaves_like 'B9 — no silent defaults (06 W5)'
|
|
117
|
+
it_behaves_like 'F1 — envelope round-trip (06 E2)'
|
|
118
|
+
it_behaves_like 'F2 — rehydration identity (06 W4, E01)'
|
|
119
|
+
it_behaves_like 'F3 — signing law (06 S3)'
|
|
120
|
+
it_behaves_like 'F4 — fencing (07 §3)'
|
|
121
|
+
it_behaves_like 'F5 — response envelope (06 E3/E4)'
|
|
122
|
+
it_behaves_like 'F6 — contract errors (06 §5)'
|
|
123
|
+
it_behaves_like 'F7 — retryability (06 F6)'
|
|
124
|
+
it_behaves_like 'R1 — state machine (07 §3)'
|
|
125
|
+
it_behaves_like 'R2 — identity (07 §1, 5-tuple law)'
|
|
126
|
+
it_behaves_like 'R3 — snapshot law (07 §5)'
|
|
127
|
+
it_behaves_like 'R4 — weight law (07 §4)'
|
|
128
|
+
it_behaves_like 'R5 — callable lifecycle (07 §6)'
|
|
129
|
+
end
|
|
130
|
+
# rubocop:enable RSpec/DescribeClass, RSpec/MultipleMemoizedHelpers
|