A-9 tests: a bundle not serving its marked tool is refused at the load; a withheld marked call is said so

The runtime's load test gains rows (run with the Python fixture, before the bundles' skip): a bundle whose
marks name a tool it does not register serves no tool of its module, and one that registers it serves.
mesh_call's failure words move into Surface.callFailed, whose rows hold that only a marked tool reaching no
responder names withheld and mesh-grants.grants_user_list.

Builds: design 46 7-approval-mark §4, §6, §22

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 05:54:49 +01:00
parent e7e20e3d13
commit b5c7bf13ea
4 changed files with 88 additions and 18 deletions
+16 -10
View File
@@ -927,6 +927,21 @@ func schemaWithoutNode(raw json.RawMessage) map[string]any {
}
// answerText is a discovery verb's answer as MCP content: JSON, indented.
// callFailed is why a call failed, as mesh_call says it: a marked tool or verb that reaches no responder names `withheld`
// and the grants part's read (design 46 7-approval-mark §6), and a target known only from an earlier round says so.
func (s *Surface) callFailed(t target, err error) string {
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 why
}
func answerText(v any) map[string]any {
var b strings.Builder
enc := json.NewEncoder(&b)
@@ -1137,16 +1152,7 @@ func (s *Surface) discover(ctx context.Context, name string, args map[string]any
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)
return failure(s.callFailed(t, err))
}
result, unread := resolveSent(s.conn, got.Result)
content := []map[string]any{{"type": "text", "text": pretty(result)}}
@@ -11,7 +11,8 @@ import (
"github.com/novox/mesh-tools/node-tools/internal/bus"
)
// A tool's own error that mentions 503 is the tool's answer, not a routing fault (issue 1529); a real no-responders still says
// A marked tool or verb that reaches no responder names withheld and the grants part's read (design 46 7-approval-mark §6);
// an unmarked one, or a marked one failing otherwise, does not. A tool's own error that mentions 503 is the tool's answer, not a routing fault (issue 1529); a real no-responders still says
// nothing serves.
func TestARealNoRespondersStillSaysNothingServes(t *testing.T) {
got := whyItFailed("scanner.scan", errors.New("the scanner refused the scan job: 503 Service Unavailable"))
@@ -23,4 +24,20 @@ func TestARealNoRespondersStillSaysNothingServes(t *testing.T) {
t.Fatalf("%v: got %q", err, got)
}
}
s := &Surface{Approvals: &fakeApprovals{marked: "shell.wipe"}}
for _, c := range []struct {
target target
err error
hinted bool
}{
{target{Address: "shell.wipe", Tool: Tool{Module: "shell", Name: "wipe"}}, bus.ErrNoResponders, true},
{target{Address: "node-login-shell.execute", Seat: true, Tool: Tool{Module: "node-login-shell", Name: "execute"}}, bus.ErrNoResponders, false},
{target{Address: "shell.tidy", Tool: Tool{Module: "shell", Name: "tidy"}}, bus.ErrNoResponders, false},
{target{Address: "shell.wipe", Tool: Tool{Module: "shell", Name: "wipe"}}, errors.New("its own failure"), false},
} {
got := s.callFailed(c.target, c.err)
if hinted := strings.Contains(got, "approval-mark/1: mesh-grants.grants_user_list names it under withheld"); hinted != c.hinted {
t.Errorf("%s (%v): %q", c.target.Address, c.err, got)
}
}
}
+11 -5
View File
@@ -554,17 +554,23 @@ func TestMeshWaitIsAnnouncedAmongSeven(t *testing.T) {
func fmtAnswer(out map[string]any) string { raw, _ := json.Marshal(out); return string(raw) }
// fakeApprovals is a gate as mesh_approvals reads it.
type fakeApprovals struct{ cancelled string }
type fakeApprovals struct {
cancelled string
// marked is the one `<module>.<tool>` its Describe says is marked.
marked string
}
func (f *fakeApprovals) List() ([]approval.Listed, error) {
return []approval.Listed{{Ask: "a1", State: "asked"}}, nil
}
func (f *fakeApprovals) Read(id string) (approval.Whole, error) { return approval.Whole{}, nil }
func (f *fakeApprovals) Applied() []approval.Marks { return nil }
func (f *fakeApprovals) Describe(_, _, _ string) (bool, string) { return false, "" }
func (f *fakeApprovals) Again(string) error { return nil }
func (f *fakeApprovals) Cancel(id string) error { f.cancelled = id; return nil }
func (f *fakeApprovals) Fill() (uint64, int64, error) { return 10, 64 << 20, nil }
func (f *fakeApprovals) Describe(module, _, name string) (bool, string) {
return f.marked != "" && f.marked == module+"."+name, "entry " + module
}
func (f *fakeApprovals) Again(string) error { return nil }
func (f *fakeApprovals) Cancel(id string) error { f.cancelled = id; return nil }
func (f *fakeApprovals) Fill() (uint64, int64, error) { return 10, 64 << 20, nil }
// A client that hangs up or cancels ends its wait at once: the subscription is closed and nothing is left
// waiting for the timeout (the review of issue 745). Over the console's own HTTP, as an agent host calls it.
+43 -2
View File
@@ -4,10 +4,12 @@ import (
"encoding/json"
"errors"
"os"
"slices"
"strings"
"testing"
"time"
"github.com/novox/mesh-tools/node-tools/internal/approval"
"github.com/novox/mesh-tools/node-tools/internal/bus"
"github.com/novox/mesh-tools/node-tools/internal/launch"
mt "github.com/novox/mesh-tools/node-tools/internal/meshtest"
@@ -192,10 +194,49 @@ func TestEachBundleIsGivenItsOwnEnvironment(t *testing.T) {
}
}
// An entrypoint that is not executable is refused by name, and the others serve (ADR 0193).
// unservingGate is an approval gate whose marks of each module name the tools given, so a bundle not registering them is
// refused at the load (design 46 7-approval-mark §4).
type unservingGate map[string][]string
func (g unservingGate) Unserved(module, _ string, registered []string) []string {
var missing []string
for _, tool := range g[module] {
if !slices.Contains(registered, tool) {
missing = append(missing, tool)
}
}
return missing
}
func (unservingGate) Run(_ approval.Call, run func() (any, error)) (any, error) { return run() }
// An entrypoint that is not executable is refused by name, and the others serve (ADR 0193). A bundle whose marks name a
// tool it does not register is refused at the load, serving no tool of its module, and one that registers it serves
// (design 46 7-approval-mark §4).
func TestAnEntrypointThatIsNotExecutableIsRefused(t *testing.T) {
mt.NeedBundles(t)
mesh := mt.New(t)
{
mesh.Issue(t, mt.MembershipOf("marked", "anchor", true, nil))
mesh.Issue(t, mt.MembershipOf("served", "anchor", true, nil))
nodeTools := connect(t, "node-tools", "anchor")
asker := connect(t, "console", "workstation")
logs := &mt.Logs{}
stop, err := Run(nodeTools, []Served{{"marked", []string{mt.Fixture("caller-echo.py")}}, {"served", []string{mt.Fixture("caller-echo.py")}}},
nil, logs.Logf, WithGate(unservingGate{"marked": {"wipe"}, "served": {"who"}}))
if err != nil {
t.Fatal(err)
}
if !logs.Has("marked's bundle", "failed to load: its marks name wipe, which it does not register (design 46 7-approval-mark §4)") {
t.Errorf("a bundle not serving its marked tool was not refused at the load:\n%s", logs.All())
}
if _, err := call(t, asker, "served.who@anchor", map[string]any{}); err != nil {
t.Errorf("a bundle serving its marked tool: %v", err)
}
if _, err := asker.Ask("marked.who@anchor", map[string]any{}, ""); !errors.Is(err, bus.ErrNoResponders) {
t.Errorf("a refused bundle's tool answered: %v", err)
}
stop()
}
mt.NeedBundles(t)
mesh.Issue(t, mt.MembershipOf("alpha", "anchor", false, nil))
mesh.Issue(t, mt.MembershipOf("plain", "anchor", false, nil))
nodeTools := connect(t, "node-tools", "anchor")