Class: Insika::ToolStore

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

Overview

DATA-DEFINED tools authored at runtime. One record per tool in the ConfigStore (scope "tools"), keyed by name. It stores the whole ToolDefinition, versions it (like SkillStore/AgentFileStore) and MASKS the credential headers (just as McpStore masks the env): headers whose name is in secret_headers never leave in plaintext to the UI — they become the sentinel __OCULTO__. Only get_raw/ all_raw (consumed by DataDefinedTool/overlay, never the screen) return the real values. Phase 5, Step A.

Record in the ConfigStore:

{ "definition" => { ...ToolDefinition#to_h... },
"updated_at" => iso8601,
"history"    => [ { "definition" =>, "at" => }, ... ] }

Constant Summary collapse

SCOPE =
"tools"
HISTORY_MAX =
20

Instance Method Summary collapse

Constructor Details

#initialize(config_store:) ⇒ ToolStore

Returns a new instance of ToolStore.



22
23
24
# File 'lib/insika/tool_store.rb', line 22

def initialize(config_store:)
  @cs = config_store
end

Instance Method Details

#allObject

-> [Hash] MASKED definitions (for the UI).



39
40
41
# File 'lib/insika/tool_store.rb', line 39

def all
  names.filter_map { |n| get(n) }
end

#all_rawObject

-> [Hash] definitions with REAL headers (for the overlay/registry). Never to the screen.



44
45
46
# File 'lib/insika/tool_store.rb', line 44

def all_raw
  names.filter_map { |n| raw_definition(n) }
end

#delete(name) ⇒ Object

-> bool (did it exist?).



69
# File 'lib/insika/tool_store.rb', line 69

def delete(name) = @cs.delete(SCOPE, name.to_s)

#get(name) ⇒ Object

-> MASKED ToolDefinition Hash (secret headers with sentinel) | nil.



27
28
29
30
# File 'lib/insika/tool_store.rb', line 27

def get(name)
  d = raw_definition(name)
  d && mask_definition(d)
end

#get_raw(name) ⇒ Object

-> ToolDefinition Hash with REAL headers | nil. Internal use (tool/overlay).



33
# File 'lib/insika/tool_store.rb', line 33

def get_raw(name) = raw_definition(name)

#namesObject

-> [String] names, lexicographic order.



36
# File 'lib/insika/tool_store.rb', line 36

def names = @cs.keys(SCOPE)

#restore(name, index) ⇒ Object

Restores version index as the current definition (new write, versions the current). -> MASKED definition Hash.



80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/insika/tool_store.rb', line 80

def restore(name, index)
  rec = record(name)
  raise Insika::NotFoundError, "tool '#{name}' not found" unless rec

  hist = rec.fetch("history", [])
  i = Integer(index)
  raise Insika::ValidationError, "version #{index} does not exist" if i.negative? || i >= hist.length

  # The historical version stores REAL headers -> rewrite directly (bypasses the
  # input masking; write only re-validates the structure).
  write(hist[i]["definition"])
end

#versions(name) ⇒ Object

-> [ { "definition" => MASKED, "at" => } ] most recent first.



72
73
74
75
76
# File 'lib/insika/tool_store.rb', line 72

def versions(name)
  (record(name)&.fetch("history", []) || []).map do |h|
    { "definition" => mask_definition(h["definition"]), "at" => h["at"] }
  end
end

#write(attrs, create_only: false) ⇒ Object

Writes (upsert). attrs is a ToolDefinition Hash (the UI may send secret headers as sentinel: preserves what was stored). create_only refuses to overwrite. Validates via ToolDefinition.from_h. -> MASKED definition Hash.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/insika/tool_store.rb', line 51

def write(attrs, create_only: false)
  definition = Insika::ToolDefinition.from_h(attrs)   # validates (raises ValidationError)
  name = definition.name
  existing = @cs.get(SCOPE, name)
  raise Insika::ValidationError, "tool '#{name}' already exists" if create_only && existing

  final = definition.to_h
  final["request"]["headers"] = reconcile_secret_headers(
    final["request"]["headers"], definition.secret_headers,
    existing&.dig("definition", "request", "headers")
  )

  rec = build_record(final, existing)
  @cs.put(SCOPE, name, rec)
  mask_definition(final)
end