package console import ( "context" "encoding/json" "errors" "fmt" "net/http" "os" "reflect" "sort" "strings" "sync" "testing" "time" "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient" opseat "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient" catalogseat "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog" stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/approval" "git.novox.be/novox/mesh-sdk/go/seatdef" "github.com/nats-io/nats-server/v2/server" "github.com/nats-io/nats.go" runnerapproval "github.com/novox/mesh-tools/node-tools/internal/approval" "github.com/novox/mesh-tools/node-tools/internal/bus" mt "github.com/novox/mesh-tools/node-tools/internal/meshtest" ) // waitingRuntime is the tool runner on a machine, issued no waits (novox/hq ADR 0670 §2): the surface over its // connection, and the mesh to publish on. func waitingRuntime(t *testing.T) (*Surface, *mt.Mesh) { t.Helper() mesh := mt.New(t) mesh.Issue(t, mt.MembershipOf("node-tools", "desk", false, nil)) conn := connect(t, "node-tools", "desk") mt.Until(t, func() error { if conn.Membership("node-tools") == nil { return os.ErrNotExist } return nil }) return NewSurface(conn, busSource{conn}, "desk.node-tools"), mesh } // halfSource is a Source that also holds a catalog half, as the tool runner's first views do. type halfSource struct { Source halves map[string]meshcatalogclient.Half } func (h halfSource) Halves() (map[string]meshcatalogclient.Half, error) { return h.halves, nil } // listening waits until the surface's connection holds n more subscriptions than before: a wait has subscribed. func listening(t *testing.T, s *Surface, before, n int) { t.Helper() mt.Until(t, func() error { if s.conn.Subscriptions() < before+n { return os.ErrNotExist } return nil }) } // callWait is one tools/call of mesh_wait, answered as an agent host reads it. func callWait(s *Surface, args map[string]any) (text string, isError bool) { raw, _ := json.Marshal(map[string]any{"name": verbWait, "arguments": args}) r := s.Handle(Request{JSONRPC: "2.0", ID: json.RawMessage("1"), Method: "tools/call", Params: raw}) result, _ := r.Result.(map[string]any) content, _ := result["content"].([]map[string]any) var parts []string for _, c := range content { parts = append(parts, c["text"].(string)) } isErr, _ := result["isError"].(bool) return strings.Join(parts, "\n"), isErr } // novox/hq ADR 0367 §1, ADR 0670 §1 (design 34 every-event §13, §15, §19): mesh_wait answers the first event that // matches — by its name and its filter — with no membership naming it: its whole name, the namespace it arrived on, its // body, when it arrived and when it was published, within a second of its publication, and ignores the others. func TestMeshWaitAnswersTheFirstMatchingEvent(t *testing.T) { s, mesh := waitingRuntime(t) type answer struct { text string isErr bool at time.Time } type heardAnswer struct { Heard struct { Subject string `json:"subject"` From string `json:"from"` Body map[string]any `json:"body"` At time.Time `json:"at"` Published *string `json:"published"` } `json:"heard"` Read string `json:"read"` } nc, err := nats.Connect(mt.URL(t)) if err != nil { t.Fatal(err) } t.Cleanup(nc.Close) done := make(chan answer, 1) before := s.conn.Subscriptions() go func() { text, isErr := callWait(s, map[string]any{"subjects": []any{"mesh-delivery-view.delivered"}, "filter": map[string]any{"module": "netcheck"}, "timeout": 20}) done <- answer{text, isErr, time.Now()} }() // Once it listens: another module's delivery, another event, then the one it waits for, with its own time. listening(t, s, before, 6) mesh.Emit(t, "mesh.mod.mesh-delivery-view.event.delivered", map[string]any{"module": "grafana"}) mesh.Emit(t, "mesh.mod.mesh-delivery-view.event.planned", map[string]any{"module": "netcheck"}) msg := nats.NewMsg("mesh.mod.mesh-delivery-view.event.delivered") msg.Header.Set("x-time", "2026-11-12T03:00:00.5Z") msg.Data = []byte(`{"module":"netcheck"}`) published := time.Now() if err := nc.PublishMsg(msg); err != nil { t.Fatal(err) } var got answer select { case got = <-done: case <-time.After(10 * time.Second): t.Fatal("mesh_wait did not answer the matching event") } if got.isErr { t.Fatalf("mesh_wait failed: %s", got.text) } if got.at.Sub(published) > time.Second { t.Errorf("answered %s after the event was published, want within a second", got.at.Sub(published)) } var heard heardAnswer if err := json.Unmarshal([]byte(got.text), &heard); err != nil { t.Fatalf("not an answer: %s", got.text) } if h := heard.Heard; h.Subject != "mesh-delivery-view.delivered" || h.From != "module" || h.Body["module"] != "netcheck" || h.At.IsZero() || h.Published == nil || *h.Published != "2026-11-12T03:00:00.5Z" || !strings.Contains(heard.Read, "only wakes you") { t.Errorf("answered %s, want netcheck's delivery from a module with its times", got.text) } // And a seat's `decided` of one asker, heard by `operator-channel.decided`: its whole name, from the seat, with no // time of its own, and the answer read only with claude_code_answer. before = s.conn.Subscriptions() go func() { text, isErr := callWait(s, map[string]any{"subjects": "operator-channel.decided", "timeout": 20}) done <- answer{text, isErr, time.Now()} }() listening(t, s, before, 6) // A seat's event, said by the router on core NATS here: no stream of the test's bus carries the seat. if err := connect(t, "messenger", "").Say("mesh.seat.operator-channel.event.decided.claude-code", []byte(`{"ask":"a1"}`)); err != nil { t.Fatal(err) } select { case got = <-done: heard = heardAnswer{} _ = json.Unmarshal([]byte(got.text), &heard) if h := heard.Heard; got.isErr || h.Subject != "operator-channel.decided.claude-code" || h.From != "seat" || h.Published != nil || !strings.Contains(heard.Read, "claude_code_answer") { t.Errorf("the asker's decided: %s", got.text) } case <-time.After(10 * time.Second): t.Fatal("mesh_wait did not answer the asker's decided") } } // It says it timed out at its timeout, and leaves no subscription behind (ADR 0367 §1, §2). func TestMeshWaitSaysItTimedOutAndKeepsNothing(t *testing.T) { s, _ := waitingRuntime(t) before := s.conn.Subscriptions() start := time.Now() text, isErr := callWait(s, map[string]any{"subjects": []any{"mesh-knowledge.reviewed"}, "timeout": 1}) if took := time.Since(start); took < time.Second || took > 3*time.Second { t.Errorf("a one-second wait took %s", took) } // Before the catalog half is read, names are not checked, and the answer says so (design 34 every-event §14). if isErr || !strings.Contains(text, `"timed-out": true`) || !strings.Contains(text, `"unchecked"`) { t.Errorf("a wait that heard nothing: %s", text) } if after := s.conn.Subscriptions(); after != before { t.Errorf("%d subscriptions before the wait, %d after it", before, after) } } // Its timeout is four minutes when none is given and ten at most: a longer one, or none at all, is refused // with the bound, never shortened in silence. func TestMeshWaitsTimeoutIsBounded(t *testing.T) { s, _ := waitingRuntime(t) if WaitDefault != 4*time.Minute || WaitLongest != 10*time.Minute { t.Errorf("the bounds are %s and %s, want 4m and 10m (ADR 0367 §1)", WaitDefault, WaitLongest) } for _, timeout := range []any{601, 0, -5, "soon"} { text, isErr := callWait(s, map[string]any{"subjects": []any{"mesh-knowledge.reviewed"}, "timeout": timeout}) if !isErr || !strings.Contains(text, "600") { t.Errorf("a timeout of %v: %s", timeout, text) } } } // novox/hq ADR 0670 §1, §2 (design 34 every-event §13, §16, §19): mesh_wait refuses loudly and never loops: a name with // a wildcard, of one token, more than 16 names, none, or a from of another word is refused at once naming the rule; a // wait whose every subscription the bus refuses answers after its timeout, in words naming no wait grant. func TestMeshWaitRefusesLoudlyAndNeverLoops(t *testing.T) { s, _ := waitingRuntime(t) var seventeen []any for i := 0; i < 17; i++ { seventeen = append(seventeen, fmt.Sprintf("mesh-knowledge.reviewed%d", i)) } for _, c := range []struct { args map[string]any says string }{ {map[string]any{"subjects": []any{"mesh-knowledge.*"}}, "[A-Za-z0-9_-]+"}, {map[string]any{"subjects": []any{"operator-channel.decided.>"}}, "[A-Za-z0-9_-]+"}, {map[string]any{"subjects": []any{"mesh-knowledge"}}, "fewer than two tokens"}, {map[string]any{"subjects": seventeen}, "at most 16"}, {map[string]any{"subjects": []any{}}, "at least one event"}, {map[string]any{"subjects": []any{"mesh-knowledge.reviewed"}, "from": "bench"}, "seat, module or stream"}, } { c.args["timeout"] = 1 start := time.Now() text, isErr := callWait(s, c.args) if !isErr || !strings.Contains(text, c.says) || strings.Contains(text, "manifest") { t.Errorf("%v: %s, want refused saying %q", c.args, text, c.says) } if took := time.Since(start); took > 500*time.Millisecond { t.Errorf("%v was refused after %s, want at once", c.args, took) } } t.Run("the catalog half", func(t *testing.T) { t.Parallel() // E §14: against the catalog half, a misspelt emitter is refused naming the half and its revision, a known emitter's // undeclared event is waited on and its timeout says so, and a declared one says nothing of it. half := NewSurface(s.conn, halfSource{busSource{s.conn}, map[string]meshcatalogclient.Half{"main": { Revision: catalogseat.CatalogRevision{Revision: 7, Definitions: []catalogseat.CatalogRevisionDefinitionsItem{{Seat: "mesh-catalog"}}}, Entries: map[string]catalogseat.CatalogEntry{"mesh-delivery-view": {Module: "mesh-delivery-view", Manifest: map[string]any{"emits": []any{"delivered"}}}}, }}}, "desk.node-tools") for _, c := range []struct { name, says string refused bool }{ {"mesh-delivry-view.delivered", "mesh-delivry-view is neither a seat nor a module of the catalog half (mesh-catalog.revision, revision 7 of main)", true}, {"mesh-delivery-view.planned", "no declaration names mesh-delivery-view.planned", false}, {"mesh-catalog.catalog-revision", "", false}, } { text, isErr := callWait(half, map[string]any{"subjects": []any{c.name}, "timeout": 1}) if isErr != c.refused || c.says != "" && !strings.Contains(text, c.says) || c.says == "" && (strings.Contains(text, "undeclared") || strings.Contains(text, "unchecked")) { t.Errorf("%s against the catalog half: %s", c.name, text) } } }) t.Run("a bus that refuses every subscription", func(t *testing.T) { t.Parallel() // The bus refusing every subscription of the call: answered after the timeout, in §16's words. b, err := server.NewServer(&server.Options{Host: "127.0.0.1", Port: server.RANDOM_PORT, NoLog: true, NoSigs: true, Users: []*server.User{{Username: "desk.node-tools", Password: "pw", Permissions: &server.Permissions{ Subscribe: &server.SubjectPermission{Allow: []string{"_INBOX.desk.node-tools.>", "mesh.assignment.desk.*"}}, }}}}) if err != nil { t.Fatal(err) } go b.Start() if !b.ReadyForConnections(10 * time.Second) { t.Fatal("the bus did not come up") } t.Cleanup(b.Shutdown) conn, err := bus.Connect(bus.Credential{URL: b.ClientURL(), Module: "node-tools", Node: "desk", User: "desk.node-tools", Password: "pw"}) if err != nil { t.Fatal(err) } conn.Logf = func(string, ...any) {} t.Cleanup(conn.Close) start := time.Now() text, isErr := callWait(NewSurface(conn, nil, "desk.node-tools"), map[string]any{"subjects": []any{"mesh-delivery-view.delivered"}, "timeout": 1}) if took := time.Since(start); took < time.Second || took > 3*time.Second { t.Errorf("a wait the bus refused whole answered after %s, want its timeout of 1s", took) } if !isErr || !strings.Contains(text, "the bus refused this account a subscription on mesh.seat.mesh-delivery-view.event.delivered") || !strings.Contains(text, "novox/hq ADR 0670 §1") || strings.Contains(text, "wait grant") || strings.Contains(text, "0367 §2") { t.Errorf("a wait the bus refused whole: %s", text) } }) // Design 34 every-event §37: a wait whose subjects overlap a marked event's, or a stream's with a `grant`, runs only on the // operator's warrant for that one wait; with none it subscribes nothing, asks once and answers at its timeout or the choice. // Each group of rows has a runtime of its own, so the waits of the groups run side by side (A-311: the one-second floor of // a wait's timeout stands, and no clock is injected). for name, group := range map[string]func(*testing.T, *Surface, *mt.Mesh){ "no warrant": markedWaitsWithNoWarrant, "a warrant": markedWaitsOnAWarrant, "the catalog half unread": markedWaitsBeforeTheHalfIsRead, } { t.Run(name, func(t *testing.T) { t.Parallel() s, mesh := waitingRuntime(t) group(t, s, mesh) }) } } // markedWaitsWithNoWarrant are §37's rows of a wait with no warrant, against a gate on the test bus, the router's records written there as the router writes them. func markedWaitsWithNoWarrant(t *testing.T, s *Surface, mesh *mt.Mesh) { gw := newGatedWaits(t, s, mesh, "src2:runner") g := gw.surface took := func(args map[string]any) (string, bool, time.Duration) { start := time.Now() text, isErr := callWait(g, args) return text, isErr, time.Since(start) } // A fixture module's marked event, no warrant: nothing of it subscribed (only its ask's decided), asked once, §12's // words at its timeout; the same wait again asks nothing new. before := g.conn.Subscriptions() go func() { listening(t, g, before, 1) mesh.Emit(t, "mesh.mod.mail.event.message", map[string]any{"n": 1}) }() text, isErr, d := took(map[string]any{"subjects": []any{"mail.message"}, "timeout": 1}) if !isErr || !strings.Contains(text, "this wait needs the operator's approval. Asked on telegram") || gw.asks() != 1 || d < time.Second || strings.Contains(text, "heard") { t.Errorf("a marked event's wait, no warrant: %s after %s, %d asks", text, d, gw.asks()) } if text, isErr, _ = took(map[string]any{"subjects": []any{"mail.message"}, "timeout": 1}); !isErr || !strings.Contains(text, "still waiting") || gw.asks() != 1 { t.Errorf("the same marked wait again: %s, %d asks", text, gw.asks()) } // A stream with a `grant` is marked whole; a name with the node after a marked event's name is marked. for i, name := range []string{"mail.inbox", "mail.message.desk"} { if text, isErr, _ := took(map[string]any{"subjects": []any{name}, "timeout": 1}); !isErr || !strings.Contains(text, "Not run") || gw.asks() != 2+i { t.Errorf("%s: %s, %d asks", name, text, gw.asks()) } } // The same names from a seat ask nothing. if text, isErr, _ := took(map[string]any{"subjects": []any{"mail.message"}, "from": "seat", "timeout": 1}); isErr || !strings.Contains(text, `"timed-out": true`) || gw.asks() != 3 { t.Errorf("from a seat: %s, %d asks", text, gw.asks()) } } // markedWaitsOnAWarrant are §37's rows of a wait chosen while it waits and of one on a warrant. func markedWaitsOnAWarrant(t *testing.T, s *Surface, mesh *mt.Mesh) { gw := newGatedWaits(t, s, mesh, "src2:runner") g := gw.surface took := func(args map[string]any) (string, bool, time.Duration) { start := time.Now() text, isErr := callWait(g, args) return text, isErr, time.Since(start) } // The most timeout a wait takes, its ask chosen while it waits: answered at the choice; the same call made then // subscribes and hears the event published after it, answered with its approval; a warrant for other names, another // filter or another timeout is refused, asked anew. long := map[string]any{"subjects": []any{"mail.message"}, "filter": map[string]any{"n": 2}, "timeout": 600} done := make(chan string, 1) go func() { text, _, d := took(long); done <- fmt.Sprintf("%s after %s", text, d) }() mt.Until(t, func() error { if gw.asks() < 1 { return os.ErrNotExist } return nil }) gw.approve(t) select { case got := <-done: if !strings.Contains(got, "make this wait again within 30 minutes") { t.Errorf("a long wait chosen while it waits: %s", got) } case <-time.After(10 * time.Second): t.Fatal("a long marked wait was not answered at the choice") } before := g.conn.Subscriptions() go func() { listening(t, g, before, 6) mesh.Emit(t, "mesh.mod.mail.event.message", map[string]any{"n": 2}) }() if text, isErr, _ := took(long); isErr || !strings.Contains(text, `"approval": "run on the operator's approval`) || !strings.Contains(text, `"subject": "mail.message"`) { t.Errorf("the same wait on its warrant: %s", text) } for _, other := range []map[string]any{ {"subjects": []any{"mail.message", "mail.inbox"}, "filter": map[string]any{"n": 2}, "timeout": 1}, {"subjects": []any{"mail.message"}, "filter": map[string]any{"n": 3}, "timeout": 1}, {"subjects": []any{"mail.message"}, "filter": map[string]any{"n": 2}, "timeout": 2}, } { asked := gw.asks() if text, isErr, _ := took(other); !isErr || !strings.Contains(text, "Not run") || gw.asks() != asked+1 { t.Errorf("another wait than the warrant's %v: %s", other, text) } } } // markedWaitsBeforeTheHalfIsRead are §37's rows of a wait before the catalog half is read, and of one whose runner build is unknown. func markedWaitsBeforeTheHalfIsRead(t *testing.T, s *Surface, mesh *mt.Mesh) { gw := newGatedWaits(t, s, mesh, "src2:runner") g := gw.surface took := func(args map[string]any) (string, bool, time.Duration) { start := time.Now() text, isErr := callWait(g, args) return text, isErr, time.Since(start) } // Before the catalog half is read nothing is asked: judged when its first view comes within the timeout, refused at // the timeout when it does not. gw.cat.mu.Lock() gw.cat.ready = errors.New("no first view yet") gw.cat.mu.Unlock() asked := gw.asks() if text, isErr, d := took(map[string]any{"subjects": []any{"mail.message"}, "timeout": 1}); !isErr || !strings.Contains(text, "the marks are not yet read") || gw.asks() != asked || d < time.Second { t.Errorf("before the catalog half is read: %s after %s", text, d) } gw.cat.came = make(chan chan struct{}) go func() { close(<-gw.cat.came) }() if text, isErr, _ := took(map[string]any{"subjects": []any{"mail.message"}, "filter": map[string]any{"n": 9}, "timeout": 1}); !isErr || !strings.Contains(text, "this wait needs the operator's approval") || gw.asks() != asked+1 { t.Errorf("the catalog half read within the timeout: %s", text) } // While the runner's own fingerprint is unknown a marked wait is refused, saying so. unknown := newGatedWaits(t, s, mesh, "") if text, isErr := callWait(unknown.surface, map[string]any{"subjects": []any{"mail.message"}, "timeout": 1}); !isErr || !strings.Contains(text, "own fingerprint is unknown") || unknown.asks() != 0 { t.Errorf("the runner's fingerprint unknown: %s", text) } } // gatedWaits is the surface with the runner's approval gate: the fixture module mail marks its event message and declares // the stream inbox with a grant; the router takes each ask as it is published, its records on the test bus. type gatedWaits struct { surface *Surface cat *waitCatalog records nats.KeyValue mu sync.Mutex asked []opseat.Ask say *bus.Conn } func newGatedWaits(t *testing.T, s *Surface, mesh *mt.Mesh, build string) *gatedWaits { gw := &gatedWaits{cat: &waitCatalog{entries: []catalogseat.CatalogEntry{{Module: "mail", Fp: "src2:mail", Manifest: map[string]any{ "emits": []any{"message"}, "needs-approval": []any{"message"}, "streams": []any{map[string]any{"name": "inbox", "events": []any{"inbox.*"}, "grant": map[string]any{"subscribe": []any{"x"}}}}}}}}, records: mesh.Bucket(t, "operator-channel-asks"), say: connect(t, "messenger", "")} state := mt.NewState() gate := approval.New(approval.Deps{Node: "desk", Runner: "node-tools", Build: build, Catalog: gw.cat, Wait: gw.cat.wait, State: state, Holder: func(string) (bool, error) { return true, nil }, Hear: func(ctx context.Context, subjects []string, within time.Duration, match func(json.RawMessage) bool) (bool, error) { _, ok, _, err := s.conn.HearFirst(ctx, subjects, within, match) return ok, err }, Channel: &operatorchannelclient.GateChannel{State: state, KeyPrefix: "desk.", Publish: func(module, subject string, body any, id string) error { a, ok := body.(opseat.Ask) if subject != opseat.AcceptSubjectAsk(module) || !ok { return nil } gw.mu.Lock() gw.asked = append(gw.asked, a) gw.mu.Unlock() gw.write(t, opseat.AsksByAskerIDValue{Asker: module, Key: module + "." + a.ID, State: "open", Headline: a.Headline, Expires: a.Expires, Opened: operatorchannelclient.FormatTime(time.Now()), OfferedOn: []string{"telegram"}}) return nil }, ReadRecord: func(module, id string) (*stdio.StateEntry, error) { e, err := gw.records.Get(module + "." + id) if errors.Is(err, nats.ErrKeyNotFound) { return nil, nil } else if err != nil { return nil, err } return &stdio.StateEntry{Key: e.Key(), Value: e.Value(), Revision: e.Revision()}, nil }}}) gw.surface = NewSurface(s.conn, busSource{s.conn}, "desk.node-tools") gw.surface.Approvals = gateApprovals{gate} return gw } func (gw *gatedWaits) write(t *testing.T, rec opseat.AsksByAskerIDValue) { raw, _ := json.Marshal(rec) if _, err := gw.records.Put(rec.Key, raw); err != nil { t.Error(err) } } func (gw *gatedWaits) asks() int { gw.mu.Lock() defer gw.mu.Unlock() return len(gw.asked) } // approve writes the router's record of the newest ask as a person's Approve, and says its decided. func (gw *gatedWaits) approve(t *testing.T) { gw.mu.Lock() a := gw.asked[len(gw.asked)-1] gw.mu.Unlock() gw.write(t, opseat.AsksByAskerIDValue{Asker: "node-tools", Key: "node-tools." + a.ID, State: "chosen", Expires: a.Expires, OfferedOn: []string{"telegram"}, Warrant: &opseat.Warrant{Ask: a.ID, Asker: "node-tools", Outcome: operatorchannelclient.OutcomeChosen, Option: approval.OptionApprove, Level: operatorchannelclient.Approve, At: operatorchannelclient.FormatTime(time.Now()), AskDigest: operatorchannelclient.Digest(a), Channel: "telegram", By: &opseat.Person{Who: operatorchannelclient.Operator, Kind: "telegram", Identity: "42", Verified: "user id verified"}}}) if err := gw.say.Say(opseat.SubjectDecided("node-tools"), []byte(`{"ask":"`+a.ID+`"}`)); err != nil { t.Fatal(err) } } // gateApprovals is the gate as the surface holds it: mesh_approvals' reads unused here, mesh_wait's gate. type gateApprovals struct{ *approval.Gate } func (gateApprovals) Fill() (uint64, int64, error) { return 0, 0, nil } // waitCatalog is a catalog half as a fixture: its entries, read or not; with came set, its first view comes once a wait // waits for it (waiting is told on it, then it closes). type waitCatalog struct { entries []catalogseat.CatalogEntry mu sync.Mutex ready error came chan chan struct{} } func (c *waitCatalog) Ready() error { c.mu.Lock() defer c.mu.Unlock() return c.ready } func (c *waitCatalog) wait(within time.Duration) error { if c.Ready() == nil { return nil } if c.came != nil { view := make(chan struct{}) c.came <- view <-view c.mu.Lock() c.ready = nil c.mu.Unlock() return nil } <-time.After(within) // the bound of the runner's own wait for the first view return c.Ready() } func (c *waitCatalog) Revision() string { return "7" } func (c *waitCatalog) Served(string) (approval.Entry, bool, string) { return approval.Entry{}, false, "not-found" } func (c *waitCatalog) Newest(string) (approval.Entry, bool) { return approval.Entry{}, false } func (c *waitCatalog) Definitions() map[string]seatdef.Definition { return nil } func (c *waitCatalog) Entries() []approval.Entry { var out []approval.Entry for _, e := range c.entries { out = append(out, runnerapproval.Entry(e)) } return out } // The mesh MCP server announces seven tools (ADR 0367 v13): ADR 0195's five, mesh_runtimes and mesh_wait, its timeout bounded. func TestMeshWaitIsAnnouncedAmongSeven(t *testing.T) { var names []string var wait map[string]any for _, tool := range discovery() { names = append(names, tool["name"].(string)) if tool["name"] == verbWait { wait = tool } } sort.Strings(names) want := []string{"mesh_approvals", "mesh_call", "mesh_describe", "mesh_first_view_again", "mesh_first_views", "mesh_machine", "mesh_overview", "mesh_runtimes", "mesh_search", "mesh_wait"} if !reflect.DeepEqual(names, want) { t.Errorf("announced %v, want %v", names, want) } if wait == nil || !isDiscovery(verbWait) { t.Fatal("mesh_wait is not announced") } schema, _ := json.Marshal(wait["inputSchema"]) for _, word := range []string{`"subjects"`, `"filter"`, `"timeout"`} { if !strings.Contains(string(schema), word) { t.Errorf("mesh_wait's schema has no %s: %s", word, schema) } } // Design 46 7-approval-mark §18: mesh_approvals lists, and records a hand act before it cancels; refused without a record. gate := &fakeApprovals{} var recorded []string s := &Surface{who: "n1.node-tools", Approvals: gate, Record: func(a map[string]any) error { if a["why"] == "unrecordable" { return errors.New("mesh-hand-acts does not answer") } recorded = append(recorded, a["what"].(string)) return nil }} if out := s.discover(context.Background(), verbApprovals, map[string]any{}); out["isError"] == true || !strings.Contains(fmtAnswer(out), "every machine") { t.Errorf("the list %v", out) } if out := s.discover(context.Background(), verbApprovals, map[string]any{"cancel": "a1", "why": "no longer needed"}); out["isError"] == true || gate.cancelled != "a1" || len(recorded) != 1 || recorded[0] != "cancelled ask a1" { t.Errorf("a cancel %v, recorded %v", out, recorded) } if out := s.discover(context.Background(), verbApprovals, map[string]any{"cancel": "a2", "why": "unrecordable"}); out["isError"] != true || gate.cancelled != "a1" { t.Errorf("a cancel without a record %v", out) } } 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 // marked is the one `.` 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(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. func TestMeshWaitEndsWhenItsClientHangsUp(t *testing.T) { s, _ := waitingRuntime(t) up, err := Serve(s, "127.0.0.1:0") if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = up.Close() }) before := s.conn.Subscriptions() ctx, cancel := context.WithCancel(context.Background()) body := `{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"mesh_wait",` + `"arguments":{"subjects":["mesh-knowledge.reviewed"],"timeout":600}}}` req, _ := http.NewRequestWithContext(ctx, http.MethodPost, "http://"+up.Address+"/mcp", strings.NewReader(body)) req.Header.Set("content-type", "application/json") go func() { _, _ = http.DefaultClient.Do(req) }() mt.Until(t, func() error { if s.conn.Subscriptions() <= before { return os.ErrNotExist } return nil }) cancel() start := time.Now() for s.conn.Subscriptions() > before { if time.Since(start) > 3*time.Second { t.Fatalf("%d subscriptions 3s after the client hung up, %d before the wait", s.conn.Subscriptions(), before) } time.Sleep(20 * time.Millisecond) } } // The tool runner stopping ends every wait in flight at once (the second review of issue 745): Close cancels // the requests' context rather than waiting up to ten minutes for a wait to time out. func TestMeshWaitEndsWhenTheConsoleCloses(t *testing.T) { s, _ := waitingRuntime(t) up, err := Serve(s, "127.0.0.1:0") if err != nil { t.Fatal(err) } before := s.conn.Subscriptions() body := `{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"mesh_wait",` + `"arguments":{"subjects":["mesh-knowledge.reviewed"],"timeout":600}}}` go func() { _, _ = http.Post("http://"+up.Address+"/mcp", "application/json", strings.NewReader(body)) }() mt.Until(t, func() error { if s.conn.Subscriptions() <= before { return os.ErrNotExist } return nil }) closed := make(chan error, 1) start := time.Now() go func() { closed <- up.Close() }() select { case <-closed: case <-time.After(5 * time.Second): t.Fatal("closing the console waited on a mesh_wait in flight") } if took := time.Since(start); took > 3*time.Second { t.Errorf("closing took %s", took) } mt.Until(t, func() error { if s.conn.Subscriptions() > before { return os.ErrExist } return nil }) }