Skip to content

Two build paths, no comparison: the dissolve pipeline runs 8 synth versions behind the pin, and verus-strip splits on a syn major #386

Description

@avrabe

[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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions