Class: Insika::OverlayToolRegistry

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

Overview

DYNAMIC tool registry: composes the CODE registry (base, built at boot, immutable) with the DATA-DEFINED tools from the ToolStore. Drop-in for ToolRegistry — the Executor/ToolCatalog/ToolEnvelope only use entries/resolve/side_effect?. Phase 5, Step B / D2.

Rules:

- COLLISION: the base (code) ALWAYS wins — a data-tool cannot hijack
the name of a code tool (security, R3). The authoring Command also
refuses to create with a colliding name (code_tool?), but the defense stays here.
- HOT: `reload` re-reads the store and swaps the dynamic index atomically — a
new/edited data-tool takes effect on the next turn without a restart (F5), mirroring
SkillCatalog.reload. An in-flight turn has already captured the index.
- PARITY (NF1): empty ToolStore ⇒ entries/resolve/side_effect? identical to the
pure base. The base (config/wiring.rb) does not even use the overlay — zero regression.

The data-tools enter as NORMAL Registry::Entry (optional: false) — they obey the same per-agent allow/deny as code tools; exposure is the operator's (the /tools matrix), not automatic just because they are "data-defined".

Instance Method Summary collapse

Constructor Details

#initialize(base:, tool_store:, http:, egress: Insika::EgressGuard, egress_options: {}, event_stream: nil) ⇒ OverlayToolRegistry

Returns a new instance of OverlayToolRegistry.



23
24
25
26
27
28
29
30
# File 'lib/insika/overlay_tool_registry.rb', line 23

def initialize(base:, tool_store:, http:, egress: Insika::EgressGuard, egress_options: {}, event_stream: nil)
  @base = base
  @tool_store = tool_store
  @http = http
  @egress = egress
  @egress_options = egress_options
  @event_stream = event_stream
end

Instance Method Details

#code_tool?(name) ⇒ Boolean

Is it a CODE tool (base)? Used by the authoring Command's validation.

Returns:

  • (Boolean)


68
# File 'lib/insika/overlay_tool_registry.rb', line 68

def code_tool?(name) = @base.names.include?(name.to_s)

#entriesObject

Base + dynamic, except dynamic ones that collide with the base (base wins).



33
34
35
# File 'lib/insika/overlay_tool_registry.rb', line 33

def entries
  @base.entries + dynamic.reject { |e| code_tool?(e.name) }
end

#namesObject



37
38
39
# File 'lib/insika/overlay_tool_registry.rb', line 37

def names
  (@base.names + dynamic.map(&:name)).uniq
end

#reloadObject

Atomic swap of the dynamic index (after writing/removing a data-tool).



62
63
64
65
# File 'lib/insika/overlay_tool_registry.rb', line 62

def reload
  @dynamic = build_dynamic
  self
end

#resolve(name) ⇒ Object

-> instance (base wins) | raise NotFoundError.



42
43
44
45
46
47
48
49
50
# File 'lib/insika/overlay_tool_registry.rb', line 42

def resolve(name)
  key = name.to_s
  return @base.resolve(key) if code_tool?(key)

  entry = dynamic.find { |e| e.name == key }
  raise Insika::NotFoundError, "'#{name}' not registered in #{self.class}" unless entry

  entry.factory.call
end

#side_effect?(name) ⇒ Boolean

-> bool; consumed by ToolEnvelope (checkpoint/skip-on-resume). Base wins.

Returns:

  • (Boolean)


53
54
55
56
57
58
59
# File 'lib/insika/overlay_tool_registry.rb', line 53

def side_effect?(name)
  key = name.to_s
  return @base.side_effect?(key) if code_tool?(key)

  entry = dynamic.find { |e| e.name == key }
  entry ? !!entry.[:side_effect] : false
end