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
This commit is contained in:
Dallin Romney 2026-09-17 13:54:04 -07:00 • committed by GitHub
parent d1b4d871a2
commit f8703ed512
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 164 additions and 189 deletions

View file

@ -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"

View file

@ -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.

View file

@ -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

View file

@ -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")"

View file

@ -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", () => {

View file

@ -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");
}
}
});

View file

@ -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,
},