[fathom (gale) — audit prompted by the relay case: same software built two ways, both green, different files]
An audit for "two paths that should produce the same artifact, with nothing
comparing them". Findings below are ones I re-verified myself after the audit
reported them; the unverified remainder is listed separately at the end.
1. CONFIRMED — the dissolve pipeline builds on a toolchain 8 minor versions behind the pin
.github/workflows/gustos-dissolve.yml:44 MELD_VERSION: "0.48.0"
.github/workflows/gustos-dissolve.yml:45 LOOM_VERSION: "1.2.0"
.github/workflows/gustos-dissolve.yml:46 SYNTH_VERSION: "0.57.0"
varve.toml layer 2026.09.3 -> synth 0.65.0, loom 1.4.1, meld 0.55.1
This workflow fetches those tarballs directly (lines 85-87) rather than
resolving through varve, so the dissolved objects CI produces come from a
different toolchain than the pinned one everything else uses. docs/toolchain-pin.md
admits the gap in its own words — "the build scripts still resolve tools through
$MELD/$LOOM/$SYNTH… this pin is additive… and does not yet replace those
paths" — and records a one-time manual byte-identity check done at layer
2026.08.4, four layers ago, never repeated.
This is the relay shape exactly: two toolchain sources for the same rebuild, no
automated agreement check. drv-cross-arch.yml has the same duplicated pin and
its comment records that it drifted twice; it is now synchronised by hand.
Nothing keeps this one in sync.
2. CONFIRMED — verus-strip resolves a different MAJOR version of syn under cargo vs Bazel
tools/verus-strip/Cargo.lock syn 3.0.5
MODULE.bazel.lock syn-2.0.117
MODULE.bazel declares crate.from_cargo(cargo_lockfile = "//tools/verus-strip:Cargo.lock"),
so Bazel is supposed to track that lockfile — but the committed
MODULE.bazel.lock froze a resolution from before the bump to syn = "3".
verus-strip is a Rust-source rewriter. Its whole job is parsing and
re-emitting Rust, which is precisely the output a parser major-version bump can
change. Two build systems, two parsers, two binaries, and nothing compares either
the binaries or their output on a fixture.
3. CONFIRMED — iso-core-fused-cm3.o is invisible to the census meant to catch exactly this
check-driver-components.py:234 enumerates with root.glob("*/*-cm3.o") — one
subdirectory, then the filename. iso-core-fused-cm3.o sits at the TOP level of
benches/gust/drivers/, so it matches nothing:
$ python3 check-driver-components.py | grep -c iso-core-fused
0
It is not gated, and not even on the UNGATED ledger that exists to record what
is not gated. A gap in the gap-tracker. Its builder is build-iso-core.sh.
This is the same one-level-glob defect already fixed in
check-object-freshness.py (where it hid 2 of 24 committed objects); the sibling
gate still has it.
4. CONFIRMED (with a correction to the audit) — fused.wasm is PUBLISHED with an unverified equivalence claim
The audit reported fused.wasm as consumed by nothing. That is wrong —
pages.yml:51 copies it into the published site:
cp ../wasm-kernel/fused.wasm web/fused.wasm
Which makes it worse, not better. build-fused.sh is invoked by no workflow,
and its header asserts "The SAME module dissolves to fused.o below — 'same wasm,
browser + dissolved-native.'" pages.yml's own comment repeats the claim. So a
checked-in artifact is shipped to GitHub Pages on the strength of an equivalence
that nothing re-checks, and whose regeneration script CI never runs.
Reported by the audit, NOT yet verified by me
Listed so they are not lost, and marked honestly as unchecked:
- 9 committed driver objects on
check-driver-components.py's UNGATED ledger
have builders but no byte-level or symbol-level comparison at all.
- The 17
renode-test/*.elf fixtures have no in-repo regeneration path; the
directory README calls them "checked-in fixtures" with a TODO to genrule them
from the .wasm.
- Where a gate does exist for the 13 thin drivers, it compares symbol names,
not bytes — so a rebuild emitting different machine code with the same imports
would pass.
wasm-tools 1.245.1 is pinned independently in 8 workflow files; all 8 agree
today, with no mechanism keeping them that way.
What would settle the unverified ones
Rebuild each committed object with tools resolved via varve which, and cmp
against the checked-in bytes. That needs a toolchain fetch, so it was outside the
audit's offline budget — but it is the single check that would convert most of
this list from "nothing compares them" to a number.
[fathom (gale) — audit prompted by the relay case: same software built two ways, both green, different files]
An audit for "two paths that should produce the same artifact, with nothing
comparing them". Findings below are ones I re-verified myself after the audit
reported them; the unverified remainder is listed separately at the end.
1. CONFIRMED — the dissolve pipeline builds on a toolchain 8 minor versions behind the pin
This workflow
fetches those tarballs directly (lines 85-87) rather thanresolving through varve, so the dissolved objects CI produces come from a
different toolchain than the pinned one everything else uses.
docs/toolchain-pin.mdadmits the gap in its own words — "the build scripts still resolve tools through
$MELD/$LOOM/$SYNTH… this pin is additive… and does not yet replace thosepaths" — and records a one-time manual byte-identity check done at layer
2026.08.4, four layers ago, never repeated.
This is the relay shape exactly: two toolchain sources for the same rebuild, no
automated agreement check.
drv-cross-arch.ymlhas the same duplicated pin andits comment records that it drifted twice; it is now synchronised by hand.
Nothing keeps this one in sync.
2. CONFIRMED —
verus-stripresolves a different MAJOR version ofsynunder cargo vs BazelMODULE.bazeldeclarescrate.from_cargo(cargo_lockfile = "//tools/verus-strip:Cargo.lock"),so Bazel is supposed to track that lockfile — but the committed
MODULE.bazel.lockfroze a resolution from before the bump tosyn = "3".verus-stripis a Rust-source rewriter. Its whole job is parsing andre-emitting Rust, which is precisely the output a parser major-version bump can
change. Two build systems, two parsers, two binaries, and nothing compares either
the binaries or their output on a fixture.
3. CONFIRMED —
iso-core-fused-cm3.ois invisible to the census meant to catch exactly thischeck-driver-components.py:234enumerates withroot.glob("*/*-cm3.o")— onesubdirectory, then the filename.
iso-core-fused-cm3.osits at the TOP level ofbenches/gust/drivers/, so it matches nothing:It is not gated, and not even on the
UNGATEDledger that exists to record whatis not gated. A gap in the gap-tracker. Its builder is
build-iso-core.sh.This is the same one-level-glob defect already fixed in
check-object-freshness.py(where it hid 2 of 24 committed objects); the siblinggate still has it.
4. CONFIRMED (with a correction to the audit) —
fused.wasmis PUBLISHED with an unverified equivalence claimThe audit reported
fused.wasmas consumed by nothing. That is wrong —pages.yml:51copies it into the published site:Which makes it worse, not better.
build-fused.shis invoked by no workflow,and its header asserts "The SAME module dissolves to fused.o below — 'same wasm,
browser + dissolved-native.'"
pages.yml's own comment repeats the claim. So achecked-in artifact is shipped to GitHub Pages on the strength of an equivalence
that nothing re-checks, and whose regeneration script CI never runs.
Reported by the audit, NOT yet verified by me
Listed so they are not lost, and marked honestly as unchecked:
check-driver-components.py'sUNGATEDledgerhave builders but no byte-level or symbol-level comparison at all.
renode-test/*.elffixtures have no in-repo regeneration path; thedirectory README calls them "checked-in fixtures" with a TODO to genrule them
from the
.wasm.not bytes — so a rebuild emitting different machine code with the same imports
would pass.
wasm-tools 1.245.1is pinned independently in 8 workflow files; all 8 agreetoday, with no mechanism keeping them that way.
What would settle the unverified ones
Rebuild each committed object with tools resolved via
varve which, andcmpagainst the checked-in bytes. That needs a toolchain fetch, so it was outside the
audit's offline budget — but it is the single check that would convert most of
this list from "nothing compares them" to a number.