From 57877ed46896a99fcd8782401ee2aa071e3a3a82 Mon Sep 17 00:00:00 2001 From: Andrew Briscoe Date: Mon, 27 Jul 2026 18:16:34 -0600 Subject: [PATCH 1/2] ci(release): verify selected prepare artifacts --- .github/workflows/release.yml | 28 +++++++++++++ .github/workflows/test.yml | 3 ++ scripts/ci/verify_release_artifacts.sh | 55 ++++++++++++++++++++++++++ scripts/test-release-artifacts.sh | 50 +++++++++++++++++++++++ 4 files changed, 136 insertions(+) create mode 100755 scripts/ci/verify_release_artifacts.sh create mode 100755 scripts/test-release-artifacts.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a788f5f..5db189d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -126,6 +126,23 @@ jobs: ref: ${{ inputs.tag }} fetch-depth: 0 + - name: Read prepare run metadata + env: + GITHUB_TOKEN: ${{ github.token }} + PREPARE_RUN_ID: ${{ inputs.prepare_run_id }} + run: | + set -euo pipefail + [[ "$PREPARE_RUN_ID" =~ ^[0-9]+$ ]] || { + echo "::error::prepare_run_id must be numeric" + exit 1 + } + curl --fail --location --silent --show-error \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/runs/${PREPARE_RUN_ID}" \ + --output "$RUNNER_TEMP/prepare-run.json" + - name: Download fsck artifacts uses: actions/download-artifact@v8 with: @@ -135,6 +152,17 @@ jobs: path: ./artifacts merge-multiple: true + - name: Verify prepared release + env: + VERSION: ${{ needs.validate-version.outputs.version }} + run: | + set -euo pipefail + bash scripts/ci/verify_release_artifacts.sh \ + "$RUNNER_TEMP/prepare-run.json" \ + ./artifacts \ + "$VERSION" \ + "$(git rev-parse HEAD)" + - name: Create GitHub Release uses: softprops/action-gh-release@v3 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 10825f8..2e3bb01 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -102,6 +102,9 @@ jobs: exit 1 fi + - name: Release artifact verification + run: bash scripts/test-release-artifacts.sh + # ───────────────────────────────────────────────────────────────────────── test: name: Test (${{ matrix.os }}) diff --git a/scripts/ci/verify_release_artifacts.sh b/scripts/ci/verify_release_artifacts.sh new file mode 100755 index 0000000..3e89e42 --- /dev/null +++ b/scripts/ci/verify_release_artifacts.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# Verify that a successful Release Prepare run produced the exact release set. + +set -euo pipefail + +die() { + printf 'release artifacts: %s\n' "$*" >&2 + exit 1 +} + +run_json="${1:?usage: verify_release_artifacts.sh }" +artifact_dir="${2:?usage: verify_release_artifacts.sh }" +version="${3:?usage: verify_release_artifacts.sh }" +commit="${4:?usage: verify_release_artifacts.sh }" + +test -f "$run_json" || die "missing workflow-run metadata: $run_json" +test -d "$artifact_dir" || die "missing artifact directory: $artifact_dir" +[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z][A-Za-z0-9-]*\.[0-9]+)?$ ]] || + die "invalid release version: $version" +[[ "$commit" =~ ^[0-9a-f]{40}$ ]] || die "invalid release commit: $commit" + +jq -e \ + --arg commit "$commit" \ + '.head_sha == $commit + and .path == ".github/workflows/release-prepare.yml" + and .event == "push" + and .status == "completed" + and .conclusion == "success"' \ + "$run_json" >/dev/null || + die "selected run is not a successful Release Prepare run for the tag commit" + +expected=( + "pagedb-fsck-${version}-linux-x64.tar.gz" + "pagedb-fsck-${version}-linux-arm64.tar.gz" + "pagedb-fsck-${version}-macos-arm64.tar.gz" + "pagedb-fsck-${version}-macos-x64.tar.gz" + "pagedb-fsck-${version}-windows-x64.zip" +) + +shopt -s nullglob dotglob +entries=("$artifact_dir"/*) +shopt -u nullglob dotglob +test "${#entries[@]}" -eq "${#expected[@]}" || + die "expected exactly ${#expected[@]} release archives" + +for entry in "${entries[@]}"; do + test -f "$entry" && test ! -L "$entry" || + die "unexpected release artifact: $entry" +done +for archive in "${expected[@]}"; do + test -f "$artifact_dir/$archive" || + die "missing release archive: $archive" +done + +printf 'release artifacts: ok\n' diff --git a/scripts/test-release-artifacts.sh b/scripts/test-release-artifacts.sh new file mode 100755 index 0000000..d0940af --- /dev/null +++ b/scripts/test-release-artifacts.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +verifier="$repo_root/scripts/ci/verify_release_artifacts.sh" +work_dir="$(mktemp -d)" +trap 'rm -rf "$work_dir"' EXIT + +version="0.2.0-beta.1" +commit="0123456789abcdef0123456789abcdef01234567" +run_json="$work_dir/run.json" +artifacts="$work_dir/artifacts" + +expect_failure() { + local label="$1" metadata="$2" expected_commit="$3" + shift + shift 2 + if bash "$verifier" "$metadata" "$artifacts" "$version" "$expected_commit" \ + >"$work_dir/failure.log" 2>&1; then + printf 'expected failure: %s\n' "$label" >&2 + exit 1 + fi +} + +jq -n --arg head_sha "$commit" \ + '{head_sha:$head_sha, path:".github/workflows/release-prepare.yml", + event:"push", status:"completed", conclusion:"success"}' >"$run_json" +mkdir "$artifacts" +touch \ + "$artifacts/pagedb-fsck-${version}-linux-x64.tar.gz" \ + "$artifacts/pagedb-fsck-${version}-linux-arm64.tar.gz" \ + "$artifacts/pagedb-fsck-${version}-macos-arm64.tar.gz" \ + "$artifacts/pagedb-fsck-${version}-macos-x64.tar.gz" \ + "$artifacts/pagedb-fsck-${version}-windows-x64.zip" + +bash "$verifier" "$run_json" "$artifacts" "$version" "$commit" + +expect_failure "different tag commit" "$run_json" \ + "ffffffffffffffffffffffffffffffffffffffff" + +jq '.conclusion = "failure"' "$run_json" >"$work_dir/failed-run.json" +expect_failure "failed prepare run" "$work_dir/failed-run.json" "$commit" + +touch "$artifacts/unexpected.txt" +expect_failure "extra artifact" "$run_json" "$commit" + +rm "$artifacts/unexpected.txt" "$artifacts/pagedb-fsck-${version}-windows-x64.zip" +expect_failure "missing archive" "$run_json" "$commit" + +printf 'release artifact verification: tests passed\n' From 452fc883031de8aa1fa27e32bf36225b294b9dc0 Mon Sep 17 00:00:00 2001 From: Farhan Syah Date: Tue, 28 Jul 2026 08:52:45 +0800 Subject: [PATCH 2/2] ci(release): gate artifact download on prepare-run provenance Split release verification into two focused checks: verify_prepare_run.sh confirms the selected Release Prepare run is a successful, tag-pushed build of the exact release commit before its artifacts are ever downloaded, and verify_release_artifacts.sh now only checks the downloaded attachment set against the expected archive list. Add regression tests for both verifiers and run them with shellcheck in CI. --- .github/workflows/release.yml | 24 ++-- .github/workflows/test.yml | 5 +- scripts/ci/test_release_verifiers.sh | 178 +++++++++++++++++++++++++ scripts/ci/verify_prepare_run.sh | 55 ++++++++ scripts/ci/verify_release_artifacts.sh | 60 +++++---- scripts/test-release-artifacts.sh | 50 ------- 6 files changed, 284 insertions(+), 88 deletions(-) create mode 100755 scripts/ci/test_release_verifiers.sh create mode 100755 scripts/ci/verify_prepare_run.sh delete mode 100755 scripts/test-release-artifacts.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5db189d..19098d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -126,9 +126,11 @@ jobs: ref: ${{ inputs.tag }} fetch-depth: 0 - - name: Read prepare run metadata + # Runs before the download so a wrong, unfinished, or foreign run is + # rejected without its archives ever reaching the runner. + - name: Verify prepare run env: - GITHUB_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ github.token }} PREPARE_RUN_ID: ${{ inputs.prepare_run_id }} run: | set -euo pipefail @@ -136,12 +138,14 @@ jobs: echo "::error::prepare_run_id must be numeric" exit 1 } - curl --fail --location --silent --show-error \ + gh api \ -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer ${GITHUB_TOKEN}" \ -H "X-GitHub-Api-Version: 2022-11-28" \ - "https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/runs/${PREPARE_RUN_ID}" \ - --output "$RUNNER_TEMP/prepare-run.json" + "repos/${GITHUB_REPOSITORY}/actions/runs/${PREPARE_RUN_ID}" \ + >"$RUNNER_TEMP/prepare-run.json" + bash scripts/ci/verify_prepare_run.sh \ + "$RUNNER_TEMP/prepare-run.json" \ + "$(git rev-parse HEAD)" - name: Download fsck artifacts uses: actions/download-artifact@v8 @@ -152,16 +156,12 @@ jobs: path: ./artifacts merge-multiple: true - - name: Verify prepared release + - name: Verify release artifacts env: VERSION: ${{ needs.validate-version.outputs.version }} run: | set -euo pipefail - bash scripts/ci/verify_release_artifacts.sh \ - "$RUNNER_TEMP/prepare-run.json" \ - ./artifacts \ - "$VERSION" \ - "$(git rev-parse HEAD)" + bash scripts/ci/verify_release_artifacts.sh ./artifacts "$VERSION" - name: Create GitHub Release uses: softprops/action-gh-release@v3 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2e3bb01..fccb97a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -102,8 +102,11 @@ jobs: exit 1 fi + - name: Shellcheck release scripts + run: shellcheck scripts/ci/*.sh + - name: Release artifact verification - run: bash scripts/test-release-artifacts.sh + run: bash scripts/ci/test_release_verifiers.sh # ───────────────────────────────────────────────────────────────────────── test: diff --git a/scripts/ci/test_release_verifiers.sh b/scripts/ci/test_release_verifiers.sh new file mode 100755 index 0000000..de87e7d --- /dev/null +++ b/scripts/ci/test_release_verifiers.sh @@ -0,0 +1,178 @@ +#!/usr/bin/env bash +# Regression tests for the release verifiers. +# +# Generated data only — no network, no tracked fixtures, no assertions about +# workflow YAML layout. Every negative case asserts the reason for the +# rejection, so a verifier that breaks in an unrelated way (bad arity, missing +# jq, a typo in a guard) cannot pass by merely exiting nonzero. + +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +verify_run="$repo_root/scripts/ci/verify_prepare_run.sh" +verify_artifacts="$repo_root/scripts/ci/verify_release_artifacts.sh" + +work_dir="$(mktemp -d)" +trap 'rm -rf "$work_dir"' EXIT + +version="0.2.0-beta.1" +commit="0123456789abcdef0123456789abcdef01234567" +other_commit="ffffffffffffffffffffffffffffffffffffffff" +artifacts="$work_dir/artifacts" +log="$work_dir/verifier.log" +failures=0 + +report_log() { + sed 's/^/ /' "$log" >&2 +} + +expect_ok() { + local label="$1" + shift + if "$@" >"$log" 2>&1; then + printf 'ok %s\n' "$label" + else + printf 'FAIL %s: expected success\n' "$label" >&2 + report_log + failures=$((failures + 1)) + fi +} + +expect_failure() { + local label="$1" want="$2" + shift 2 + if "$@" >"$log" 2>&1; then + printf 'FAIL %s: expected failure, got success\n' "$label" >&2 + failures=$((failures + 1)) + elif grep -qF -- "$want" "$log"; then + printf 'ok %s\n' "$label" + else + printf 'FAIL %s: expected message containing: %s\n' "$label" "$want" >&2 + report_log + failures=$((failures + 1)) + fi +} + +# A successful prepare run for $commit, optionally mutated by a jq filter. +write_run() { + local out="$1" filter="${2:-.}" + jq -n --arg head_sha "$commit" \ + '{head_sha: $head_sha, + path: ".github/workflows/release-prepare.yml", + event: "push", + status: "completed", + conclusion: "success"}' | + jq "$filter" >"$out" +} + +seed_artifacts() { + rm -rf "$artifacts" + mkdir -p "$artifacts" + local label + for label in linux-x64 linux-arm64 macos-arm64 macos-x64; do + : >"$artifacts/pagedb-fsck-${version}-${label}.tar.gz" + done + : >"$artifacts/pagedb-fsck-${version}-windows-x64.zip" +} + +# ── prepare-run provenance ────────────────────────────────────────────────── + +run_json="$work_dir/run.json" +write_run "$run_json" + +expect_ok "prepare run: successful tag build" \ + bash "$verify_run" "$run_json" "$commit" + +expect_failure "prepare run: different tag commit" "run head_sha is" \ + bash "$verify_run" "$run_json" "$other_commit" + +write_run "$work_dir/failed.json" '.conclusion = "failure"' +expect_failure "prepare run: unsuccessful conclusion" "run conclusion is 'failure'" \ + bash "$verify_run" "$work_dir/failed.json" "$commit" + +write_run "$work_dir/in-progress.json" '.status = "in_progress" | .conclusion = null' +expect_failure "prepare run: still running" "run status is 'in_progress'" \ + bash "$verify_run" "$work_dir/in-progress.json" "$commit" + +write_run "$work_dir/other-workflow.json" '.path = ".github/workflows/test.yml"' +expect_failure "prepare run: different workflow" "run path is" \ + bash "$verify_run" "$work_dir/other-workflow.json" "$commit" + +write_run "$work_dir/dispatched.json" '.event = "workflow_dispatch"' +expect_failure "prepare run: not a tag push" "run event is 'workflow_dispatch'" \ + bash "$verify_run" "$work_dir/dispatched.json" "$commit" + +write_run "$work_dir/truncated.json" 'del(.conclusion)' +expect_failure "prepare run: absent field" "run conclusion is ''" \ + bash "$verify_run" "$work_dir/truncated.json" "$commit" + +printf 'not json' >"$work_dir/garbage.json" +expect_failure "prepare run: malformed metadata" "not valid JSON" \ + bash "$verify_run" "$work_dir/garbage.json" "$commit" + +expect_failure "prepare run: absent metadata file" "missing workflow-run metadata" \ + bash "$verify_run" "$work_dir/nonexistent.json" "$commit" + +expect_failure "prepare run: malformed commit" "invalid release commit" \ + bash "$verify_run" "$run_json" "HEAD" + +# ── release attachment set ────────────────────────────────────────────────── + +seed_artifacts +expect_ok "artifacts: exact release set" \ + bash "$verify_artifacts" "$artifacts" "$version" + +seed_artifacts +: >"$artifacts/unexpected.txt" +expect_failure "artifacts: extra attachment" "unexpected release attachment: unexpected.txt" \ + bash "$verify_artifacts" "$artifacts" "$version" + +seed_artifacts +: >"$artifacts/.hidden" +expect_failure "artifacts: hidden attachment" "unexpected release attachment: .hidden" \ + bash "$verify_artifacts" "$artifacts" "$version" + +# Renamed rather than deleted: the attachment count stays at five, so this +# reaches the by-name check instead of tripping a coarser cardinality guard. +seed_artifacts +mv "$artifacts/pagedb-fsck-${version}-windows-x64.zip" "$artifacts/pagedb-fsck-${version}-windows-x86.zip" +expect_failure "artifacts: missing archive at full count" \ + "missing release archive: pagedb-fsck-${version}-windows-x64.zip" \ + bash "$verify_artifacts" "$artifacts" "$version" + +seed_artifacts +rm "$artifacts/pagedb-fsck-${version}-macos-x64.tar.gz" +expect_failure "artifacts: absent archive" \ + "missing release archive: pagedb-fsck-${version}-macos-x64.tar.gz" \ + bash "$verify_artifacts" "$artifacts" "$version" + +seed_artifacts +rm "$artifacts/pagedb-fsck-${version}-linux-x64.tar.gz" +ln -s /etc/hostname "$artifacts/pagedb-fsck-${version}-linux-x64.tar.gz" +expect_failure "artifacts: symlinked archive" "is not a regular file" \ + bash "$verify_artifacts" "$artifacts" "$version" + +seed_artifacts +rm "$artifacts/pagedb-fsck-${version}-linux-arm64.tar.gz" +mkdir "$artifacts/pagedb-fsck-${version}-linux-arm64.tar.gz" +expect_failure "artifacts: directory in place of archive" "is not a regular file" \ + bash "$verify_artifacts" "$artifacts" "$version" + +seed_artifacts +expect_failure "artifacts: version mismatch" "missing release archive" \ + bash "$verify_artifacts" "$artifacts" "9.9.9" + +expect_failure "artifacts: malformed version" "invalid release version" \ + bash "$verify_artifacts" "$artifacts" "not-a-version" + +expect_failure "artifacts: absent directory" "missing artifact directory" \ + bash "$verify_artifacts" "$work_dir/nonexistent" "$version" + +# ──────────────────────────────────────────────────────────────────────────── + +if test "$failures" -ne 0; then + printf 'release artifact verification: %d test(s) failed\n' "$failures" >&2 + exit 1 +fi + +printf 'release artifact verification: tests passed\n' diff --git a/scripts/ci/verify_prepare_run.sh b/scripts/ci/verify_prepare_run.sh new file mode 100755 index 0000000..5ac4108 --- /dev/null +++ b/scripts/ci/verify_prepare_run.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# Gate the maintainer-supplied Release Prepare run before any of its artifacts +# are downloaded. The run must be a successful, tag-pushed execution of the +# release prepare workflow for the exact commit the release tag resolves to. +# +# Provenance only. Nothing here inspects archives; the published attachment set +# is checked separately by verify_release_artifacts.sh once it is on disk. +# Archive contents are trusted on the strength of this gate — a run pinned to +# the tag commit, the prepare workflow file, and a successful conclusion — so +# there is deliberately no digest comparison anywhere in the release path. + +set -euo pipefail + +usage='usage: verify_prepare_run.sh ' + +die() { + printf 'prepare run: %s\n' "$*" >&2 + exit 1 +} + +run_json="${1:?$usage}" +commit="${2:?$usage}" + +test -f "$run_json" || die "missing workflow-run metadata: $run_json" +[[ "$commit" =~ ^[0-9a-f]{40}$ ]] || die "invalid release commit: $commit" +jq -e . "$run_json" >/dev/null 2>&1 || + die "workflow-run metadata is not valid JSON: $run_json" + +# Checked field-by-field rather than as one boolean so a rejection names the +# field that disagreed — the maintainer needs to know whether they picked the +# wrong run, the wrong tag, or a run that has not finished. +fields=(head_sha path event status conclusion) +expected=( + "$commit" + ".github/workflows/release-prepare.yml" + push + completed + success +) + +index=0 +while IFS= read -r actual; do + test "$index" -lt "${#fields[@]}" || die "unexpected workflow-run metadata shape" + test "$actual" = "${expected[$index]}" || + die "run ${fields[$index]} is '$actual', expected '${expected[$index]}'" + index=$((index + 1)) +done < <( + jq -r '[.head_sha, .path, .event, .status, .conclusion] + | map(if . == null then "" else tostring end) + | .[]' "$run_json" +) + +test "$index" -eq "${#fields[@]}" || die "incomplete workflow-run metadata: $run_json" + +printf 'prepare run: ok\n' diff --git a/scripts/ci/verify_release_artifacts.sh b/scripts/ci/verify_release_artifacts.sh index 3e89e42..89a5e68 100755 --- a/scripts/ci/verify_release_artifacts.sh +++ b/scripts/ci/verify_release_artifacts.sh @@ -1,34 +1,29 @@ #!/usr/bin/env bash -# Verify that a successful Release Prepare run produced the exact release set. +# Verify the downloaded release attachments are exactly the archive set the +# Release Prepare workflow publishes for this version — every expected archive +# present, nothing else alongside them. +# +# Provenance of the producing run is established beforehand by +# verify_prepare_run.sh; this script only decides what gets attached. set -euo pipefail +usage='usage: verify_release_artifacts.sh ' + die() { printf 'release artifacts: %s\n' "$*" >&2 exit 1 } -run_json="${1:?usage: verify_release_artifacts.sh }" -artifact_dir="${2:?usage: verify_release_artifacts.sh }" -version="${3:?usage: verify_release_artifacts.sh }" -commit="${4:?usage: verify_release_artifacts.sh }" +artifact_dir="${1:?$usage}" +version="${2:?$usage}" -test -f "$run_json" || die "missing workflow-run metadata: $run_json" test -d "$artifact_dir" || die "missing artifact directory: $artifact_dir" [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z][A-Za-z0-9-]*\.[0-9]+)?$ ]] || die "invalid release version: $version" -[[ "$commit" =~ ^[0-9a-f]{40}$ ]] || die "invalid release commit: $commit" - -jq -e \ - --arg commit "$commit" \ - '.head_sha == $commit - and .path == ".github/workflows/release-prepare.yml" - and .event == "push" - and .status == "completed" - and .conclusion == "success"' \ - "$run_json" >/dev/null || - die "selected run is not a successful Release Prepare run for the tag commit" +# Mirrors the build matrix in .github/workflows/release-prepare.yml. Adding a +# release target means changing both lists in the same commit. expected=( "pagedb-fsck-${version}-linux-x64.tar.gz" "pagedb-fsck-${version}-linux-arm64.tar.gz" @@ -37,19 +32,34 @@ expected=( "pagedb-fsck-${version}-windows-x64.zip" ) +# Both directions are checked by name. A count comparison would be shorter but +# would let one absence and one intruder cancel out, and it reports "wrong +# number of files" where the useful message is which file. +for archive in "${expected[@]}"; do + path="$artifact_dir/$archive" + # Symlinks are rejected ahead of the existence test so a dangling link is + # reported as what it is rather than as an absent archive. + if test -L "$path" || { test -e "$path" && test ! -f "$path"; }; then + die "release archive is not a regular file: $archive" + elif test ! -e "$path"; then + die "missing release archive: $archive" + fi +done + shopt -s nullglob dotglob entries=("$artifact_dir"/*) shopt -u nullglob dotglob -test "${#entries[@]}" -eq "${#expected[@]}" || - die "expected exactly ${#expected[@]} release archives" for entry in "${entries[@]}"; do - test -f "$entry" && test ! -L "$entry" || - die "unexpected release artifact: $entry" -done -for archive in "${expected[@]}"; do - test -f "$artifact_dir/$archive" || - die "missing release archive: $archive" + name="${entry##*/}" + known="" + for archive in "${expected[@]}"; do + if test "$name" = "$archive"; then + known=1 + break + fi + done + test -n "$known" || die "unexpected release attachment: $name" done printf 'release artifacts: ok\n' diff --git a/scripts/test-release-artifacts.sh b/scripts/test-release-artifacts.sh deleted file mode 100755 index d0940af..0000000 --- a/scripts/test-release-artifacts.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -verifier="$repo_root/scripts/ci/verify_release_artifacts.sh" -work_dir="$(mktemp -d)" -trap 'rm -rf "$work_dir"' EXIT - -version="0.2.0-beta.1" -commit="0123456789abcdef0123456789abcdef01234567" -run_json="$work_dir/run.json" -artifacts="$work_dir/artifacts" - -expect_failure() { - local label="$1" metadata="$2" expected_commit="$3" - shift - shift 2 - if bash "$verifier" "$metadata" "$artifacts" "$version" "$expected_commit" \ - >"$work_dir/failure.log" 2>&1; then - printf 'expected failure: %s\n' "$label" >&2 - exit 1 - fi -} - -jq -n --arg head_sha "$commit" \ - '{head_sha:$head_sha, path:".github/workflows/release-prepare.yml", - event:"push", status:"completed", conclusion:"success"}' >"$run_json" -mkdir "$artifacts" -touch \ - "$artifacts/pagedb-fsck-${version}-linux-x64.tar.gz" \ - "$artifacts/pagedb-fsck-${version}-linux-arm64.tar.gz" \ - "$artifacts/pagedb-fsck-${version}-macos-arm64.tar.gz" \ - "$artifacts/pagedb-fsck-${version}-macos-x64.tar.gz" \ - "$artifacts/pagedb-fsck-${version}-windows-x64.zip" - -bash "$verifier" "$run_json" "$artifacts" "$version" "$commit" - -expect_failure "different tag commit" "$run_json" \ - "ffffffffffffffffffffffffffffffffffffffff" - -jq '.conclusion = "failure"' "$run_json" >"$work_dir/failed-run.json" -expect_failure "failed prepare run" "$work_dir/failed-run.json" "$commit" - -touch "$artifacts/unexpected.txt" -expect_failure "extra artifact" "$run_json" "$commit" - -rm "$artifacts/unexpected.txt" "$artifacts/pagedb-fsck-${version}-windows-x64.zip" -expect_failure "missing archive" "$run_json" "$commit" - -printf 'release artifact verification: tests passed\n'