Run each part of a repository's own check in the toolchain it declares (hq issue 302)
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery held for a person: merged, and the controller opened no walk for it within 10m0s — nothing it holds follows that branch, or the merge was…

mesh-lab's merge-check.sh runs in the TypeScript toolchain, which holds no Go
compiler, so its replays register was never compiled before a merge and a
broken one would have merged green. A script now declares a further part with
'# mesh-check-also: <toolchain> <script>'; the build seat runs it in that
toolchain's own container, and mesh/repo-check passes only when every part does.
This commit is contained in:
jochen
2026-10-08 00:09:30 +02:00
parent db953e7da8
commit 98ef7bac6e
2 changed files with 168 additions and 25 deletions
+95 -25
View File
@@ -39,6 +39,11 @@ import (
// quality, run when present in the toolchain it declares (`# mesh-check-toolchain: go|typescript`
// among its first lines; go when it declares none). A repository that reaches the build seat with
// none is said as a warning: it is the mesh's, and nothing of its own is tested before it merges.
// **A repository in two languages declares the other part too** (`# mesh-check-also: <toolchain>
// <script>`, one line each, among the same first lines): each named script is run from the root in
// its own toolchain's container, after merge-check.sh, and the layer passes only when every part
// does (novox/hq issue 302 — the lab's Go replays went unchecked because its script runs in the
// TypeScript toolchain, which holds no Go compiler).
//
// One check:
//
@@ -178,6 +183,32 @@ const noScript = "the repository declares no " + CheckScript + ": none of its ow
// toolchainLine is how a merge-check.sh declares the toolchain it runs in.
var toolchainLine = regexp.MustCompile(`^#\s*mesh-check-toolchain:\s*([a-z0-9-]+)\s*$`)
// alsoLine is how a merge-check.sh declares a further part of the repository's check, run in a toolchain
// of its own: `# mesh-check-also: <toolchain> <script>`.
var alsoLine = regexp.MustCompile(`^#\s*mesh-check-also:\s*([a-z0-9-]+)\s+(\S+)\s*$`)
// ScriptPart is one part of a repository's own check: a script, run from the repository's root, and the
// toolchain it runs in.
type ScriptPart struct {
Toolchain string
Script string
}
// ScriptParts is every part of a repository's own check: its merge-check.sh in the toolchain it declares,
// then each further part it declares among its first twenty lines (`# mesh-check-also: <toolchain>
// <script>`), in the order declared. A declared script that is not a path inside the repository is
// answered as a part all the same, for the check to refuse by name rather than to drop.
func ScriptParts(script []byte) []ScriptPart {
parts := []ScriptPart{{Toolchain: ScriptToolchain(script), Script: CheckScript}}
lines := bufio.NewScanner(bytes.NewReader(script))
for i := 0; i < 20 && lines.Scan(); i++ {
if m := alsoLine.FindStringSubmatch(strings.TrimSpace(lines.Text())); m != nil {
parts = append(parts, ScriptPart{Toolchain: m[1], Script: m[2]})
}
}
return parts
}
// ScriptToolchain is the language a merge-check.sh declares it runs in, among its first twenty lines;
// go when it declares none.
func ScriptToolchain(script []byte) string {
@@ -416,32 +447,11 @@ func Check(ctx context.Context, run Runner, spec CheckSpec, workspace, registry
case timedOut():
v.Repo = &Layer{Verdict: "error", Summary: fmt.Sprintf("the check ran past %s before its %s ran", CheckTimeout, CheckScript)}
default:
language := ScriptToolchain(script)
image := spec.Toolchains[language]
if language == "go" && image == "" {
image = spec.Toolchain
}
if image == "" {
v.Repo = &Layer{Verdict: "error", Summary: fmt.Sprintf("its %s runs in the %s toolchain, which the mesh does "+
"not hold", CheckScript, language)}
break
}
say("check", "running its %s in the mesh's %s toolchain", CheckScript, language)
fmt.Fprintf(&out, "--- its %s (%s toolchain)\n", CheckScript, language)
var own tail
cmd := exec.CommandContext(ctx, "docker", LabelledArgs("docker", in(image, tree, env, "sh", CheckScript), spec.ID)...)
inItsOwnGroup(cmd)
w := io.MultiWriter(&out, &own)
cmd.Stdout, cmd.Stderr = w, w
switch err := cmd.Run(); {
case timedOut():
v.Repo = &Layer{Verdict: "error", Summary: fmt.Sprintf("its %s ran past %s and was ended", CheckScript, CheckTimeout)}
case ctx.Err() != nil:
v.Repo = ownCheck(ctx, spec, ScriptParts(script), tree, &out, timedOut, func(image string, script string) *exec.Cmd {
return exec.CommandContext(ctx, "docker", LabelledArgs("docker", in(image, tree, env, "sh", script), spec.ID)...)
}, say)
if v.Repo == nil {
return v, ctx.Err()
case err != nil:
v.Repo = &Layer{Verdict: "fail", Summary: "its " + CheckScript + " failed: " + whatFailed(own.String())}
default:
v.Repo = &Layer{Verdict: "pass", Summary: "its " + CheckScript + " passed"}
}
}
@@ -452,6 +462,66 @@ func Check(ctx context.Context, run Runner, spec CheckSpec, workspace, registry
return v, nil
}
// ownCheck runs the repository's own check, part by part, each in its toolchain's own container: the
// layer's verdict. Nil when the check was ended from outside (not past its bound), which the caller
// answers as the context's error.
func ownCheck(ctx context.Context, spec CheckSpec, parts []ScriptPart, tree string, out *tail, timedOut func() bool,
command func(image, script string) *exec.Cmd, say func(step, format string, args ...any)) *Layer {
var ran []string
for i, part := range parts {
named := part.Script
if i > 0 {
named = CheckScript + "'s part " + part.Script
}
image := spec.Toolchains[part.Toolchain]
if part.Toolchain == "go" && image == "" {
image = spec.Toolchain
}
if image == "" {
return &Layer{Verdict: "error", Summary: fmt.Sprintf("%sits %s runs in the %s toolchain, which the mesh does "+
"not hold", passedSoFar(ran), named, part.Toolchain)}
}
if i > 0 && !filepath.IsLocal(part.Script) {
return &Layer{Verdict: "fail", Summary: fmt.Sprintf("%s declares a part %q that is not a path inside the "+
"repository", CheckScript, part.Script)}
}
if i > 0 {
if info, err := os.Stat(filepath.Join(tree, part.Script)); err != nil || info.IsDir() {
return &Layer{Verdict: "fail", Summary: fmt.Sprintf("%s declares a part %s, which the repository does "+
"not hold", CheckScript, part.Script)}
}
}
say("check", "running its %s in the mesh's %s toolchain", part.Script, part.Toolchain)
fmt.Fprintf(out, "--- its %s (%s toolchain)\n", part.Script, part.Toolchain)
var own tail
cmd := command(image, part.Script)
inItsOwnGroup(cmd)
w := io.MultiWriter(out, &own)
cmd.Stdout, cmd.Stderr = w, w
switch err := cmd.Run(); {
case timedOut():
return &Layer{Verdict: "error", Summary: fmt.Sprintf("its %s ran past %s and was ended", part.Script, CheckTimeout)}
case ctx.Err() != nil:
return nil
case err != nil:
return &Layer{Verdict: "fail", Summary: passedSoFar(ran) + "its " + part.Script + " failed: " + whatFailed(own.String())}
}
ran = append(ran, fmt.Sprintf("%s (%s)", part.Script, part.Toolchain))
}
if len(ran) == 1 {
return &Layer{Verdict: "pass", Summary: "its " + CheckScript + " passed"}
}
return &Layer{Verdict: "pass", Summary: "its " + CheckScript + " passed, each part in its toolchain: " + strings.Join(ran, ", ")}
}
// passedSoFar is what of a check's parts passed before the one that did not, said first.
func passedSoFar(ran []string) string {
if len(ran) == 0 {
return ""
}
return strings.Join(ran, ", ") + " passed; "
}
// gateLayer runs the gate: the touched manifests through `module check`, every machine composed with the
// change, and the replays of what the mesh runs. It answers the gate's verdict and summary.
func gateLayer(ctx context.Context, spec CheckSpec, tree, root, gate, verdictFile string, env []string,