diff --git a/modules/mesh-cli/README.md b/modules/mesh-cli/README.md index 9615aab1..6dfb4cab 100644 --- a/modules/mesh-cli/README.md +++ b/modules/mesh-cli/README.md @@ -35,9 +35,11 @@ everywhere say `mesh-cli`. In interactive zsh the alias hides any other `nox` (A It meets every refusal of the generic `command` verb, and a change made at the terminal alone is refused with the reason. - **refused**: any other account, root included. Run it as yourself, not under `sudo`. -3. `mesh-cli` prints standard output and standard error as the command wrote them, says why a line was not the - terminal or was refused, says when the output was cut to fit one bus message, and exits with the command's - status. +3. A line still running after ten seconds is followed by its call until it ends. `mesh-cli` prints standard + output and standard error as the command wrote them, says why a line was not the terminal or was refused, says + when the output was cut to fit one bus message, and exits with the command's status. When no answer comes in + time it says the line may have run and names its call; "nothing ran" is said only of a line no controller + took. A process that runs for a verb (it carries `MESH_VERB`) is refused before anything is asked: a verb is never the operator. @@ -49,11 +51,14 @@ operator. Give settings inline: `nox settings set "$(cat settings.json)" --node `. - **No streaming.** The answer arrives when the command ends, within the controller's bound on a call (five minutes). Interrupting `mesh-cli` stops the wait, not the command. +- **The `MESH_VERB` refusal is a courtesy.** A verb's process is refused before asking, but the gate is the + controller: it judges the account the node-engine vouches for, whatever the program says. - **No other node is the terminal.** Phase 2 of ADR 0272 adds a node whose agents run under an account of their own (ADR 0266), judged unable to become root. ## Tests -`go test ./...`: the line is asked as given and the answer printed with its exit; a reason, a refusal and a cut +`go test ./...`: the module places exactly one file (one archive resource, one Go bundle compiled to the binary +alone); the line is asked as given and the answer printed with its exit; a reason, a refusal, a cut and an outcome not known are said and a refusal never exits 0; a verb's process is refused before asking; an engine that does not answer is said. diff --git a/modules/mesh-cli/cmd/mesh-cli/main.go b/modules/mesh-cli/cmd/mesh-cli/main.go index 7b61cde9..f5019e5e 100644 --- a/modules/mesh-cli/cmd/mesh-cli/main.go +++ b/modules/mesh-cli/cmd/mesh-cli/main.go @@ -69,6 +69,13 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int { if !a.Terminal && a.Why != "" { fmt.Fprintln(stderr, "mesh-cli: "+a.Why) } + if a.Unknown != "" { + // Asked, and what came of it not known: never said as nothing having run. + fmt.Fprintln(stderr, "mesh-cli: "+a.Unknown) + if a.Exit == 0 { + return 1 + } + } if a.Refused != "" { fmt.Fprintln(stderr, "mesh-cli: "+a.Refused) if a.Exit == 0 { diff --git a/modules/mesh-cli/cmd/mesh-cli/main_test.go b/modules/mesh-cli/cmd/mesh-cli/main_test.go index 37076633..a38238fe 100644 --- a/modules/mesh-cli/cmd/mesh-cli/main_test.go +++ b/modules/mesh-cli/cmd/mesh-cli/main_test.go @@ -93,3 +93,14 @@ func TestAnEngineThatDoesNotAnswerIsSaid(t *testing.T) { t.Fatalf("exit %d, said %q", code, errOut.String()) } } + +// An outcome not known is said as such — the line may have run — and never as nothing having run. +func TestAnOutcomeNotKnownIsSaid(t *testing.T) { + t.Setenv("MESH_VERB", "") + engine(t, cli.NotKnown("call-4", "no answer within 6m0s")) + var out, errOut bytes.Buffer + code := run(context.Background(), []string{"push", "laptop"}, &out, &errOut) + if code == 0 || !strings.Contains(errOut.String(), "may have run") || !strings.Contains(errOut.String(), "call-4") { + t.Fatalf("exit %d, said %q", code, errOut.String()) + } +} diff --git a/modules/mesh-cli/cmd/mesh-cli/manifest_test.go b/modules/mesh-cli/cmd/mesh-cli/manifest_test.go new file mode 100644 index 00000000..bf143a9b --- /dev/null +++ b/modules/mesh-cli/cmd/mesh-cli/manifest_test.go @@ -0,0 +1,56 @@ +package main + +import ( + "encoding/json" + "os" + "testing" +) + +// **The archive places exactly one file, /usr/local/bin/mesh-cli** (review of novox/hq ADR 0272): one archive +// resource, unpacking one artifact into /usr/local/bin, and that artifact a compiled Go bundle — the builder writes +// such a bundle as its binary alone (`go build -o /`), named mesh-cli — with nothing the runtime would +// load or put beside it. A second file in the bundle, or a second resource writing there, fails here. +func TestTheArchivePlacesExactlyOneFile(t *testing.T) { + raw, err := os.ReadFile("../../module.json") + if err != nil { + t.Fatal(err) + } + var m struct { + Resources []map[string]any `json:"resources"` + Build struct { + Artifacts []map[string]any `json:"artifacts"` + } `json:"build"` + } + if err := json.Unmarshal(raw, &m); err != nil { + t.Fatal(err) + } + if len(m.Resources) != 1 { + t.Fatalf("the module declares %d resources; it places one file", len(m.Resources)) + } + r := m.Resources[0] + if r["type"] != "archive" || r["path"] != "/usr/local/bin" { + t.Fatalf("its resource is %v", r) + } + if len(m.Build.Artifacts) != 1 { + t.Fatalf("it builds %d artifacts", len(m.Build.Artifacts)) + } + a := m.Build.Artifacts[0] + if a["name"] != r["artifact"] || a["kind"] != "bundle" || a["language"] != "go" || a["binary"] != "mesh-cli" || + a["from"] != "cmd/mesh-cli" { + t.Fatalf("its artifact is %v, unpacked as %v", a, r["artifact"]) + } + for _, extra := range []string{"loads", "env", "context", "files"} { + if _, has := a[extra]; has { + t.Fatalf("its artifact says %q, which would put more than the binary in the bundle or load it", extra) + } + } + entries, err := os.ReadDir(".") + if err != nil { + t.Fatal(err) + } + for _, e := range entries { + if e.IsDir() { + t.Fatalf("cmd/mesh-cli holds a directory %s, which a build could carry into the bundle", e.Name()) + } + } +} diff --git a/modules/mesh-cli/go.mod b/modules/mesh-cli/go.mod index 566150bb..4d6d5938 100644 --- a/modules/mesh-cli/go.mod +++ b/modules/mesh-cli/go.mod @@ -2,4 +2,4 @@ module mesh-cli go 1.22 -require git.novox.be/novox/mesh-sdk/go v0.1.8-0.20261009100314-bfe82315f42c +require git.novox.be/novox/mesh-sdk/go v0.1.8-0.20261009103748-a5a040c9049b diff --git a/modules/mesh-cli/go.sum b/modules/mesh-cli/go.sum index eb901752..22f2ed57 100644 --- a/modules/mesh-cli/go.sum +++ b/modules/mesh-cli/go.sum @@ -1,2 +1,2 @@ -git.novox.be/novox/mesh-sdk/go v0.1.8-0.20261009100314-bfe82315f42c h1:8hjQ3FYt7mGdZv+HX0Vk+n3pdaS3W5EIlwsrvSXXhRY= -git.novox.be/novox/mesh-sdk/go v0.1.8-0.20261009100314-bfe82315f42c/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY= +git.novox.be/novox/mesh-sdk/go v0.1.8-0.20261009103748-a5a040c9049b h1:HmasScQ02QeFlAFDnmRCOEserbtDJJtutK+acBmGlNs= +git.novox.be/novox/mesh-sdk/go v0.1.8-0.20261009103748-a5a040c9049b/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=