diff --git a/cmd/mesh-controller/main.go b/cmd/mesh-controller/main.go index 49af526b..7f3337f1 100644 --- a/cmd/mesh-controller/main.go +++ b/cmd/mesh-controller/main.go @@ -360,6 +360,9 @@ func connectionName(command, verb string) string { // removedByName refuses a command the controller no longer has, naming what replaced it (novox/hq ADR 0501 §4, §5; // ADR 0429 §18; ADR 0503 §3; issue 1851, A11, and issue 1318). func removedByName(command string) error { + if why, removed := removedInRelease6(command); removed { + return &removedCommand{msg: fmt.Sprintf("`%s` no longer exists: %s", command, why)} + } replacement := map[string]string{ "build": "the controller asks no build (ADR 0501 §5): the builder builds every fingerprint the indexer writes, a " + "module is registered from its done key, and a branch is tried with `try`; a module from a repository the " + @@ -382,6 +385,8 @@ func removedByName(command string) error { "resume, or its capacity", "forget-checks": "build-agent makes no check build from R4e (ADR 0503 §3), so there is nothing to let go of", "delivery-plan": "a delivery's plan is read with `delivery-order` and the checker's verdict (ADR 0503 §3)", + "durations": "the controller measures no sends, tiers or applies since release 6 (ADR 0539 §17): the haves and wants " + + "on the bus carry what a delivery took", "delivery-check": "the checker judges every head and every delivery group (ADR 0503 §3, ADR 0527): the controller asks " + "nothing and says no verdict of a pull request", }[command] diff --git a/cmd/mesh-controller/seatverbs.go b/cmd/mesh-controller/seatverbs.go index ddf3c875..460be8a2 100644 --- a/cmd/mesh-controller/seatverbs.go +++ b/cmd/mesh-controller/seatverbs.go @@ -58,6 +58,9 @@ func argvFor(verb string, args map[string]any) ([]string, error) { if removedVerbs[verb] { return nil, removedByName(verb) } + if why, removed := removedInRelease6(verb); removed { + return nil, &removedCommand{msg: fmt.Sprintf("`%s` no longer exists: %s", verb, why)} + } a, err := readArguments(verb, args) if err != nil { return nil, err @@ -93,13 +96,15 @@ type verbArguments struct { } // removedVerbs are the controller seat's verbs R4e removed, kept in its table with no input so a caller is answered -// by name (ADR 0501 §5, ADR 0503 §3, ADR 0450 §8). +// by name (ADR 0501 §5, ADR 0503 §3, ADR 0450 §8). The verbs release 6 removed are catalogue.RemovedControllerVerbs. var removedVerbs = map[string]bool{"build": true, "rebuild": true, "forget-checks": true, "delivery-plan": true, - "delivery-check": true, "queue": true, "cancel": true, "clear": true, "kill": true, "replay": true, "pause": true, "resume": true, - // Release 6 (ADR 0539 §5): a delivery is each machine following its wants, so the verbs that planned, started, stopped, - // retried or closed one are gone. - "plans": true, "plan": true, "push": true, "deliver": true, "delivery-stop": true, "delivery-backups-again": true, - "delivery-walks": true, "delivery-order": true} + "delivery-check": true, "queue": true, "cancel": true, "clear": true, "kill": true, "replay": true, "pause": true, "resume": true} + +// removedInRelease6 says a verb is one release 6 removed (ADR 0539, ADR 0429 §8): refused by name, with its replacement. +func removedInRelease6(verb string) (string, bool) { + why, removed := catalogue.RemovedControllerVerbs[verb] + return why, removed +} // controllerVerb is this binary's own definition of a verb: what it runs is what it declares, so the // arguments are checked against the table compiled beside argvFor, not a row a newer or older build @@ -1187,47 +1192,6 @@ var inProcess = map[string]bool{"tools": true, "calls": true, "doctor": true, // issue 1937). "held": true} -// pushFileVar names the file the controller's push settings are written to (module.json, `push-wait`). -const pushFileVar = "MESH_PUSH_FILE" - -// pushWait is how long a push to a named machine waits for its answer (novox/hq issue 629): the call's own -// `wait` when given, else the controller's setting `push-wait`, else nothing. No length is written here: the -// setting's default is the module's, and a setting that cannot be read waits for nothing, as a push did -// before, and is said. -func pushWait(given string) (time.Duration, error) { - if strings.TrimSpace(given) != "" { - d, ok := durationSetting(given) - if !ok { - return 0, fmt.Errorf("push: wait %q is no duration: say it as \"90s\", \"2m\", or \"0\" to wait for nothing", given) - } - return d, nil - } - path := os.Getenv(pushFileVar) - if path == "" { - return 0, nil - } - raw, err := os.ReadFile(path) - if err != nil { - fmt.Fprintf(os.Stderr, "the push settings cannot be read (%v); a push waits for nothing\n", err) - return 0, nil - } - var values map[string]any - if err := json.Unmarshal(raw, &values); err != nil { - fmt.Fprintf(os.Stderr, "the push settings are not JSON (%v); a push waits for nothing\n", err) - return 0, nil - } - v, has := values["push-wait"] - if !has || v == "" { - return 0, nil - } - d, ok := durationSetting(v) - if !ok { - fmt.Fprintf(os.Stderr, "the setting push-wait=%v is no duration; a push waits for nothing\n", v) - return 0, nil - } - return d, nil -} - // answersFirst is a command line whose caller is answered before it runs: a push, by its verb or // through `command`. A push sends the machine holding the bus first when its user list changed, the // broker reloads, and a reload forgets every answer the bus was about to permit — so an answer diff --git a/cmd/mesh-controller/seatverbs_test.go b/cmd/mesh-controller/seatverbs_test.go index 06c525b8..5ca9659b 100644 --- a/cmd/mesh-controller/seatverbs_test.go +++ b/cmd/mesh-controller/seatverbs_test.go @@ -3,8 +3,13 @@ package main import ( "errors" "fmt" + "io/fs" + "os" + "path/filepath" "strings" "testing" + + "git.novox.be/novox/mesh-sdk/go/catalogue" ) // `builds` given a build's id no longer reads a log from the bus: the build seat is retired (ADR 0525 clause 7). @@ -226,3 +231,58 @@ func TestAQuotedValueRoundTrips(t *testing.T) { } } } + +// **A verb release 6 removed is refused by name, with what replaces it** (ADR 0539, ADR 0429 §8, issue 1945 B10): every +// verb of catalogue.RemovedControllerVerbs is answered "no longer exists" naming itself and its replacement, whatever +// arguments it is given, and the tool-call path and the terminal's command line give the same words. +func TestTheVerbsReleaseSixRemovedAreRefusedByName(t *testing.T) { + if len(catalogue.RemovedControllerVerbs) == 0 { + t.Fatal("the SDK lists no removed controller verb") + } + for verb, why := range catalogue.RemovedControllerVerbs { + argv, err := argvFor(verb, map[string]any{"node": "anchor", "why": "w"}) + if err == nil || len(argv) != 0 { + t.Errorf("%s was composed into %v", verb, argv) + continue + } + for _, want := range []string{"`" + verb + "`", "no longer exists", why} { + if !strings.Contains(err.Error(), want) { + t.Errorf("%s: the refusal %q lacks %q", verb, err, want) + } + } + if cmd := removedByName(verb); cmd == nil || cmd.Error() != err.Error() { + t.Errorf("%s: the terminal says %v, the tool says %v", verb, cmd, err) + } + } +} + +// The controller's size, printed against the 111,386 non-test lines its repository held before release 6 (acceptance test 20, ADR +// 0539 §17): raw lines of every non-test Go file of the module, which the pull request reports. +func TestTheControllersNonTestLinesArePrinted(t *testing.T) { + const before = 111386 + total := 0 + err := filepath.WalkDir("../..", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() && (d.Name() == ".git" || d.Name() == "testdata") { + return filepath.SkipDir + } + if d.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") { + return nil + } + raw, err := os.ReadFile(path) + if err != nil { + return err + } + total += strings.Count(string(raw), "\n") + return nil + }) + if err != nil { + t.Fatal(err) + } + t.Logf("non-test Go lines: %d against %d before release 6 (%+d)", total, before, total-before) + if total >= before { + t.Errorf("the controller holds %d non-test lines, no fewer than the %d it held before release 6", total, before) + } +} diff --git a/module.json b/module.json index d41ce99e..eb6ee64c 100644 --- a/module.json +++ b/module.json @@ -25,23 +25,6 @@ }, "secrets-owner": "mesh-controller", "prepares": true, - "settings": { - "merge-window": { - "kind": "preference", - "default": "90s", - "why": "merges are collected while they keep coming, and a batch is planned once no merge came for this long (novox/hq ADR 0276)" - }, - "merge-window-at-most": { - "kind": "preference", - "default": "10m", - "why": "a busy period never holds a batch longer than this after its first merge (novox/hq ADR 0276)" - }, - "push-wait": { - "kind": "preference", - "default": "2m", - "why": "how long a push to a named machine waits for that machine's answer, so its call says how it went; a call's own wait overrides it, and 0 waits for nothing (novox/hq issue 629)" - } - }, "tools": [ "tools", "calls", @@ -124,23 +107,6 @@ "place": "mesh", "owner": "mesh-controller" }, - { - "id": "merge-window", - "type": "file", - "path": "${dir:mesh-state}/merge-window.json", - "mode": "0600", - "owner": "mesh-controller", - "content": "{\n \"merge-window\": \"${setting:merge-window}\",\n \"merge-window-at-most\": \"${setting:merge-window-at-most}\"\n}\n" - }, - { - "id": "push", - "trusted": false, - "type": "file", - "path": "${dir:mesh-state}/push.json", - "mode": "0600", - "owner": "mesh-controller", - "content": "{\n \"push-wait\": \"${setting:push-wait}\"\n}\n" - }, { "id": "controller", "type": "process", @@ -164,9 +130,7 @@ "MESH_BROKER_MANAGEMENT_PORT": "${seat:mesh-broker:15672}", "MESH_BROKER_ADDRESS_PORT": "${seat:mesh-broker:5671}", "MESH_BUS_NATS_FILE": "${dir:mesh-state}/bus", - "MESH_CONTROLLER_VERSION": "${version}", - "MESH_MERGE_WINDOW_FILE": "${dir:mesh-state}/merge-window.json", - "MESH_PUSH_FILE": "${dir:mesh-state}/push.json" + "MESH_CONTROLLER_VERSION": "${version}" }, "replaces": [ "server"