From f8703ed51262a1e1725f3787cfb6057a9797a1db Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Thu, 17 Sep 2026 13:54:04 -0700 Subject: [PATCH] fix: Telegram QA rejects frozen candidates after release branch advances (#151039) * fix: Telegram QA rejects frozen candidates after release branch advances * refactor(release): share frozen candidate context checks --- .github/workflows/openclaw-release-checks.yml | 59 ++-------- .../extended-stable.md | 6 + scripts/release-context-contains.sh | 33 ++++++ scripts/release-telegram-provenance.sh | 83 +++++-------- test/scripts/ci-workflow-guards.test.ts | 39 ------ ...nclaw-release-telegram-qa-workflow.test.ts | 111 +++++++++++------- .../package-acceptance-workflow.test.ts | 22 ++-- 7 files changed, 164 insertions(+), 189 deletions(-) create mode 100644 scripts/release-context-contains.sh diff --git a/.github/workflows/openclaw-release-checks.yml b/.github/workflows/openclaw-release-checks.yml index 9d3e53a46c41..750304408bf1 100644 --- a/.github/workflows/openclaw-release-checks.yml +++ b/.github/workflows/openclaw-release-checks.yml @@ -246,10 +246,8 @@ jobs: refs/tags/*) normalized_context_ref="${normalized_context_ref#refs/tags/}" ;; esac if [[ "$normalized_context_ref" =~ ^(release/[0-9]{4}\.([1-9]|1[0-2])\.[1-9][0-9]*(-[1-9][0-9]*)?|extended-stable/[0-9]{4}\.([1-9]|1[0-2])\.33)$ ]]; then - context_kind=branch fetch_ref="refs/heads/${normalized_context_ref}" elif [[ "$normalized_context_ref" =~ ^v[0-9]{4}\.([1-9]|1[0-2])\.[1-9][0-9]*((-(alpha|beta)\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]]; then - context_kind=tag fetch_ref="refs/tags/${normalized_context_ref}" else echo "target_context_ref must be a canonical OpenClaw release branch or tag." >&2 @@ -259,52 +257,8 @@ jobs: echo "target_context_ref requires ref to be a full 40-character commit SHA." >&2 exit 1 fi - echo "normalized_ref=${normalized_context_ref}" >> "$GITHUB_OUTPUT" - echo "context_kind=${context_kind}" >> "$GITHUB_OUTPUT" echo "fetch_ref=${fetch_ref}" >> "$GITHUB_OUTPUT" - - name: Validate trusted QA tooling eligibility - id: trusted_qa_tooling - if: steps.trusted_qa_context.outputs.fetch_ref != '' - env: - CONTEXT_FETCH_REF: ${{ steps.trusted_qa_context.outputs.fetch_ref }} - CONTEXT_KIND: ${{ steps.trusted_qa_context.outputs.context_kind }} - CONTEXT_REF: ${{ steps.trusted_qa_context.outputs.normalized_ref }} - TARGET_REF: ${{ inputs.ref }} - TRUSTED_REPOSITORY_URL: https://github.com/${{ github.repository }}.git - run: | - set -euo pipefail - context_repo="$(mktemp -d)" - trap 'rm -rf -- "$context_repo"' EXIT - git init --bare --quiet "$context_repo" - if ! GIT_TERMINAL_PROMPT=0 git -C "$context_repo" fetch --quiet --no-tags --filter=blob:none \ - "$TRUSTED_REPOSITORY_URL" "$CONTEXT_FETCH_REF"; then - echo "Failed to fetch trusted QA tooling context ${CONTEXT_REF}." >&2 - exit 1 - fi - context_sha="$(git -C "$context_repo" rev-parse --verify 'FETCH_HEAD^{commit}')" - normalized_context_sha="$(printf '%s' "$context_sha" | tr '[:upper:]' '[:lower:]')" - normalized_target_sha="$(printf '%s' "$TARGET_REF" | tr '[:upper:]' '[:lower:]')" - case "$CONTEXT_KIND" in - tag) - if [[ "$normalized_context_sha" != "$normalized_target_sha" ]]; then - echo "Trusted QA tooling tag ${CONTEXT_REF} resolves to ${context_sha} and does not match target ${TARGET_REF}." >&2 - exit 1 - fi - ;; - branch) - if ! git -C "$context_repo" merge-base --is-ancestor "$normalized_target_sha" "$normalized_context_sha" 2>/dev/null; then - echo "Target ${TARGET_REF} is not reachable from branch ${CONTEXT_REF} at ${context_sha}." >&2 - exit 1 - fi - ;; - *) - echo "Trusted QA tooling context kind is invalid: ${CONTEXT_KIND}." >&2 - exit 1 - ;; - esac - echo "eligible=true" >> "$GITHUB_OUTPUT" - - name: Checkout trusted workflow helper uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -313,6 +267,19 @@ jobs: path: workflow fetch-depth: 1 + - name: Validate trusted QA tooling eligibility + id: trusted_qa_tooling + if: steps.trusted_qa_context.outputs.fetch_ref != '' + env: + CONTEXT_FETCH_REF: ${{ steps.trusted_qa_context.outputs.fetch_ref }} + TARGET_REF: ${{ inputs.ref }} + TRUSTED_REPOSITORY_URL: https://github.com/${{ github.repository }}.git + run: | + set -euo pipefail + bash "${GITHUB_WORKSPACE}/workflow/scripts/release-context-contains.sh" \ + "$TRUSTED_REPOSITORY_URL" "$CONTEXT_FETCH_REF" "$TARGET_REF" ancestor + echo "eligible=true" >> "$GITHUB_OUTPUT" + - name: Setup admission Node.js env: REQUESTED_NODE_VERSION: "24.x" diff --git a/docs/reference/full-release-validation/extended-stable.md b/docs/reference/full-release-validation/extended-stable.md index e58b923f2623..ad69f4ed57cf 100644 --- a/docs/reference/full-release-validation/extended-stable.md +++ b/docs/reference/full-release-validation/extended-stable.md @@ -176,3 +176,9 @@ that selects Telegram, conflicts with the waiver and is rejected. The declaratio target version bind the immutable execution plan, manifest, and reuse identity; the publisher carries the waiver into release verification notes. The beta-only package deferral above remains unchanged. + +Source Telegram QA uses the release checks' shared context check: an exact candidate +SHA must remain an ancestor of its canonical branch, or equal its release tag. +Both build and execution admission independently repeat that check and retain +candidate-version, signature/merge-attribution, and live maintainer checks. +Advancing a release branch does not select a new candidate or invalidate the old one. diff --git a/scripts/release-context-contains.sh b/scripts/release-context-contains.sh new file mode 100644 index 000000000000..1c94b6a461d6 --- /dev/null +++ b/scripts/release-context-contains.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Compare against one fetched snapshot, without executing candidate code. +repository_url="$1" +context_ref="$2" +target_sha="$3" +relationship="${4:-exact}" +context_repo="$(mktemp -d)" +trap 'rm -rf -- "$context_repo"' EXIT +git init --bare --quiet "$context_repo" +if ! GIT_TERMINAL_PROMPT=0 git -C "$context_repo" fetch --quiet --no-tags --filter=blob:none \ + "$repository_url" "$context_ref"; then + echo "Failed to fetch trusted QA tooling context ${context_ref}." >&2 + exit 1 +fi +context_sha="$(git -C "$context_repo" rev-parse --verify 'FETCH_HEAD^{commit}')" +target_sha="$(printf '%s' "$target_sha" | tr '[:upper:]' '[:lower:]')" +case "$context_ref:$relationship" in + refs/heads/*:ancestor) + if ! git -C "$context_repo" merge-base --is-ancestor "$target_sha" "$context_sha" 2>/dev/null; then + echo "Target ${target_sha} is not reachable from branch ${context_ref} at ${context_sha}." >&2 + exit 1 + fi + ;; + refs/heads/*:exact|refs/tags/*:exact|refs/tags/*:ancestor) + if [[ "$context_sha" != "$target_sha" ]]; then + echo "Release context ${context_ref} resolves to ${context_sha} and does not match target ${target_sha}." >&2 + exit 1 + fi + ;; + *) exit 1 ;; +esac diff --git a/scripts/release-telegram-provenance.sh b/scripts/release-telegram-provenance.sh index 53e43a66094e..ea4eca5915e7 100644 --- a/scripts/release-telegram-provenance.sh +++ b/scripts/release-telegram-provenance.sh @@ -120,72 +120,51 @@ compare_status="$( )" trusted_reason="" trusted_release_branch="" -if [[ -n "$context_release_branch" ]]; then - branch_sha="$( - git -C "$remote_git_dir" ls-remote --exit-code --refs origin \ - "refs/heads/${context_release_branch}" | - awk 'NR == 1 { print $1 } END { if (NR != 1) exit 1 }' || - true - )" - if [[ "$branch_sha" == "$candidate_sha" ]]; then - trusted_reason="release-branch-head" - trusted_release_branch="$context_release_branch" +release_ref="${context_release_branch:-$context_release_tag}" +relationship=exact +if [[ -n "$release_ref" ]]; then + if [[ "$TARGET_REF" =~ ^[a-f0-9]{40}$ && "$TARGET_REF" == "$candidate_sha" ]]; then + relationship=ancestor fi -elif [[ -n "$context_release_tag" ]]; then - tag_refs="$( - git -C "$remote_git_dir" 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 [[ "$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 - branch_sha="$( - git -C "$remote_git_dir" 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" - trusted_release_branch="$normalized_ref" - elif [[ "$TARGET_REF" =~ ^refs/tags/v ]] || [[ "$TARGET_REF" =~ ^v ]]; then - normalized_tag="${TARGET_REF#refs/tags/}" - tag_refs="$( - git -C "$remote_git_dir" ls-remote --exit-code origin \ - "refs/tags/${normalized_tag}" "refs/tags/${normalized_tag}^{}" - )" - awk -v sha="$candidate_sha" '$1 == sha { found = 1 } END { exit(found ? 0 : 1) }' \ - <<<"$tag_refs" - trusted_reason="release-tag" - elif [[ "$TARGET_REF" =~ ^[a-f0-9]{40}$ && "$TARGET_REF" == "$candidate_sha" ]]; then - matching_release_branches="$( + release_ref="${TARGET_REF#refs/heads/}" + release_ref="${release_ref#refs/tags/}" + if [[ "$TARGET_REF" =~ ^[a-f0-9]{40}$ && "$TARGET_REF" == "$candidate_sha" ]]; then + release_ref="$( 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 }' + $0 ~ /^extended-stable\/[0-9]{4}\.[1-9][0-9]*\.33$/ { refs[++n] = $0 } + END { if (n == 1) print refs[1] }' )" - if [[ "$(wc -l <<<"$matching_release_branches" | tr -d ' ')" == "1" && - -n "$matching_release_branches" ]]; then - trusted_reason="release-branch-head" - trusted_release_branch="$matching_release_branches" - else - matching_release_tags="$( + if [[ -z "$release_ref" ]]; then + release_ref="$( git -C "$remote_git_dir" ls-remote origin 'refs/tags/v*' | awk -v sha="$candidate_sha" '$1 == sha { sub(/\^\{\}$/, "", $2); print $2 }' | - sort -u + sort -u | head -n 1 )" - if [[ -n "$matching_release_tags" ]]; then - trusted_reason="release-tag" - fi + release_ref="${release_ref#refs/tags/}" fi fi fi +fetch_ref="" +if [[ "$release_ref" =~ ^(release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*|extended-stable/[0-9]{4}\.[1-9][0-9]*\.33)$ ]]; then + fetch_ref="refs/heads/${release_ref}" + reason=release-branch +elif [[ "$release_ref" == v* ]]; then + fetch_ref="refs/tags/${release_ref}" + reason=release-tag +fi +if [[ -n "$fetch_ref" ]] && bash "${GITHUB_WORKSPACE}/scripts/release-context-contains.sh" \ + "https://github.com/${GITHUB_REPOSITORY}.git" "$fetch_ref" "$candidate_sha" "$relationship" 2>/dev/null; then + trusted_reason="$reason" + trusted_release_branch="$release_ref" +fi + if [[ -z "$trusted_reason" && -n "$frozen_release_branch_pattern" && "$TARGET_REF" =~ ^[a-f0-9]{40}$ && "$TARGET_REF" == "$candidate_sha" ]]; then matching_frozen_release_branches="$( @@ -231,8 +210,8 @@ if [[ "$trusted_reason" != "main-ancestor" ]]; then fi permission_actor="$signer" if [[ "$signature_status" == "missing" || "$signer" == "web-flow" ]]; then - if [[ "$trusted_reason" != "release-branch-head" || -z "$trusted_release_branch" ]]; then - echo "Unsigned or GitHub web-flow candidates require an exact release branch head." >&2 + if [[ "$trusted_reason" != "release-branch" || -z "$trusted_release_branch" ]]; then + echo "Unsigned or GitHub web-flow candidates require canonical release branch provenance." >&2 exit 1 fi merge_pr_candidates="$(jq -c '.data.repository.object.associatedPullRequests.nodes' <<<"$candidate_metadata_json")" diff --git a/test/scripts/ci-workflow-guards.test.ts b/test/scripts/ci-workflow-guards.test.ts index 217345689e60..cb2662aa774b 100644 --- a/test/scripts/ci-workflow-guards.test.ts +++ b/test/scripts/ci-workflow-guards.test.ts @@ -18901,7 +18901,6 @@ fi const fullReleaseWorkflow = readWorkflow(".github/workflows/full-release-validation.yml"); const releaseWorkflow = readReleaseChecksWorkflow(); const telegramWorkflow = readWorkflow(".github/workflows/openclaw-release-telegram-qa.yml"); - const telegramProvenanceHelper = readFileSync("scripts/release-telegram-provenance.sh", "utf8"); const fullReleaseDispatchStep = fullReleaseWorkflow.jobs.release_checks_candidate.steps.find( (step: WorkflowStep) => step.name === "Dispatch release checks candidate phase", ); @@ -18957,44 +18956,6 @@ fi 'bash "${GITHUB_WORKSPACE}/scripts/release-telegram-provenance.sh"', ); } - expect(telegramProvenanceHelper).toContain( - 'if [[ "$candidate_version" == "$release_version" ]]; then', - ); - expect(telegramProvenanceHelper).toContain( - 'elif [[ "$candidate_version" =~ ^${release_version_pattern}-beta\\.[0-9]+$ ]]; then', - ); - expect(telegramProvenanceHelper).toContain( - 'frozen_release_branch_pattern="^release/${candidate_version_pattern}-code-frozen(-r[1-9][0-9]*)?$"', - ); - expect(telegramProvenanceHelper).toContain( - '"$TARGET_REF" =~ ^[a-f0-9]{40}$ && "$TARGET_REF" == "$candidate_sha"', - ); - expect(telegramProvenanceHelper).toContain('trusted_reason="frozen-release-branch-head"'); - expect(telegramProvenanceHelper).toContain( - '"$signature_status" != "valid" || "$signer" == "web-flow"', - ); - expect(telegramProvenanceHelper).toContain('context_release_branch="$normalized_context_ref"'); - expect(telegramProvenanceHelper).toContain('context_release_tag="$normalized_context_ref"'); - expect(telegramProvenanceHelper).toContain( - "Telegram candidate version ${candidate_version} does not belong to release ${release_version}.", - ); - expect(telegramProvenanceHelper).toContain( - "Telegram candidate version ${candidate_version} does not match context ${normalized_context_ref}.", - ); - expect(telegramProvenanceHelper).toContain( - 'select(.state == "OPEN" and .headRepository.nameWithOwner == $repo and', - ); - expect(telegramProvenanceHelper).toContain( - 'select(.state == "MERGED" and .baseRepository.nameWithOwner == $repo and', - ); - expect(telegramProvenanceHelper).toContain(".mergeCommit.oid == $sha)]"); - expect(telegramProvenanceHelper).toContain( - 'if [[ "$(jq \'length\' <<<"$matching_merge_prs")" != "1" ]]; then', - ); - expect(telegramProvenanceHelper).toContain( - 'if [[ "$permission" != "admin" && "$role_name" != "maintain" ]]; then', - ); - expect(telegramProvenanceHelper).not.toContain(".baseRefName =="); }); it("checks out the complete trusted Release Decision scripts tree", () => { diff --git a/test/scripts/openclaw-release-telegram-qa-workflow.test.ts b/test/scripts/openclaw-release-telegram-qa-workflow.test.ts index 20e34f671ab1..c3793f22dddc 100644 --- a/test/scripts/openclaw-release-telegram-qa-workflow.test.ts +++ b/test/scripts/openclaw-release-telegram-qa-workflow.test.ts @@ -226,6 +226,7 @@ function runCandidateProvenance( provenanceBlock: ProvenanceBlock, params: { branchHeads?: string[]; + releaseCompareStatus?: "ahead" | "behind" | "diverged" | "identical"; candidateVersion?: string; messageHeadline?: string; directPullRequest?: { @@ -326,6 +327,11 @@ exit 64 join(fakeBin, "git"), `#!/usr/bin/env bash set -euo pipefail +if [[ "$*" == *"init --bare"* || "$*" == *"fetch --quiet"* ]]; then [[ -n "$FAKE_REMOTE_SHA" ]]; exit; fi +if [[ "$*" == *"FETCH_HEAD^{commit}"* ]]; then printf '%s\\n' "$FAKE_REMOTE_SHA"; exit 0; fi +if [[ "$*" == *"merge-base --is-ancestor"* ]]; then + [[ "$TARGET_SHA" == "$FAKE_REMOTE_SHA" || "$FAKE_RELEASE_COMPARE_STATUS" == ahead ]]; exit +fi if [[ "$*" == *"rev-parse HEAD"* ]]; then printf '%s\\n' "$TARGET_SHA"; exit 0; fi if [[ "$*" == *"ls-remote"* ]]; then if [[ "$*" == *"refs/tags/"* && "$FAKE_REMOTE_REF" != refs/tags/* ]]; then exit 0; fi @@ -363,6 +369,7 @@ exit 64 permission: params.permission === "admin" ? "admin" : "write", role_name: params.permission ?? "maintain", }), + FAKE_RELEASE_COMPARE_STATUS: params.releaseCompareStatus ?? "diverged", FAKE_REMOTE_REF: remoteRef, FAKE_REMOTE_SHA: params.remoteSha ?? candidateSha, CANDIDATE_GIT_DIR: @@ -575,6 +582,56 @@ describe("release Telegram QA workflow", () => { } }); + it("keeps exact release candidates trusted when the canonical branch advances", () => { + for (const provenanceBlock of PROVENANCE_BLOCKS) { + for (const targetContextRef of ["release/2026.7.35", "extended-stable/2026.7.33"]) { + for (const signature of ["maintainer", "web-flow", "missing"] as const) { + const result = runCandidateProvenance(provenanceBlock, { + candidateVersion: "2026.7.35", + remoteSha: "b".repeat(40), + releaseCompareStatus: "ahead", + targetContextRef, + signature, + mergedPullRequests: [{ baseRefName: targetContextRef }], + }); + expect(result.status, `${provenanceBlock.stepName}/${signature}: ${result.stderr}`).toBe( + 0, + ); + expect(result.stdout).toContain("Telegram candidate trust reason: release-branch"); + } + } + } + }); + + it("rejects untrusted or uncontained candidates after the release branch advances", () => { + const cases = [ + { releaseCompareStatus: "behind" as const }, + { releaseCompareStatus: "diverged" as const }, + { targetRef: "extended-stable/2026.7.33" }, + { remoteSha: "" }, + { openPr: true }, + { signature: "invalid" as const }, + { permission: "write" as const }, + { mergedPullRequests: [] }, + { mergedPullRequests: [{ mergeCommitOid: "c".repeat(40) }] }, + { mergedPullRequests: [{ baseRepository: "fork/openclaw" }] }, + ]; + for (const provenanceBlock of PROVENANCE_BLOCKS) { + for (const params of cases) { + const result = runCandidateProvenance(provenanceBlock, { + candidateVersion: "2026.7.35", + remoteSha: "b".repeat(40), + releaseCompareStatus: "ahead", + targetContextRef: "extended-stable/2026.7.33", + signature: "web-flow", + mergedPullRequests: [{ baseRefName: "extended-stable/2026.7.33" }], + ...params, + }); + expect(result.status, `${provenanceBlock.stepName}: ${JSON.stringify(params)}`).not.toBe(0); + } + } + }); + it("accepts only strict signed frozen beta branch heads in both provenance blocks", () => { for (const provenanceBlock of PROVENANCE_BLOCKS) { const frozen = runCandidateProvenance(provenanceBlock, { @@ -669,51 +726,21 @@ describe("release Telegram QA workflow", () => { }); it("attributes web-flow release heads through a unique integration-base merge", () => { - const results = PROVENANCE_BLOCKS.flatMap((provenanceBlock) => - ["2026.7.1", "2026.7.1-beta.3"].map((candidateVersion) => ({ - candidateVersion, - provenanceBlock, - result: runCandidateProvenance(provenanceBlock, { + for (const provenanceBlock of PROVENANCE_BLOCKS) { + for (const candidateVersion of ["2026.7.1", "2026.7.1-beta.3"]) { + const result = runCandidateProvenance(provenanceBlock, { candidateVersion, mergedPullRequests: [{ baseRefName: "release-integration/2026.7.1-repair-2" }], signature: "web-flow", targetContextRef: "release/2026.7.1", - }), - })), - ); - expect( - results.map(({ candidateVersion, provenanceBlock, result }) => ({ - block: provenanceBlock.stepName, - candidateVersion, - status: result.status, - stderr: result.stderr, - })), - ).toEqual([ - { - block: "Validate candidate release provenance", - candidateVersion: "2026.7.1", - status: 0, - stderr: "", - }, - { - block: "Validate candidate release provenance", - candidateVersion: "2026.7.1-beta.3", - status: 0, - stderr: "", - }, - { - block: "Revalidate candidate release provenance", - candidateVersion: "2026.7.1", - status: 0, - stderr: "", - }, - { - block: "Revalidate candidate release provenance", - candidateVersion: "2026.7.1-beta.3", - status: 0, - stderr: "", - }, - ]); + }); + expect( + result.status, + `${provenanceBlock.stepName}/${candidateVersion}: ${result.stderr}`, + ).toBe(0); + expect(result.stderr).toBe(""); + } + } }); it("verifies an exact merged PR directly when commit associations are missing", () => { @@ -727,7 +754,7 @@ describe("release Telegram QA workflow", () => { directPullRequest: {}, }); expect(result.status, result.stderr).toBe(0); - expect(result.stdout).toContain("Telegram candidate trust reason: release-branch-head"); + expect(result.stdout).toContain("Telegram candidate trust reason: release-branch"); } } }); diff --git a/test/scripts/package-acceptance-workflow.test.ts b/test/scripts/package-acceptance-workflow.test.ts index cb320acf5bbe..f39af2e4f307 100644 --- a/test/scripts/package-acceptance-workflow.test.ts +++ b/test/scripts/package-acceptance-workflow.test.ts @@ -3119,6 +3119,11 @@ function runReleaseChecksShellStep( workdir = tempDirs.make("release-checks-shell-step-"), ) { const step = workflowStep(workflowJob(RELEASE_CHECKS_WORKFLOW, "resolve_target"), stepName); + mkdirSync(join(workdir, "workflow", "scripts"), { recursive: true }); + copyFileSync( + "scripts/release-context-contains.sh", + join(workdir, "workflow", "scripts", "release-context-contains.sh"), + ); const outputPath = resolve(workdir, "github-output"); writeFileSync(outputPath, "", "utf8"); const result = spawnSync("bash", ["-c", step.run ?? ""], { @@ -3126,6 +3131,7 @@ function runReleaseChecksShellStep( encoding: "utf8", env: { ...env, + GITHUB_WORKSPACE: workdir, GITHUB_OUTPUT: outputPath, PATH: process.env.PATH, }, @@ -12024,12 +12030,9 @@ printf '%s\\n' "$DEEPSEEK_API_KEY" "$DEEPINFRA_API_KEY"`, expect(eligibility.env?.TRUSTED_REPOSITORY_URL).toBe( "https://github.com/${{ github.repository }}.git", ); - expect(eligibility.run).toContain('context_repo="$(mktemp -d)"'); - expect(eligibility.run).toContain("git init --bare --quiet"); - expect(eligibility.run).toContain("--filter=blob:none"); - expect(eligibility.run).toContain("FETCH_HEAD^{commit}"); - expect(eligibility.run).not.toContain("git checkout"); - expect(eligibility.run).not.toContain("git worktree"); + expect(resolveStepNames.indexOf("Checkout trusted workflow helper")).toBeLessThan( + resolveStepNames.indexOf("Validate trusted QA tooling eligibility"), + ); for (const contextRef of [ "release/2026.8.1", @@ -12045,8 +12048,9 @@ printf '%s\\n' "$DEEPSEEK_API_KEY" "$DEEPINFRA_API_KEY"`, TARGET_REF: targetSha, }); expect(result.status, `${contextRef}: ${result.stderr}`).toBe(0); - expect(output, contextRef).toContain( - `normalized_ref=${contextRef.replace(/^refs\/(heads|tags)\//u, "")}\n`, + const normalizedRef = contextRef.replace(/^refs\/(heads|tags)\//u, ""); + expect(output, contextRef).toBe( + `fetch_ref=refs/${normalizedRef.startsWith("v") ? "tags" : "heads"}/${normalizedRef}\n`, ); } @@ -12103,9 +12107,7 @@ printf '%s\\n' "$DEEPSEEK_API_KEY" "$DEEPINFRA_API_KEY"`, const { output, result } = runReleaseChecksShellStep( "Validate trusted QA tooling eligibility", { - CONTEXT_KIND: contextKind, CONTEXT_FETCH_REF: fetchRef, - CONTEXT_REF: fetchRef.replace(/^refs\/(heads|tags)\//u, ""), TARGET_REF: targetRef, TRUSTED_REPOSITORY_URL: fixture.repoUrl, },