1111 description : " Dry run (skip release upload)"
1212 type : boolean
1313 default : false
14+ recovery-target :
15+ description : " Build and tag the resolver-derived release commit instead of the branch head"
16+ type : boolean
17+ default : false
1418
1519jobs :
1620 build :
@@ -41,14 +45,29 @@ jobs:
4145 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
4246 run : scripts/resolve-native-cli-release-target.sh >> "$GITHUB_OUTPUT"
4347
48+ - name : Check out resolver-derived recovery target
49+ if : github.event_name == 'workflow_dispatch' && inputs.recovery-target && (steps.release.outputs.publish == 'true' || steps.release.outputs.release == 'true')
50+ env :
51+ TARGET_SHA : ${{ steps.release.outputs.sha }}
52+ run : |
53+ set -eu
54+ # Why the ancestor check: recovery may only rebuild a commit that already
55+ # landed on the approved branch; any other target must fail closed.
56+ if ! git merge-base --is-ancestor "${TARGET_SHA}" "${GITHUB_SHA}"; then
57+ echo "Recovery target ${TARGET_SHA} is not an ancestor of approved event commit ${GITHUB_SHA}." >&2
58+ exit 1
59+ fi
60+ git checkout --detach "${TARGET_SHA}"
61+
4462 - name : Verify release target matches build commit
4563 if : steps.release.outputs.publish == 'true' || steps.release.outputs.release == 'true'
4664 env :
4765 TARGET_SHA : ${{ steps.release.outputs.sha }}
4866 run : |
4967 set -eu
50- if [ "${TARGET_SHA}" != "${GITHUB_SHA}" ]; then
51- echo "Release target ${TARGET_SHA} does not match approved build commit ${GITHUB_SHA}." >&2
68+ build_sha=$(git rev-parse HEAD)
69+ if [ "${TARGET_SHA}" != "${build_sha}" ]; then
70+ echo "Release target ${TARGET_SHA} does not match build source commit ${build_sha}." >&2
5271 exit 1
5372 fi
5473
@@ -104,7 +123,7 @@ jobs:
104123 set -eu
105124 mkdir -p release-input
106125 {
107- printf 'build_sha=%s\n' "${GITHUB_SHA} "
126+ printf 'build_sha=%s\n' "$(git rev-parse HEAD) "
108127 printf 'target_sha=%s\n' "${TARGET_SHA}"
109128 printf 'release_tag=%s\n' "${RELEASE_TAG}"
110129 printf 'version=%s\n' "${RELEASE_VERSION}"
@@ -156,6 +175,8 @@ jobs:
156175 - name : Verify release metadata
157176 env :
158177 GITHUB_REPOSITORY : ${{ github.repository }}
178+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
179+ RECOVERY_TARGET : ${{ github.event_name == 'workflow_dispatch' && inputs.recovery-target }}
159180 run : |
160181 set -eu
161182 metadata_value() {
@@ -178,9 +199,26 @@ jobs:
178199 event_ref=$(metadata_value event_ref)
179200 BUILD_SHA="${build_sha}"
180201 TARGET_SHA="${target_sha}"
181- if [ "${BUILD_SHA}" != "${GITHUB_SHA}" ] || [ "${TARGET_SHA}" != "${GITHUB_SHA}" ]; then
182- echo "Build SHA and release target must match the approved event commit." >&2
183- exit 1
202+ if [ "${RECOVERY_TARGET}" = "true" ]; then
203+ if [ "${BUILD_SHA}" != "${TARGET_SHA}" ]; then
204+ echo "Recovery build SHA must match the release target commit." >&2
205+ exit 1
206+ fi
207+ # Why an API comparison: the metadata artifact comes from the unprivileged
208+ # build job, so ancestry on the approved event commit is re-verified from
209+ # an independent source before the privileged job tags that commit.
210+ compare_status=$(gh api "repos/${GITHUB_REPOSITORY}/compare/${TARGET_SHA}...${GITHUB_SHA}" --jq '.status')
211+ if [ "${compare_status}" != "ahead" ] && [ "${compare_status}" != "identical" ]; then
212+ echo "Recovery target ${TARGET_SHA} is not an ancestor of approved event commit ${GITHUB_SHA}." >&2
213+ exit 1
214+ fi
215+ release_sha="${TARGET_SHA}"
216+ else
217+ if [ "${BUILD_SHA}" != "${GITHUB_SHA}" ] || [ "${TARGET_SHA}" != "${GITHUB_SHA}" ]; then
218+ echo "Build SHA and release target must match the approved event commit." >&2
219+ exit 1
220+ fi
221+ release_sha="${GITHUB_SHA}"
184222 fi
185223 if [ "${event_ref}" != "refs/heads/main" ] && [ "${event_ref}" != "refs/heads/v3-beta" ]; then
186224 echo "Unsupported release ref ${event_ref}." >&2
@@ -201,6 +239,7 @@ jobs:
201239 validate_boolean publish "${should_publish}"
202240 validate_boolean release "${should_release}"
203241 validate_boolean dry_run "${dry_run}"
242+ printf 'RELEASE_SHA=%s\n' "${release_sha}" >> "$GITHUB_ENV"
204243 printf 'RELEASE_TAG=%s\n' "${release_tag}" >> "$GITHUB_ENV"
205244 printf 'SHOULD_PUBLISH=%s\n' "${should_publish}" >> "$GITHUB_ENV"
206245 printf 'SHOULD_RELEASE=%s\n' "${should_release}" >> "$GITHUB_ENV"
@@ -258,8 +297,8 @@ jobs:
258297 *) printf '%s\n' "${tag_error}" >&2; exit "${tag_status}" ;;
259298 esac
260299 fi
261- if [ -n "${tag_sha}" ] && [ "${tag_sha}" != "${GITHUB_SHA }" ]; then
262- echo "Release tag ${RELEASE_TAG} does not match approved build commit ${GITHUB_SHA }." >&2
300+ if [ -n "${tag_sha}" ] && [ "${tag_sha}" != "${RELEASE_SHA }" ]; then
301+ echo "Release tag ${RELEASE_TAG} does not match approved release commit ${RELEASE_SHA }." >&2
263302 exit 1
264303 fi
265304 if gh release view "${RELEASE_TAG}" --json isDraft,targetCommitish > release-input/release-state.json 2>/dev/null; then
@@ -273,7 +312,7 @@ jobs:
273312 fi
274313 prerelease_flag=""
275314 case "${RELEASE_TAG}" in *-beta.*) prerelease_flag="--prerelease" ;; esac
276- gh release create "${RELEASE_TAG}" --draft --title "${RELEASE_TAG}" --notes-file release-input/release-notes.md --target "${GITHUB_SHA }" ${prerelease_flag}
315+ gh release create "${RELEASE_TAG}" --draft --title "${RELEASE_TAG}" --notes-file release-input/release-notes.md --target "${RELEASE_SHA }" ${prerelease_flag}
277316 echo "release_published=false" >> "$GITHUB_OUTPUT"
278317
279318 - name : Attest native CLI release assets
@@ -312,8 +351,8 @@ jobs:
312351 run : |
313352 set -eu
314353 tag_sha=$(gh api "repos/${GITHUB_REPOSITORY}/commits/${RELEASE_TAG}" --jq '.sha')
315- if [ "${tag_sha}" != "${GITHUB_SHA }" ]; then
316- echo "Release tag ${RELEASE_TAG} does not match approved build commit ${GITHUB_SHA }." >&2
354+ if [ "${tag_sha}" != "${RELEASE_SHA }" ]; then
355+ echo "Release tag ${RELEASE_TAG} does not match approved release commit ${RELEASE_SHA }." >&2
317356 exit 1
318357 fi
319358 while IFS=' ' read -r checksum asset_path; do
@@ -348,8 +387,8 @@ jobs:
348387 run : |
349388 set -eu
350389 tag_sha=$(gh api "repos/${GITHUB_REPOSITORY}/commits/${RELEASE_TAG}" --jq '.sha')
351- if [ "${tag_sha}" != "${GITHUB_SHA }" ]; then
352- echo "Release tag ${RELEASE_TAG} does not match approved build commit ${GITHUB_SHA }." >&2
390+ if [ "${tag_sha}" != "${RELEASE_SHA }" ]; then
391+ echo "Release tag ${RELEASE_TAG} does not match approved release commit ${RELEASE_SHA }." >&2
353392 exit 1
354393 fi
355394 case "${RELEASE_TAG}" in *-beta.*) gh release edit "${RELEASE_TAG}" --draft=false --prerelease ;; *) gh release edit "${RELEASE_TAG}" --draft=false ;; esac
0 commit comments