Class: Terret::Tools::Registry
- Inherits:
-
Hames::Service
- Object
- Hames::Service
- Terret::Tools::Registry
- Defined in:
- lib/terret/tools.rb
Overview
ctx.tools — scoped registry + guarded execution pipeline. Registration is an effect (unloading a plugin removes its tools). Execution runs the three-waterfall pipeline: pre_execute (validate / veto / rewrite) -> execute (a provider may replace execution wholesale) -> post_execute (truncate / redact).
Instance Method Summary collapse
-
#execute(call, ctx:) ⇒ Object
Execution runs the three-waterfall pipeline: pre_execute (validate / veto / rewrite) -> execute (a provider may replace execution wholesale) -> post_execute (truncate / redact).
- #fetch(name) ⇒ Object
-
#install_floor(ctx = @ctx, &predicate) ⇒ Object
Install the authoritative deny-by-default floor.
-
#register(name:, description:, params: {}, mutating: false, approval: :never, concurrency: :serial, ctx: @ctx, &handler) ⇒ Object
Returns the registration's disposer.
- #schemas ⇒ Object
- #start(ctx) ⇒ Object
Instance Method Details
#execute(call, ctx:) ⇒ Object
Execution runs the three-waterfall pipeline: pre_execute (validate /
veto / rewrite) -> execute (a provider may replace execution
wholesale) -> post_execute (truncate / redact). Waterfalls dispatch
on ctx, which callers set to the AGENT's forked context so
per-agent policy listeners ride the fork (root listeners still run
first — fork dispatch chains parent-first). ctx is required — a
forgotten kwarg must fail loudly, not silently skip per-agent policy.
One thing a handler is given beyond its arguments: a handler that
declares a session_id: keyword receives the executing call's
session. It is injected here and never model-supplied — it is not a
property of any tool's params schema, and the merge in #handler_args
puts the Call's own value LAST so an argument carrying that name
cannot name somebody else's session.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/terret/tools.rb', line 63 def execute(call, ctx:) admitted = ctx.waterfall("tools/pre_execute", call) return Result.new(id: call.id, content: nil, error: admitted.reason) if admitted.is_a?(Veto) # The deny-by-default floor is AUTHORITATIVE. It runs here — on the # exact call the pre_execute waterfall admitted, rewrites included — # not as a waterfall listener a sibling could register ahead of and # short-circuit past. A pre_execute listener can make policy stricter # (a Veto above), never looser: it cannot admit a tool the floor # denies, because that admission never reaches execution. This is the # autonomous safety mechanism (docs/security.md); a listener from a # third-party bundle must not be able to defeat it. if @floor && (veto = @floor.call(admitted)).is_a?(Veto) return Result.new(id: call.id, content: nil, error: veto.reason) end result = ctx.waterfall("tools/execute", admitted) do |c| begin d = fetch(c.name) Result.new(id: c.id, content: d.handler.call(**handler_args(d, c)), error: nil) rescue Failure => e Result.new(id: c.id, content: nil, error: e.) rescue => e Result.new(id: c.id, content: nil, error: "#{e.class}: #{e.message}") end end ctx.waterfall("tools/post_execute", result) end |
#fetch(name) ⇒ Object
47 |
# File 'lib/terret/tools.rb', line 47 def fetch(name) = @defs.fetch(name.to_s) |
#install_floor(ctx = @ctx, &predicate) ⇒ Object
Install the authoritative deny-by-default floor. The floor is a single predicate #execute consults on the admitted call, deliberately NOT a tools/pre_execute listener: a listener is a peer another row's listener can register ahead of and short-circuit past (the mount-pass bypass that defeated the floor), whereas this gate sees the call that will actually run and its Veto is final. The predicate answers a Veto to deny and anything else to admit. Recorded as an effect of the mounting row (ctx defaults to the Registry's own root), so unloading that row removes the floor; a second install replaces the first and disposal restores whatever it replaced. Only one floor is active — the deny-by-default policy is one floor, with per-session and per-agent variation layered above it (policy/updated and per-fork AllowLists).
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/terret/tools.rb', line 104 def install_floor(ctx = @ctx, &predicate) ctx.effect do previous = @floor # The floor is the autonomous safety mechanism, so a second install # silently swapping it out is worth surfacing. Not refused — a # legitimate re-mount or hot reconfigure disposes the old floor first # (restoring @floor to nil) and then re-installs, so that path sees no # previous floor and stays quiet; only a genuine replace-while-active # warns. if previous warn "terret: install_floor replaced an active tool floor; the " \ "deny-by-default safety floor is now the one just installed" end @floor = predicate -> { @floor = previous } end end |
#register(name:, description:, params: {}, mutating: false, approval: :never, concurrency: :serial, ctx: @ctx, &handler) ⇒ Object
Returns the registration's disposer. The roster itself stays global
(visibility is the AllowList's job, not this method's) — but the
effect that puts a Definition in the roster is recorded on ctx,
which defaults to the registry's own root and so preserves every
existing call site. A caller that passes its forked agent ctx ties
OWNERSHIP to that fork: disposing the agent reaps the registration,
closing the M6-recorded bleed where an agent-registered tool (one
that can carry filesystem authority) outlived the agent that made it.
36 37 38 39 40 41 42 43 44 |
# File 'lib/terret/tools.rb', line 36 def register(name:, description:, params: {}, mutating: false, approval: :never, concurrency: :serial, ctx: @ctx, &handler) d = Definition.new(name: name.to_s, description:, params:, handler:, mutating:, approval:, concurrency:) ctx.effect do @defs[d.name] = d -> { @defs.delete(d.name) } end end |
#schemas ⇒ Object
46 |
# File 'lib/terret/tools.rb', line 46 def schemas = @defs.values.map(&:schema) |
#start(ctx) ⇒ Object
22 23 24 25 26 |
# File 'lib/terret/tools.rb', line 22 def start(ctx) @ctx = ctx @defs = {} @floor = nil end |