Module: Legion::Extensions::Llm::Inventory::Identity

Defined in:
lib/legion/extensions/llm/inventory/identity.rb

Overview

Canonical field normalization plus the ONE 5-tuple lane-id composer. The lane id is tier:provider_family:instance_id:type:model, composed here and ONLY here (G22). Every surface that needs an id calls compose_lane_id; validation is shape/parse checks against Taxonomies (R1 boundary contract — it enforces the shape the code produces). The code produces ONLY 5-tuples; nothing else is guarded against.

Defined Under Namespace

Classes: InstanceKey

Class Method Summary collapse

Class Method Details

.compose_lane_id(tier:, provider_family:, instance_id:, type:, model:) ⇒ Object

The ONE 5-tuple composer (G22): byte-identical to the v0.6.16 ScopedRefresher.compose_id. A pure join of the five identity fields — no normalization here; the fields are normalized by their owners (TIERS/TYPES symbols, InstanceKey, normalize_text) before composing.



48
49
50
# File 'lib/legion/extensions/llm/inventory/identity.rb', line 48

def compose_lane_id(tier:, provider_family:, instance_id:, type:, model:)
  "#{tier}:#{provider_family}:#{instance_id}:#{type}:#{model}"
end

.instance_id(config) ⇒ Object

M6: the ONE config→instance-id derivation (R6 owner law). Instance identity is the operator's CONFIG NAME from the provider config; when the operator configured no instance name, "default" is the ordinary label (no reserved values — see InstanceKey). Consumers CARRY this derivation; they never mint a local variant (node canonical names, family fallbacks, and read-time synthesis are deleted).



97
98
99
100
101
# File 'lib/legion/extensions/llm/inventory/identity.rb', line 97

def instance_id(config)
  return config.instance_id.to_s if config.respond_to?(:instance_id) && config.instance_id

  'default'
end

.normalize_enum(value:, field:, allowed:) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
# File 'lib/legion/extensions/llm/inventory/identity.rb', line 32

def normalize_enum(value:, field:, allowed:)
  raise Errors::ValidationError, "#{field} must be a String or Symbol" unless text_input?(value)

  trimmed = value.to_s.strip
  raise Errors::ValidationError, "#{field} must not be empty" if trimmed.empty?

  candidate = trimmed.to_sym
  raise Errors::ValidationError, "#{field} is not one of the allowed values" unless allowed.include?(candidate)

  candidate
end

.normalize_text(value:, field:) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/legion/extensions/llm/inventory/identity.rb', line 19

def normalize_text(value:, field:)
  raise Errors::ValidationError, "#{field} must be a String or Symbol" unless text_input?(value)

  string = value.to_s
  raise Errors::ValidationError, "#{field} is not valid UTF-8" unless valid_utf8?(string)

  trimmed = utf8(string).strip
  raise Errors::ValidationError, "#{field} must not be empty" if trimmed.empty?
  raise Errors::ValidationError, "#{field} must be NFC-normalized" unless trimmed.unicode_normalized?(:nfc)

  trimmed.freeze
end

.parse_lane_id(id) ⇒ Object

Bounded parse: split(':', 5) — the 5th part keeps its colons, which is load-bearing: model names contain ':' (Ollama model:tag, Bedrock model ids). Structural only — field semantics are validate_lane_id!'s.



55
56
57
58
59
60
61
62
# File 'lib/legion/extensions/llm/inventory/identity.rb', line 55

def parse_lane_id(id)
  raise Errors::ValidationError, 'lane id must be a String or Symbol' unless text_input?(id)

  parts = id.to_s.split(':', 5)
  raise Errors::ValidationError, "lane id must have exactly 5 parts, got #{parts.length}" unless parts.length == 5

  parts.freeze
end

.validate_lane_id!(value:) ⇒ Object

Shape validation (R1 boundary contract): exactly 5 parts (bounded split — the model keeps its colons), part 1 in TIERS, part 4 in TYPES, parts 2/3/5 nonempty NFC. Any value that is not a 5 tuple fails the part-count check on its own — there is no special case for anything else.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/legion/extensions/llm/inventory/identity.rb', line 69

def validate_lane_id!(value:)
  parts = parse_lane_id(value)
  tier, provider_family, instance_id, type, model = parts
  raise Errors::ValidationError, "lane id tier must be a Taxonomies::TIERS value, got #{tier}" \
    unless Taxonomies::TIERS.include?(tier.to_sym)
  raise Errors::ValidationError, "lane id type must be a Taxonomies::TYPES value, got #{type}" \
    unless Taxonomies::TYPES.include?(type.to_sym)
  raise Errors::ValidationError, 'lane id provider_family must be a nonempty NFC String' \
    unless nonempty_nfc?(provider_family)
  raise Errors::ValidationError, 'lane id instance_id must be a nonempty NFC String' \
    unless nonempty_nfc?(instance_id)
  raise Errors::ValidationError, 'lane id model must be a nonempty NFC String' \
    unless nonempty_nfc?(model)

  value
end