fix(release): preserve frozen candidate identity

This commit is contained in:
Vincent Koc
2026-08-11 10:30:56 +08:00
parent abb856ece4
commit c3e3f276f2
9 changed files with 400 additions and 48 deletions
@@ -894,9 +894,8 @@ jobs:
if [[ "$child_rerun_group" == "release-checks" ]]; then
child_rerun_group=all
fi
release_checks_target_ref="${TARGET_CONTEXT_REF:-$TARGET_REF}"
args=(
-f ref="$release_checks_target_ref"
-f ref="$TARGET_SHA"
-f expected_sha="$TARGET_SHA"
-f provider="$PROVIDER"
-f mode="$MODE"
@@ -908,6 +907,7 @@ jobs:
-f rerun_group="$child_rerun_group"
)
if [[ -n "${TARGET_CONTEXT_REF// }" ]]; then
args+=(-f target_context_ref="$TARGET_CONTEXT_REF")
args+=(-f allow_frozen_target_scenario_omissions=true)
fi
if [[ -n "${LIVE_SUITE_FILTER// }" ]]; then
@@ -9,6 +9,11 @@ on:
description: Branch, tag, or full commit SHA to validate
required: true
type: string
target_context_ref:
description: Optional canonical release branch or tag context for an exact-SHA target
required: false
default: ""
type: string
expected_sha:
description: Optional full SHA that ref must resolve to
required: false
@@ -1974,7 +1979,7 @@ jobs:
- name: Dispatch and await trusted Telegram QA
env:
GH_TOKEN: ${{ github.token }}
TARGET_REF: ${{ needs.resolve_target.outputs.ref }}
TARGET_CONTEXT_REF: ${{ inputs.target_context_ref }}
TARGET_SHA: ${{ needs.resolve_target.outputs.revision }}
shell: bash
run: |
@@ -1983,13 +1988,6 @@ jobs:
workflow="openclaw-release-telegram-qa.yml"
run_id=""
run_name=""
telegram_target_ref="$TARGET_REF"
case "$telegram_target_ref" in
validation/target-* | refs/heads/validation/target-*)
telegram_target_ref="$TARGET_SHA"
;;
esac
find_child_runs() {
RUN_NAME="$run_name" gh api -X GET \
"repos/${GITHUB_REPOSITORY}/actions/workflows/${workflow}/runs" \
@@ -2041,7 +2039,8 @@ jobs:
--ref main \
-f dispatch_id="$dispatch_id" \
-f expected_trusted_workflow_sha="$expected_trusted_workflow_sha" \
-f target_ref="$telegram_target_ref" \
-f target_context_ref="$TARGET_CONTEXT_REF" \
-f target_ref="$TARGET_SHA" \
-f target_sha="$TARGET_SHA"
for _ in $(seq 1 60); do
@@ -15,6 +15,11 @@ on:
description: Trusted release ref whose exact candidate SHA should be validated
required: true
type: string
target_context_ref:
description: Optional canonical release branch or tag context for an exact-SHA candidate
required: false
default: ""
type: string
target_sha:
description: Exact full candidate commit SHA
required: true
@@ -40,6 +45,11 @@ on:
description: Trusted release ref whose exact candidate SHA should be validated
required: true
type: string
target_context_ref:
description: Optional canonical release branch or tag context for an exact-SHA candidate
required: false
default: ""
type: string
target_sha:
description: Exact full candidate commit SHA
required: true
@@ -73,6 +83,7 @@ jobs:
CALLER_WORKFLOW_SHA: ${{ github.workflow_sha }}
EXPECTED_TRUSTED_WORKFLOW_SHA: ${{ inputs.expected_trusted_workflow_sha }}
JOB_CONTEXT: ${{ toJSON(job) }}
TARGET_CONTEXT_REF: ${{ inputs.target_context_ref }}
TARGET_REF: ${{ inputs.target_ref }}
TARGET_SHA: ${{ inputs.target_sha }}
WORKFLOW_REF: ${{ github.workflow_ref }}
@@ -90,6 +101,18 @@ jobs:
echo "Telegram QA identity or target is malformed." >&2
exit 1
fi
normalized_context_ref="${TARGET_CONTEXT_REF:-}"
normalized_context_ref="${normalized_context_ref#refs/heads/}"
normalized_context_ref="${normalized_context_ref#refs/tags/}"
if [[ -n "$normalized_context_ref" &&
! "$normalized_context_ref" =~ ^(release/[0-9]{4}\.[0-9]+\.[0-9]+|extended-stable/[0-9]{4}\.[0-9]+\.33|v[0-9]{4}\.[0-9]+\.[0-9]+(-(alpha|beta)\.[0-9]+)?)$ ]]; then
echo "Telegram QA target context must be a canonical release branch or tag." >&2
exit 1
fi
if [[ -n "$normalized_context_ref" && "$TARGET_REF" != "$TARGET_SHA" ]]; then
echo "Telegram QA release context requires an exact-SHA target ref." >&2
exit 1
fi
INVOCATION_MODE=reusable
if [[ "$WORKFLOW_REF" == "$expected_ref" ]]; then
INVOCATION_MODE=dispatch
@@ -262,6 +285,7 @@ jobs:
id: provenance
env:
GH_TOKEN: ${{ github.token }}
TARGET_CONTEXT_REF: ${{ inputs.target_context_ref }}
TARGET_REF: ${{ inputs.target_ref }}
TARGET_SHA: ${{ inputs.target_sha }}
shell: bash
@@ -304,6 +328,42 @@ jobs:
candidate_sha="$(git -C .candidate rev-parse HEAD)"
[[ "$candidate_sha" == "$TARGET_SHA" ]]
normalized_context_ref="${TARGET_CONTEXT_REF:-}"
normalized_context_ref="${normalized_context_ref#refs/heads/}"
normalized_context_ref="${normalized_context_ref#refs/tags/}"
context_release_branch=""
context_release_tag=""
frozen_release_branch_pattern=""
if [[ "$normalized_context_ref" =~ ^release/([0-9]{4}\.[0-9]+\.[0-9]+)$ ]]; then
release_version="${BASH_REMATCH[1]}"
release_version_pattern="${release_version//./\\.}"
candidate_version="$(jq -er '.version' .candidate/package.json)"
if [[ "$candidate_version" == "$release_version" ]]; then
context_release_branch="$normalized_context_ref"
elif [[ "$candidate_version" =~ ^${release_version_pattern}-beta\.[0-9]+$ ]]; then
candidate_version_pattern="${candidate_version//./\\.}"
frozen_release_branch_pattern="^release/${candidate_version_pattern}-code-frozen(-r[1-9][0-9]*)?$"
else
echo "Telegram candidate version ${candidate_version} does not belong to release ${release_version}." >&2
exit 1
fi
elif [[ "$normalized_context_ref" =~ ^extended-stable/([0-9]{4}\.[0-9]+\.33)$ ]]; then
context_version="${BASH_REMATCH[1]}"
candidate_version="$(jq -er '.version' .candidate/package.json)"
if [[ "$candidate_version" != "$context_version" ]]; then
echo "Telegram candidate version ${candidate_version} does not match context ${normalized_context_ref}." >&2
exit 1
fi
context_release_branch="$normalized_context_ref"
elif [[ "$normalized_context_ref" =~ ^v([0-9]{4}\.[0-9]+\.[0-9]+(-(alpha|beta)\.[0-9]+)?)$ ]]; then
context_version="${BASH_REMATCH[1]}"
candidate_version="$(jq -er '.version' .candidate/package.json)"
if [[ "$candidate_version" != "$context_version" ]]; then
echo "Telegram candidate version ${candidate_version} does not match context ${normalized_context_ref}." >&2
exit 1
fi
context_release_tag="$normalized_context_ref"
fi
repository_owner="${GITHUB_REPOSITORY%%/*}"
repository_name="${GITHUB_REPOSITORY#*/}"
candidate_metadata_json="$(
@@ -334,18 +394,41 @@ jobs:
)"
trusted_reason=""
trusted_release_branch=""
if [[ "$compare_status" == "ahead" || "$compare_status" == "identical" ]]; then
if [[ -n "$context_release_branch" ]]; then
branch_sha="$(
git -C .candidate ls-remote --exit-code --refs origin \
"refs/heads/${context_release_branch}" |
awk 'NR == 1 { print $1 } END { if (NR != 1) exit 1 }'
)"
[[ "$branch_sha" == "$candidate_sha" ]]
trusted_reason="release-branch-head"
trusted_release_branch="$context_release_branch"
elif [[ -n "$context_release_tag" ]]; then
tag_refs="$(
git -C .candidate ls-remote --exit-code origin \
"refs/tags/${context_release_tag}" "refs/tags/${context_release_tag}^{}"
)"
awk -v sha="$candidate_sha" '$1 == sha { found = 1 } END { exit(found ? 0 : 1) }' \
<<<"$tag_refs"
trusted_reason="release-tag"
elif [[ -z "$frozen_release_branch_pattern" &&
( "$compare_status" == "ahead" || "$compare_status" == "identical" ) ]]; then
trusted_reason="main-ancestor"
else
normalized_ref="${TARGET_REF#refs/heads/}"
if [[ "$normalized_ref" =~ ^(release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*|extended-stable/[0-9]{4}\.[1-9][0-9]*\.33)$ ]]; then
if [[ "$normalized_ref" =~ ^(release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*|extended-stable/[0-9]{4}\.[1-9][0-9]*\.33)$ ]] ||
[[ -n "$frozen_release_branch_pattern" && "$normalized_ref" =~ $frozen_release_branch_pattern ]]; then
branch_sha="$(
git -C .candidate ls-remote --exit-code --refs origin \
"refs/heads/${normalized_ref}" |
awk 'NR == 1 { print $1 } END { if (NR != 1) exit 1 }'
)"
[[ "$branch_sha" == "$candidate_sha" ]]
trusted_reason="release-branch-head"
if [[ -n "$frozen_release_branch_pattern" && "$normalized_ref" =~ $frozen_release_branch_pattern ]]; then
trusted_reason="frozen-release-branch-head"
else
trusted_reason="release-branch-head"
fi
trusted_release_branch="$normalized_ref"
elif [[ "$TARGET_REF" =~ ^refs/tags/v ]] || [[ "$TARGET_REF" =~ ^v ]]; then
normalized_tag="${TARGET_REF#refs/tags/}"
@@ -361,13 +444,20 @@ jobs:
gh_with_retry api --paginate \
"repos/${GITHUB_REPOSITORY}/commits/${candidate_sha}/branches-where-head" \
--jq '.[].name' |
awk '$0 ~ /^release\/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$/ ||
$0 ~ /^extended-stable\/[0-9]{4}\.[1-9][0-9]*\.33$/ { print }'
awk -v frozen="$frozen_release_branch_pattern" \
'(frozen != "" && $0 ~ frozen) ||
(frozen == "" &&
($0 ~ /^release\/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$/ ||
$0 ~ /^extended-stable\/[0-9]{4}\.[1-9][0-9]*\.33$/)) { print }'
)"
if [[ "$(wc -l <<<"$matching_release_branches" | tr -d ' ')" == "1" && -n "$matching_release_branches" ]]; then
trusted_reason="release-branch-head"
if [[ -n "$frozen_release_branch_pattern" && "$matching_release_branches" =~ $frozen_release_branch_pattern ]]; then
trusted_reason="frozen-release-branch-head"
else
trusted_reason="release-branch-head"
fi
trusted_release_branch="$matching_release_branches"
else
elif [[ -z "$frozen_release_branch_pattern" ]]; then
matching_release_tags="$(
git -C .candidate ls-remote origin 'refs/tags/v*' |
awk -v sha="$candidate_sha" '$1 == sha { sub(/\^\{\}$/, "", $2); print $2 }' |
@@ -403,6 +493,11 @@ jobs:
exit 1
fi
signer="$(jq -r '.data.repository.object.signature.signer.login // ""' <<<"$signature_json")"
if [[ "$trusted_reason" == "frozen-release-branch-head" &&
( "$signature_status" != "valid" || "$signer" == "web-flow" ) ]]; then
echo "Frozen release candidate ${candidate_sha} requires a valid maintainer signature." >&2
exit 1
fi
permission_actor="$signer"
if [[ "$signature_status" == "missing" || "$signer" == "web-flow" ]]; then
if [[ "$trusted_reason" != "release-branch-head" || -z "$trusted_release_branch" ]]; then
@@ -1004,7 +1099,9 @@ jobs:
- name: Revalidate candidate release provenance
id: revalidate_provenance
env:
CANDIDATE_ROOT: ${{ steps.extract_candidate.outputs.candidate_root }}
GH_TOKEN: ${{ github.token }}
TARGET_CONTEXT_REF: ${{ inputs.target_context_ref }}
TARGET_REF: ${{ inputs.target_ref }}
TARGET_SHA: ${{ inputs.target_sha }}
shell: bash
@@ -1046,6 +1143,42 @@ jobs:
}
candidate_sha="$TARGET_SHA"
normalized_context_ref="${TARGET_CONTEXT_REF:-}"
normalized_context_ref="${normalized_context_ref#refs/heads/}"
normalized_context_ref="${normalized_context_ref#refs/tags/}"
context_release_branch=""
context_release_tag=""
frozen_release_branch_pattern=""
if [[ "$normalized_context_ref" =~ ^release/([0-9]{4}\.[0-9]+\.[0-9]+)$ ]]; then
release_version="${BASH_REMATCH[1]}"
release_version_pattern="${release_version//./\\.}"
candidate_version="$(jq -er '.version' "${CANDIDATE_ROOT}/package.json")"
if [[ "$candidate_version" == "$release_version" ]]; then
context_release_branch="$normalized_context_ref"
elif [[ "$candidate_version" =~ ^${release_version_pattern}-beta\.[0-9]+$ ]]; then
candidate_version_pattern="${candidate_version//./\\.}"
frozen_release_branch_pattern="^release/${candidate_version_pattern}-code-frozen(-r[1-9][0-9]*)?$"
else
echo "Telegram candidate version ${candidate_version} does not belong to release ${release_version}." >&2
exit 1
fi
elif [[ "$normalized_context_ref" =~ ^extended-stable/([0-9]{4}\.[0-9]+\.33)$ ]]; then
context_version="${BASH_REMATCH[1]}"
candidate_version="$(jq -er '.version' "${CANDIDATE_ROOT}/package.json")"
if [[ "$candidate_version" != "$context_version" ]]; then
echo "Telegram candidate version ${candidate_version} does not match context ${normalized_context_ref}." >&2
exit 1
fi
context_release_branch="$normalized_context_ref"
elif [[ "$normalized_context_ref" =~ ^v([0-9]{4}\.[0-9]+\.[0-9]+(-(alpha|beta)\.[0-9]+)?)$ ]]; then
context_version="${BASH_REMATCH[1]}"
candidate_version="$(jq -er '.version' "${CANDIDATE_ROOT}/package.json")"
if [[ "$candidate_version" != "$context_version" ]]; then
echo "Telegram candidate version ${candidate_version} does not match context ${normalized_context_ref}." >&2
exit 1
fi
context_release_tag="$normalized_context_ref"
fi
repository_owner="${GITHUB_REPOSITORY%%/*}"
repository_name="${GITHUB_REPOSITORY#*/}"
candidate_metadata_json="$(
@@ -1073,17 +1206,39 @@ jobs:
)"
trusted_reason=""
trusted_release_branch=""
if [[ "$compare_status" == "ahead" || "$compare_status" == "identical" ]]; then
if [[ -n "$context_release_branch" ]]; then
branch_sha="$(
git ls-remote --exit-code --refs origin "refs/heads/${context_release_branch}" |
awk 'NR == 1 { print $1 } END { if (NR != 1) exit 1 }'
)"
[[ "$branch_sha" == "$candidate_sha" ]]
trusted_reason="release-branch-head"
trusted_release_branch="$context_release_branch"
elif [[ -n "$context_release_tag" ]]; then
tag_refs="$(
git ls-remote --exit-code origin \
"refs/tags/${context_release_tag}" "refs/tags/${context_release_tag}^{}"
)"
awk -v sha="$candidate_sha" '$1 == sha { found = 1 } END { exit(found ? 0 : 1) }' \
<<<"$tag_refs"
trusted_reason="release-tag"
elif [[ -z "$frozen_release_branch_pattern" &&
( "$compare_status" == "ahead" || "$compare_status" == "identical" ) ]]; then
trusted_reason="main-ancestor"
else
normalized_ref="${TARGET_REF#refs/heads/}"
if [[ "$normalized_ref" =~ ^(release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*|extended-stable/[0-9]{4}\.[1-9][0-9]*\.33)$ ]]; then
if [[ "$normalized_ref" =~ ^(release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*|extended-stable/[0-9]{4}\.[1-9][0-9]*\.33)$ ]] ||
[[ -n "$frozen_release_branch_pattern" && "$normalized_ref" =~ $frozen_release_branch_pattern ]]; then
branch_sha="$(
git ls-remote --exit-code --refs origin "refs/heads/${normalized_ref}" |
awk 'NR == 1 { print $1 } END { if (NR != 1) exit 1 }'
)"
[[ "$branch_sha" == "$candidate_sha" ]]
trusted_reason="release-branch-head"
if [[ -n "$frozen_release_branch_pattern" && "$normalized_ref" =~ $frozen_release_branch_pattern ]]; then
trusted_reason="frozen-release-branch-head"
else
trusted_reason="release-branch-head"
fi
trusted_release_branch="$normalized_ref"
elif [[ "$TARGET_REF" =~ ^refs/tags/v ]] || [[ "$TARGET_REF" =~ ^v ]]; then
normalized_tag="${TARGET_REF#refs/tags/}"
@@ -1099,13 +1254,20 @@ jobs:
gh_with_retry api --paginate \
"repos/${GITHUB_REPOSITORY}/commits/${candidate_sha}/branches-where-head" \
--jq '.[].name' |
awk '$0 ~ /^release\/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$/ ||
$0 ~ /^extended-stable\/[0-9]{4}\.[1-9][0-9]*\.33$/ { print }'
awk -v frozen="$frozen_release_branch_pattern" \
'(frozen != "" && $0 ~ frozen) ||
(frozen == "" &&
($0 ~ /^release\/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$/ ||
$0 ~ /^extended-stable\/[0-9]{4}\.[1-9][0-9]*\.33$/)) { print }'
)"
if [[ "$(wc -l <<<"$matching_release_branches" | tr -d ' ')" == "1" && -n "$matching_release_branches" ]]; then
trusted_reason="release-branch-head"
if [[ -n "$frozen_release_branch_pattern" && "$matching_release_branches" =~ $frozen_release_branch_pattern ]]; then
trusted_reason="frozen-release-branch-head"
else
trusted_reason="release-branch-head"
fi
trusted_release_branch="$matching_release_branches"
else
elif [[ -z "$frozen_release_branch_pattern" ]]; then
matching_release_tags="$(
git ls-remote origin 'refs/tags/v*' |
awk -v sha="$candidate_sha" '$1 == sha { sub(/\^\{\}$/, "", $2); print $2 }' |
@@ -1138,6 +1300,11 @@ jobs:
exit 1
fi
signer="$(jq -r '.data.repository.object.signature.signer.login // ""' <<<"$signature_json")"
if [[ "$trusted_reason" == "frozen-release-branch-head" &&
( "$signature_status" != "valid" || "$signer" == "web-flow" ) ]]; then
echo "Frozen release candidate ${candidate_sha} requires a valid maintainer signature." >&2
exit 1
fi
permission_actor="$signer"
if [[ "$signature_status" == "missing" || "$signer" == "web-flow" ]]; then
if [[ "$trusted_reason" != "release-branch-head" || -z "$trusted_release_branch" ]]; then