Commit267c0839Recorded30 Jul 2026Repositorycore-channel

Harden the cross-checkout gate so a do-nothing binary cannot pass it

Message

This gate reported green from 2026-07-25 to 2026-07-30 while testing nothing. It was not badly written: set -eu, no pipelines, and a success marker printed only after the command are exactly the right shape, and the failure did propagate once there was a failure to propagate. It was hollowed out from outside. The released sigil of that period had no env subcommand and exits 0 for unknown subcommands, so the one command that mattered never ran and never complained.

Correct authorship was not enough, so the gate no longer trusts exit status alone. It now requires evidence the built program actually ran -- its own stdout -- and checks the lock step produced a non-empty lock. Either condition alone can lie: a non-zero status with no output, or a zero status from a binary that never ran anything.

Also asserts git and minisign are present as a separate loud step. A missing minisign is the sharp case: it surfaces through sigil as "root signature ... failed", accusing the trust chain rather than the environment.

The regression test for the gate itself is stated in its header: run it against a sigil older than 0.18.0 and it must FAIL. Before this commit it passed.

Changed
 scripts/gate-crosscheckout.sh | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 55 insertions(+), 2 deletions(-)
Diff
scripts/gate-crosscheckout.shmodified
@@ -1,9 +1,42 @@
1
#!/bin/sh
2
set -eu
3
+4
# PROPERTY UNDER TEST: everything needed to get from a BARE GIT CLONE to a
+5
# running program is either committed, or hash-pinned and fetchable. Nothing
+6
# may depend on the checkout it was authored in -- not an untracked file, not a
+7
# forgotten `git add`, not a warm store, not an absolute path. Hence: fresh
+8
# clone at a new path, fresh HOME, fresh store.
+9
#
+10
# WHY THE ASSERTIONS BELOW ARE ON OBSERVED OUTPUT, NOT EXIT STATUS.
+11
# This gate reported green from 2026-07-25 to 2026-07-30 while testing nothing.
+12
# It was not badly written: `set -eu`, no pipelines, and a success marker
+13
# printed only after the command are exactly the right shape, and the failure
+14
# did propagate. It was hollowed out from OUTSIDE -- the released sigil of the
+15
# day had no `env` subcommand and exited 0 for unknown subcommands, so the one
+16
# command that mattered never ran and never complained. Correct authorship was
+17
# not enough. A binary that does nothing successfully must not be able to pass
+18
# this gate, so the check below requires evidence the built program actually
+19
# ran: its own stdout.
+20
#
+21
# To confirm this gate still works, run it against a sigil older than 0.18.0.
+22
# It must FAIL. Before the hardening it passed.
+23
24
repo=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
25
sigil_bin=${SIGIL_BIN:?set SIGIL_BIN to a sigil binary with env support (0.18.0+)}
26
registry=${SIGIL_REGISTRY_METADATA:?set SIGIL_REGISTRY_METADATA to root-signed registry metadata this binary accepts}
+27
+28
# Assert each external dependency separately and loudly. Folded into an `&&`
+29
# chain, an absent tool short-circuits to a non-zero status indistinguishable
+30
# from the failure this gate exists to report. A missing `minisign` is the
+31
# sharp case: it surfaces as "root signature ... failed", which accuses the
+32
# trust chain rather than the environment.
+33
for tool in git minisign; do
+34
command -v "$tool" >/dev/null 2>&1 || {
+35
echo "cross-checkout gate: FATAL: $tool is not on PATH; the gate did not run" >&2
+36
exit 1
+37
}
+38
done
+39
40
scratch=$(mktemp -d /tmp/sigil-channel-clone.XXXXXX)
41
trap 'rm -rf "$scratch"' EXIT HUP INT TERM
42
git clone --quiet --no-hardlinks "$repo" "$scratch/core-channel"
@@ -15,15 +48,35 @@ SIGIL_BOOTSTRAP_ZIG_URL="${SIGIL_BOOTSTRAP_ZIG_URL:-}" \
48
# Authoring: resolve demo/env.sgl against the clone's signed catalogue and
49
# realize it. `env shell` consumes an env.lock, never environment source, so
50
# the lock step is separate and comes first.
+51
lock_status=0
52
SIGIL_REGISTRY_METADATA="$registry" \
53
HOME="$scratch/home" SIGIL_CHANNEL="$scratch/core-channel" \
54
"$sigil_bin" env --store "$scratch/store" lock \
21
-f "$scratch/core-channel/demo/env.sgl" --lock-file "$scratch/env.lock"
+55
-f "$scratch/core-channel/demo/env.sgl" --lock-file "$scratch/env.lock" \
+56
>"$scratch/lock.out" 2>&1 || lock_status=$?
+57
+58
if [ "$lock_status" -ne 0 ] || [ ! -s "$scratch/env.lock" ]; then
+59
echo "cross-checkout gate: FAILED at lock" >&2
+60
echo " exit status: $lock_status; env.lock present and non-empty: no" >&2
+61
sed 's/^/ | /' "$scratch/lock.out" >&2
+62
exit 1
+63
fi
64
65
# Consumption: deliberately WITHOUT SIGIL_REGISTRY_METADATA and without
66
# SIGIL_CHANNEL, which also holds the lock-only consumption boundary honest.
+67
run_status=0
68
HOME="$scratch/home" \
69
"$sigil_bin" env --store "$scratch/store" shell \
27
-f "$scratch/env.lock" -- hello
+70
-f "$scratch/env.lock" -- hello >"$scratch/hello.out" 2>&1 || run_status=$?
+71
+72
# Both conditions, because either alone can lie: a non-zero status with no
+73
# output, or a zero status from a binary that never ran the program at all.
+74
if [ "$run_status" -ne 0 ] || ! grep -q '^Hello, world!$' "$scratch/hello.out"; then
+75
echo "cross-checkout gate: FAILED at run" >&2
+76
echo " exit status: $run_status" >&2
+77
echo " expected 'Hello, world!' on stdout; captured output follows" >&2
+78
sed 's/^/ | /' "$scratch/hello.out" >&2
+79
exit 1
+80
fi
81
82
echo "cross-checkout gate: fresh clone bootstrapped, locked, and ran hello from the lock"