package main import ( "bytes" "context" "encoding/json" "io" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "time" ) // The tests' own standard input is the runner's, which may be a pipe nobody closes: read as a terminal. func init() { standardInput = func() (io.Reader, bool) { return nil, true } } // asked is one call the stand-in console took: the console's tool, and for mesh_call the address and its arguments. type asked struct { Tool string Address string Args string } // console is a stand-in for this machine's console: it answers MCP tools/call over HTTP as node-tools does, with the // answer the test gives per address (an error when it starts with "!"), and records what it was asked. func console(t *testing.T, answers map[string]string) *[]asked { t.Helper() var got []asked srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/mcp" || r.Method != http.MethodPost { http.NotFound(w, r) return } var req struct { Method string `json:"method"` Params struct { Name string `json:"name"` Arguments struct { Address string `json:"address"` Arguments map[string]any `json:"arguments"` Query string `json:"query"` } `json:"arguments"` } `json:"params"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Method != "tools/call" { t.Errorf("the console was asked %v %v", req.Method, err) } a := req.Params.Arguments args, _ := json.Marshal(a.Arguments) got = append(got, asked{req.Params.Name, a.Address, string(args)}) key := a.Address if req.Params.Name != "mesh_call" { key = req.Params.Name } text, ok := answers[key] if !ok { text = `{"ok":true}` } isError := strings.HasPrefix(text, "!") w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]any{"jsonrpc": "2.0", "id": 1, "result": map[string]any{ "content": []any{map[string]any{"type": "text", "text": strings.TrimPrefix(text, "!")}, map[string]any{"type": "text", "text": "answered by ace"}}, "isError": isError}}) })) t.Cleanup(srv.Close) t.Setenv("MESH_CONSOLE", srv.URL+"/mcp") t.Setenv("MESH_VERB", "") return &got } func runLine(args ...string) (int, string, string) { var out, errOut bytes.Buffer code := run(context.Background(), args, &out, &errOut) return code, out.String(), errOut.String() } // Every owned line calls the seat verb or module tool that owns its job, with the arguments the owner reads // (novox/hq issue 1126; hq .work/1126/nox-map.md), and prints its answer. func TestEachLineCallsItsOwner(t *testing.T) { now = func() time.Time { return time.Date(2026, 11, 2, 10, 0, 0, 0, time.UTC) } t.Cleanup(func() { now = time.Now }) for _, c := range []struct { line string address string args string }{ {"node show ace", "mesh-module-assigner.placement", `{"node":"ace"}`}, {"node public-domain ace", "mesh-module-assigner.placement", `{"node":"ace"}`}, {"node public-domain ace example.org --why moved", "mesh-module-assigner.propose", `{"changes":[{"field":"public-domain","node":"ace","op":"decide","value":"example.org"}],"why":"moved"}`}, {"plan ace", "mesh-delivery-view.assigner_wants", `{"node":"ace"}`}, {"haves", "mesh-delivery-view.node_haves", `{}`}, {"composition ace plex", "ace/node-engine.composition", `{"module":"plex"}`}, {"seats", "mesh-module-assigner.placement", `{"seats":"true"}`}, {"assign ace plex sonarr --why=media", "mesh-module-assigner.propose", `{"changes":[{"module":"plex","node":"ace","op":"place"},{"module":"sonarr","node":"ace","op":"place"}],"why":"media"}`}, {"unassign ace plex --why gone", "mesh-module-assigner.propose", `{"changes":[{"module":"plex","node":"ace","op":"take-off"}],"why":"gone"}`}, {"pin ace postgres-database novox postgres --why one", "mesh-module-assigner.propose", `{"changes":[{"node":"ace","op":"pin","provider":{"module":"postgres","node":"novox"},"provision":"postgres-database"}],"why":"one"}`}, {"unpin ace postgres-database --why back", "mesh-module-assigner.propose", `{"changes":[{"node":"ace","op":"unpin","provision":"postgres-database"}],"why":"back"}`}, {"deliveries --state merged --json", "mesh-delivery.deliveries", `{"state":"merged"}`}, {"delivery novox/mesh-sdk@dff0ce2abb21", "mesh-delivery.show", `{"id":"novox/mesh-sdk@dff0ce2abb21"}`}, {"checks novox/mesh-catalog --number 591", "mesh-delivery.checks", `{"number":"591","repository":"novox/mesh-catalog"}`}, {"try novox/mesh-catalog abc123 --level unit", "mesh-delivery-checker.try", `{"head":"abc123","level":"unit","repository":"novox/mesh-catalog"}`}, {"builds", "mesh-delivery-builder.builder_builds_since", `{"since":"2026-11-01T10:00:00Z"}`}, {"upgrade nats --policy record --why bus", "mesh-wants.upgrade", `{"module":"nats","policy":"record","why":"bus"}`}, {"bus", "mesh-wants.bus", `{}`}, {"conditions --severity urgent", "mesh-conditions.conditions", `{"severity":"urgent"}`}, {"conditions show machine.g14.root-not-free", "mesh-conditions.conditions", `{"key":"machine.g14.root-not-free"}`}, {"conditions silence k1 --for 4h --why known", "mesh-conditions.silence", `{"for":"4h","key":"k1","why":"known"}`}, {"conditions history --days 3", "mesh-conditions.conditions", `{"days":"3","history":"true"}`}, {"conditions history --key k1", "mesh-conditions.conditions_history", `{"key":"k1"}`}, {"doctor", "mesh-self-check.doctor", `{}`}, {"doctor run", "mesh-self-check.doctor", `{"run":true}`}, {"doctor probe D6", "mesh-self-check.doctor", `{"probe":"D6"}`}, {"healers --days 2", "mesh-healers.healers", `{"days":2}`}, {"hand-act record restarted the view --why stuck --cause view", "mesh-hand-acts.hand-act", `{"cause":"view","what":"restarted the view","why":"stuck"}`}, {"hand-acts --days 3", "mesh-hand-acts.hand-acts", `{"days":3}`}, {"settings show zsh --node g14", "mesh-config.config", `{"action":"list","module":"zsh","node":"g14"}`}, {"settings set zsh execute withhold --node g14 --why test", "mesh-config.config", `{"action":"set","key":"execute","module":"zsh","node":"g14","value":"withhold","why":"test"}`}, {"settings clear zsh execute --node g14 --why test", "mesh-config.config", `{"action":"clear","key":"execute","module":"zsh","node":"g14","why":"test"}`}, {"config history --key k --limit 5", "mesh-config.config", `{"action":"history","key":"k","limit":5}`}, {"operator key show", "mesh-vault.vault_status", `{}`}, // A cleanup without --confirm deletes nothing, so no hand act is recorded for it (issue 1126 review 2). {"cleanup delete ace.plex --why dry", "mesh-vault.provisioner_delete", `{"consumer":"ace.plex","why":"dry"}`}, {"collect --confirm --why space", "mesh-artifact-sweep.collect", `{"confirm":"true","why":"space"}`}, {"images ace", "mesh-artifact-sweep.images", `{"node":"ace"}`}, {"licence use g14 work", "anthropic-licence-manager.switch", `{"consumer":"g14","licence":"work"}`}, {"module show plex", "mesh-delivery-view.indexer_module", `{"module":"plex"}`}, {"issues apostrophe bug --status open --limit 5", "issue-tracker.list", `{"limit":5,"search":"apostrophe bug","status":"open"}`}, {"issue 1126 --brief", "issue-tracker.get", `{"brief":true,"number":1126}`}, {"kb ask what holds about the merge window", "knowledge-base.ask", `{"question":"what holds about the merge window"}`}, {"kb get 587 --reasoning", "knowledge-base.get", `{"number":587,"reasoning":true}`}, {"progress 47", "knowledge-base.progress", `{"of":"design 47"}`}, {"enrol issue new1 --why laptop --expires 2h", "mesh-node-enroller.issue", `{"expires":"2h","node":"new1","why":"laptop"}`}, {"token issue --new new1 --why laptop", "mesh-node-enroller.issue", `{"node":"new1","why":"laptop"}`}, {"enrol tokens", "mesh-node-enroller.tokens", `{}`}, {"call ace/node-service-manager.status {\"unit\":\"plex\"}", "ace/node-service-manager.status", `{"unit":"plex"}`}, {"ask mesh-catalog catalog_modules", "mesh-catalog.catalog_modules", `{}`}, } { got := console(t, nil) args := strings.Fields(c.line) if strings.HasPrefix(c.line, "call ") { args = []string{"call", "ace/node-service-manager.status", `{"unit":"plex"}`} } code, out, errOut := runLine(args...) if code != 0 || len(*got) != 1 || (*got)[0].Tool != "mesh_call" || (*got)[0].Address != c.address || (*got)[0].Args != c.args { t.Errorf("nox %s: exit %d, asked %+v, stderr %q; want %s %s", c.line, code, *got, errOut, c.address, c.args) continue } if !strings.Contains(out, `"ok":true`) { t.Errorf("nox %s printed %q, not the owner's answer", c.line, out) } } } // A line the controller ran that no seat owns says so and what to use, exits 1 and calls nothing; a line nox cannot // read says how it is written, exits 2 and calls nothing. func TestALineWithNoOwnerOrNotReadCallsNothingAndSaysWhy(t *testing.T) { for _, c := range []struct { line string code int err string }{ {"push ace", 1, "mesh-delivery-assigner.assigner_put"}, {"migrate", 1, "no seat or module does this now"}, {"secret accept ace plex token", 1, "mesh-vault.secret-ask"}, {"node add new1", 1, "nox enrol issue"}, {"pause", 1, "node-build-go.pause"}, {"operator key make", 1, "operator key show"}, {"assign ace plex", 2, "needs --why"}, {"issue twelve", 2, "not an issue's number"}, {"conditions --bogus x", 2, "--bogus is not a flag"}, {"settings set zsh {\"execute\":\"withhold\"} --node g14 --why x", 2, "set each key"}, {"healers --days soon", 2, "takes a number"}, {"frobnicate", 2, "is not a command"}, } { got := console(t, nil) args := strings.Fields(c.line) code, _, errOut := runLine(args...) if code != c.code || len(*got) != 0 || !strings.Contains(errOut, c.err) { t.Errorf("nox %s: exit %d, asked %+v, stderr %q; want exit %d saying %q", c.line, code, *got, errOut, c.code, c.err) } if c.code == 1 && !strings.Contains(errOut, "issue 1126") { t.Errorf("nox %s does not say the controller is unassigned: %q", c.line, errOut) } } } // What the console says is printed as said: an answer it marks an error on standard error with exit 1, a console that // does not answer as nothing called, a verb's process refused before anything is called; nodes are one line each. func TestAnswersRefusalsAndSilenceAreSaid(t *testing.T) { got := console(t, map[string]string{"mesh-node-enroller.tokens": "!nothing serves seat:mesh-node-enroller.tokens"}) if code, out, errOut := runLine("enrol", "tokens"); code != 1 || out != "" || !strings.Contains(errOut, "nothing serves") { t.Errorf("an error answer: exit %d, stdout %q, stderr %q", code, out, errOut) } placement := `{"keys":{"novox":{"placement":{"decided":{"address":"10.10.0.1","site":"hub"},"modules":[{},{}]}},` + `"ace":{"placement":{"decided":{"address":"10.10.0.2"},"modules":[{}]}}}}` console(t, map[string]string{"mesh-module-assigner.placement": placement}) if code, out, _ := runLine("nodes"); code != 0 || !strings.HasPrefix(out, "ace ") || !strings.Contains(out, "10.10.0.1") || !strings.Contains(out, "2 modules") || !strings.HasSuffix(out, "answered by ace\n") { t.Errorf("nodes: exit %d, %q", code, out) } got = console(t, map[string]string{"mesh_search": `{"matches":[]}`}) if code, out, _ := runLine("search", "restart", "a", "unit"); code != 0 || (*got)[0].Tool != "mesh_search" || !strings.Contains(out, "matches") { t.Errorf("search: exit %d, asked %+v, %q", code, *got, out) } t.Setenv("MESH_CONSOLE", "http://127.0.0.1:1/mcp") if code, _, errOut := runLine("conditions"); code != 1 || !strings.Contains(errOut, "nothing was called") { t.Errorf("no console: exit %d, %q", code, errOut) } t.Setenv("MESH_VERB", "some.verb") if code, _, errOut := runLine("conditions"); code != 1 || !strings.Contains(errOut, "a verb is never the operator") { t.Errorf("a verb's process: exit %d, %q", code, errOut) } } // The status is the delivery view's. When the view does not answer, that is said first, the conditions and the // deliveries are shown under their own addresses, and the exit is 1: they are never passed off as the status. func TestStatusIsTheViewsAndItsSilenceIsSaid(t *testing.T) { got := console(t, map[string]string{"mesh-delivery-view.view_status": `{"well":true}`}) if code, out, _ := runLine("status"); code != 0 || len(*got) != 1 || !strings.Contains(out, `"well":true`) { t.Errorf("status: exit %d, asked %+v, %q", code, *got, out) } got = console(t, map[string]string{"mesh-delivery-view.view_status": "!mesh-delivery-view.view_status did not answer within 30s", "mesh-conditions.conditions": `{"conditions":[]}`, "mesh-delivery.deliveries": `{"deliveries":[]}`}) code, out, errOut := runLine("status") if code != 1 || len(*got) != 3 || !strings.Contains(errOut, "did not answer") || !strings.Contains(errOut, "not the status") || !strings.Contains(out, "== mesh-conditions.conditions") || !strings.Contains(out, "== mesh-delivery.deliveries") { t.Errorf("status, the view silent: exit %d, asked %+v, stdout %q, stderr %q", code, *got, out, errOut) } } // The console's address comes from the file the module writes from its mcp-endpoint; MESH_CONSOLE overrides it; with // neither, the port node-tools declares. An answer given as a server-sent event is read like one given as JSON. func TestTheConsoleIsFoundAndItsAnswerRead(t *testing.T) { t.Setenv("MESH_CONSOLE", "") dir := t.TempDir() before := consoleFile consoleFile = filepath.Join(dir, "console") t.Cleanup(func() { consoleFile = before }) if url, _ := consoleAddress(); url != defaultConsole { t.Errorf("no file: %s", url) } if err := os.WriteFile(consoleFile, []byte("http://127.0.0.1:4999/mcp\n"), 0o644); err != nil { t.Fatal(err) } if url, from := consoleAddress(); url != "http://127.0.0.1:4999/mcp" || from != consoleFile { t.Errorf("the file: %s from %s", url, from) } t.Setenv("MESH_CONSOLE", "http://127.0.0.1:5000/mcp") if url, _ := consoleAddress(); url != "http://127.0.0.1:5000/mcp" { t.Errorf("the variable: %s", url) } a, err := decodeAnswer([]byte("event: message\ndata: {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"hi\"}],\"isError\":true}}\n\n")) if err != nil || a.Text != "hi" || !a.IsError { t.Errorf("an event: %+v %v", a, err) } if _, err := decodeAnswer([]byte("")); err == nil { t.Error("a page that is no answer was read as one") } } // Every command's line is in the help, and none is defined twice. func TestEveryLineIsInTheHelpOnce(t *testing.T) { seen := map[string]bool{} help := usage() for _, c := range commands { w := strings.Join(c.words, " ") if seen[w] { t.Errorf("nox %s is defined twice", w) } seen[w] = true if c.build != nil && !strings.Contains(help, "nox "+w+" ") { t.Errorf("nox %s is not in the help", w) } if c.build == nil && c.instead == "" { t.Errorf("nox %s has neither an owner nor an instead", w) } if _, dup := unowned[c.words[0]]; dup && len(c.words) == 1 { t.Errorf("nox %s is both owned and unowned", w) } } } // ADR 0590 ยง8 (issue 1126 review 1): each sensitive vault verb judges its own caller, and nox reaches the vault as its // machine's runtime, so nox first records the hand act that warrants the line through mesh-hand-acts (the vault verb as // its verb, what it acts on as its what) and names it as `act`; a recording refused calls the vault not at all. func TestASensitiveLineRecordsItsHandActAndNamesIt(t *testing.T) { recorded := `{"act":{"id":"act-7","verb":"mesh-vault.x"},"said":"recorded"}` for _, c := range []struct{ line, address, verb, what, args string }{ {"secret export", "mesh-vault.secret_vault_export", "mesh-vault.secret_vault_export", "the vault's sealed copies", `{"act":"act-7"}`}, {"rotate --node ace --module plex --name token --why leak", "mesh-vault.rotate", "mesh-vault.rotate", "own/ace/plex/token", `{"act":"act-7","module":"plex","name":"token","node":"ace","why":"leak"}`}, {"rotate --provider novox --provision postgres --why yearly", "mesh-vault.rotate", "mesh-vault.rotate", "provision/novox/postgres", `{"act":"act-7","provider":"novox","provision":"postgres","why":"yearly"}`}, {"retire approve c1 c2 --why done", "mesh-vault.provisioner_retire_approve", "mesh-vault.provisioner_retire_approve", "c1 c2", `{"act":"act-7","consumers":["c1","c2"],"why":"done"}`}, {"cleanup delete ace.plex --why done --confirm", "mesh-vault.provisioner_delete", "mesh-vault.provisioner_delete", "ace.plex", `{"act":"act-7","confirm":"ace.plex","consumer":"ace.plex","why":"done"}`}, } { got := console(t, map[string]string{"mesh-hand-acts.hand-act": recorded}) code, _, errOut := runLine(strings.Fields(c.line)...) if code != 0 || len(*got) != 2 || (*got)[0].Address != "mesh-hand-acts.hand-act" || (*got)[1].Address != c.address || (*got)[1].Args != c.args { t.Errorf("nox %s: exit %d, asked %+v, stderr %q", c.line, code, *got, errOut) continue } var act map[string]any _ = json.Unmarshal([]byte((*got)[0].Args), &act) if act["verb"] != c.verb || act["what"] != c.what || act["why"] == "" || act["cause"] == "" { t.Errorf("nox %s recorded %v", c.line, act) } } got := console(t, map[string]string{"mesh-hand-acts.hand-act": "!nothing serves seat:mesh-hand-acts.hand-act"}) if code, _, errOut := runLine("secret", "export"); code != 1 || len(*got) != 1 || !strings.Contains(errOut, "nothing serves") { t.Errorf("a refused recording: exit %d, asked %+v, %q", code, *got, errOut) } }