Verify the seed's inputs instead of its previous output
verify-sources.sh read seed/MANIFEST, which pins the seed TARBALL. So build-seed.sh checked the artifact it was about to overwrite and never verified a single source it was about to compile. The name said verify-sources; the behaviour was verify-the-previous-output. It passed every time, which is why it survived.
It also could not survive a re-pin: once MANIFEST's hash moves ahead of the tarball on disk, a rebuild refuses for a reason that has nothing to do with its sources.
MANIFEST.sources is the input pin, and it is what this now verifies. Added a refusal when the loop verifies zero files, because a loop that checks nothing exits 0 and prints nothing, which reads exactly like a clean run.
Sabotage-tested green -> red -> green: five sources verified, EXIT=0; one byte appended to patch-2.8.tar.xz -> EXIT=1 with expected and actual printed; restored -> EXIT=0.
This commit is separate from the re-pin that follows because it is a pre-existing defect, like the glob fix before it.
seed/scripts/verify-sources.sh | 39 +++++++++++++++++++++++++++++++++++++--
1 file changed, 37 insertions(+), 2 deletions(-)seed/scripts/verify-sources.shmodified
#!/bin/sh# Verify the INPUTS the seed is about to be built from.## This script read `$seed/MANIFEST` until 2026-07-31, and MANIFEST pins the# seed TARBALL -- the OUTPUT. So `build-seed.sh` checked the artifact it was# about to overwrite and never checked a single source it was about to compile.# The name said "verify-sources"; the behaviour was "verify the previous# output". It passed every time, which is exactly why nobody noticed.## It also could not survive a re-pin: the moment MANIFEST's hash moves ahead of# the tarball on disk, a rebuild refuses for a reason that has nothing to do# with its sources.## MANIFEST.sources is the input pin, and it is what this verifies.set -euseed=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)manifest="$seed/MANIFEST.sources"[ -f "$manifest" ] || { echo "verify-sources: no $manifest" >&2; exit 1; }checked=0while read -r hash url file; do case "$hash" in ''|'#'*) continue ;; esac [ -f "$seed/$file" ] || { echo "verify-sources: missing source $file (run bootstrap or gate-crosspath.sh)" >&2 exit 1 } actual=$(sha256sum "$seed/$file" | awk '{print $1}') [ "$actual" = "$hash" ] || { echo "sha256 mismatch: $file" >&2; exit 1; } [ "$actual" = "$hash" ] || { echo "verify-sources: sha256 mismatch: $file" >&2 echo " expected $hash" >&2 echo " actual $actual" >&2 exit 1 } echo "verified $file"done < "$seed/MANIFEST" checked=$((checked+1))done < "$manifest"# A loop that verified nothing exits 0 and prints nothing, which reads exactly# like a clean run. Refuse instead.[ "$checked" -gt 0 ] || { echo "verify-sources: REFUSING: $manifest listed no sources to verify" >&2 exit 1}echo "verify-sources: $checked source(s) verified against MANIFEST.sources"