From 6bfdbc575844b302b3c42226f0163ab7dfb13f1d Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 23 Oct 2026 15:34:11 +0200 Subject: [PATCH] Take the build agent and the toolchain images out of the catalogue's prose, and refuse the word where it comes back outside the one module still naming it (issue 1295, E6) --- checks/words/buildagent-allowed | 3 + checks/words/buildagent_test.go | 57 ++++++++++++++ checks/words/main.go | 77 ++++++++++--------- .../claude-code/cmd/claude-code/proposals.go | 2 +- modules/dev-tools/README.md | 6 +- modules/gitea/README.md | 2 +- .../gitea/cmd/repositories/repositories.go | 4 +- modules/gitea/provisioner/teams.ts | 2 +- modules/lab/tools/runs.ts | 2 +- modules/mesh-issues/README.md | 2 +- modules/mesh-issues/cmd/mesh-issues/check.go | 2 +- .../mesh-issues/cmd/mesh-issues/tracker.go | 2 +- 12 files changed, 114 insertions(+), 47 deletions(-) create mode 100644 checks/words/buildagent-allowed create mode 100644 checks/words/buildagent_test.go diff --git a/checks/words/buildagent-allowed b/checks/words/buildagent-allowed new file mode 100644 index 00000000..916dafcf --- /dev/null +++ b/checks/words/buildagent-allowed @@ -0,0 +1,3 @@ +# The modules of the catalogue whose texts still name the build agent or the toolchain images, and why (checks/words, +# buildagent_test.go). One module directory a line, with its reason after a #. It only shrinks. +modules/mesh-build-go # build.go:45 says "the build agent's recipe" in a step's description: the asks lane's module (issue 1887), not E6's to edit diff --git a/checks/words/buildagent_test.go b/checks/words/buildagent_test.go new file mode 100644 index 00000000..febc6e29 --- /dev/null +++ b/checks/words/buildagent_test.go @@ -0,0 +1,57 @@ +package main + +import ( + "os" + "strings" + "testing" +) + +// The build agent and the toolchain images are gone (novox/hq ADR 0524 §5, issue 1295 and E6 of milestone R4g): no text a module +// can show names them. Where the word stands for a reason that is another lane's or still waits for its change, buildagent-allowed +// names the module's directory with that reason and the date it is dated from; the list only shrinks: a directory on it that no +// longer names the word is refused, so the line is taken out. +var gone = []string{"build-agent", "build agent", "toolchain image"} + +func allowedModules(t *testing.T) map[string]string { + t.Helper() + raw, err := os.ReadFile("buildagent-allowed") + if err != nil { + t.Fatal(err) + } + out := map[string]string{} + for _, line := range strings.Split(string(raw), "\n") { + entry, why, _ := strings.Cut(line, "#") + if entry = strings.TrimSpace(entry); entry != "" && !strings.HasPrefix(line, "#") { + if strings.TrimSpace(why) == "" { + t.Fatalf("%q is allowed without a reason", entry) + } + out[entry] = strings.TrimSpace(why) + } + } + return out +} + +func TestNoTextOfTheCatalogueNamesTheBuildAgentOutsideTheModulesAllowed(t *testing.T) { + texts, _, err := collect("../..") + if err != nil { + t.Fatal(err) + } + allowed := allowedModules(t) + seen := map[string]bool{} + for _, f := range check(gone, texts) { + module := f + if rest, ok := strings.CutPrefix(f, "modules/"); ok { + module = "modules/" + strings.SplitN(rest, "/", 2)[0] + } + if _, ok := allowed[module]; ok { + seen[module] = true + continue + } + t.Errorf("%s (the build agent and the toolchain images are gone: say what stands in their place)", f) + } + for module, why := range allowed { + if !seen[module] { + t.Errorf("buildagent-allowed names %s (%s), which names the word no more: take the line out, so it cannot come back", module, why) + } + } +} diff --git a/checks/words/main.go b/checks/words/main.go index b9bfe776..9db6b1e6 100644 --- a/checks/words/main.go +++ b/checks/words/main.go @@ -58,41 +58,7 @@ func run(root string) int { fmt.Fprintln(os.Stderr, "words: retired-words lists no word — the check would pass on anything") return 2 } - var texts []text - files := 0 - err = filepath.WalkDir(filepath.Join(root, "modules"), func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - switch d.Name() { - case "node_modules", "dist", "vendor", "test", "tests", "testdata", ".git": - return filepath.SkipDir - } - return nil - } - rel, _ := filepath.Rel(root, path) - name := d.Name() - var found []text - switch { - case strings.HasSuffix(name, "_test.go"), strings.Contains(name, ".test."), strings.Contains(name, ".spec."): - return nil - case strings.HasSuffix(name, ".go"): - found, err = goStrings(path, rel) - case strings.HasSuffix(name, ".ts"), strings.HasSuffix(name, ".js"), strings.HasSuffix(name, ".mjs"): - found, err = scriptStrings(path, rel) - case name == "module.json": - found, err = jsonStrings(path, rel) - default: - return nil - } - if err != nil { - return fmt.Errorf("%s: %w", rel, err) - } - files++ - texts = append(texts, found...) - return nil - }) + texts, files, err := collect(root) if err != nil { fmt.Fprintln(os.Stderr, "words:", err) return 2 @@ -128,6 +94,47 @@ func run(root string) int { return 0 } +// collect reads every text a module can show under root: the strings of its own code that is not a test, and every string of +// its manifests. +func collect(root string) ([]text, int, error) { + var texts []text + files := 0 + err := filepath.WalkDir(filepath.Join(root, "modules"), func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + switch d.Name() { + case "node_modules", "dist", "vendor", "test", "tests", "testdata", ".git": + return filepath.SkipDir + } + return nil + } + rel, _ := filepath.Rel(root, path) + name := d.Name() + var found []text + switch { + case strings.HasSuffix(name, "_test.go"), strings.Contains(name, ".test."), strings.Contains(name, ".spec."): + return nil + case strings.HasSuffix(name, ".go"): + found, err = goStrings(path, rel) + case strings.HasSuffix(name, ".ts"), strings.HasSuffix(name, ".js"), strings.HasSuffix(name, ".mjs"): + found, err = scriptStrings(path, rel) + case name == "module.json": + found, err = jsonStrings(path, rel) + default: + return nil + } + if err != nil { + return fmt.Errorf("%s: %w", rel, err) + } + files++ + texts = append(texts, found...) + return nil + }) + return texts, files, err +} + // readList reads retired-words: one word or phrase a line; blank lines and lines starting with # are not words. func readList(path string) ([]string, error) { f, err := os.Open(path) diff --git a/modules/claude-code/cmd/claude-code/proposals.go b/modules/claude-code/cmd/claude-code/proposals.go index dbde6538..9f878b66 100644 --- a/modules/claude-code/cmd/claude-code/proposals.go +++ b/modules/claude-code/cmd/claude-code/proposals.go @@ -951,7 +951,7 @@ func HeardEnd(p Paths, store ProposalStore, w asks.Warrant, now time.Time) (stri return "", nil } // Said to a proposer waiting on it only once the discard is kept: what it reads next is already there (the - // flake of the router's refusal test under a loaded build agent, 2026-10-12). + // flake of the router's refusal test under a loaded build node, 2026-10-12). tell := func() { endedMu.Lock() ended[w.Ask] = w diff --git a/modules/dev-tools/README.md b/modules/dev-tools/README.md index 64eb8d9a..3bf50c27 100644 --- a/modules/dev-tools/README.md +++ b/modules/dev-tools/README.md @@ -19,8 +19,8 @@ Assign it to every node, with the `go` and `nodejs` modules: it requires their p | git and jq | `git`, `jq` | package | All are in the distribution's official repositories, so the node-engine's package manager installs them on any -node. The versions are the distribution's: the toolchain images that run `mesh/repo-check` stay the judge of a -pull request, and a local run is the developer's early answer, not the verdict. +node. The versions are the distribution's: the build modules' toolchains, which the checker runs a pull request's tests with, stay the +judge of a pull request, and a local run is the developer's early answer, not the verdict. Nothing else is placed: no file, no service, no environment. A module's own dependencies come from its lock -file (`go.mod`, `package-lock.json`), as in the toolchain images. +file (`go.mod`, `package-lock.json`), as in the build modules. diff --git a/modules/gitea/README.md b/modules/gitea/README.md index 6e204169..4db08d83 100644 --- a/modules/gitea/README.md +++ b/modules/gitea/README.md @@ -161,7 +161,7 @@ listed repository: - one in the organisation is only put right: main's rule narrowed, `mesh/check` required in place of `mesh/merge-gate` and `mesh/repo-check`, and every other status already there kept. A main that requires `mesh/repo-check` alone (hq's did, until issue 1296) is given `mesh/check` in its place too, since nothing writes - `mesh/repo-check` once build-agent is gone (ADR 0527 §2); `mesh/check` is never dropped. + `mesh/repo-check` once the merge check's second layer is gone (ADR 0527 §2); `mesh/check` is never dropped. Every other repository of the organisation whose main requires `mesh/merge-gate` or `mesh/repo-check` has the old statuses swapped for `mesh/check`, and only its statuses (issue 1301): the switch reaches every repository the merge gate judged, with diff --git a/modules/gitea/cmd/repositories/repositories.go b/modules/gitea/cmd/repositories/repositories.go index d1c8b000..a76ad993 100644 --- a/modules/gitea/cmd/repositories/repositories.go +++ b/modules/gitea/cmd/repositories/repositories.go @@ -39,8 +39,8 @@ const ( // wanted is what a main rule requiring have should require: have with GateStatus and OwnCheckStatus replaced by // CheckStatus, every other status kept in its order (ADR 0447 §1). A rule requiring OwnCheckStatus alone, as hq's -// did, is swapped too: the checker judges hq like any repository and nothing writes OwnCheckStatus once build-agent -// is gone (ADR 0527 §2, issue 1296). This reads the rule, not a list of names (lead decision L11 of R4e). +// did, is swapped too: the checker judges hq like any repository and nothing writes OwnCheckStatus once the merge +// check's second layer is gone (ADR 0527 §2, issue 1296). This reads the rule, not a list of names (lead decision L11 of R4e). func wanted(have []string) []string { out := []string{} for _, s := range have { diff --git a/modules/gitea/provisioner/teams.ts b/modules/gitea/provisioner/teams.ts index 3183af79..61100be8 100644 --- a/modules/gitea/provisioner/teams.ts +++ b/modules/gitea/provisioner/teams.ts @@ -2,7 +2,7 @@ // half of the provisioner around it — pure of the forge's address, so it is tested with a stand-in. // // **Two teams, by what the consumer said it needs** (novox/hq issue 624). A consumer that publishes — -// the build agent, whose builds publish the SDK and every TypeScript module's packages — joins `packages`: +// the build modules, whose builds publish the SDK and every TypeScript module's packages — joins `packages`: // code read on the org's repositories and write on its packages. A consumer that contributes // `{"access": "read"}` joins `readers`: code read and package read, and no write. The lab is the first: it // clones branches that are not yet reviewed and runs their code beside its credential, and code that is diff --git a/modules/lab/tools/runs.ts b/modules/lab/tools/runs.ts index c65bfb41..a253c069 100644 --- a/modules/lab/tools/runs.ts +++ b/modules/lab/tools/runs.ts @@ -166,7 +166,7 @@ export async function npmRegistry(forge: string, get: typeof fetch = fetch, publ * The forge credential a run's clones may present, as a git credential-store line — * scheme://user:password@host[:port] — for the forge's own origin, or undefined when the lab holds none. * - * **The mesh's credential for the forge, the way the build agent holds it** (novox/hq issue 624): the lab + * **The mesh's credential for the forge, the way the build modules hold it** (novox/hq issue 624): the lab * requires the package registry, the forge's provisioner binds it an account (`as`) and seals that * account's password, and the forge that answers npm is the forge that hosts the repositories. Private * repositories (mesh-tools-go) are cloned with it; public ones clone as before. Written to a file git reads diff --git a/modules/mesh-issues/README.md b/modules/mesh-issues/README.md index 581a29b3..803adfc8 100644 --- a/modules/mesh-issues/README.md +++ b/modules/mesh-issues/README.md @@ -34,7 +34,7 @@ what it refuses. folders until the import, and hq ADR 0284): a status is one of `open`, `diagnosing`, `located`, `fixing`, `resolved`, `wontfix`; `diagnosing` and `fixing` need `by`; `located` needs `located-in`; `resolved` is reached only from `fixing` and needs `located-in` and `fixed-by`; a core issue (located in the controller, the node-engine, the tool runner, -the SDK, or the catalogue's bus, forge or build agent) resolves with `replay` or `replay-none`; `wontfix` +the SDK, the delivery parts, or the catalogue's bus, forge or build modules) resolves with `replay` or `replay-none`; `wontfix` needs a `reason`. A refused change changes nothing and says which rule refused it. **Who works on it** (hq ADR 0284). A move to `diagnosing` or `fixing` names in `by` the issue's **worker**: who diff --git a/modules/mesh-issues/cmd/mesh-issues/check.go b/modules/mesh-issues/cmd/mesh-issues/check.go index 50ec850e..b29b45a9 100644 --- a/modules/mesh-issues/cmd/mesh-issues/check.go +++ b/modules/mesh-issues/cmd/mesh-issues/check.go @@ -175,7 +175,7 @@ func (o Owners) locatedIn(e string) (string, error) { // core is an owner whose issues are core issues (hq cycle.py, to-be 45): the controller, the node-engine, // the tool runner and the console, the SDK, the delivery parts, and the catalogue's bus, forge and build modules -// (issue 1041; the build agent is gone, ADR 0524 §5). +// (issue 1041; ADR 0524 §5). var core = regexp.MustCompile(`\b(mesh-controller|mesh-host|mesh-tools|mesh-sdk|node-tools|mesh-delivery|` + `mesh-build-(go|node|image)|mesh-catalog\s+modules/(nats|gitea))\b`) diff --git a/modules/mesh-issues/cmd/mesh-issues/tracker.go b/modules/mesh-issues/cmd/mesh-issues/tracker.go index ec5adb20..327506f7 100644 --- a/modules/mesh-issues/cmd/mesh-issues/tracker.go +++ b/modules/mesh-issues/cmd/mesh-issues/tracker.go @@ -547,7 +547,7 @@ func cycleRules(is *Issue, from, to string) error { } if IsCore(is.LocatedIn) && is.Replay == "" && is.ReplayNone == "" { return fmt.Errorf("issue %d is a core issue (it is located in the controller, the node-engine, the tool "+ - "runner, the SDK, or the catalogue's bus, forge or build agent): it resolves with replay, the replay's id "+ + "runner, the SDK, the delivery parts, or the catalogue's bus, forge or build modules): it resolves with replay, the replay's id "+ "in mesh-lab's replays register, or replay-none, why no replay is possible (ADR 0237)", is.Number) } case StatusWontfix: