Class: Insika::CapabilityRegistry

Inherits:
Object
  • Object
show all
Defined in:
lib/insika/capability_registry.rb

Overview

Intent→implementation resolution. Pure INDIRECTION: does NOT inherit from Registry (which holds executables) — it holds Providers (resolution metadata) and returns the impl_name that ANOTHER registry instantiates. Immutable post-boot by construction (only boot/loader registers), like Registry.

Defined Under Namespace

Classes: Provider

Instance Method Summary collapse

Constructor Details

#initializeCapabilityRegistry

kind: :tool | :workflow priority: Integer | nil (nil = lowest; inherits plugin precedence) available: callable -> bool (default -> { true }; never nil in a registered Provider)



14
15
16
17
# File 'lib/insika/capability_registry.rb', line 14

def initialize
  # capability(Symbol) -> [Provider], na ordem de registro (proxy de announce)
  @providers = Hash.new { |h, k| h[k] = [] }
end

Instance Method Details

#capabilitiesObject



41
# File 'lib/insika/capability_registry.rb', line 41

def capabilities = @providers.keys

#deregister_plugin(plugin_id) ⇒ Object

Loader rollback, symmetric to Registry#deregister_plugin. Removes only the plugin's Providers; capabilities with no remaining provider drop from capabilities (clears the key so the Hash.new-with-block won't recreate it empty).



46
47
48
49
50
# File 'lib/insika/capability_registry.rb', line 46

def deregister_plugin(plugin_id)
  @providers.each_value { |list| list.delete_if { |p| p.plugin == plugin_id.to_s } }
  @providers.delete_if { |_cap, list| list.empty? }
  nil
end

#providers(capability) ⇒ Object



39
# File 'lib/insika/capability_registry.rb', line 39

def providers(capability) = @providers[capability.to_sym].dup

#register(capability, impl_name:, kind:, plugin: nil, priority: nil, available: nil) ⇒ Object

Unlike Registry#register, there is NO "first wins": registering the same capability more than once is the normal case (providers competing) — dedup/tie-breaking happens in resolve, not here.



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/insika/capability_registry.rb', line 22

def register(capability, impl_name:, kind:, plugin: nil, priority: nil, available: nil)
  unless %i[tool workflow].include?(kind)
    raise ArgumentError, "invalid kind: #{kind.inspect} (use :tool or :workflow)"
  end

  if kind == :workflow
    warn "[capability_registry] '#{capability}' registered with kind: :workflow — " \
         "exposure to the agent deferred (L5)"
  end

  @providers[capability.to_sym] << Provider.new(
    capability: capability.to_sym, impl_name: impl_name.to_s, kind: kind,
    plugin: plugin&.to_s, priority: priority, available: available || -> { true }
  )
  self
end

#resolve(capability, profile:, context: {}, event_stream: nil) ⇒ Object

-> chosen Provider | raise CapabilityUnavailable | raise CapabilityAmbiguous. PURE and deterministic: same input → same choice or same error. No IO beyond the Provider's own available.call. Emits :capability_resolved when event_stream: is present (audit).



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/insika/capability_registry.rb', line 56

def resolve(capability, profile:, context: {}, event_stream: nil)
  candidates = providers(capability)
  candidates = candidates.select { |p| p.available.call }
  candidates = apply_deny(candidates, profile)

  raise CapabilityUnavailable.new(capability: capability) if candidates.empty?

  chosen = pick_top(candidates, capability)
  event_stream&.emit(Insika::Event.new(
                       type: :capability_resolved,
                       data: {
                         capability: capability.to_sym,
                         chosen: chosen.impl_name,
                         candidates: candidates.map do |p|
                           { impl_name: p.impl_name, plugin: p.plugin, priority: p.priority }
                         end
                       }
                     ))
  chosen
end