fix: the file the gate passed is the file published, and stable takes only the approved one

tools/release-gate.sh is the one gate: a fresh install, and the upgrade
from what canary and stable serve, each on a room of that channel, plus
any older release rooms are still on (RELEASE_GATE_FROM). The releases it
upgrades from are fetched over HTTPS and checked against their manifests,
so it needs no credential and a CI runner can run it.

update_canary.sh VERSION PACKAGE gates and publishes that file without
rebuilding it, since a second build is a second, untested file; the hash
is taken before the gate and checked after it. update_stable.sh VERSION
SHA256 refuses anything but the file that was approved.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ekklesia
2026-09-29 18:05:54 -05:00
co-authored by Claude Opus 5.5
parent 6c04f743ed
commit a9700536bf
5 changed files with 274 additions and 43 deletions
+34 -6
View File
@@ -244,20 +244,48 @@ check("container service startup is suppressed", "/usr/sbin/policy-rc.d" in vali
print()
print("6. canary release gate")
validation = canary.index('"$ROOT/tools/validate-trixie-release.sh"')
validation = canary.index('"$ROOT/tools/release-gate.sh"')
package_copy = canary.index('cp -n "$package_path"')
first_upload = canary.index("release_upload_package")
check("Trixie validation runs before the repository copy", validation < package_copy)
check("Trixie validation runs before any upload", validation < first_upload)
check("the release gate runs before the repository copy", validation < package_copy)
check("the release gate runs before any upload", validation < first_upload)
# A version is checked before anything is built for it, and again after
# the long gate, immediately before the first upload.
check("a spent version is refused before the build",
canary.index("release_check_new_version") < canary.index("build-release.sh"))
check("and again after the gate",
validation < canary.rindex("release_check_new_version") < first_upload)
check("the stable rooms' upgrade is rehearsed on the same file before publishing",
"VALIDATION_CHANNEL=stable" in canary
and canary.index("VALIDATION_CHANNEL=stable") < first_upload)
# A package handed in is the one published: a second build is a second,
# untested file.
build_at = canary.index('"$ROOT/tools/build-release.sh"')
check("a package handed in is not rebuilt",
canary.rindex('if [[ -n "$prebuilt" ]]', 0, build_at) < build_at
and "else" in canary[canary.rindex('if [[ -n "$prebuilt" ]]', 0, build_at):build_at])
check("and must say it is the version being published",
'release_package_version "$package_path")" != "$version"' in canary)
check("the file is hashed before the gate and checked after it",
canary.index('hash="$(sha256sum "$package_path"') < validation
< canary.index("changed while it was being gated") < first_upload)
check("the repository copy is proven to be the gated file",
package_copy < canary.index("is not the file that passed the gate") < first_upload)
gate = (root / "tools/release-gate.sh").read_text()
gate_code = "\n".join(line for line in gate.splitlines()
if not line.lstrip().startswith("#"))
check("the gate runs a fresh install",
'"$ROOT/tools/validate-trixie-release.sh" "$candidate"\n' in gate_code)
check("and the upgrade from what canary and stable serve, as rooms of each",
"for channel in canary stable" in gate_code
and 'VALIDATION_CHANNEL="$channel"' in gate_code)
check("the stable rooms' older releases can be rehearsed too",
"VALIDATION_CHANNEL=stable" in gate_code and "RELEASE_GATE_FROM" in canary)
check("what it upgrades from is checked against its manifest's checksum",
'if [[ "$got" != "$from_hash" ]]' in gate_code)
check("the gate needs no credential and writes nothing to the server",
"release_remote" not in gate_code and "ssh" not in gate_code
and "rsync" not in gate_code)
check("the gate stops at the first failure",
"set -euo pipefail" in gate and "inherit_errexit" in gate)
check("the published channel is read back afterwards",
canary.index("release_verify_channel canary") > canary.index("release_publish_manifest canary"))
# The gate checks convergence, not just that the package installs: an
+24
View File
@@ -274,8 +274,21 @@ fake_deb 0.2.9 "$LOCAL/packages/ekklesia-classroom_0.2.9_amd64.deb"
check "refuses a package that was built but never uploaded" \
refuses "$ROOT/update_stable.sh" 0.2.9
rm "$LOCAL/packages/ekklesia-classroom_0.2.9_amd64.deb"
check "refuses a checksum that is not one" \
refuses "$ROOT/update_stable.sh" 0.2.6.1 not-a-checksum
check "refuses a file that is not the one approved" \
refuses "$ROOT/update_stable.sh" 0.2.6.1 "$(printf '0%.0s' {1..64})"
check "stable is still 0.2.2 after all of that" \
test "$(cat "$SERVER/stable/manifest.json")" = "$stable_before"
approved="$(sha256sum "$LOCAL/packages/ekklesia-classroom_0.2.6.1_amd64.deb" | awk '{print $1}')"
check "promotes the approved file, named by its checksum" \
"$ROOT/update_stable.sh" 0.2.6.1 "$approved"
check "and stable now serves exactly that file" \
bash -c "grep -q '\"sha256\": \"$approved\"' '$SERVER/stable/manifest.json' \
&& cmp -s '$SERVER/stable/latest.deb' '$LOCAL/packages/ekklesia-classroom_0.2.6.1_amd64.deb'"
# Back to 0.2.2 for the updater's cases below.
printf '%s\n' "$stable_before" > "$SERVER/stable/manifest.json"
ln -sfn ../packages/ekklesia-classroom_0.2.2_amd64.deb "$SERVER/stable/latest.deb"
echo
echo "7. update_canary.sh"
@@ -286,6 +299,17 @@ check "without a version it builds nothing and names both choices" \
check "a published version is refused before anything is built" \
bash -c "! '$ROOT/update_canary.sh' 0.2.6 2>&1 | grep -q 'Validating sources'"
check "a malformed version is refused" refuses "$ROOT/update_canary.sh" 0.2.6.1.1
# A package handed in is gated and published as it is, so it has to be
# the release named, before anything is gated at all.
fake_deb 0.2.6.3 "$WORK/ekklesia-classroom_0.2.6.2_amd64.deb"
check "a package handed in that says it is another version is refused" \
bash -c "out=\$('$ROOT/update_canary.sh' 0.2.6.2 '$WORK/ekklesia-classroom_0.2.6.2_amd64.deb' 2>&1); \
[[ \$? -ne 0 ]] && grep -q 'says it is 0.2.6.3' <<< \"\$out\" && ! grep -q 'release gate' <<< \"\$out\""
fake_deb 0.2.6.2 "$WORK/renamed.deb"
check "and so is one that is not named as the release" \
bash -c "out=\$('$ROOT/update_canary.sh' 0.2.6.2 '$WORK/renamed.deb' 2>&1); \
[[ \$? -ne 0 ]] && grep -q 'is not named' <<< \"\$out\" && ! grep -q 'release gate' <<< \"\$out\""
rm -f "$WORK/ekklesia-classroom_0.2.6.2_amd64.deb" "$WORK/renamed.deb"
# ---------------------------------------------------------------------------
echo