-- safety.lua — workflow safeguards for tool execution. -- Phase 2: M.confirm_tool_call only (per-call confirm gate, with config-driven -- auto-approve policy). See docs/PHASE2.md §6. -- Phase 3 (deferred): destructive-op heuristic + Norris autonomous gate. local rl = require("ffi.readline") local json = require("dkjson") local M = {} -- Render the call as `name({"path":"/tmp"})` for the confirm prompt. -- Truncate to keep one-line prompts. local function pretty_call(name, args) local body = "" if args and next(args) then local ok, encoded = pcall(json.encode, args) if ok then body = (#encoded <= 80) and encoded or (encoded:sub(1, 77) .. "...") else body = "..." end end return name .. "(" .. body .. ")" end -- Ask the user whether tool `name` may be called with `args`, consulting -- `cfg.mcp.auto_approve` first. Policy keys: -- "." → exact-match auto-approve -- ".*" → whole-server auto-approve -- Anything else falls back to a [y/N] prompt; empty / non-"y" answer rejects. function M.confirm_tool_call(name, args, cfg) local policy = (cfg and cfg.mcp and cfg.mcp.auto_approve) or {} if policy[name] then return true end local alias = name:match("^([^.]+)%.") if alias and policy[alias .. ".*"] then return true end local prompt = ("call '%s'? [y/N] "):format(pretty_call(name, args)) local ans = rl.readline(prompt) or "" return ans:lower():sub(1, 1) == "y" end -- ---------------------------------------------------------------- Phase 3 stubs -- Destructive-op heuristic for Norris autonomous mode. Not part of the -- Phase 2 surface (see docs/PHASE2.md §10 / PHASE0.md §11 row 3). function M.is_destructive(cmd) error("safety.is_destructive: not implemented (Phase 3)") end function M.norris_step(plan, broker, executor) error("safety.norris_step: not implemented (Phase 3)") end return M