novox/hq issue 280. An image is not byte-reproducible, so ADR 0236's rule that a rebuild with identical artifacts is no move never held for one. On 2026-10-06 a catalogue merge (mesh-catalog b792bf4b) rebuilt the bus without touching it. The new image digest was read as a new bus build, and every send to the control node was refused until a planned bus upgrade.
What changes
Builder (internal/builder/source.go): every build gets a source fingerprint, src1: followed by a sha256 over:
the module name and the git tree of its directory at the commit (git rev-parse HEAD:<path>). This covers the manifest, the recipes and the code: the recipe and the compiler see only that directory.
the git tree of each artifact context's clone;
every base it was handed, by digest only, so the registry address it was copied to does not count;
for a bundle, the toolchain image's digest and a hash of the builder's toolchain recipe.
A build that resolves packages from the mesh registry at build time gets no fingerprint. That covers a package artifact, a TS bundle with packages of its own, and an image that reads .npmrc. Such builds fall back to artifact identity, as before. The fingerprint is carried as source-fingerprint on the build outcome.
Inventory: migration 0074 adds build.source_fingerprint. The new internal/inventory/source.go adds:
a module's builds fall into runs with one fingerprint, and the oldest build of the run stands for it. A gate-failed build breaks a run.
Held answers the standing build's artifacts, so modules standing on a same-source rebuild get the same base.
SourceFingerprints, StandingBuild and SameSourceCommits are new.
Controller:
takeIn registers a same-source rebuild at its new commit with the standing build's manifest (the old artifacts). No machine is ever sent a new digest for an unchanged source, and a later push cannot restart the bus behind the planned step's back.
moveFacts.identical (holds, cascades, release plans) and busPending.same (busHeld, bus upgrade) treat equal fingerprints as one build.
A plan's build whose run includes the commit every running machine was sent is marked sent with "no move": no send, no gate.
Identical artifacts stay a second way to be no move.
Tests
internal/builder/source_test.go: one tree and one base give one fingerprint, even with the base mirrored to another registry address. A changed tree or a moved base gives another fingerprint. A registry-decided build has none.
cmd/mesh-controller/same_source_test.go:
An image module rebuilt from an unchanged source with a new digest keeps its old digest registered at the new commit; Held hands out the old digest, and the two builds are identical. A changed source moves. A build without a fingerprint is judged by its artifacts alone.
The bus rebuilt from an unchanged source with a new digest: busHeld is empty, bus upgrade has nothing to do and takes no snapshot, and the shelf keeps the image the machine runs. A real change to its source demands the planned step.
A plan sends and gates nothing for an unchanged source; for a changed source it does a gated send to the first machine.
Full suite: go test -race ./... with a throwaway postgres and per-test buses, plus gofmt and vet, all green (GOFLAGS=-mod=vendor GOPROXY=off).
Note on deployment
Fingerprints exist only for builds made by a builder carrying this change. Builds recorded earlier, including the two bus builds of 2026-10-06, have none and are judged by their artifacts, exactly as now.
Why b792bf4b rebuilt the bus
It was not the pre-278 shared-path rule. The merge added merge-check.sh at the catalogue repository's root, and a root file is shared code under issue 278's rule as well. plans what-if with the merge's files and module-dirs=modules/gitea still plans 103 modules; without the root file it plans gitea alone. Narrowing that rule is left open in issue 280, because the builder reads only a module's own directory. With this PR the wide rebuild costs build time and moves nothing.
novox/hq issue 280. An image is not byte-reproducible, so ADR 0236's rule that a rebuild with identical artifacts is no move never held for one. On 2026-10-06 a catalogue merge (mesh-catalog b792bf4b) rebuilt the bus without touching it. The new image digest was read as a new bus build, and every send to the control node was refused until a planned `bus upgrade`.
## What changes
- **Builder** (`internal/builder/source.go`): every build gets a **source fingerprint**, `src1:` followed by a sha256 over:
- the module name and the git tree of its directory at the commit (`git rev-parse HEAD:<path>`). This covers the manifest, the recipes and the code: the recipe and the compiler see only that directory.
- the git tree of each artifact context's clone;
- every base it was handed, by digest only, so the registry address it was copied to does not count;
- for a bundle, the toolchain image's digest and a hash of the builder's toolchain recipe.
A build that resolves packages from the mesh registry at build time gets **no** fingerprint. That covers a package artifact, a TS bundle with packages of its own, and an image that reads `.npmrc`. Such builds fall back to artifact identity, as before. The fingerprint is carried as `source-fingerprint` on the build outcome.
- **Inventory**: migration 0074 adds `build.source_fingerprint`. The new `internal/inventory/source.go` adds:
- a module's builds fall into runs with one fingerprint, and the oldest build of the run stands for it. A gate-failed build breaks a run.
- `Held` answers the standing build's artifacts, so modules standing on a same-source rebuild get the same base.
- `SourceFingerprints`, `StandingBuild` and `SameSourceCommits` are new.
- **Controller**:
- `takeIn` registers a same-source rebuild at its new commit with the **standing build's manifest** (the old artifacts). No machine is ever sent a new digest for an unchanged source, and a later push cannot restart the bus behind the planned step's back.
- `moveFacts.identical` (holds, cascades, release plans) and `busPending.same` (`busHeld`, `bus upgrade`) treat equal fingerprints as one build.
- A plan's build whose run includes the commit every running machine was sent is marked sent with "no move": no send, no gate.
- Identical artifacts stay a second way to be no move.
## Tests
- `internal/builder/source_test.go`: one tree and one base give one fingerprint, even with the base mirrored to another registry address. A changed tree or a moved base gives another fingerprint. A registry-decided build has none.
- `cmd/mesh-controller/same_source_test.go`:
- An image module rebuilt from an unchanged source with a new digest keeps its old digest registered at the new commit; `Held` hands out the old digest, and the two builds are identical. A changed source moves. A build without a fingerprint is judged by its artifacts alone.
- **The bus** rebuilt from an unchanged source with a new digest: `busHeld` is empty, `bus upgrade` has nothing to do and takes no snapshot, and the shelf keeps the image the machine runs. A real change to its source demands the planned step.
- A plan sends and gates nothing for an unchanged source; for a changed source it does a gated send to the first machine.
- Full suite: `go test -race ./...` with a throwaway postgres and per-test buses, plus gofmt and vet, all green (GOFLAGS=-mod=vendor GOPROXY=off).
## Note on deployment
Fingerprints exist only for builds made by a builder carrying this change. Builds recorded earlier, including the two bus builds of 2026-10-06, have none and are judged by their artifacts, exactly as now.
## Why b792bf4b rebuilt the bus
It was **not** the pre-278 shared-path rule. The merge added `merge-check.sh` at the catalogue repository's root, and a root file is shared code under issue 278's rule as well. `plans` what-if with the merge's files and `module-dirs=modules/gitea` still plans 103 modules; without the root file it plans gitea alone. Narrowing that rule is left open in issue 280, because the builder reads only a module's own directory. With this PR the wide rebuild costs build time and moves nothing.
mesh/merge-gate error: the check could not run: a throwaway postgres:17-alpine could not be raised: docker run --label mesh.build=build-1791317509716888018…
An image is not byte-reproducible, so ADR 0236's 'same artifacts is no move'
never held for one: a catalogue merge that did not touch the bus rebuilt it,
and every send to the control node waited for a planned bus upgrade.
The builder now records a source fingerprint per build (module tree, context
trees, bases and toolchains by digest). A rebuild with the fingerprint of the
build it repeats is registered with that build's artifacts, handed to modules
standing on it, holds no push, demands no bus step, and a plan sends and
gates nothing for it. Identical artifacts remain a second way to be no move.
Merge check: ERROR at 792352df — the check could not run: a throwaway postgres:17-alpine could not be raised: docker run --label mesh.build=build-1791317509716888018 -d --rm --name build-1791317509716888018-store -p 127.0.0.1::5432 -e POSTGRES_PASSWORD=check postgres:17-alpine: exit status 125
Run by the build seat on shanks as build-1791317509716888018 (builds --log build-1791317509716888018).
**Merge check: ERROR** at `792352df` — the check could not run: a throwaway postgres:17-alpine could not be raised: docker run --label mesh.build=build-1791317509716888018 -d --rm --name build-1791317509716888018-store -p 127.0.0.1::5432 -e POSTGRES_PASSWORD=check postgres:17-alpine: exit status 125
Run by the build seat on shanks as `build-1791317509716888018` (`builds --log build-1791317509716888018`).
Deliverynovox/mesh-controller@792352dfad8c — delivered since 2026-10-06T23:05:28Z
Machines
build-agent on ace: passed — healthy 3 times over 2m43s
build-agent on the rest running it: sent
mesh-controller on novox: passed — healthy 3 times over 3m47s
mesh-controller on the rest running it: sent
route-proxy on ace: passed — healthy 3 times over 2m30s
route-proxy on the rest running it: sent
Transitions
2026-10-06 23:05 (new) → proposed (announced): novox/mesh-controller#99's head announced
2026-10-06 23:05 proposed → held (merged-unchecked): merged without a passing check — a verdict known with it is taken first: only a person decides that it goes on
2026-10-06 23:05 held → delivering (go): its walk started on a word that was not this owner's: the controller's own path, which waits for nobody, or a person's plans go
2026-10-06 23:05 delivering → delivered (done): its walk is done: every machine of its deploy plan passed, or was left as its policy says
The commit's note under refs/notes/mesh-plan keeps every transition: git log --notes=mesh-plan.
<!-- mesh-delivery:view -->
**Delivery** `novox/mesh-controller@792352dfad8c` — **delivered** since 2026-10-06T23:05:28Z
**Machines**
- build-agent on ace: passed — healthy 3 times over 2m43s
- build-agent on the rest running it: sent
- mesh-controller on novox: passed — healthy 3 times over 3m47s
- mesh-controller on the rest running it: sent
- route-proxy on ace: passed — healthy 3 times over 2m30s
- route-proxy on the rest running it: sent
**Transitions**
- 2026-10-06 23:05 (new) → proposed (announced): novox/mesh-controller#99's head announced
- 2026-10-06 23:05 proposed → held (merged-unchecked): merged without a passing check — a verdict known with it is taken first: only a person decides that it goes on
- 2026-10-06 23:05 held → delivering (go): its walk started on a word that was not this owner's: the controller's own path, which waits for nobody, or a person's `plans go`
- 2026-10-06 23:05 delivering → delivered (done): its walk is done: every machine of its deploy plan passed, or was left as its policy says
The commit's note under `refs/notes/mesh-plan` keeps every transition: `git log --notes=mesh-plan`.
This repo is archived. You cannot comment on pull requests.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
novox/hq issue 280. An image is not byte-reproducible, so ADR 0236's rule that a rebuild with identical artifacts is no move never held for one. On 2026-10-06 a catalogue merge (mesh-catalog b792bf4b) rebuilt the bus without touching it. The new image digest was read as a new bus build, and every send to the control node was refused until a planned
bus upgrade.What changes
Builder (
internal/builder/source.go): every build gets a source fingerprint,src1:followed by a sha256 over:git rev-parse HEAD:<path>). This covers the manifest, the recipes and the code: the recipe and the compiler see only that directory.A build that resolves packages from the mesh registry at build time gets no fingerprint. That covers a package artifact, a TS bundle with packages of its own, and an image that reads
.npmrc. Such builds fall back to artifact identity, as before. The fingerprint is carried assource-fingerprinton the build outcome.Inventory: migration 0074 adds
build.source_fingerprint. The newinternal/inventory/source.goadds:Heldanswers the standing build's artifacts, so modules standing on a same-source rebuild get the same base.SourceFingerprints,StandingBuildandSameSourceCommitsare new.Controller:
takeInregisters a same-source rebuild at its new commit with the standing build's manifest (the old artifacts). No machine is ever sent a new digest for an unchanged source, and a later push cannot restart the bus behind the planned step's back.moveFacts.identical(holds, cascades, release plans) andbusPending.same(busHeld,bus upgrade) treat equal fingerprints as one build.Tests
internal/builder/source_test.go: one tree and one base give one fingerprint, even with the base mirrored to another registry address. A changed tree or a moved base gives another fingerprint. A registry-decided build has none.cmd/mesh-controller/same_source_test.go:Heldhands out the old digest, and the two builds are identical. A changed source moves. A build without a fingerprint is judged by its artifacts alone.busHeldis empty,bus upgradehas nothing to do and takes no snapshot, and the shelf keeps the image the machine runs. A real change to its source demands the planned step.go test -race ./...with a throwaway postgres and per-test buses, plus gofmt and vet, all green (GOFLAGS=-mod=vendor GOPROXY=off).Note on deployment
Fingerprints exist only for builds made by a builder carrying this change. Builds recorded earlier, including the two bus builds of 2026-10-06, have none and are judged by their artifacts, exactly as now.
Why b792bf4b rebuilt the bus
It was not the pre-278 shared-path rule. The merge added
merge-check.shat the catalogue repository's root, and a root file is shared code under issue 278's rule as well.planswhat-if with the merge's files andmodule-dirs=modules/giteastill plans 103 modules; without the root file it plans gitea alone. Narrowing that rule is left open in issue 280, because the builder reads only a module's own directory. With this PR the wide rebuild costs build time and moves nothing.Merge check: ERROR at
792352df— the check could not run: a throwaway postgres:17-alpine could not be raised: docker run --label mesh.build=build-1791317509716888018 -d --rm --name build-1791317509716888018-store -p 127.0.0.1::5432 -e POSTGRES_PASSWORD=check postgres:17-alpine: exit status 125Run by the build seat on shanks as
build-1791317509716888018(builds --log build-1791317509716888018).Delivery
novox/mesh-controller@792352dfad8c— delivered since 2026-10-06T23:05:28ZMachines
Transitions
plans goThe commit's note under
refs/notes/mesh-plankeeps every transition:git log --notes=mesh-plan.