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)

This commit is contained in:
2026-10-23 15:34:11 +02:00
parent c4fc565180
commit 6bfdbc5758
12 changed files with 114 additions and 47 deletions
+3
View File
@@ -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
+57
View File
@@ -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)
}
}
}
+42 -35
View File
@@ -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)
@@ -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
+3 -3
View File
@@ -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.
+1 -1
View File
@@ -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
@@ -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 {
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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`)
@@ -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: