wac plug produces a different artifact on every invocation from identical
inputs. The components are semantically identical; only their serialization
order changes.
Reproduction
wac-cli 0.6.0, macOS arm64. Five component "plugs" into one socket:
wac plug cascade.wasm \
--plug iekf.wasm --plug position.wasm --plug attitude.wasm \
--plug rate.wasm --plug mixer.wasm -o out.wasm
Run twice, same inputs, same binary:
run 1: 3b15c2e365796e6d... 98335 bytes
run 2: 2f348129b91628a3... 98335 bytes
A third and fourth run give two more distinct hashes. Size is always identical.
It is ordering, not content
$ wasm-tools component wit out1.wasm > wit1
$ wasm-tools component wit out2.wasm > wit2
$ diff wit1 wit2 # no output — byte-identical
So the outputs are the same component, serialized differently.
Byte-level: 33.59% of bytes differ (33033 of 98335), across 1347 contiguous
runs, first at offset 0x5c37. At that offset:
run 1: ... vehicle-state ... waypoint ...
run 2: ... vehicle-state ... attitude-setpoint ...
Nested components are emitted in a different order each run. That is what
iteration over a HashMap/HashSet under Rust's randomly-seeded RandomState
looks like — I have not read enough of wac's internals to point at the exact
collection, but the signature seems clear enough to be worth reporting rather
than sitting on.
Why it matters to us
- Attestation. We cosign-sign artifacts and publish digests. For anything
wac composed, "rebuild from these inputs and check you get our digest"
cannot work — a rebuild from byte-identical inputs produces a different
digest every time. That converts a reproducibility claim into a
trust-the-builder claim.
- Bazel. We drive
wac_plug through rules_wasm_component. Build systems
assume a rule with identical inputs yields identical outputs; a
non-deterministic action undermines cache correctness and makes any
downstream digest unstable.
- Diffing releases. Comparing two releases' composed artifacts by digest is
how a consumer sees "nothing changed". Right now every rebuild looks like a
change.
Suggested fix
Sort deterministically wherever composition iterates over a keyed collection
before emitting — by name, or by insertion order via an IndexMap — so the
serialization is a pure function of the inputs.
Happy to test a patch against the reproduction above; it is a five-component
compose that takes under a second.
Environment
wac-cli 0.6.0
macOS 15 (arm64)
inputs: 6 wasm components, no_std, wasm32-unknown-unknown, 0 memory.grow
wac plugproduces a different artifact on every invocation from identicalinputs. The components are semantically identical; only their serialization
order changes.
Reproduction
wac-cli 0.6.0, macOS arm64. Five component "plugs" into one socket:
Run twice, same inputs, same binary:
A third and fourth run give two more distinct hashes. Size is always identical.
It is ordering, not content
So the outputs are the same component, serialized differently.
Byte-level: 33.59% of bytes differ (33033 of 98335), across 1347 contiguous
runs, first at offset
0x5c37. At that offset:Nested components are emitted in a different order each run. That is what
iteration over a
HashMap/HashSetunder Rust's randomly-seededRandomStatelooks like — I have not read enough of wac's internals to point at the exact
collection, but the signature seems clear enough to be worth reporting rather
than sitting on.
Why it matters to us
wac composed, "rebuild from these inputs and check you get our digest"
cannot work — a rebuild from byte-identical inputs produces a different
digest every time. That converts a reproducibility claim into a
trust-the-builder claim.
wac_plugthrough rules_wasm_component. Build systemsassume a rule with identical inputs yields identical outputs; a
non-deterministic action undermines cache correctness and makes any
downstream digest unstable.
how a consumer sees "nothing changed". Right now every rebuild looks like a
change.
Suggested fix
Sort deterministically wherever composition iterates over a keyed collection
before emitting — by name, or by insertion order via an
IndexMap— so theserialization is a pure function of the inputs.
Happy to test a patch against the reproduction above; it is a five-component
compose that takes under a second.
Environment