Issues 282-284: the merge gate judged nothing, the seat's check disagreed with agents' machines, a seat and a module of one name were unreachable

This commit is contained in:
jochen
2026-10-07 01:59:45 +02:00
parent 35d81e962e
commit 41a0e77748
3 changed files with 199 additions and 0 deletions
@@ -0,0 +1,79 @@
---
status: located
opened: 2026-10-07
located-in: [mesh-controller cmd/mesh-controller, mesh-controller internal/facts, mesh-controller internal/builder]
fixed-by: mesh-controller PR #104
amended-design:
---
# 282. The merge gate compared broken to broken, and passed
## Symptom
On 2026-10-07 every pull request the build seat checked through the gate came back with the same
verdict:
> gate PASS — every machine composes with the change as it did without (0 of 4 compose)
That was four pull requests to the controller and the node-engine. **No machine composed** in the gate's
store, with the change or without, though all four compose on the mesh. The gate's rule is "what composes
in the base and not with the change fails". With nothing composing in the base, that rule can never fire.
So the gate validated nothing and said PASS. Each verdict carried, as notes only, one line per machine:
"composes on the mesh and not as the snapshot raised it … judged by what the change adds".
## Diagnosis
Reproduced off the mesh with the controller's own `merge-gate` against the live facts snapshot and a
throwaway store. The base was refused on every machine. Each cause was a fact the gate did not raise
from the snapshot:
1. **No bus account.** Since issue 203, composition refuses a module whose own secret `broker` has no
minted account behind it ("has no bus credential: nothing was issued"). The snapshot lists that
secret among each machine's accepted secrets. The gate stored a stand-in value for it but minted no
account. So every machine running a module on the bus (the build agent, the route proxy, the
distribution registry) failed. That covers all four.
2. **No outward links.** With that fixed, every machine failed next on "has not reported which of its
links face outside" (ADR 0140): a filter is written around those links. The snapshot did not carry
them.
3. **Settings refused on the way in.** The gate kept each settings layer through `settings set`, which
judges a layer alone. A mesh-wide layer whose module needs a value that only a machine's layer gives
was refused, though the mesh holds both and composes them. A media server's access path was refused
too. The scrub had withheld a path that read as a key, and the bare word `withheld` is not an
absolute path.
4. **The JSON verdict was not alone on stdout.** Once machines composed, composition printed its own
lines (bus users without a credential) before the verdict. The build seat would then have read no
verdict at all.
Ruled out: the change under check. The same four refusals appear with no change at all.
## Fix
- The gate mints the account with the credential for every accepted `broker` of a module that reads
one. The snapshot carries each machine's outward links, scrubbed. For a snapshot from an older
controller, the gate stands one in for a machine that composes on the mesh. Layers are kept as the
mesh holds them (`KeepSettings`), because composition still judges every layer. A withheld path keeps
a path's shape, both in the scrub and in the gate's stand-ins.
- **A baseline that does not compose is an error, never a pass.** A machine that composes on the mesh
and not in the gate is listed under "could not judge". The verdict is then `error`, the build seat
reports it in the gate's own words, and the command exits non-zero.
- In `--json` mode, only the verdict is written to stdout.
Measured on the live snapshot of 2026-10-06 with the fixed controller: **4 of 4 compose** in the base.
The resources composed match the mesh's own declaration for each machine, apart from pseudonyms (which
the snapshot now applies to resource names too) and the machine's bus membership file.
## How it is checked
The controller's tests `TestIssue282AModuleOnTheBusComposesInTheGate` and
`TestIssue282AMachineTheGateCannotRaiseIsAnErrorNeverAPass`: a module on the bus composes in the gate,
and a machine the gate cannot raise turns the verdict to `error`. `TestAWithheldPathIsStoodInForByAPath`
and the facts package's `TestAWithheldPathStaysAPath` cover the path stand-ins.
## Left open
- A change to the controller is judged by its own controller, so its pull requests see this fix at once.
Every other pull request is judged by the controller the mesh runs, and sees the fix only after this
one is merged and rolled out.
- The snapshot does not say whether a machine holds a bus membership, so the gate composes none. It is
one resource, the same with the change and without.
- The snapshot's `sources` still name each repository by the forge's address and owner.
@@ -0,0 +1,71 @@
---
status: located
opened: 2026-10-07
located-in: [mesh-controller internal/builder, mesh-controller cmd/mesh-controller, mesh-host internal/bootstrap]
fixed-by: mesh-controller PR #104, mesh-host PR #46
amended-design:
---
# 283. A merge check passed on an agent's machine and failed on the build seat
## Symptom
On 2026-10-07 the repository layer (`mesh/repo-check`) failed on the build seat for pull requests whose
`merge-check.sh` had passed on the machine of the agent who wrote them:
- three node-engine pull requests: "its merge-check.sh failed: internal/bootstrap/publish_test.go";
- two controller pull requests: "its merge-check.sh failed: FAIL".
Neither summary says what failed.
## Diagnosis
The build seat runs the script in a container of the toolchain image the mesh holds, as the user the
seat's service runs as, against a throwaway store and bus. The repositories it reads are cloned beside
the checkout at the refs the controller asks for. The agents ran the same script in their own
environment. Three differences, each one the environment and not the change:
1. **Another Go.** The toolchain is one release of Go and the agents' machines run the next. Their
`gofmt` disagree on how to lay out a `return` of several composite literals spanning lines. Each
one reformats the other's output, so no file using that construct satisfies both. The node-engine's
script failed at `gofmt -l` on the seat. Its last line, the file's name, became the summary.
2. **Siblings at another ref.** The controller's test that compares the installer's first bus user list
with what the controller derives reads the node-engine checkout beside it. On the seat that checkout
was at the commit the mesh runs. On the agent's machine it was at the agent's feature branch, which
carried the matching change. The test failed on the seat, correctly for that change judged on its
own, and the summary was the script's last line: "FAIL".
3. **Another user.** The seat runs its check containers as root, over checkouts root owns. Run as root
over checkouts another user cloned, `git` refuses the repository ("dubious ownership") and Go's VCS
stamping fails the judge's build.
## Fix
- **The seat is the reference, and a check can be run as it runs.** `mesh-controller check-here` runs
the same code the seat runs (`builder.Check`) on the checkout's HEAD. It builds the ask the controller
would make: the gate's modules and judge by the planner's answer over the facts snapshot, and the
siblings at the refs the snapshot names. It runs in the toolchain the snapshot names, as the seat's
user, against a throwaway store and bus of the mesh's versions. The snapshot now carries the
toolchains (without the store's address) and the refs cloned beside a check. Both come from the same
rule the controller asks the seat with.
- Check containers set `safe.directory` for the check's own checkouts, so whoever cloned them, git
and Go's build read them.
- A failed script is summarised by what failed: the first failing test, the first failing package, or
the files the toolchain's `gofmt` would change.
- The node-engine's test is laid out so both releases' `gofmt` agree.
## How it is checked
`check-here` on the node-engine's main reproduced the seat's failure: "not gofmt'd by the toolchain's
gofmt: internal/bootstrap/publish_test.go". It passed after the fix. The builder's test
`TestAFailedScriptIsSaidByWhatFailed` and the controller's `TestACheckByHandClonesWhatTheSeatClones`
cover the summary and the refs.
## Left open
- A change spread over two repositories as a delivery group (ADR 0239) is still checked one pull
request at a time on the repository layer, with the siblings at what the mesh runs. A test that reads
a sibling fails for each member alone. The group's composed check runs the gate over every head
together, but skips each member's own script. A member's script should run with the group's other
heads beside it.
- The toolchain's Go is older than the agents'. Raising it is a change to the toolchain module, not to
the check.
@@ -0,0 +1,49 @@
---
status: located
opened: 2026-10-07
located-in: [mesh-tools node-tools/internal/console]
fixed-by: mesh-tools PR #19
amended-design:
---
# 284. A seat and a module of one name were both unreachable
## Symptom
On 2026-10-07 the console's overview listed the seat `mesh-delivery` as held on the control node with
**no verbs**. Every address with the name was refused the same way, the module's own tools included:
> the seat mesh-delivery has no verb deliveries; it has
That covered `mesh-delivery.deliveries` and `<node>/mesh-delivery.delivery_status`. Neither the seat's
verbs nor the module's tools could be called through the console. The controller's own record of the
seat was whole: `mesh-controller.tools` listed its ten verbs.
## Diagnosis
`mesh-delivery` is both the delivery's seat and the module holding it (ADR 0239). The module answers the
seat's verbs with tools of the same names, and has tools of its own as well. The console builds its
index from the runtimes' announcements, and kept one table of what it had listed, keyed
`<prefix>.<name>`. The module's tool `mesh-delivery.deliveries` and the seat's verb
`mesh-delivery.deliveries` shared a key. The module's came first, so the seat was listed with no verb.
Resolution then looked for a seat of the address's prefix before anything else. It found the
verb-less seat and refused, so the module's tools were never reached, even through a node-qualified
address.
Ruled out: the controller's seat records (seeded with the verbs, as `tools` shows) and the module's
announcement (the seat's holder is heard, which is why the overview names the machine).
## Fix
- The seat's verbs and the modules' tools are keyed apart, so both are listed.
- When a seat and a module share a name, the address goes to the module's tool in two cases: the seat
has no such verb, or a machine is named for a seat held once for the mesh. `<node>/<module>.<tool>` is
a module on one machine. A seat with no verbs at all says that its holder announced none, instead of
ending on an empty list.
## How it is checked
The console's `TestASeatAndAModuleOfOneNameAreEachReached` covers this: the seat listed with its verbs,
the module with its tools, and each address resolved to the seat or the module. The test launches no
bundle, so the repository's `merge-check.sh` runs it even where the console's other tests cannot run.