Files
DeepSeek-Reasonix/scripts/verify-stable-release-artifacts.sh
T
SivanCola e7a0f1163b fix(release): prevent skipped stable publishers (#6629)
Problem:
The stable orchestrator reported success after approval even though the CLI, npm, and Desktop publication jobs were skipped through a transitive skipped dependency. The existing recovery dispatch also could not use a workflow fix without moving the already-published tags.

Root cause:
Each reusable publisher joined an orchestrated guard with a deliberately skipped standalone gate. The cache guard used an explicit status condition, but its descendants did not, so GitHub propagated the skipped ancestor through the dependency chain. Recovery also checked out and executed control scripts from the old release candidate.

Fix:
Add explicit success conditions to every affected publication stage, keep recovery authorization and verification on protected main-v2 while building the immutable historical tag SHA, and reject non-ancestor or mismatched tag sets. Add a stable postflight that verifies required CLI/Desktop release assets and the exact npm latest version so a no-op release cannot stay green.

Verification:
- bash scripts/release-workflows.test.sh
- go run github.com/rhysd/actionlint/cmd/[email protected] -ignore 'label "windows-11-arm" is unknown' on all release workflows
- bash -n on all changed release scripts
- verify-stable-release-artifacts.sh against public v1.17.14 artifacts
- ./scripts/cache-guard.sh
- go test ./...
- git diff --check
2026-07-18 08:36:22 +08:00

65 lines
2.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# Verify that a stable orchestration produced every public release channel.
set -euo pipefail
repository="${RELEASE_REPOSITORY:?RELEASE_REPOSITORY is required}"
version="${RELEASE_VERSION:?RELEASE_VERSION is required}"
cli_tag="${CLI_TAG:?CLI_TAG is required}"
desktop_tag="${DESKTOP_TAG:?DESKTOP_TAG is required}"
attempts="${VERIFY_ATTEMPTS:-6}"
delay="${VERIFY_DELAY_SECONDS:-10}"
if [[ ! "$version" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
echo "::error::RELEASE_VERSION must be stable semver, got: $version" >&2
exit 1
fi
if [ "$cli_tag" != "v$version" ] || [ "$desktop_tag" != "desktop-v$version" ]; then
echo "::error::release tags do not match version $version: cli=$cli_tag desktop=$desktop_tag" >&2
exit 1
fi
tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/reasonix-release-postflight.XXXXXX")"
cleanup() {
case "$tmp_dir" in
*/reasonix-release-postflight.*) rm -rf -- "$tmp_dir" ;;
*) echo "refusing to clean unexpected postflight directory: $tmp_dir" >&2 ;;
esac
}
trap cleanup EXIT
gh release view "$cli_tag" --repo "$repository" --json isDraft,isPrerelease,assets >"$tmp_dir/cli.json"
jq -e '
.isDraft == false and .isPrerelease == false and
([.assets[].name] as $names |
["SHA256SUMS", "reasonix-darwin-amd64.tar.gz", "reasonix-darwin-arm64.tar.gz",
"reasonix-linux-amd64.tar.gz", "reasonix-linux-arm64.tar.gz",
"reasonix-windows-amd64.zip", "reasonix-windows-arm64.zip"] |
all(. as $required | $names | index($required)))
' "$tmp_dir/cli.json" >/dev/null
gh release view "$desktop_tag" --repo "$repository" --json isDraft,isPrerelease,assets >"$tmp_dir/desktop.json"
jq -e '
.isDraft == false and .isPrerelease == false and
([.assets[].name] as $names |
($names | index("latest.json")) and
(["Reasonix-darwin-universal.dmg", "Reasonix-linux-amd64.deb",
"Reasonix-linux-amd64.tar.gz", "Reasonix-windows-amd64-installer.exe",
"Reasonix-windows-arm64-installer.exe"] |
all(. as $required | ($names | index($required)) and ($names | index($required + ".minisig")))))
' "$tmp_dir/desktop.json" >/dev/null
for attempt in $(seq 1 "$attempts"); do
latest="$(npm view reasonix dist-tags.latest 2>/dev/null || true)"
if [ "$latest" = "$version" ]; then
echo "stable release postflight OK: cli=$cli_tag desktop=$desktop_tag npm-latest=$latest"
exit 0
fi
echo "npm latest -> ${latest:-<unreadable>}, want $version (attempt $attempt/$attempts)"
if [ "$attempt" -lt "$attempts" ]; then
sleep "$delay"
fi
done
echo "::error::npm latest did not reach $version" >&2
exit 1