lex-llm 0.7.2 → 0.7.6

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.
@@ -52,6 +52,8 @@ module Legion
52
52
  require_relative 'llm/inventory/errors'
53
53
  require_relative 'llm/inventory/immutable_value'
54
54
  require_relative 'llm/inventory/identity'
55
+ require_relative 'llm/inventory/weight_schema'
56
+ require_relative 'llm/inventory/weight_reconciler'
55
57
  require_relative 'llm/inventory/evidence'
56
58
  require_relative 'llm/inventory/callable_handle'
57
59
  require_relative 'llm/inventory/probe_token'
@@ -164,10 +164,10 @@ RSpec.shared_examples 'an SSOT v3 provider adapter' do
164
164
  expect(ssot_harness.inference_call_count(callable: context[:callable])).to eq(1)
165
165
  end
166
166
 
167
- it 'rejects provider/model defaults and a nil instance' do
167
+ it 'accepts "default" as a plain instance_id label and rejects a nil instance' do
168
168
  identity = Legion::Extensions::Llm::Inventory::Identity
169
- expect { identity::InstanceKey.new(provider_family: ssot_harness.provider_family, instance_id: 'default') }
170
- .to raise_error(Legion::Extensions::Llm::Inventory::Errors::ValidationError)
169
+ key = identity::InstanceKey.new(provider_family: ssot_harness.provider_family, instance_id: 'default')
170
+ expect(key.instance_id).to eq('default')
171
171
  expect { identity::InstanceKey.new(provider_family: ssot_harness.provider_family, instance_id: nil) }
172
172
  .to raise_error(Legion::Extensions::Llm::Inventory::Errors::ValidationError)
173
173
  end
@@ -119,11 +119,44 @@ RSpec.describe Legion::Extensions::Llm::Fleet::ProviderResponder do
119
119
  end
120
120
 
121
121
  describe 'execution_contract marker validation' do
122
+ it 'preserves false values for both envelope key spellings' do
123
+ symbol_envelope = described_class::FleetEnvelope.new(data: { execution_contract: false })
124
+ string_envelope = described_class::FleetEnvelope.new(data: { 'execution_contract' => false })
125
+
126
+ expect(symbol_envelope[:execution_contract]).to be(false)
127
+ expect(string_envelope[:execution_contract]).to be(false)
128
+ end
129
+
122
130
  it 'accepts a legacy v2 envelope with no marker' do
123
131
  envelope = described_class.parse_payload(payload)
124
132
  expect { described_class.check_envelope!(envelope, provider_family: :ollama) }.not_to raise_error
125
133
  end
126
134
 
135
+ it 'accepts an explicitly nil marker as legacy v2' do
136
+ envelope = described_class.parse_payload(payload.merge(execution_contract: nil))
137
+ expect { described_class.check_envelope!(envelope, provider_family: :ollama) }.not_to raise_error
138
+ end
139
+
140
+ { symbol: { execution_contract: false }, string: { 'execution_contract' => false } }.each do |spelling, marker|
141
+ it "rejects a false #{spelling}-key marker before provider or registry dispatch" do
142
+ registry = class_spy(Legion::Extensions::Llm::Inventory::Registry)
143
+ allow(provider_class).to receive(:new).and_call_original
144
+
145
+ expect do
146
+ described_class.call(
147
+ payload: payload.merge(marker),
148
+ provider_family: :ollama,
149
+ provider_class: provider_class,
150
+ provider_instances: provider_instances,
151
+ registry: registry
152
+ )
153
+ end.to raise_error(ArgumentError, /unknown execution_contract: false/)
154
+
155
+ expect(provider_class).not_to have_received(:new)
156
+ expect(registry).not_to have_received(:snapshot)
157
+ end
158
+ end
159
+
127
160
  it 'rejects an unknown nonempty marker' do
128
161
  envelope = described_class.parse_payload(payload.merge(execution_contract: 'made_up'))
129
162
  expect { described_class.check_envelope!(envelope, provider_family: :ollama) }
@@ -125,4 +125,24 @@ RSpec.describe Legion::Extensions::Llm::Fleet::WorkerExecution do
125
125
 
126
126
  expect(Legion::Extensions::Llm::Fleet::TokenValidator).to have_received(:release_replay!).with('jti-2')
127
127
  end
128
+
129
+ describe '.envelope_value' do
130
+ it 'preserves false values for both key spellings' do
131
+ expect(described_class.envelope_value({ execution_contract: false }, :execution_contract)).to be(false)
132
+ expect(described_class.envelope_value({ 'execution_contract' => false }, :execution_contract)).to be(false)
133
+ end
134
+
135
+ it 'prefers a present symbol key even when its value is false' do
136
+ mixed = { execution_contract: false, 'execution_contract' => 'exact_offering_v1' }
137
+
138
+ expect(described_class.envelope_value(mixed, :execution_contract)).to be(false)
139
+ end
140
+
141
+ it 'preserves legacy absence and nil while returning the exact marker unchanged' do
142
+ expect(described_class.envelope_value({}, :execution_contract)).to be_nil
143
+ expect(described_class.envelope_value({ execution_contract: nil }, :execution_contract)).to be_nil
144
+ expect(described_class.envelope_value({ execution_contract: 'exact_offering_v1' }, :execution_contract))
145
+ .to eq('exact_offering_v1')
146
+ end
147
+ end
128
148
  end
@@ -202,9 +202,16 @@ RSpec.describe Legion::Extensions::Llm::Inventory::Identity do
202
202
  .to raise_error(errors::ValidationError)
203
203
  end
204
204
 
205
- it 'rejects the reserved instance_id "default"' do
206
- expect { instance_key_class.new(provider_family: 'vllm', instance_id: 'default') }
207
- .to raise_error(errors::ValidationError)
205
+ it 'accepts "default" as a plain instance_id label (no reserved values — v2 parity)' do
206
+ key = instance_key_class.new(provider_family: 'vllm', instance_id: 'default')
207
+ expect(key.instance_id).to eq('default')
208
+
209
+ other = instance_key_class.new(provider_family: 'vllm', instance_id: ' default ')
210
+ expect(key).to eq(other)
211
+ expect(key.hash).to eq(other.hash)
212
+
213
+ expect(key.to_h).to eq(provider_family: :vllm, instance_id: 'default', physical_id: nil)
214
+ expect(key.inspect).to include('instance_id="default"')
208
215
  end
209
216
 
210
217
  it 'rejects nil fields' do
@@ -248,7 +255,7 @@ RSpec.describe Legion::Extensions::Llm::Inventory::Identity do
248
255
  expect({ bare => :found }[with_physical]).to eq(:found)
249
256
  end
250
257
 
251
- it 'does not reserve "default" for physical_id (only instance_id is reserved)' do
258
+ it 'accepts "default" for physical_id (no field is reserved)' do
252
259
  key = instance_key_class.new(provider_family: :vllm, instance_id: 'apollo', physical_id: 'default')
253
260
  expect(key.physical_id).to eq('default')
254
261
  end
@@ -57,7 +57,57 @@ RSpec.describe Legion::Extensions::Llm::Inventory::Records do
57
57
  )
58
58
  end
59
59
 
60
+ def build_lane(**overrides)
61
+ Legion::Extensions::Llm::Inventory::LaneRecord.new(
62
+ lane_id: lane_id, offering_id: offering_id, instance_key: instance_key, provider_family: :vllm,
63
+ instance_id: 'h200', model: 'gemma4', tier: :local, operation: :chat, capability_evidence: {},
64
+ quota_domain: nil, metadata: {}, callable_handle: callable_handle, publication_source: :provider_catalog,
65
+ **scalar_evidence_kwargs, **overrides
66
+ )
67
+ end
68
+
69
+ shared_examples 'an atomic immutable weight pair' do
70
+ it 'defaults both omitted fields to the frozen identity pair' do
71
+ record = build_record
72
+ expect(record.weight_inputs).to eq(tier: 100, provider: 100, instance: 100, model_or_offering: 100)
73
+ expect(record.weight_inputs).to be_frozen
74
+ expect(record.base_weight).to eq(100_000_000)
75
+ end
76
+
77
+ it 'requires the pair atomically and rejects every invalid shape' do
78
+ valid = { tier: 150, provider: 100, instance: 115, model_or_offering: 100 }
79
+ expect { build_record(weight_inputs: valid) }.to raise_error(errors::ValidationError)
80
+ expect { build_record(base_weight: 172_500_000) }.to raise_error(errors::ValidationError)
81
+ expect { build_record(weight_inputs: [], base_weight: 1) }.to raise_error(errors::ValidationError)
82
+ expect { build_record(weight_inputs: valid.merge('tier' => 150).except(:tier), base_weight: 172_500_000) }
83
+ .to raise_error(errors::ValidationError)
84
+ expect { build_record(weight_inputs: valid.merge(provider: -1), base_weight: -172_500) }
85
+ .to raise_error(errors::ValidationError)
86
+ expect { build_record(weight_inputs: valid.merge(provider: 100.0), base_weight: 172_500_000) }
87
+ .to raise_error(errors::ValidationError)
88
+ expect { build_record(weight_inputs: valid, base_weight: 172_500_000.0) }
89
+ .to raise_error(errors::ValidationError)
90
+ expect { build_record(weight_inputs: valid, base_weight: 1) }.to raise_error(errors::ValidationError)
91
+ end
92
+
93
+ it 'defensively freezes a supplied valid pair' do
94
+ source = { tier: 150, provider: 100, instance: 115, model_or_offering: 100 }
95
+ record = build_record(weight_inputs: source, base_weight: 172_500_000)
96
+ source[:tier] = 1
97
+
98
+ expect(record.weight_inputs).to eq(tier: 150, provider: 100, instance: 115, model_or_offering: 100)
99
+ expect(record.weight_inputs).to be_frozen
100
+ expect(record.base_weight).to eq(172_500_000)
101
+ end
102
+ end
103
+
60
104
  describe inventory::OfferingDraft do
105
+ def build_record(**overrides)
106
+ build_draft(**overrides)
107
+ end
108
+
109
+ it_behaves_like 'an atomic immutable weight pair'
110
+
61
111
  it 'builds a valid frozen draft' do
62
112
  draft = build_draft
63
113
  expect(draft).to be_frozen
@@ -112,6 +162,12 @@ RSpec.describe Legion::Extensions::Llm::Inventory::Records do
112
162
  end
113
163
 
114
164
  describe inventory::OfferingRecord do
165
+ def build_record(**overrides)
166
+ build_offering_record(**overrides)
167
+ end
168
+
169
+ it_behaves_like 'an atomic immutable weight pair'
170
+
115
171
  it 'validates offering_id reproduction and exposes operation views' do
116
172
  record = build_offering_record
117
173
  expect(record.supported_operations).to eq(%i[chat])
@@ -138,15 +194,12 @@ RSpec.describe Legion::Extensions::Llm::Inventory::Records do
138
194
  end
139
195
 
140
196
  describe inventory::LaneRecord do
141
- def build_lane(**overrides)
142
- Legion::Extensions::Llm::Inventory::LaneRecord.new(
143
- lane_id: lane_id, offering_id: offering_id, instance_key: instance_key, provider_family: :vllm,
144
- instance_id: 'h200', model: 'gemma4', tier: :local, operation: :chat, capability_evidence: {},
145
- quota_domain: nil, metadata: {}, callable_handle: callable_handle, publication_source: :provider_catalog,
146
- **scalar_evidence_kwargs, **overrides
147
- )
197
+ def build_record(**overrides)
198
+ build_lane(**overrides)
148
199
  end
149
200
 
201
+ it_behaves_like 'an atomic immutable weight pair'
202
+
150
203
  it 'builds a valid lane whose id reproduces' do
151
204
  expect(build_lane.lane_id).to eq(lane_id)
152
205
  end
@@ -74,6 +74,24 @@ RSpec.describe Legion::Extensions::Llm::Inventory::Registry do
74
74
  expect(snapshot.publication_status(instance_key: key).state).to eq(:complete)
75
75
  end
76
76
 
77
+ it 'copies the draft weight pair unchanged onto offering and lane records' do
78
+ weights = { tier: 150, provider: 100, instance: 115, model_or_offering: 100 }
79
+ claim_and_activate(
80
+ key: key, callable: callable, coordinator: coordinator,
81
+ weight_inputs: weights, base_weight: 172_500_000
82
+ )
83
+ snapshot = described_class.snapshot
84
+ offering = snapshot.offerings_for(instance_key: key).first
85
+ lane = snapshot.lanes_for(instance_key: key).first
86
+
87
+ expect(offering.weight_inputs).to eq(weights)
88
+ expect(offering.base_weight).to eq(172_500_000)
89
+ expect(lane.weight_inputs).to eq(weights)
90
+ expect(lane.base_weight).to eq(172_500_000)
91
+ expect(offering.weight_inputs).to be_frozen
92
+ expect(lane.weight_inputs).to be_frozen
93
+ end
94
+
77
95
  it 'accepts an explicit complete empty activation as complete, not initializing' do
78
96
  token = described_class.claim_instance(instance_key: key, callable: callable, probe_request_handle: coordinator)
79
97
  probe = described_class.readiness_probe_started(instance_key: key, publisher_token: token)
@@ -253,8 +253,11 @@ RSpec.describe Legion::Extensions::Llm::Inventory::ScopedRefresher do
253
253
  end
254
254
 
255
255
  it 'projects an available instance into an exact five-field legacy lane and round-trips' do
256
+ allow(Legion::Extensions::Llm::Taxonomies).to receive(:lane_type_for).and_call_original
257
+
256
258
  result = adapter.sync_snapshot(snapshot: activate_chat, instance_key: key, mutation_reason: :activated)
257
259
  expect(result).to eq(:applied)
260
+ expect(Legion::Extensions::Llm::Taxonomies).to have_received(:lane_type_for).with(operation: :chat)
258
261
 
259
262
  legacy_id = 'local:vllm:h200:inference:gemma4'
260
263
  expect(fake_inventory.lanes).to have_key(legacy_id)
@@ -0,0 +1,308 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'open3'
5
+ require 'rbconfig'
6
+ require 'legion/extensions/llm/inventory/records'
7
+ require_relative '../../../../support/ssot_registry_helpers'
8
+
9
+ RSpec.describe 'Inventory::WeightReconciler' do
10
+ include SsotRegistryHelpers
11
+
12
+ inventory = Legion::Extensions::Llm::Inventory
13
+
14
+ let(:reconciler) { inventory.const_get(:WeightReconciler) }
15
+ let(:tracker_class) { inventory.const_get(:DormantWeightTracker) }
16
+ let(:key) { instance_key(family: 'vllm', instance: 'helios') }
17
+ let(:mutex) { Mutex.new }
18
+ let(:identity_draft) { drafts.first }
19
+
20
+ it 'loads its Set dependency when directly required without the ambient constant' do
21
+ script = <<~RUBY
22
+ require 'tmpdir'
23
+
24
+ Object.send(:remove_const, :Set)
25
+ $LOADED_FEATURES.delete_if { |path| File.basename(path) == 'set.rb' }
26
+ Dir.mktmpdir do |directory|
27
+ File.write(File.join(directory, 'set.rb'), <<~'SET')
28
+ class Set
29
+ def initialize(values = []) = @values = values.to_a.uniq
30
+ def difference(other) = self.class.new(@values.reject { |value| other.include?(value) })
31
+ def sort_by(&block) = @values.sort_by(&block)
32
+ def replace(other) = (@values = other.to_a; self)
33
+ def include?(value) = @values.include?(value)
34
+ def to_a = @values.dup
35
+ end
36
+ SET
37
+ $LOAD_PATH.unshift(directory)
38
+ require 'legion/extensions/llm/inventory/weight_reconciler'
39
+ tracker = Legion::Extensions::Llm::Inventory::DormantWeightTracker.new
40
+ abort 'wrong result' unless tracker.observe(configured_keys: [[:vllm]], published_keys: []).size == 1
41
+ end
42
+ RUBY
43
+
44
+ _stdout, stderr, status = Open3.capture3(
45
+ RbConfig.ruby, '-Ilib', '-e', script,
46
+ chdir: File.expand_path('../../../../..', __dir__)
47
+ )
48
+
49
+ expect(status.success?).to be(true), stderr
50
+ end
51
+
52
+ def settings_for(provider: {}, tier: 100)
53
+ {
54
+ extensions: { llm: { vllm: provider } },
55
+ llm: { routing: { tier_weights: { local: tier } } }
56
+ }
57
+ end
58
+
59
+ def state_for(draft: identity_draft, published: true, sequence: 0)
60
+ {
61
+ instance_key: key, offerings: [draft].freeze, published: published,
62
+ sequence: sequence, publisher_token: 'ptok:v1:test'
63
+ }
64
+ end
65
+
66
+ it 'rebuilds every offering with the current weight pair in a frozen array' do
67
+ rebuilt = reconciler.rebuild_offerings(
68
+ settings: settings_for(provider: { weight: 110 }), instance_key: key,
69
+ offerings: [identity_draft]
70
+ )
71
+
72
+ expect(rebuilt).to be_frozen
73
+ expect(rebuilt.first.weight_inputs)
74
+ .to eq(tier: 100, provider: 110, instance: 100, model_or_offering: 100)
75
+ expect(rebuilt.first.base_weight).to eq(110_000_000)
76
+ expect(rebuilt.first).to eq(
77
+ identity_draft.with(
78
+ weight_inputs: { tier: 100, provider: 110, instance: 100, model_or_offering: 100 },
79
+ base_weight: 110_000_000
80
+ )
81
+ )
82
+ end
83
+
84
+ it 'returns false and publishes nothing when the rebuilt offerings are equivalent' do
85
+ state = state_for
86
+ replace = instance_spy(Proc)
87
+
88
+ result = reconciler.commit_if_changed!(
89
+ settings: settings_for, instance_id: 'helios', state: state,
90
+ discovered_offerings: [identity_draft], mutex: mutex,
91
+ equivalent: ->(previous, current) { previous == current }, replace: replace
92
+ )
93
+
94
+ expect(result).to be(false)
95
+ expect(replace).not_to have_received(:call)
96
+ expect(state).to include(sequence: 0, offerings: [identity_draft])
97
+ end
98
+
99
+ it 'publishes a changed frozen array then atomically updates sequence, cache, and signature' do
100
+ state = state_for
101
+ published = []
102
+ signature = ->(offerings) { offerings.map { |draft| [draft.base_weight, draft.model] } }
103
+ replace = lambda do |instance_id:, state:, offerings:, sequence:|
104
+ published << { instance_id: instance_id, state: state, offerings: offerings, sequence: sequence }
105
+ end
106
+
107
+ result = reconciler.commit_if_changed!(
108
+ settings: settings_for(provider: { weight: 110 }), instance_id: 'helios', state: state,
109
+ discovered_offerings: [identity_draft], mutex: mutex,
110
+ equivalent: ->(previous, current) { previous == current }, replace: replace,
111
+ stable_signature: signature
112
+ )
113
+
114
+ expect(result).to be(true)
115
+ expect(published.one?).to be(true)
116
+ expect(published.first).to include(instance_id: 'helios', state: state, sequence: 1)
117
+ expect(published.first[:offerings]).to be_frozen
118
+ expect(published.first[:offerings]).to be_a(Array)
119
+ expect(state[:sequence]).to eq(1)
120
+ expect(state[:offerings]).to equal(published.first[:offerings])
121
+ expect(state[:signature]).to eq([[110_000_000, 'gemma4']])
122
+ end
123
+
124
+ it 'updates an unpublished cache without replacing or advancing its sequence' do
125
+ state = state_for(published: false)
126
+ replace = instance_spy(Proc)
127
+
128
+ result = reconciler.commit_if_changed!(
129
+ settings: settings_for(provider: { weight: 110 }), instance_id: 'helios', state: state,
130
+ discovered_offerings: [identity_draft], mutex: mutex,
131
+ equivalent: ->(previous, current) { previous == current }, replace: replace
132
+ )
133
+
134
+ expect(result).to be(true)
135
+ expect(replace).not_to have_received(:call)
136
+ expect(state[:sequence]).to eq(0)
137
+ expect(state[:offerings].first.base_weight).to eq(110_000_000)
138
+ end
139
+
140
+ it 'leaves published state unchanged when replacement raises so the next pass can retry' do
141
+ state = state_for
142
+ before = state.dup
143
+ replace = ->(**) { raise 'publisher down' }
144
+
145
+ expect do
146
+ reconciler.commit_if_changed!(
147
+ settings: settings_for(provider: { weight: 110 }), instance_id: 'helios', state: state,
148
+ discovered_offerings: [identity_draft], mutex: mutex,
149
+ equivalent: ->(previous, current) { previous == current }, replace: replace
150
+ )
151
+ end.to raise_error(RuntimeError, 'publisher down')
152
+ expect(state).to eq(before)
153
+ end
154
+
155
+ it 'tracks initializing state and activates it only while the same object remains tracked' do
156
+ states = {}
157
+ state = state_for(published: true)
158
+ reconciler.track_initializing!(states: states, state_key: 'helios', state: state, mutex: mutex)
159
+ expect(state[:published]).to be(false)
160
+ expect(states['helios']).to equal(state)
161
+
162
+ activations = []
163
+ activated = reconciler.activate_tracked!(
164
+ settings: settings_for(provider: { weight: 120 }), instance_id: 'helios',
165
+ state_key: 'helios', state: state, states: states, mutex: mutex,
166
+ probe_token: :probe, activation_sequence: ->(tracked) { tracked.fetch(:sequence) },
167
+ activate: ->(**kwargs) { activations << kwargs }
168
+ )
169
+ expect(activated).to be(true)
170
+ expect(activations.first[:offerings].first.base_weight).to eq(120_000_000)
171
+ expect(state[:offerings]).to equal(activations.first[:offerings])
172
+ expect(state[:published]).to be(true)
173
+
174
+ states.delete('helios')
175
+ expect(
176
+ reconciler.activate_tracked!(
177
+ settings: settings_for, instance_id: 'helios', state_key: 'helios', state: state,
178
+ states: states, mutex: mutex, probe_token: :probe,
179
+ activation_sequence: ->(tracked) { tracked.fetch(:sequence) }, activate: ->(**) { raise 'not called' }
180
+ )
181
+ ).to be(false)
182
+ end
183
+
184
+ it 'leaves initializing state unchanged when activation publication raises' do
185
+ state = state_for(published: false)
186
+ states = { 'helios' => state }
187
+ before = state.dup
188
+
189
+ expect do
190
+ reconciler.activate_tracked!(
191
+ settings: settings_for(provider: { weight: 120 }), instance_id: 'helios',
192
+ state_key: 'helios', state: state, states: states, mutex: mutex,
193
+ probe_token: :probe, activation_sequence: ->(tracked) { tracked.fetch(:sequence) + 1 },
194
+ activate: ->(**) { raise 'activation down' }
195
+ )
196
+ end.to raise_error(RuntimeError, 'activation down')
197
+ expect(state).to eq(before)
198
+ end
199
+
200
+ it 'logs each dormant key once, clears it on appearance, and logs re-disappearance' do
201
+ tracker = tracker_class.new
202
+ model_settings = settings_for(provider: { models: { ghost: { weight: 125 } } })
203
+ states = { 'helios' => state_for }
204
+ logged = []
205
+ observe = lambda do
206
+ reconciler.observe_dormant!(
207
+ settings: model_settings, provider_family: :vllm, states: states,
208
+ mutex: mutex, tracker: tracker, dormant_logger: ->(key_value) { logged << key_value }
209
+ )
210
+ end
211
+
212
+ observe.call
213
+ observe.call
214
+ expect(logged).to eq([[:vllm, :model, 'ghost']])
215
+
216
+ states['helios'] = state_for(draft: drafts(model: 'ghost', native: 'ghost').first)
217
+ observe.call
218
+ states['helios'] = state_for
219
+ observe.call
220
+ expect(logged).to eq([[:vllm, :model, 'ghost'], [:vllm, :model, 'ghost']])
221
+
222
+ tracker.clear!
223
+ observe.call
224
+ expect(logged.size).to eq(3)
225
+ end
226
+
227
+ it 'does not let an unpublished state satisfy a configured key and rejects malformed scopes' do
228
+ tracker = tracker_class.new
229
+ logged = []
230
+ states = { 'helios' => state_for(published: false) }
231
+ settings = settings_for(provider: { models: { gemma4: { weight: 125 } } })
232
+ reconciler.observe_dormant!(
233
+ settings: settings, provider_family: :vllm, states: states, mutex: mutex,
234
+ tracker: tracker, dormant_logger: ->(value) { logged << value }
235
+ )
236
+ expect(logged).to include([:vllm, :model, 'gemma4'])
237
+
238
+ expect do
239
+ reconciler.observe_dormant!(
240
+ settings: settings_for(provider: { models: false }), provider_family: :vllm,
241
+ states: {}, mutex: mutex, tracker: tracker, dormant_logger: ->(_value) {}
242
+ )
243
+ end.to raise_error(ArgumentError, /weight configuration scope must be a Hash/)
244
+ end
245
+
246
+ it 'uses the exact provider, instance, model, instance-model, and offering dormant keys' do
247
+ offering_id = inventory::Identity.offering_id(instance_key: key, provider_native_key: 'gemma4')
248
+ settings = settings_for(
249
+ provider: {
250
+ weight: false,
251
+ models: { gemma4: { weight: 101 } },
252
+ instances: { helios: { weight: 0, models: { gemma4: { weight: 102 } } } },
253
+ offerings: { offering_id => { weight: 103 } }
254
+ },
255
+ tier: 777
256
+ )
257
+ tracker = tracker_class.new
258
+ logged = []
259
+
260
+ reconciler.observe_dormant!(
261
+ settings: settings, provider_family: :vllm, states: {}, mutex: mutex,
262
+ tracker: tracker, dormant_logger: ->(value) { logged << value }
263
+ )
264
+ expect(logged).to contain_exactly(
265
+ %i[vllm provider],
266
+ [:vllm, :instance, 'helios'],
267
+ [:vllm, :model, 'gemma4'],
268
+ [:vllm, :instance, 'helios', :model, 'gemma4'],
269
+ [:vllm, :offering, offering_id]
270
+ )
271
+ expect(logged.flatten).not_to include(:tier)
272
+
273
+ reconciler.observe_dormant!(
274
+ settings: settings, provider_family: :vllm,
275
+ states: { 'helios' => state_for }, mutex: mutex,
276
+ tracker: tracker, dormant_logger: ->(value) { logged << value }
277
+ )
278
+ expect(logged.size).to eq(5)
279
+ end
280
+
281
+ it 'serializes concurrent ordinary commits and leaves cache equal to the final publication' do
282
+ state = state_for
283
+ publications = []
284
+ ready = Queue.new
285
+ start = Queue.new
286
+ replace = lambda do |offerings:, sequence:, **|
287
+ publications << [sequence, offerings]
288
+ end
289
+ run = lambda do |weight|
290
+ ready << true
291
+ start.pop
292
+ reconciler.commit_if_changed!(
293
+ settings: settings_for(provider: { weight: weight }), instance_id: 'helios', state: state,
294
+ discovered_offerings: [identity_draft], mutex: mutex,
295
+ equivalent: ->(previous, current) { previous == current }, replace: replace
296
+ )
297
+ end
298
+ threads = [Thread.new { run.call(110) }, Thread.new { run.call(120) }]
299
+ 2.times { ready.pop }
300
+ 2.times { start << true }
301
+ threads.each(&:join)
302
+
303
+ expect(publications.map(&:first)).to eq([1, 2])
304
+ expect(state[:sequence]).to eq(2)
305
+ expect(state[:offerings]).to equal(publications.last.last)
306
+ expect(state[:offerings].first.weight_inputs).to eq(publications.last.last.first.weight_inputs)
307
+ end
308
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'legion/extensions/llm/inventory/identity'
5
+ require 'legion/extensions/llm/inventory/weight_schema'
6
+
7
+ RSpec.describe 'Inventory::WeightSchema' do
8
+ inventory = Legion::Extensions::Llm::Inventory
9
+
10
+ let(:schema) { inventory.const_get(:WeightSchema) }
11
+ let(:instance_key) do
12
+ inventory::Identity::InstanceKey.new(provider_family: :vllm, instance_id: 'helios')
13
+ end
14
+
15
+ def settings_for(provider: {})
16
+ { extensions: { llm: { vllm: provider } }, llm: { routing: { tier_weights: { direct: 100 } } } }
17
+ end
18
+
19
+ def inputs(settings, native: 'deployment-x', model: 'model-y')
20
+ schema.weight_inputs(
21
+ settings: settings, instance_key: instance_key,
22
+ provider_native_key: native, model: model, tier: :direct
23
+ )
24
+ end
25
+
26
+ it 'multiplies independent scopes without double-counting and lets offering override model' do
27
+ offering_id = inventory::Identity.offering_id(instance_key: instance_key, provider_native_key: 'deployment-x')
28
+ settings = settings_for(
29
+ provider: {
30
+ weight: 100,
31
+ models: { 'model-y' => { weight: 200 } },
32
+ instances: { helios: { weight: 115 } },
33
+ offerings: { offering_id => { weight: 300 } }
34
+ }
35
+ )
36
+
37
+ result = inputs(settings)
38
+ expect(result).to eq(tier: 100, provider: 100, instance: 115, model_or_offering: 300)
39
+ expect(schema.base_weight(result)).to eq(345_000_000)
40
+ end
41
+
42
+ it 'uses provider model weight and lets instance model weight override it' do
43
+ provider = { models: { 'model-y' => { weight: 200 } }, instances: { helios: {} } }
44
+ expect(inputs(settings_for(provider: provider))[:model_or_offering]).to eq(200)
45
+
46
+ provider[:instances][:helios][:models] = { 'model-y' => { weight: 250 } }
47
+ expect(inputs(settings_for(provider: provider))[:model_or_offering]).to eq(250)
48
+ end
49
+
50
+ it 'preserves zero, rejects false, and defaults all missing axes to identity' do
51
+ zero = inputs(settings_for(provider: { weight: 0, instances: { helios: {} } }))
52
+ expect(zero[:provider]).to eq(0)
53
+ expect(schema.base_weight(zero)).to eq(0)
54
+
55
+ expect { inputs(settings_for(provider: { weight: false })) }
56
+ .to raise_error(ArgumentError, /weight component/)
57
+
58
+ identity = inputs(settings_for)
59
+ expect(identity).to eq(tier: 100, provider: 100, instance: 100, model_or_offering: 100)
60
+ expect(schema.base_weight(identity)).to eq(100_000_000)
61
+ end
62
+
63
+ it 'derives offering identity from the provider-native key rather than the model' do
64
+ deployment_id = inventory::Identity.offering_id(
65
+ instance_key: instance_key, provider_native_key: 'deployment-x'
66
+ )
67
+ model_id = inventory::Identity.offering_id(instance_key: instance_key, provider_native_key: 'model-y')
68
+ settings = settings_for(provider: { offerings: { deployment_id => { weight: 321 }, model_id => { weight: 999 } } })
69
+
70
+ expect(inputs(settings)[:model_or_offering]).to eq(321)
71
+ end
72
+
73
+ it 'rejects every present malformed configuration scope instead of defaulting it' do
74
+ cases = {
75
+ 'extensions.llm' => { extensions: { llm: false } },
76
+ 'extensions.llm.vllm' => { extensions: { llm: { vllm: false } } },
77
+ 'provider.instances' => settings_for(provider: { instances: false }),
78
+ 'provider.instances.helios' => settings_for(provider: { instances: { helios: false } }),
79
+ 'llm.routing.tier_weights' => { extensions: { llm: {} }, llm: { routing: { tier_weights: false } } },
80
+ 'provider.offerings' => settings_for(provider: { offerings: false }),
81
+ 'provider.models.model-y' => settings_for(provider: { models: { 'model-y' => false } }),
82
+ 'instance.models.model-y' => settings_for(
83
+ provider: { instances: { helios: { models: { 'model-y' => false } } } }
84
+ )
85
+ }
86
+
87
+ cases.each do |path, settings|
88
+ expect { inputs(settings) }.to raise_error(ArgumentError, /#{Regexp.escape(path)}/)
89
+ end
90
+ end
91
+ end