From 6501afb8b76503ecf90239c1c5f9fb803eda70e8 Mon Sep 17 00:00:00 2001 From: Brett Kinny Date: Thu, 16 Jul 2026 20:05:52 +1000 Subject: [PATCH] Isolate multi-platform release verification --- .github/workflows/e2e.yml | 3 +++ CHANGELOG.md | 3 ++- tests/test-e2e-evidence.sh | 20 ++++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index ba9bd3d..c5526d6 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -668,6 +668,9 @@ jobs: docker pull --platform "$platform" "$IMAGE@$DIGEST" docker run --rm --platform "$platform" "$IMAGE@$DIGEST" \ grep -qx "$GITHUB_REF_NAME" /usr/local/lib/squarebox/VERSION + # Docker's classic image store cannot associate two platform + # members with the same manifest-list digest at once. + docker image rm "$IMAGE@$DIGEST" >/dev/null done - name: Create, sign, and verify digest-bound release assets diff --git a/CHANGELOG.md b/CHANGELOG.md index 848e560..dc6c869 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,7 +60,8 @@ - Release-candidate failures caused by a Compose UID-remap startup race, cross-UID Evidence file permissions, LazyGit's lowercase Linux asset name, Gum release binaries built with a vulnerable Go standard library, and raw - lifecycle assertions racing synchronous Box-tier reconciliation. + lifecycle assertions racing synchronous Box-tier reconciliation; release + preparation now isolates multi-platform pulls in Docker's local image store. ### Removed diff --git a/tests/test-e2e-evidence.sh b/tests/test-e2e-evidence.sh index a6f075e..d97401c 100755 --- a/tests/test-e2e-evidence.sh +++ b/tests/test-e2e-evidence.sh @@ -160,6 +160,26 @@ for release_doc in \ grep -Fq 'immutable' "$release_doc" done +# Docker's classic image store must release the shared manifest-list digest +# only after each explicit-platform image has verified its embedded version. +ruby - "$WORKFLOW" <<'RUBY' +require "yaml" + +workflow = YAML.safe_load_file(ARGV.fetch(0), aliases: true) +step = workflow.fetch("jobs").fetch("prepare-release").fetch("steps").find do |candidate| + candidate["name"] == "Verify exact Candidate identity and architecture content" +end +abort "Candidate identity verification step is missing" unless step +script = step.fetch("run") +loop_body = script[/for platform in linux\/amd64 linux\/arm64; do\n(?.*?)\n\s*done/m, :body] +abort "Candidate platform verification loop is missing" unless loop_body +pull = loop_body.index('docker pull --platform "$platform" "$IMAGE@$DIGEST"') +verify = loop_body.index('grep -qx "$GITHUB_REF_NAME" /usr/local/lib/squarebox/VERSION') +remove = loop_body.index('docker image rm "$IMAGE@$DIGEST"') +ordered = pull && verify && remove && pull < verify && verify < remove +abort "Candidate platform images are not isolated after verification" unless ordered +RUBY + # Stable Release preparation and publication are deliberately separate jobs. # The final job selects the protected production environment only for stable # versions; prereleases use an unprotected environment and continue