mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 10:45:59 -10:00
Problem: The stable helper could consume immutable version tags if main-v2 advanced while exact-SHA CI or the protected relay was starting. Manual CLI and Desktop recovery also still accepted RC tags despite the official-only release contract. Root cause: The helper sampled main-v2 once, pushed only tag refs, and the orchestrator required the tags to equal the later branch tip. The candidate was not proven to be the commit that changed the target reviewed Notes record. Fix: Bind normal candidates to their reviewed Notes change, revalidate exact push CI from the protected control plane, include main-v2 as a guarded ref in the atomic tag transaction, and accept the immutable candidate after later protected branch advances. Restrict manual CLI and Desktop recovery to official versions while retaining historical reusable-workflow parsing. Verification: - bash scripts/release-stable.test.sh - bash scripts/release-workflows.test.sh - node --test scripts/release-notes.test.mjs - actionlint on all active release workflows - site contract tests, SignPath contract tests, Desktop control-plane test, cache guard, and git diff --check
91 lines
3.2 KiB
Bash
Executable File
91 lines
3.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Validate one stable release tag set and emit the values shared by all release
|
|
# workflows. Stable releases are deliberately all-or-nothing: the CLI, npm, and
|
|
# desktop tags must resolve to one commit. Normal publication accepts the Notes
|
|
# candidate after main-v2 advances beyond it; the protected workflow separately
|
|
# revalidates that candidate's Notes change and exact push CI. Recovery may also
|
|
# target an older commit while the control plane stays on current main-v2.
|
|
set -euo pipefail
|
|
|
|
release_tag="${RELEASE_TAG:?RELEASE_TAG is required}"
|
|
release_remote="${RELEASE_REMOTE:-origin}"
|
|
allow_recovery="${ALLOW_STABLE_RECOVERY:-false}"
|
|
|
|
case "$allow_recovery" in
|
|
true | false) ;;
|
|
*)
|
|
echo "::error::ALLOW_STABLE_RECOVERY must be true or false, got: $allow_recovery" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
if [[ ! "$release_tag" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
|
|
echo "::error::stable release tag must be vMAJOR.MINOR.PATCH, got: $release_tag" >&2
|
|
exit 1
|
|
fi
|
|
|
|
version="${release_tag#v}"
|
|
cli_tag="$release_tag"
|
|
npm_tag="npm-v${version}"
|
|
desktop_tag="desktop-v${version}"
|
|
|
|
checkout_sha="$(git rev-parse HEAD^{commit})"
|
|
main_sha="$(git ls-remote "$release_remote" refs/heads/main-v2 | awk 'NR == 1 { print $1 }')"
|
|
if [ -z "$main_sha" ]; then
|
|
echo "::error::cannot resolve $release_remote/main-v2" >&2
|
|
exit 1
|
|
fi
|
|
|
|
git fetch --quiet --no-tags "$release_remote" refs/heads/main-v2
|
|
if ! git merge-base --is-ancestor "$checkout_sha" "$main_sha"; then
|
|
echo "::error::release control checkout $checkout_sha is not on $release_remote/main-v2 history ($main_sha)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
release_sha="$(
|
|
git ls-remote --tags "$release_remote" "refs/tags/$cli_tag" "refs/tags/$cli_tag^{}" |
|
|
awk '/\^\{\}$/ { print $1; found = 1; exit } NR == 1 { first = $1 } END { if (!found) print first }'
|
|
)"
|
|
if [ -z "$release_sha" ]; then
|
|
echo "::error::required stable release tag is missing: $cli_tag" >&2
|
|
exit 1
|
|
fi
|
|
git fetch --quiet --no-tags "$release_remote" "refs/tags/$cli_tag"
|
|
if ! git merge-base --is-ancestor "$release_sha" "$main_sha"; then
|
|
echo "::error::stable tag $cli_tag points to $release_sha, which is not an ancestor of $release_remote/main-v2 ($main_sha)" >&2
|
|
exit 1
|
|
fi
|
|
if [ "$release_sha" != "$main_sha" ]; then
|
|
mode="candidate"
|
|
[ "$allow_recovery" = "true" ] && mode="recovery"
|
|
echo "stable $mode: $cli_tag remains on main-v2 history at $release_sha; current main-v2 is $main_sha"
|
|
fi
|
|
|
|
for tag in "$cli_tag" "$npm_tag" "$desktop_tag"; do
|
|
# Prefer the peeled commit for annotated tags; lightweight tags only return
|
|
# the first line. Both forms are valid release refs.
|
|
tag_sha="$(
|
|
git ls-remote --tags "$release_remote" "refs/tags/$tag" "refs/tags/$tag^{}" |
|
|
awk '/\^\{\}$/ { print $1; found = 1; exit } NR == 1 { first = $1 } END { if (!found) print first }'
|
|
)"
|
|
if [ -z "$tag_sha" ]; then
|
|
echo "::error::required stable release tag is missing: $tag" >&2
|
|
exit 1
|
|
fi
|
|
if [ "$tag_sha" != "$release_sha" ]; then
|
|
echo "::error::$tag points to $tag_sha, expected $release_sha" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
output_file="${GITHUB_OUTPUT:-/dev/stdout}"
|
|
{
|
|
echo "version=$version"
|
|
echo "cli_tag=$cli_tag"
|
|
echo "npm_tag=$npm_tag"
|
|
echo "desktop_tag=$desktop_tag"
|
|
echo "sha=$release_sha"
|
|
} >>"$output_file"
|
|
|
|
echo "stable release resolved: version=$version sha=$release_sha"
|