Files
mesh-tools/node-tools/internal/console/approvals.go
T
2026-11-12 06:25:56 +01:00

100 lines
4.1 KiB
Go

package console
import (
"log"
"strings"
"git.novox.be/novox/mesh-sdk/go/approval"
)
// verbApprovals is the mesh MCP server's tool that reads and steers what the gate asked on this machine (design 46
// 7-approval-mark §18).
const verbApprovals = "mesh_approvals"
// Approvals is the gate of this machine's tool runner, as mesh_approvals and mesh_describe read it.
type Approvals interface {
List() ([]approval.Listed, error)
Read(id string) (approval.Whole, error)
Applied() []approval.Marks
Describe(module, seat, name string) (bool, string)
Again(id string) error
Cancel(id string) error
// Fill is the state approvals' bytes against its cap, every machine's keys together.
Fill() (bytes uint64, max int64, err error)
}
func approvalsTool() map[string]any {
str := func(desc string) map[string]any { return map[string]any{"type": "string", "description": desc} }
return map[string]any{"name": verbApprovals, "inputSchema": map[string]any{"type": "object", "properties": map[string]any{
"ask": str("an ask's id: its whole arguments and the act's digest"),
"record": str("an ask's id: ask mesh-hand-acts.warranted again for its run, recorded in the hand-act log first"),
"cancel": str("an ask's id: take that open ask back, recorded in the hand-act log first"),
"why": str("why, for record and cancel, kept in the hand-act log"),
}}, "description": "The calls that needed the operator's approval on this machine: with no argument, those asked in " +
"the last day, newest first, with their state and fingerprint, the marks the tool runner applies (each module's " +
"marked tools and verbs with their source and the catalog revision read) and its state's fill; `ask` reads one " +
"whole, `record` asks its hand-act again, `cancel` takes an open ask back."}
}
// approvalsAnswer is mesh_approvals.
func (s *Surface) approvalsAnswer(args map[string]any) map[string]any {
if s.Approvals == nil {
return failure("this tool runner has no approval gate")
}
str := func(k string) string { v, _ := args[k].(string); return strings.TrimSpace(v) }
why := str("why")
if why == "" {
why = "asked by hand with no reason given"
}
act := func(id, what, cause string, do func(string) error) map[string]any {
// A hand act, recorded through the seat mesh-hand-acts before it is done (§18); one that cannot be recorded is refused.
log.Printf("[mesh-tools] hand act: %s %s ask %s: %s", s.who, what, id, why)
if err := s.recordAct(map[string]any{"what": what + " ask " + id, "why": why, "cause": cause, "verb": verbApprovals}); err != nil {
return failure("the hand act was not recorded through mesh-hand-acts, so nothing was done: " + err.Error())
}
if err := do(id); err != nil {
return failure(err.Error())
}
return answerText(map[string]any{"done": what + " ask " + id, "recorded": "mesh-hand-acts.hand-act"})
}
switch {
case str("record") != "":
return act(str("record"), "asked the hand-act of", "approval-record", s.Approvals.Again)
case str("cancel") != "":
return act(str("cancel"), "cancelled", "approval-cancel", s.Approvals.Cancel)
case str("ask") != "":
w, err := s.Approvals.Read(str("ask"))
if err != nil {
return failure(err.Error())
}
return answerText(w)
}
asks, err := s.Approvals.List()
out := map[string]any{"asks": asks, "marks": s.Approvals.Applied()}
if err != nil {
out["asks"], out["asks_why"] = []approval.Listed{}, "the state approvals cannot be read: "+err.Error()
}
if b, max, err := s.Approvals.Fill(); err != nil {
out["fill_why"] = err.Error()
} else {
out["fill"] = map[string]any{"bytes": b, "max_bytes": max, "of": "every machine's keys together"}
}
return answerText(out)
}
// marked is what mesh_describe and a call that reached no one say of a marked tool or verb (§6, §18).
func (s *Surface) marked(module, seat, name string) (bool, string) {
if s.Approvals == nil {
return false, ""
}
return s.Approvals.Describe(module, seat, name)
}
// markedTool is marked for a resolved address.
func (s *Surface) markedTool(t target) (bool, string) {
if t.Seat {
return s.marked("", t.Tool.Module, t.Tool.Name)
}
return s.marked(t.Tool.Module, "", t.Tool.Name)
}