From fbd1744620c7046d1e7f800cc2f5a220407612fb Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 9 Oct 2026 13:53:28 +0200 Subject: [PATCH] Start a rehearsal only at the controller's terminal as main now judges it (hq ADR 0259, ADR 0272) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rehearse refused only a verb's process. Since mesh-cli (ADR 0272 §4) the serving controller runs an ordinary mesh-cli line without a verb, naming its caller: such a line, from an agent's account, read as the terminal and could start a question the operator did not ask. rehearse now asks startedAtTheTerminal. And main's mesh-cli test helper asked is cliAsked, beside the asker's asked. --- cmd/mesh-controller/meshcli_test.go | 34 ++++++++++++++-------------- cmd/mesh-controller/rehearse.go | 13 +++++++---- cmd/mesh-controller/rehearse_test.go | 16 +++++++++++++ 3 files changed, 41 insertions(+), 22 deletions(-) diff --git a/cmd/mesh-controller/meshcli_test.go b/cmd/mesh-controller/meshcli_test.go index 8baf28a3..569d53b9 100644 --- a/cmd/mesh-controller/meshcli_test.go +++ b/cmd/mesh-controller/meshcli_test.go @@ -24,7 +24,7 @@ var cliNodes = []inventory.Node{ {Name: "unnamed"}, } -func asked(account string, uid uint32, line ...string) link.CLIAsked { +func cliAsked(account string, uid uint32, line ...string) link.CLIAsked { return link.CLIAsked{Line: line, Account: account, UID: uid} } @@ -39,13 +39,13 @@ func TestMeshCLIIsTheTerminalOnlyForTheControlNodesOperator(t *testing.T) { refused string why string }{ - {"the control-node's operator", "control", asked("operator", 1000, "status"), control, true, "", "the controller's terminal"}, - {"another node's operator", "laptop", asked("operator", 1000, "status"), control, false, "", "agents on laptop may run as operator"}, - {"another account", "control", asked("agent", 1001, "status"), control, false, "operator account (operator) only", ""}, - {"root", "control", asked("root", 0, "status"), control, false, "never root", ""}, - {"a node with no operator account", "unnamed", asked("operator", 1000, "status"), control, false, "does not know unnamed's operator account", ""}, - {"a node the mesh does not know", "elsewhere", asked("operator", 1000, "status"), control, false, "not a node this mesh knows", ""}, - {"two control-nodes", "control", asked("operator", 1000, "status"), []string{"control", "laptop"}, false, "", "2 control-nodes"}, + {"the control-node's operator", "control", cliAsked("operator", 1000, "status"), control, true, "", "the controller's terminal"}, + {"another node's operator", "laptop", cliAsked("operator", 1000, "status"), control, false, "", "agents on laptop may run as operator"}, + {"another account", "control", cliAsked("agent", 1001, "status"), control, false, "operator account (operator) only", ""}, + {"root", "control", cliAsked("root", 0, "status"), control, false, "never root", ""}, + {"a node with no operator account", "unnamed", cliAsked("operator", 1000, "status"), control, false, "does not know unnamed's operator account", ""}, + {"a node the mesh does not know", "elsewhere", cliAsked("operator", 1000, "status"), control, false, "not a node this mesh knows", ""}, + {"two control-nodes", "control", cliAsked("operator", 1000, "status"), []string{"control", "laptop"}, false, "", "2 control-nodes"}, } for _, c := range cases { v := judgeCLI(c.node, c.asked, cliNodes, c.control) @@ -70,7 +70,7 @@ func TestTheTerminalRunsWithoutAVerbAndAnOrdinaryCallNamesMeshCLI(t *testing.T) t.Setenv(servedVar, "1") ctx := context.Background() - a := runForMeshCLI(ctx, "control", asked("operator", 1000, "status"), cliVerdict{terminal: true, why: "the terminal"}) + a := runForMeshCLI(ctx, "control", cliAsked("operator", 1000, "status"), cliVerdict{terminal: true, why: "the terminal"}) if a.Exit != 0 || a.Refused != "" || !a.Terminal { t.Fatalf("the terminal's line did not run: %+v", a) } @@ -79,7 +79,7 @@ func TestTheTerminalRunsWithoutAVerbAndAnOrdinaryCallNamesMeshCLI(t *testing.T) t.Fatalf("the terminal's line ran with %s", got) } - a = runForMeshCLI(ctx, "laptop", asked("operator", 1000, "status"), cliVerdict{why: "not the terminal"}) + a = runForMeshCLI(ctx, "laptop", cliAsked("operator", 1000, "status"), cliVerdict{why: "not the terminal"}) if a.Exit != 0 || a.Terminal || a.Why != "not the terminal" { t.Fatalf("an ordinary line did not run as one: %+v", a) } @@ -93,21 +93,21 @@ func TestAnOrdinaryCallMeetsTheCommandVerbsRefusals(t *testing.T) { t.Setenv(echoEnvironment, "1") ctx := context.Background() ordinary := cliVerdict{why: "not the terminal"} - a := runForMeshCLI(ctx, "laptop", asked("operator", 1000, "cleanup", "delete", "x"), ordinary) + a := runForMeshCLI(ctx, "laptop", cliAsked("operator", 1000, "cleanup", "delete", "x"), ordinary) if a.Refused == "" || len(a.Stdout) != 0 || a.Exit != 1 || a.Why != "not the terminal" { t.Fatalf("a repair without --why ran as an ordinary call: %+v", a) } - a = runForMeshCLI(ctx, "laptop", asked("operator", 1000, "settings", "set", "claude-code", "{}"), ordinary) + a = runForMeshCLI(ctx, "laptop", cliAsked("operator", 1000, "settings", "set", "claude-code", "{}"), ordinary) if a.Refused != "" || !strings.Contains(string(a.Stdout), `verb="mesh-cli"`) { t.Fatalf("an ordinary settings set did not run through the settings verb's path with MESH_VERB set: %+v", a) } for _, server := range []string{"serve", "api", "board"} { - a := runForMeshCLI(ctx, "control", asked("operator", 1000, server), cliVerdict{terminal: true}) + a := runForMeshCLI(ctx, "control", cliAsked("operator", 1000, server), cliVerdict{terminal: true}) if a.Refused == "" || len(a.Stdout) != 0 { t.Fatalf("%s was run for mesh-cli: %+v", server, a) } } - a = runForMeshCLI(ctx, "control", asked("agent", 1001, "status"), cliVerdict{refused: "agent is not answered"}) + a = runForMeshCLI(ctx, "control", cliAsked("agent", 1001, "status"), cliVerdict{refused: "agent is not answered"}) if a.Refused != "agent is not answered" || len(a.Stdout) != 0 { t.Fatalf("a refused line ran: %+v", a) } @@ -184,9 +184,9 @@ func TestEveryMeshCLILineIsSaidInTheJournal(t *testing.T) { cliJournal = func(line string) { said = append(said, line) } t.Cleanup(func() { cliJournal = was }) ctx := link.WithCallID(context.Background(), "call-1") - runForMeshCLI(ctx, "control", asked("operator", 1000, "settings", "set", "x", `{"password":"s3cret"}`), + runForMeshCLI(ctx, "control", cliAsked("operator", 1000, "settings", "set", "x", `{"password":"s3cret"}`), cliVerdict{terminal: true, why: "the terminal"}) - runForMeshCLI(ctx, "control", asked("agent", 1001, "status"), cliVerdict{refused: "agent is not answered"}) + runForMeshCLI(ctx, "control", cliAsked("agent", 1001, "status"), cliVerdict{refused: "agent is not answered"}) all := strings.Join(said, "\n") if len(said) != 2 || !strings.Contains(all, "call-1") || !strings.Contains(all, "operator on control") || !strings.Contains(all, "as the controller's terminal") || !strings.Contains(all, "refused") { @@ -213,7 +213,7 @@ func TestAnOrdinaryLineRunsNothingTheCommandVerbWouldRefuse(t *testing.T) { if _, err := ordinaryLine(line); err == nil { t.Errorf("%q composed as an ordinary line", line) } - a := runForMeshCLI(context.Background(), "laptop", asked("operator", 1000, line...), cliVerdict{why: "not the terminal"}) + a := runForMeshCLI(context.Background(), "laptop", cliAsked("operator", 1000, line...), cliVerdict{why: "not the terminal"}) if a.Refused == "" || len(a.Stdout) != 0 { t.Errorf("%q ran as an ordinary line: %+v", line, a) } diff --git a/cmd/mesh-controller/rehearse.go b/cmd/mesh-controller/rehearse.go index 5ec46044..15836946 100644 --- a/cmd/mesh-controller/rehearse.go +++ b/cmd/mesh-controller/rehearse.go @@ -12,7 +12,8 @@ package main // as on any other: it claims the ask once, checks the act is the one bound, performs nothing, and records the // hand-act `warrant` with who answered, through which channel, and the proofs. `hand-acts` then shows it. // -// **The terminal's alone**: a command a verb runs (MESH_VERB set) is refused, so no agent starts a rehearsal — a +// **The terminal's alone** (startedAtTheTerminal): a command a verb runs, an ordinary mesh-cli line and anything the +// serving controller started are refused, so no agent starts a rehearsal — a // rehearsal is a question the operator expects, and one an agent could start would teach them to approve what they // did not ask for. @@ -22,7 +23,6 @@ import ( "errors" "flag" "fmt" - "os" "time" "github.com/nats-io/nats.go" @@ -68,9 +68,12 @@ func doesRehearsal(act conditions.Action) string { } func rehearseCommand(ctx context.Context, args []string) error { - if os.Getenv(verbVar) != "" { - return errors.New("rehearse is the controller's terminal's alone: a verb may not start one, so no agent asks " + - "the operator a question they did not start (novox/hq ADR 0259)") + // The terminal as main judges it (startedAtTheTerminal): not a verb, not the serving controller or anything it + // started, and a mesh-cli line only when it is the control-node's operator's (novox/hq ADR 0272 §4). + if !startedAtTheTerminal() { + return errors.New("rehearse is the controller's terminal's alone: a verb, a mesh-cli line from anybody but " + + "the control-node's operator, or a process the serving controller started may not start one, so no agent " + + "asks the operator a question they did not start (novox/hq ADR 0259)") } set := flag.NewFlagSet("rehearse", flag.ContinueOnError) lasts := set.Duration("for", 15*time.Minute, "how long the question waits for an answer") diff --git a/cmd/mesh-controller/rehearse_test.go b/cmd/mesh-controller/rehearse_test.go index 4cfdeb20..df738810 100644 --- a/cmd/mesh-controller/rehearse_test.go +++ b/cmd/mesh-controller/rehearse_test.go @@ -2,6 +2,7 @@ package main import ( "context" + "github.com/novox/mesh-controller/internal/link" "strings" "testing" "time" @@ -54,6 +55,21 @@ func TestARehearsalIsTheTerminalsAlone(t *testing.T) { if err := rehearseCommand(context.Background(), nil); err == nil || !strings.Contains(err.Error(), "terminal") { t.Fatalf("a verb started a rehearsal: %v", err) } + // Nor a mesh-cli line from anybody but the control-node's operator (hq ADR 0272 §4): run without a verb, + // naming its caller, and without the terminal's mark — and nor anything the serving controller started. + for name, env := range map[string]map[string]string{ + "an ordinary mesh-cli line": {verbVar: "", link.CallerVar: "laptop/agent"}, + "a process the serving controller ran": {verbVar: "", servedVar: "1"}, + } { + t.Run(name, func(t *testing.T) { + for k, v := range env { + t.Setenv(k, v) + } + if err := rehearseCommand(context.Background(), nil); err == nil || !strings.Contains(err.Error(), "terminal") { + t.Fatalf("%s started a rehearsal: %v", name, err) + } + }) + } } // askerRehearsalFor is how long the test's rehearsal waits.