Files
mesh-tools/node-tools/internal/console/address.go
T
jschoubben 6851ee1657 A-7 tools: mesh_approvals reads and steers what was asked; mesh_describe says a mark and its source
The mesh MCP server gains mesh_approvals: with no argument the calls asked on the machine
in the last day, newest first, with state and fingerprint, the marks the gate applies with
their sources and the catalog revision, and the state's fill against its cap (every
machine's keys together); an ask read whole with the act's digest; record asking the
hand-act again, and cancel taking an open ask back, each recorded in the hand-act log first
and refused when it cannot be. mesh_describe answers needs-approval with its source, and a
marked call that reached no one names withheld and mesh-grants.grants_user_list.

Builds: design 46 7-approval-mark §18, §6 (the mesh MCP server's withheld answer), §22 (eight tools)

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 05:24:41 +01:00

1192 lines
44 KiB
Go

package console
// The mesh's tools found by address, not announced whole (novox/hq ADR 0195, to-be 34 §3a).
//
// The console announces seven tools — five to find and call, mesh_runtimes to say which runtimes
// discovery heard, and mesh_wait to wait on the bus for an event (novox/hq ADR 0367). Everything the mesh answers is reached through them by one address per layer:
//
// <seat>.<verb> a seat held once for the mesh — its holder answers
// <node>/<seat>.<verb> a seat held once per machine — that machine's holder answers
// <node>/<module>.<tool> a module assigned to a machine — that assignment answers
// <module>.<tool> also, for a module whose instances are interchangeable (ADR 0160)
//
// A module that is not interchangeable is called with its machine or refused, naming the machines
// it runs on: "whichever answers" is no answer for state a machine holds.
//
// Every discovery verb asks the mesh when it is called — kept a few seconds at most, never for a
// session — so a tool that arrived a minute ago is found without the client reconnecting.
import (
assigner "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"context"
"encoding/json"
"errors"
"fmt"
"sort"
"strings"
"sync"
"time"
"github.com/novox/mesh-tools/node-tools/internal/announce"
"github.com/novox/mesh-tools/node-tools/internal/bus"
"github.com/novox/mesh-tools/node-tools/internal/firstview"
)
// IndexKept is how long what the mesh answered is kept before it is asked again: long enough that
// one agent turn's search, describe and call ask once, short enough that nothing goes stale.
var IndexKept = 5 * time.Second
// The seven tools the console announces. Names of the API's kind — letters, digits, `_`, `-`.
const (
verbOverview = "mesh_overview"
verbMachine = "mesh_machine"
verbSearch = "mesh_search"
verbDescribe = "mesh_describe"
verbCall = "mesh_call"
verbRuntimes = "mesh_runtimes"
verbFirstViews = "mesh_first_views"
verbFirstAgain = "mesh_first_view_again"
verbWait = "mesh_wait"
)
// searchCap is how many matches a search answers before it says how many more there were.
const searchCap = 25
const grammar = "Addresses: `<seat>.<verb>` for a seat held once for the mesh (e.g. `mesh-module-assigner.assignments`); " +
"`<node>/<seat>.<verb>` for a seat every machine holds (e.g. `ace/node-packet-filter.rules`); " +
"`<node>/<module>.<tool>` for a module on one machine (e.g. `novox/postgres.postgres_list_databases`); " +
"and `<module>.<tool>` also for a module whose instances are interchangeable."
// discovery is the seven tools as tools/list announces them.
func discovery() []map[string]any {
str := func(desc string) map[string]any { return map[string]any{"type": "string", "description": desc} }
obj := func(props map[string]any, required ...string) map[string]any {
s := map[string]any{"type": "object", "properties": props}
if len(required) > 0 {
s["required"] = required
}
return s
}
return []map[string]any{
{"name": verbOverview, "inputSchema": obj(map[string]any{}),
"description": "The mesh at a glance: the seats it holds once for the whole mesh with their verbs, the seats " +
"every machine holds, and its machines. Start here, then `mesh_machine` for one machine. " + grammar},
{"name": verbMachine, "inputSchema": obj(map[string]any{"node": str("the machine, as mesh_overview names it")}, "node"),
"description": "One machine: the seats it holds with their verbs, and the modules assigned to it with their tools — " +
"each with the address to describe or call it by. " + grammar},
{"name": verbSearch, "inputSchema": obj(map[string]any{"query": str("words to find in tool names and descriptions, e.g. `postgres databases` — or the shell command you would have run, e.g. `journalctl -u x`, `docker ps`")}, "query"),
"description": "Find tools anywhere in the mesh by words: every match's address and a line of what it does, across the " +
"mesh's seats, the machines' seats and every module on every machine, best first. A shell command finds the tool " +
"that replaces it — search before reaching for ssh, journalctl, systemctl or docker. " + grammar},
{"name": verbDescribe, "inputSchema": obj(map[string]any{"address": str("the tool's address")}, "address"),
"description": "What one tool does and the arguments it takes, as a JSON schema. The machine is in the address, " +
"never an argument. " + grammar},
{"name": verbCall, "inputSchema": obj(map[string]any{
"address": str("the tool's address"),
"arguments": map[string]any{"type": "object", "description": "the tool's arguments, as mesh_describe gives its schema"},
}, "address"),
"description": "Call one tool by its address with its arguments; the answer says which machine gave it. " + grammar},
{"name": verbRuntimes, "inputSchema": obj(map[string]any{}),
"description": "Which runtimes answered discovery, asked now: for each, its machine, how long its answer took to " +
"arrive, its size in bytes, how many modules and tools it announced, whether it was shortened to fit the " +
"bus, and when it was last heard — and every runtime or machine expected that was not heard. Read-only; " +
"for when an address is said to be missing or on another machine."},
{"name": verbFirstViews, "inputSchema": obj(map[string]any{}),
"description": "The tool runner's first views: the assigner's assignments and seat-holders, and the catalog half's registrations, the gate's source and the view of each watched branch (which modules exist, each with its manifest): for each, the owner, the tool " +
"asked, the revision held and when it was made, how many keys, when it was asked and answered, how often " +
"it was asked and how often by hand, whether its condition is raised, and why it was last asked. Read-only."},
{"name": verbFirstAgain, "inputSchema": obj(map[string]any{
"why": map[string]any{"type": "string", "description": "why it is asked again, kept in the log"},
}), "description": "Ask the tool runner's four first views again, now, and say what each answers. A hand act, recorded in " +
"the runner's log with its reason; for when a picture is doubted, or after an owner's state was made again."},
waitTool(),
approvalsTool(),
}
}
func isDiscovery(name string) bool {
switch name {
case verbOverview, verbMachine, verbSearch, verbDescribe, verbCall, verbRuntimes, verbFirstViews, verbFirstAgain, verbWait, verbApprovals:
return true
}
return false
}
// seatInfo is a seat as the mesh's records define it, and who holds it where.
type seatInfo struct {
Seat string
Scope string // "mesh" or "node"
Verbs []Tool
Holders []holder
}
type holder struct {
Module string `json:"module"`
Node string `json:"node"`
}
// moduleInfo is a module that answers tools: where it runs, whether any instance will do, and its
// tools as one of its instances described them.
type moduleInfo struct {
Module string
On []string
Interchangeable bool
Tools []Tool
}
// index is what the mesh answered about itself, at one moment.
type index struct {
Seats []seatInfo
Machines []string
Modules map[string]*moduleInfo
NotAnswering []string
Listing *Listing // the flat catalogue the call path resolves subjects with
Discovery announce.Discovery
// Recorded is where the assigner places each module that declares tools.
Recorded map[string][]string
// Unheard is every runtime whose answer this index lacks: it said it was there and did not say
// what it serves in time, or it answered before and not now. An address it might answer is never
// called missing while it is here (2026-10-05).
Unheard []unheard
// Placed is the state seat-holders as the seat mesh-module-assigner's holder wrote it (novox/hq ADR 0571 clause 4), by its
// keys `<seat>` and `<seat>.<node>`, read with assigner.HoldersOf; PlacedWhy says why it could not be read.
Placed map[string][]byte
PlacedWhy string
// Replaces is what each verb and tool replaces (novox/hq ADR 0245):
// `seat:<seat>.<verb>` and `<module>.<tool>`.
Replaces map[string][]string
// Earlier is every runtime not heard this round whose last answer, within RuntimeForgotten, this
// index holds instead (novox/hq issue 554), by runtimeKey: when it was heard.
Earlier map[string]time.Time
// Stale is every module on a machine (`mod:<module>@<node>`) and every seat's holder
// (`seat:<seat>@<node>`) this index knows only from an earlier answer: from whom, and when.
Stale map[string]staleFrom
}
// staleFrom is where an address known only from an earlier round of discovery was heard.
type staleFrom struct {
Runtime string
Machine string
At time.Time
}
// say is the sentence a call or a description carries for it.
func (f staleFrom) say() string {
who := f.Runtime
if f.Machine != "" {
who += " on " + f.Machine
}
return fmt.Sprintf("found from what %s announced %s ago: it did not answer the latest round of discovery",
who, time.Since(f.At).Round(time.Second))
}
// earlier is a runtime's last answer, used for a round that did not hear it.
type earlier struct {
Heard announce.Heard
At time.Time
}
func endpointKey(e announce.Endpoint) string {
if e.Kind == announce.KindSeat {
return "seat:" + e.Seat + "@" + e.Node
}
return "mod:" + e.Module + "@" + e.Node
}
// unheard is one runtime discovery did not hear in full, and why.
type unheard struct {
Runtime string `json:"runtime"`
Machine string `json:"machine,omitempty"`
Why string `json:"why"`
}
// unheardOn is what is unheard on one machine; with no machine, everything unheard.
func (x *index) unheardOn(node string) []unheard {
var out []unheard
for _, u := range x.Unheard {
if node == "" || u.Machine == node || u.Machine == "" {
out = append(out, u)
}
}
return out
}
// sayUnheard is the unheard, as a sentence's end.
func sayUnheard(us []unheard) string {
var parts []string
for _, u := range us {
who := u.Runtime
if u.Machine != "" {
who += " on " + u.Machine
}
parts = append(parts, who+" ("+u.Why+")")
}
return strings.Join(parts, "; ")
}
func (x *index) seat(name string) *seatInfo {
for i := range x.Seats {
if x.Seats[i].Seat == name {
return &x.Seats[i]
}
}
return nil
}
func findTool(tools []Tool, name string) *Tool {
for i := range tools {
if tools[i].Name == name {
return &tools[i]
}
}
return nil
}
// recordedModule is a module as the bus holds it: where the assigner seat places it, and whether its manifest declares
// tools — what should announce itself, and where (novox/hq ADR 0571).
type recordedModule struct {
Module string `json:"module"`
On []string `json:"on"`
Tools bool `json:"tools"`
// Replaces is what each of its own tools replaces (novox/hq ADR 0245).
Replaces map[string][]string `json:"replaces,omitempty"`
}
// indexOn asks the mesh what it holds (novox/hq ADR 0197): what answers, from every runtime's own
// announcement on the bus — one `$SRV.INFO` request — and what should, from the indexer's records
// read as JSON. Nothing is inferred from a roster and nothing is parsed from print.
//
// `held` is what the console holds already, so an instance whose announcement is unchanged is not
// asked for it again; `before` is the last answer of every runtime this round did not hear, which the
// index holds instead, marked with its age (novox/hq issue 554).
func indexOn(conn *bus.Conn, src Source, held announce.Known, before func(announce.Discovery) []earlier) (*index, error) {
var wg sync.WaitGroup
var placed map[string][]byte
var placedErr error
var recorded []recordedModule
var known []string
var recordsWhy []string
wg.Add(2)
// Which modules there are and where they are placed, and which machines the mesh has, from the bus (ADR 0571
// clauses 4 and 5): the indexer's modules and manifests and the assigner seat's assignments, never any other record.
go func() { defer wg.Done(); recorded, known, recordsWhy = recordsOn(src) }()
// Who holds which seat on which node, from the assigner (ADR 0571 clause 4): no other call.
go func() { defer wg.Done(); placed, placedErr = seatHoldersOn(src) }()
d, err := announce.Gather(conn, held)
wg.Wait()
if err != nil {
return nil, err
}
var endpoints []announce.Endpoint
fresh := map[string]bool{}
for _, h := range d.Heard {
for _, e := range announce.Endpoints(h.Info) {
fresh[endpointKey(e)] = true
endpoints = append(endpoints, e)
}
}
// **A runtime not heard this round is not gone** (novox/hq issue 554): a round in which no answer
// crossed the laptop's link in time replaced a full index with an empty one, and every seat and
// module call was refused. What it announced last, within RuntimeForgotten, stands — said with its age.
stale := map[string]staleFrom{}
earlierAt := map[string]time.Time{}
if before != nil {
for _, b := range before(d) {
earlierAt[runtimeKey(b.Heard.Info.Name, b.Heard.Info.ID)] = b.At
for _, e := range announce.Endpoints(b.Heard.Info) {
k := endpointKey(e)
if fresh[k] {
continue
}
if _, have := stale[k]; !have {
stale[k] = staleFrom{Runtime: b.Heard.Info.Name, Machine: b.Heard.Machine(), At: b.At}
}
endpoints = append(endpoints, e)
}
}
}
x, announced, machines := indexOfEndpoints(endpoints)
l := x.Listing
x.Discovery = d
x.Stale, x.Earlier = stale, earlierAt
x.Placed = placed
if placedErr != nil {
x.PlacedWhy = placedErr.Error()
}
for _, s := range d.Silent {
x.Unheard = append(x.Unheard, unheard{Runtime: s.Name, Machine: s.Machine,
Why: fmt.Sprintf("it answered PING; what it serves did not arrive within %s", announce.Patience)})
}
// What should have answered: every assignment of a module that declares tools. Silence is named;
// a module with no tools is never a name here.
x.Replaces = replacesOf(catalogue.Seats(), recorded)
for _, m := range recorded {
if !m.Tools {
continue
}
x.Recorded[m.Module] = append([]string{}, m.On...)
for _, n := range m.On {
// A holder of a seat held once for the mesh announces no machine — the seat is the mesh's,
// not a machine's — so what it announced without one answers for wherever it is assigned.
if !announced[m.Module][n] && !announced[m.Module][""] {
l.NotAnswering = append(l.NotAnswering, m.Module+" on "+n)
}
}
}
l.NotAnswering = append(l.NotAnswering, recordsWhy...)
if x.PlacedWhy != "" {
l.NotAnswering = append(l.NotAnswering, firstview.SeatHoldersRead+" (who holds which seat by assignment could not be read: "+
x.PlacedWhy+")")
}
for _, u := range x.Unheard {
l.NotAnswering = append(l.NotAnswering, "the runtime "+sayUnheard([]unheard{u}))
}
sort.Strings(l.NotAnswering)
x.NotAnswering = l.NotAnswering
for _, n := range known {
machines[n] = true
}
for n := range machines {
x.Machines = append(x.Machines, n)
}
sort.Strings(x.Machines)
return x, nil
}
// Source is where the console reads what it indexes the mesh by: the assigner's assignments and seat-holders, by
// `<owner>.<state>` (firstview.AssignmentsRead and SeatHoldersRead), and the modules of the catalog half with their entries.
// The tool runner's is its first views (firstview.Views), never the owners' state (novox/hq ADR 0615 §1; design 47 G §196, §594).
type Source interface {
// State is every current value of a state by key; an error says why it cannot be told yet.
State(name string) (map[string][]byte, error)
// Modules is every module the catalog half holds, by name, with its entry; an error says why it cannot be told yet.
Modules() (map[string]meshcatalog.CatalogEntry, error)
}
// seatHoldersOn reads the state seat-holders whole (novox/hq ADR 0571 clause 4), as firstview.SeatHoldersRead.
func seatHoldersOn(src Source) (map[string][]byte, error) {
return src.State(firstview.SeatHoldersRead)
}
// manifestFacts is what the console takes of a manifest: the tools it declares and what each replaces.
type manifestFacts struct {
Tools []string `json:"tools"`
Replaces map[string][]string `json:"replaces"`
}
// recordsOn is every module the indexer holds, with where the assigner seat places it and whether it declares tools,
// and every machine the assignments name (moduleNames, assigner.MachineNames). What cannot be read is said, one line
// per state, for the listing's not answering.
func recordsOn(src Source) (recorded []recordedModule, machines []string, why []string) {
held, err := src.State(firstview.AssignmentsRead)
if err != nil {
why = append(why, firstview.AssignmentsRead+" (where modules are placed could not be read, so what is missing cannot "+
"be said: "+err.Error()+")")
}
machines = assigner.MachineNames(held)
on := map[string][]string{}
// The assignments' keys in either form, a node key and its module keys or the form before (research 072 row 37).
assembled, err := assigner.AssignmentsOf(held)
if err != nil {
why = append(why, firstview.AssignmentsRead+" (not every key is an assignment: "+err.Error()+")")
}
for _, node := range machines {
for _, m := range assembled[node].Modules {
on[m.Module] = append(on[m.Module], node)
}
}
entries, err := src.Modules()
if err != nil {
return nil, machines, append(why, "the catalog half's modules (which modules there are could not be read: "+err.Error()+")")
}
names := make([]string, 0, len(entries))
for name := range entries {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
r := recordedModule{Module: name, On: on[name]}
if m := entries[name].Manifest; len(m) > 0 {
facts, err := manifestOf(m)
if err != nil {
why = append(why, name+" (its manifest could not be read, so whether it answers tools is not known: "+err.Error()+")")
} else {
r.Tools, r.Replaces = len(facts.Tools) > 0, facts.Replaces
}
}
recorded = append(recorded, r)
}
return recorded, machines, why
}
// manifestOf is what the console takes of the manifest an entry of the catalog half carries whole (design 47 G §443).
func manifestOf(manifest map[string]any) (manifestFacts, error) {
raw, err := json.Marshal(manifest)
if err != nil {
return manifestFacts{}, err
}
var facts manifestFacts
if err := json.Unmarshal(raw, &facts); err != nil {
return manifestFacts{}, err
}
return facts, nil
}
// placedHolders is who holds seat (on node, for a seat held per machine) by assignment, or none.
func (x *index) placedHolders(seat, node string) []meshmoduleassigner.SeatHoldersHoldersItem {
if len(x.Placed) == 0 {
return nil
}
hs, err := assigner.HoldersOf(x.Placed, seat, node)
if err != nil || len(hs) > 0 || node != "" {
return hs
}
// A seat held per machine, asked without one: every node's key of it.
var all []meshmoduleassigner.SeatHoldersHoldersItem
for key := range x.Placed {
if s, n, ok := strings.Cut(key, "."); ok && s == seat {
if more, err := assigner.HoldersOf(x.Placed, seat, n); err == nil {
all = append(all, more...)
}
}
}
sort.Slice(all, func(i, j int) bool { return all[i].Node+"/"+all[i].Module < all[j].Node+"/"+all[j].Module })
return all
}
func containsHolder(hs []holder, h holder) bool {
for _, x := range hs {
if x == h {
return true
}
}
return false
}
func (s *Surface) index() (*index, error) { return s.indexAsked(false) }
// indexAsked is what the mesh answered, asked again when `fresh` or when the kept answer is old.
//
// **A round that heard nothing is not kept** (novox/hq issue 554): the index it gives holds what every
// runtime announced last, marked with its age, and the next question asks again rather than living
// with it for IndexKept.
func (s *Surface) indexAsked(fresh bool) (*index, error) {
s.mu.Lock()
if !fresh && s.idx != nil && time.Since(s.idxAt) <= IndexKept {
x := s.idx
s.mu.Unlock()
return x, nil
}
s.mu.Unlock()
x, err := indexOn(s.conn, s.src, s.held, s.before)
if err != nil {
return nil, err
}
s.mu.Lock()
s.remember(x)
s.sayRound(x)
s.idx = x
if !x.Discovery.Empty() {
s.idxAt = time.Now()
}
s.mu.Unlock()
return x, nil
}
// indexOfEndpoints is what the announced endpoints say: every seat with its verbs and holders, every
// module with its tools and machines, the flat listing, and which module announced on which machine.
func indexOfEndpoints(endpoints []announce.Endpoint) (*index, map[string]map[string]bool, map[string]bool) {
l := &Listing{Tools: []Tool{}, NotAnswering: []string{}}
x := &index{Modules: map[string]*moduleInfo{}, Listing: l, Recorded: map[string][]string{}}
announced := map[string]map[string]bool{} // module → node → announced something
seats := map[string]*seatInfo{}
// <module>.<tool>, or seat:<seat>.<verb>, → index in l.Tools. **Apart**, because a seat and a module may
// share a name — mesh-delivery is the delivery's seat and the module holding it (novox/hq ADR 0239), and
// the module answers the seat's verbs with tools of the same names: in one namespace the module's tool
// took the key first, and the seat was listed with no verb at all (novox/hq issue 287).
toolAt := map[string]int{}
machines := map[string]bool{}
for _, e := range endpoints {
if e.Node != "" {
machines[e.Node] = true
}
if announced[e.Module] == nil {
announced[e.Module] = map[string]bool{}
}
announced[e.Module][e.Node] = true
switch e.Kind {
case announce.KindSeat:
st := seats[e.Seat]
if st == nil {
st = &seatInfo{Seat: e.Seat, Scope: e.Scope}
seats[e.Seat] = st
}
if st.Scope != "node" && e.Scope == "node" {
st.Scope = "node"
}
h := holder{Module: e.Module, Node: e.Node}
if !containsHolder(st.Holders, h) {
st.Holders = append(st.Holders, h)
}
key := "seat:" + e.Seat + "." + e.Tool
if _, have := toolAt[key]; !have {
toolAt[key] = len(l.Tools)
t := Tool{Module: e.Seat, Name: e.Tool, Description: e.Description, Input: e.Schema, Seat: true, Scope: st.Scope}
l.Tools = append(l.Tools, t)
st.Verbs = append(st.Verbs, t)
}
case announce.KindTool:
m := x.Modules[e.Module]
if m == nil {
m = &moduleInfo{Module: e.Module}
x.Modules[e.Module] = m
}
if e.Node != "" && !contains(m.On, e.Node) {
m.On = append(m.On, e.Node)
}
m.Interchangeable = m.Interchangeable || e.Interchangeable
key := e.Module + "." + e.Tool
i, have := toolAt[key]
if !have {
i = len(l.Tools)
toolAt[key] = i
l.Tools = append(l.Tools, Tool{Module: e.Module, Name: e.Tool, Description: e.Description, Input: e.Schema})
}
if !contains(l.Tools[i].Subjects, e.Subject) {
l.Tools[i].Subjects = append(l.Tools[i].Subjects, e.Subject)
}
}
}
// A tool's subjects as a call looks them up: the plain one any instance answers first, then each
// machine's.
for i := range l.Tools {
t := &l.Tools[i]
if t.Seat {
continue
}
plain := "mesh.mod." + t.Module + ".tool." + t.Name
sort.SliceStable(t.Subjects, func(a, b int) bool {
if (t.Subjects[a] == plain) != (t.Subjects[b] == plain) {
return t.Subjects[a] == plain
}
return t.Subjects[a] < t.Subjects[b]
})
}
for _, t := range l.Tools {
if !t.Seat {
x.Modules[t.Module].Tools = append(x.Modules[t.Module].Tools, t)
}
}
for _, m := range x.Modules {
sort.Strings(m.On)
}
for _, st := range seats {
sort.Slice(st.Holders, func(a, b int) bool { return st.Holders[a].Node < st.Holders[b].Node })
x.Seats = append(x.Seats, *st)
}
sort.Slice(x.Seats, func(i, j int) bool { return x.Seats[i].Seat < x.Seats[j].Seat })
sort.SliceStable(l.Tools, func(i, j int) bool {
return l.Tools[i].Module+"."+l.Tools[i].Name < l.Tools[j].Module+"."+l.Tools[j].Name
})
return x, announced, machines
}
// target is what an address resolves to.
type target struct {
Address string
Key string // the call key the existing path takes: seat:<s>.<v>[@node] or <m>.<t>[@node]
Name string // <seat>.<verb> or <module>.<tool>, for the listing's subject lookup
Node string
Tool Tool
Seat bool
// Stale is set when the target is known only from an earlier round of discovery (novox/hq issue 554).
Stale *staleFrom
}
// staleOf is where a target was heard, when every place it answers is known only from an earlier round.
func (x *index) staleOf(keys []string) *staleFrom {
if len(keys) == 0 || len(x.Stale) == 0 {
return nil
}
var first *staleFrom
for _, k := range keys {
f, ok := x.Stale[k]
if !ok {
return nil
}
if first == nil {
first = &f
}
}
return first
}
// resolve turns an address into exactly one target, or says why it cannot.
func resolve(x *index, address string) (target, error) {
address = strings.TrimSpace(address)
node, rest, hasNode := strings.Cut(address, "/")
if !hasNode {
rest, node = address, ""
}
dot := strings.Index(rest, ".")
if dot <= 0 || dot == len(rest)-1 || strings.Contains(rest, "/") {
return target{}, fmt.Errorf("%q is not an address. %s", address, grammar)
}
prefix, name := rest[:dot], rest[dot+1:]
s := x.seat(prefix)
if s != nil && x.Modules[prefix] != nil && findTool(x.Modules[prefix].Tools, name) != nil {
// **A seat and a module of one name** (novox/hq issue 287): mesh-delivery is the delivery's seat and
// the module holding it. The module's tool is meant when the seat has no such verb, or when a machine
// is named for a seat held once for the mesh — `<node>/<module>.<tool>` is a module on one machine.
if findTool(s.Verbs, name) == nil || (node != "" && s.Scope != "node") {
s = nil
}
}
if s != nil {
verb := findTool(s.Verbs, name)
if verb == nil {
if len(s.Verbs) == 0 {
return target{}, fmt.Errorf("the seat %s has no verb %s: its holder announced none", prefix, name)
}
return target{}, fmt.Errorf("the seat %s has no verb %s; it has %s", prefix, name, toolNames(s.Verbs))
}
if s.Scope == "node" {
if node == "" {
return target{}, fmt.Errorf("%s is held once per machine: write <node>/%s — it is held on %s",
prefix, rest, orNobody(nodesOf(s.Holders)))
}
return target{Address: node + "/" + rest, Key: "seat:" + rest + "@" + node, Name: rest, Node: node, Tool: *verb, Seat: true,
Stale: x.staleOf([]string{"seat:" + prefix + "@" + node})}, nil
}
if node != "" {
return target{}, fmt.Errorf("%s is held once for the whole mesh: write %s, without a machine", prefix, rest)
}
var holders []string
for _, h := range s.Holders {
holders = append(holders, "seat:"+prefix+"@"+h.Node)
}
return target{Address: rest, Key: "seat:" + rest, Name: rest, Tool: *verb, Seat: true, Stale: x.staleOf(holders)}, nil
}
m := x.Modules[prefix]
if m == nil {
if on := x.Recorded[prefix]; len(on) > 0 {
why := "it did not announce itself on the bus"
if us := x.unheardOnAny(on); len(us) > 0 {
why = "discovery did not hear in full from " + sayUnheard(us)
}
return target{}, fmt.Errorf("%s is assigned to %s in the mesh's records, but nothing that answered discovery serves it: %s. "+
"Ask again in a moment; mesh_runtimes shows which runtimes answered", prefix, strings.Join(on, ", "), why)
}
if hs := x.placedHolders(prefix, node); len(hs) > 0 {
var held []string
for _, h := range hs {
held = append(held, h.Module+" on "+h.Node)
}
return target{}, fmt.Errorf("the seat %s is held by %s by assignment, but no holder of it announced itself on the bus. "+
"Ask again in a moment; mesh_runtimes shows which runtimes answered", prefix, strings.Join(held, ", "))
}
if len(x.Unheard) > 0 {
return target{}, fmt.Errorf("nothing that answered discovery is called %s, but not every runtime answered: %s — "+
"%s may be theirs. Ask again in a moment; mesh_runtimes shows which runtimes answered", prefix, sayUnheard(x.Unheard), prefix)
}
if x.Discovery.Empty() {
return target{}, fmt.Errorf("nothing answered discovery at all within %s, not even PING, and this console holds no "+
"earlier answer: whether anything is called %s is not known. Ask again in a moment; mesh_runtimes shows which "+
"runtimes answered", x.Discovery.Waited.Round(time.Millisecond), prefix)
}
return target{}, fmt.Errorf("nothing in the mesh is called %s: no seat, and no module that answers tools. "+
"mesh_search finds a tool by words", prefix)
}
tool := findTool(m.Tools, name)
if tool == nil {
return target{}, fmt.Errorf("%s has no tool %s; it has %s", prefix, name, toolNames(m.Tools))
}
if node == "" {
if !m.Interchangeable {
return target{}, fmt.Errorf("%s keeps state on each machine it runs on, so a call names the machine: "+
"write <node>/%s — it runs on %s", prefix, rest, orNobody(m.On))
}
var on []string
for _, n := range m.On {
on = append(on, "mod:"+prefix+"@"+n)
}
return target{Address: rest, Key: rest, Name: rest, Tool: *tool, Stale: x.staleOf(on)}, nil
}
if len(m.On) > 0 && !contains(m.On, node) {
if us := x.unheardOn(node); len(us) > 0 || contains(x.Recorded[prefix], node) {
why := "it did not announce itself there"
if len(us) > 0 {
why = "discovery did not hear in full from " + sayUnheard(us)
}
return target{}, fmt.Errorf("%s on %s did not answer discovery: %s. It was heard on %s. "+
"Ask again in a moment; mesh_runtimes shows which runtimes answered", prefix, node, why, orNobody(m.On))
}
return target{}, fmt.Errorf("%s does not run on %s; it runs on %s", prefix, node, orNobody(m.On))
}
return target{Address: node + "/" + rest, Key: rest + "@" + node, Name: rest, Node: node, Tool: *tool,
Stale: x.staleOf([]string{"mod:" + prefix + "@" + node})}, nil
}
// unheardOnAny is what is unheard on any of these machines.
func (x *index) unheardOnAny(nodes []string) []unheard {
var out []unheard
for _, u := range x.Unheard {
if u.Machine == "" || contains(nodes, u.Machine) {
out = append(out, u)
}
}
return out
}
func contains(xs []string, s string) bool {
for _, x := range xs {
if x == s {
return true
}
}
return false
}
func toolNames(ts []Tool) string {
names := make([]string, 0, len(ts))
for _, t := range ts {
names = append(names, t.Name)
}
sort.Strings(names)
return strings.Join(names, ", ")
}
func nodesOf(hs []holder) []string {
var out []string
for _, h := range hs {
if h.Node != "" && !contains(out, h.Node) {
out = append(out, h.Node)
}
}
sort.Strings(out)
return out
}
func orNobody(nodes []string) string {
if len(nodes) == 0 {
return "no machine the mesh knows of"
}
return strings.Join(nodes, ", ")
}
// firstLine is a description's first line, for a list.
func firstLine(s string) string {
s = strings.TrimSpace(s)
if i := strings.IndexAny(s, "\n"); i >= 0 {
s = s[:i]
}
if len(s) > 160 {
s = s[:157] + "…"
}
return s
}
// schemaAsPassed is a tool's schema as an agent passes it at this address. Where the address names
// the machine, `node` is taken out: the machine is in the address. Where it does not — a seat held
// once for the mesh, or an interchangeable module — `node` is the tool's own argument, and is kept.
//
// **Taking it out everywhere made the mesh's own verbs lie** (novox/hq issue 244): the old controller verbs
// `push`, `plan`, `assign`, `pin` and `settings` take the machine they act on as `node`, and were
// described without it — `push` as taking nothing at all, so a push naming one machine arrived as a
// push of every machine behind.
func schemaAsPassed(t target) map[string]any {
if t.Node == "" {
return asSchema(t.Tool.Input)
}
return schemaWithoutNode(t.Tool.Input)
}
// argumentsAsSent are a call's arguments as the tool at this address receives them, or why the call
// is refused. **Nothing given is dropped without a word** (novox/hq issue 244):
// - where the address names the machine, a `node` naming the same machine is redundant and taken
// out; one naming another machine is refused — which of the two was meant is not guessed;
// - where it does not, `node` is the tool's own argument and goes to it like any other;
// - a seat's schema is the mesh's record of the verb, so an argument a seat's verb does not
// declare is refused here, naming it, rather than sent to be passed over.
func argumentsAsSent(t target, given map[string]any) (map[string]any, error) {
args := map[string]any{}
for k, v := range given {
args[k] = v
}
if n, has := args["node"]; has && t.Node != "" {
if named, _ := n.(string); strings.TrimSpace(named) != t.Node {
return nil, fmt.Errorf("%s names the machine %s in its address and %v in its arguments: say it once, in the address",
t.Address, t.Node, n)
}
delete(args, "node")
}
if !t.Seat {
return args, nil
}
t, held := seatDefinitionHeld(t)
if !held {
// Nothing here says what the verb takes: its holder judges the arguments (issue 1966).
return args, nil
}
declared := map[string]bool{}
var names []string
if p, ok := schemaAsPassed(t)["properties"].(map[string]any); ok {
for k := range p {
declared[k] = true
names = append(names, k)
}
}
sort.Strings(names)
var strangers []string
for k := range args {
if !declared[k] {
strangers = append(strangers, k)
}
}
if len(strangers) > 0 {
sort.Strings(strangers)
takes := "nothing"
if len(names) > 0 {
takes = strings.Join(names, ", ")
}
return nil, fmt.Errorf("%s takes no argument %s — it takes %s (mesh_describe %s); nothing was sent",
t.Address, strings.Join(strangers, ", "), takes, t.Address)
}
return args, nil
}
// seatDefinitionHeld is a seat verb's target with the input the console judges a call by, and whether anything here
// says what the verb takes (issue 1966): the schema its holder announced when it declares an argument, else the verb's
// input in the seat's definition this runner holds. Neither holds while the runner waits for the seat list holding
// only the bootstrapped definitions and the holder announced an empty schema: then a call's arguments are passed on to
// the holder, which judges its own input, and mesh_describe says the definition is not held.
func seatDefinitionHeld(t target) (target, bool) {
if p, ok := asSchema(t.Tool.Input)["properties"].(map[string]any); ok && len(p) > 0 {
return t, true
}
seat, ok := catalogue.SeatNamed(t.Tool.Module)
if !ok {
return t, false
}
for _, v := range seat.Serves {
if v.Name == t.Tool.Name && v.Input != nil {
if raw, err := json.Marshal(v.Input); err == nil {
t.Tool.Input = raw
}
}
}
return t, true
}
// notHeld is what mesh_describe says of a seat verb whose definition this runner does not hold.
func notHeld(seat string) string {
return "the seat list this tool runner holds has no definition of " + seat + " yet (it holds the bootstrapped ones " +
"until it reads the list): what the verb takes is not known here, so the arguments of a call are passed to " +
"its holder, which judges them"
}
// schemaWithoutNode is a tool's schema with `node` taken out, for an address that names the machine.
func schemaWithoutNode(raw json.RawMessage) map[string]any {
schema := asSchema(raw)
out := map[string]any{}
for k, v := range schema {
out[k] = v
}
if p, ok := schema["properties"].(map[string]any); ok {
props := map[string]any{}
for k, v := range p {
if k != "node" {
props[k] = v
}
}
out["properties"] = props
}
if r, ok := schema["required"].([]any); ok {
var keep []any
for _, k := range r {
if k != "node" {
keep = append(keep, k)
}
}
if len(keep) == 0 {
delete(out, "required")
} else {
out["required"] = keep
}
}
return out
}
// answerText is a discovery verb's answer as MCP content: JSON, indented.
func answerText(v any) map[string]any {
var b strings.Builder
enc := json.NewEncoder(&b)
enc.SetEscapeHTML(false) // `<node>/…` is read by an agent, not embedded in a page
enc.SetIndent("", " ")
_ = enc.Encode(v)
return map[string]any{"content": []map[string]any{{"type": "text", "text": strings.TrimRight(b.String(), "\n")}}}
}
func failure(text string) map[string]any {
return map[string]any{"content": []map[string]any{{"type": "text", "text": text}}, "isError": true}
}
// discover answers one of the discovery verbs.
func (s *Surface) discover(ctx context.Context, name string, args map[string]any) map[string]any {
if name == verbRuntimes {
return s.runtimesAnswer()
}
if name == verbFirstViews || name == verbFirstAgain {
return s.firstViewsAnswer(ctx, name, args)
}
if name == verbWait {
return s.waitAnswer(ctx, args)
}
if name == verbApprovals {
return s.approvalsAnswer(args)
}
x, err := s.index()
if err != nil {
return failure("the mesh's discovery failed: " + err.Error())
}
str := func(k string) string { v, _ := args[k].(string); return strings.TrimSpace(v) }
switch name {
case verbOverview:
type verbLine struct {
Address string `json:"address"`
Description string `json:"description"`
}
var mesh, node []map[string]any
for _, st := range x.Seats {
var verbs []verbLine
for _, v := range st.Verbs {
addr := st.Seat + "." + v.Name
if st.Scope == "node" {
addr = "<node>/" + addr
}
verbs = append(verbs, verbLine{addr, firstLine(v.Description)})
}
entry := map[string]any{"seat": st.Seat, "verbs": verbs}
if st.Scope == "node" {
entry["held on"] = nodesOf(st.Holders)
node = append(node, entry)
} else {
if h := nodesOf(st.Holders); len(h) > 0 {
entry["held on"] = h
}
mesh = append(mesh, entry)
}
}
modules := 0
tools := 0
for _, m := range x.Modules {
modules++
tools += len(m.Tools)
}
return answerText(map[string]any{
"seats of the mesh": mesh,
"seats every machine": node,
"machines": x.Machines,
"modules with tools": fmt.Sprintf("%d modules, %d tools — mesh_machine lists a machine's, mesh_search finds one", modules, tools),
"not answering": x.NotAnswering,
})
case verbMachine:
node := str("node")
if node == "" {
return failure("mesh_machine needs `node`: one of " + orNobody(x.Machines))
}
if !contains(x.Machines, node) {
return failure(fmt.Sprintf("the mesh knows no machine %q; it has %s", node, orNobody(x.Machines)))
}
var seats []map[string]any
for _, st := range x.Seats {
if st.Scope != "node" || !contains(nodesOf(st.Holders), node) {
continue
}
var verbs []string
for _, v := range st.Verbs {
verbs = append(verbs, node+"/"+st.Seat+"."+v.Name)
}
var by string
for _, h := range st.Holders {
if h.Node == node {
by = h.Module
}
}
seats = append(seats, map[string]any{"seat": st.Seat, "held by": by, "verbs": verbs})
}
var modules []map[string]any
names := make([]string, 0, len(x.Modules))
for n := range x.Modules {
names = append(names, n)
}
sort.Strings(names)
for _, n := range names {
m := x.Modules[n]
if !contains(m.On, node) {
continue
}
var tools []map[string]string
for _, t := range m.Tools {
tools = append(tools, map[string]string{"address": node + "/" + m.Module + "." + t.Name, "does": firstLine(t.Description)})
}
entry := map[string]any{"module": m.Module, "tools": tools}
if m.Interchangeable {
entry["interchangeable"] = "any instance answers <module>.<tool> as well"
}
modules = append(modules, entry)
}
out := map[string]any{"machine": node, "seats": seats, "modules": modules}
if seats == nil {
out["seats"] = []map[string]any{}
}
if modules == nil {
out["modules"] = []map[string]any{}
}
if us := x.unheardOn(node); len(us) > 0 {
out["incomplete"] = "discovery did not hear in full from " + sayUnheard(us) +
"; what is listed may be missing what it serves. Ask again in a moment; mesh_runtimes shows which runtimes answered"
}
return answerText(out)
case verbSearch:
query := strings.ToLower(str("query"))
if query == "" {
return failure("mesh_search needs `query`: words to find, e.g. `postgres databases`, or the command you would " +
"have run, e.g. `journalctl` or `docker ps`")
}
hits := x.search(query)
out := map[string]any{"matches": hits}
if len(hits) > searchCap {
out["matches"] = hits[:searchCap]
out["more"] = fmt.Sprintf("%d more; narrow the words", len(hits)-searchCap)
}
if len(hits) == 0 {
out["matches"] = []hit{}
out["hint"] = "nothing matched every word; try fewer words, or mesh_overview and mesh_machine to browse. " +
"If no tool does it, one is created in the module that owns it, on its seat — never worked around (novox/hq ADR 0245)"
}
if len(x.Unheard) > 0 {
out["incomplete"] = "discovery did not hear in full from " + sayUnheard(x.Unheard) +
"; what they serve is not searched. mesh_runtimes shows which runtimes answered"
}
return answerText(out)
case verbDescribe:
t, err := resolve(x, str("address"))
if err != nil {
return failure(err.Error())
}
if !t.Seat && len(t.Tool.Input) == 0 {
// Discovery carries a seat's schema, not a module tool's: asked of the runtime serving it.
t.Tool.Input = inputOf(s.conn, t.Tool.Module, t.Tool.Name)
}
description := t.Tool.Description
if description == "" {
description = t.Name
}
held := true
if t.Seat {
t, held = seatDefinitionHeld(t)
}
described := map[string]any{"address": t.Address, "description": description, "arguments": schemaAsPassed(t)}
if !held {
described["arguments"] = notHeld(t.Tool.Module)
}
key := t.Tool.Module + "." + t.Tool.Name
if t.Seat {
key = "seat:" + key
}
if r := x.Replaces[key]; len(r) > 0 {
described["instead_of"] = r
}
if t.Stale != nil {
described["found"] = t.Stale.say()
}
if ok, source := s.markedTool(t); ok {
described["needs-approval"] = map[string]any{"source": source,
"says": "the first call asks the operator and runs nothing; the same call again within 30 minutes of the Approve runs once"}
}
return answerText(described)
case verbCall:
t, err := resolve(x, str("address"))
if err != nil {
return failure(err.Error())
}
given, _ := args["arguments"].(map[string]any)
callArgs, err := argumentsAsSent(t, given)
if err != nil {
return failure(err.Error())
}
var got bus.Answered
if t.Seat {
got, err = s.conn.Ask(t.Key, callArgs, "")
} else {
got, err = callTool(s.conn, t.Key, callArgs, nil, x.Listing)
}
if err != nil {
why := whyItFailed(t.Address, err)
if ok, _ := s.markedTool(t); ok && errors.Is(err, bus.ErrNoResponders) {
// A marked subject withheld from a machine whose tool runner does not honour the mark (§6).
why += "; it needs the operator's approval, so it may be withheld where the machine's tool runner does not state " +
"approval-mark/1: mesh-grants.grants_user_list names it under withheld"
}
if t.Stale != nil {
why += " (" + t.Stale.say() + ")"
}
return failure(why)
}
result, unread := resolveSent(s.conn, got.Result)
content := []map[string]any{{"type": "text", "text": pretty(result)}}
if unread != "" {
content = append(content, map[string]any{"type": "text", "text": unread})
}
if got.Node != "" {
content = append(content, map[string]any{"type": "text", "text": "answered by " + got.Node})
}
if t.Stale != nil {
content = append(content, map[string]any{"type": "text", "text": t.Stale.say()})
}
return map[string]any{"content": content}
}
return failure("no discovery verb " + name)
}
// inputOf is a module tool's input schema, asked of a runtime that serves the module — its `tools` verb,
// answered on the module's plain subject by any instance — because discovery no longer carries it: every
// tool's schema made a machine's one discovery answer outgrow the bus (2026-10-04). Nil when nothing
// answers, which describes the tool as taking nothing rather than failing the description.
func inputOf(conn *bus.Conn, module, tool string) json.RawMessage {
got, err := conn.Ask(module+".tools", map[string]any{}, "")
if err != nil {
return nil
}
var answer struct {
Tools []struct {
Name string `json:"name"`
Input json.RawMessage `json:"input"`
} `json:"tools"`
}
if json.Unmarshal(got.Result, &answer) != nil {
return nil
}
for _, t := range answer.Tools {
if t.Name == tool {
return t.Input
}
}
return nil
}