Raise the mesh as it is in the gate, call a baseline that does not compose an error, and let a check run by hand as the seat runs it
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 superseded: a newer head of the same pull request

The gate composed 0 of 4 machines with the change and without, and passed every change: the store it
raised held each module's bus credential but no account for it (issue 203's refusal), no outward links
(so no filter could be composed), and refused settings the mesh holds. Now the account is minted with
its credential, the facts carry each machine's outward links (a stand-in for an older snapshot), the
mesh's layers are kept as held, and a withheld path keeps a path's shape. A machine the mesh composes
that the gate cannot raise makes the verdict an error, never a pass; the verdict alone is on stdout.

A merge-check.sh that passed on an agent's machine failed on the build seat: a newer gofmt, siblings at
a feature branch, another user. `mesh-controller check-here` runs builder.Check with the ask the
controller would make, from facts that now name the toolchains and the refs cloned beside; a failed
script is said by what failed. (novox/hq issues 282, 283)
This commit is contained in:
jochen
2026-10-07 01:33:18 +02:00
parent 72d7802415
commit a011743c69
12 changed files with 640 additions and 31 deletions
+48 -4
View File
@@ -81,6 +81,9 @@ type CheckSpec struct {
// Toolchains is every toolchain the mesh holds, by language, for a script that declares another.
Toolchain string
Toolchains map[string]string
// User is who a check's containers run as, uid:gid: the builder's own when empty — on the build seat,
// the user its service runs as. A check run by hand (`mesh-controller check-here`) says the seat's.
User string
// Group are a delivery group's other heads (novox/hq ADR 0239), each cloned beside this one at its head
// and composed with it by the gate as one future state. The repository's own check is not run for a
// group: each member's pull request runs its own.
@@ -326,10 +329,18 @@ func Check(ctx context.Context, run Runner, spec CheckSpec, workspace, registry
if spec.Toolchain == "" {
return CheckVerdict{}, errors.New("the mesh holds no Go toolchain to run a check in")
}
user := spec.User
if user == "" {
user = fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid())
}
in := func(image, dir string, env []string, command ...string) []string {
// As the builder itself: what a check writes into the workspace is the builder's to remove.
args := []string{"run", "--rm", "--network", "host", "--volume", workspace + ":" + workspace, "--workdir", dir,
"--user", fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid()), "--env", "HOME=" + workspace}
"--user", user, "--env", "HOME=" + workspace,
// The check's own checkouts, whoever cloned them: git in a container of another user than the
// one that cloned refuses a repository it does not own ("dubious ownership"), and Go's build
// stamps the version from git — a judge that would not build for want of it.
"--env", "GIT_CONFIG_COUNT=1", "--env", "GIT_CONFIG_KEY_0=safe.directory", "--env", "GIT_CONFIG_VALUE_0=*"}
for _, e := range env {
args = append(args, "--env", e)
}
@@ -421,7 +432,7 @@ func Check(ctx context.Context, run Runner, spec CheckSpec, workspace, registry
case ctx.Err() != nil:
return v, ctx.Err()
case err != nil:
v.Repo = &Layer{Verdict: "fail", Summary: "its " + CheckScript + " failed: " + lastLine(own.String())}
v.Repo = &Layer{Verdict: "fail", Summary: "its " + CheckScript + " failed: " + whatFailed(own.String())}
default:
v.Repo = &Layer{Verdict: "pass", Summary: "its " + CheckScript + " passed"}
}
@@ -503,8 +514,14 @@ func gateLayer(ctx context.Context, spec CheckSpec, tree, root, gate, verdictFil
Verdict string `json:"verdict"`
Summary string `json:"summary"`
}
if raw, err := os.ReadFile(verdictFile); err == nil && json.Unmarshal(raw, &said) == nil && said.Verdict == "fail" {
return "fail", said.Summary
if raw, err := os.ReadFile(verdictFile); err == nil && json.Unmarshal(raw, &said) == nil {
switch said.Verdict {
case "fail":
return "fail", said.Summary
case "error":
// The gate could not raise the mesh as it is (novox/hq issue 282): said in its own words.
return "error", said.Summary
}
}
// The gate could not judge: not the change's fault, and never a pass.
return "error", "the merge gate could not judge the change: " + lastLine(out.String())
@@ -816,6 +833,33 @@ func (t *tail) String() string {
return strings.Join(lines, "\n")
}
// whatFailed is the line of a failed script's output that says what failed, for the status a pull request
// shows: the first failing test, the first failing package, the files not formatted — a bare "FAIL" or a
// file's name said nothing a reader could act on (novox/hq issue 283) — and the last line otherwise.
func whatFailed(s string) string {
lines := strings.Split(strings.TrimSpace(s), "\n")
for i, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "not gofmt'd:") {
var files []string
for _, f := range lines[i+1:] {
if f = strings.TrimSpace(f); f != "" {
files = append(files, f)
}
}
return "not gofmt'd by the toolchain's gofmt: " + strings.Join(files, ", ")
}
}
for _, prefix := range []string{"--- FAIL:", "FAIL\t", "panic:"} {
for _, line := range lines {
if line = strings.TrimSpace(line); strings.HasPrefix(line, prefix) {
return line
}
}
}
return lastLine(s)
}
func lastLine(s string) string {
lines := strings.Split(strings.TrimSpace(s), "\n")
return strings.TrimSpace(lines[len(lines)-1])