Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 140 additions & 10 deletions .github/workflows/automated-review-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ on:

permissions: {}

# Coalesce replaceable state reconciliations before they allocate a runner.
# Source updates and merge-group checks keep unique run IDs so none are lost.
concurrency:
group: >-
${{
github.event_name == 'workflow_run' &&
format('{0}-wakeup-{1}', github.workflow, github.event.workflow_run.display_title) ||
github.event_name == 'issue_comment' && github.event.issue.pull_request &&
format('{0}-comment-pr-{1}', github.workflow, github.event.issue.number) ||
format('{0}-run-{1}', github.workflow, github.run_id)
}}
cancel-in-progress: false

jobs:
target:
name: resolve automated review target
Expand Down Expand Up @@ -55,7 +68,7 @@ jobs:
const match = /^(?:refs\/heads\/)?gh-readonly-queue\/.+\/pr-([1-9]\d*)-([0-9a-f]{40})$/i.exec(headRef);
const pullNumber = match ? Number(match[1]) : undefined;
return Number.isSafeInteger(pullNumber)
? { pullNumber, sourceHeadSha: match[2].toLowerCase() }
? { pullNumber, baseSha: match[2].toLowerCase() }
: undefined;
};
const parseReviewWakeupIdentity = (run) => {
Expand Down Expand Up @@ -102,6 +115,13 @@ jobs:
core.setOutput("key", `run-${context.runId}`);
return;
}
const mergeGroupBaseSha =
context.payload.merge_group?.base_sha?.toLowerCase();
if (queueEntry.baseSha !== mergeGroupBaseSha) {
core.setFailed("Merge queue ref does not match its base commit");
core.setOutput("key", `run-${context.runId}`);
return;
}
}
const pullNumber = queueEntry?.pullNumber ??
context.payload.pull_request?.number ??
Expand All @@ -113,8 +133,8 @@ jobs:
core.setOutput("key", `run-${context.runId}`);
return;
}
let headSha = queueEntry?.sourceHeadSha;
if (!headSha) {
let headSha;
{
try {
const response = await github.rest.git.getRef({
owner: context.repo.owner,
Expand Down Expand Up @@ -528,6 +548,14 @@ jobs:
core.setFailed("The review status boundary is invalid");
return;
}
const parseMergeQueuePullNumber = (headRef) => {
if (typeof headRef !== "string") return undefined;
const match = /^(?:refs\/heads\/)?gh-readonly-queue\/.+\/pr-([1-9]\d*)-([0-9a-f]{40})$/i.exec(headRef);
const number = match ? Number(match[1]) : undefined;
return Number.isSafeInteger(number)
? { pullNumber: number, baseSha: match[2].toLowerCase() }
: undefined;
};
const collectAll = async (endpoint, parameters, source) => {
const items = [];
for await (
Expand All @@ -546,13 +574,35 @@ jobs:
}
return items;
};
const resolveQueueRefTarget = async (ref) => {
let response;
try {
response = await github.rest.git.getRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref,
});
} catch (error) {
if (error?.status === 404) return undefined;
throw error;
}
const sha = response?.data?.object?.sha;
if (!fullSha.test(sha ?? "")) {
throw new Error("Merge queue ref response has a malformed commit");
}
return sha;
};
const publishIndependentFailure = async () => {
const pull = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pullNumber,
});
if (pull.data?.head?.sha?.toLowerCase() !== headSha.toLowerCase()) {
const pullHeadSha = pull.data?.head?.sha;
if (!fullSha.test(pullHeadSha ?? "")) {
throw new Error("Pull request head response has a malformed commit");
}
if (pullHeadSha.toLowerCase() !== headSha.toLowerCase()) {
return { headSha, queueFailures: 0, skipped: true };
}
const baseRepositoryId = pull.data?.base?.repo?.id;
Expand Down Expand Up @@ -611,21 +661,98 @@ jobs:
{
owner: context.repo.owner,
repo: context.repo.repo,
ref: `heads/gh-readonly-queue/${baseRef}/pr-${pullNumber}-${headSha}`,
ref: "heads/gh-readonly-queue/",
},
"merge queue refs",
);
const seen = new Set();
const expectedQueueRef =
`refs/heads/gh-readonly-queue/${baseRef}/pr-${pullNumber}-${headSha}`;
for (const queueRef of refs) {
if (queueRef?.ref !== expectedQueueRef) continue;
const parsed = parseMergeQueuePullNumber(queueRef?.ref);
if (parsed?.pullNumber !== pullNumber) continue;
const mergeGroupSha = queueRef?.object?.sha;
if (!fullSha.test(mergeGroupSha ?? "")) {
throw new Error("Merge queue ref has a malformed commit");
}
if (seen.has(mergeGroupSha.toLowerCase())) continue;
seen.add(mergeGroupSha.toLowerCase());
const latestPull = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pullNumber,
});
const latestHeadSha = latestPull.data?.head?.sha;
if (!fullSha.test(latestHeadSha ?? "")) {
throw new Error("Pull request head response has a malformed commit");
}
if (latestHeadSha.toLowerCase() !== headSha.toLowerCase()) {
continue;
}
let targetVerified = false;
try {
const bindingResponse = await github.graphql(
`query ActiveMergeQueueBinding($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
number
state
headRefOid
baseRefName
mergeQueueEntry {
baseCommit { oid }
headCommit { oid }
}
}
}
}`,
{
owner: context.repo.owner,
repo: context.repo.repo,
number: pullNumber,
},
);
const boundPull = bindingResponse?.repository?.pullRequest;
const boundEntry = boundPull?.mergeQueueEntry;
const activeBindingMismatch =
boundPull?.number !== pullNumber ||
boundPull?.state !== "OPEN" ||
boundPull?.headRefOid?.toLowerCase() !== headSha.toLowerCase() ||
boundPull?.baseRefName !== baseRef ||
boundEntry?.baseCommit?.oid?.toLowerCase() !==
parsed.baseSha ||
boundEntry?.headCommit?.oid?.toLowerCase() !==
mergeGroupSha.toLowerCase();
if (!activeBindingMismatch) {
const liveRef = await github.rest.git.getRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: queueRef.ref.slice("refs/".length),
});
targetVerified =
liveRef?.data?.object?.sha?.toLowerCase() ===
mergeGroupSha.toLowerCase();
}
} catch {
targetVerified = false;
}
if (!targetVerified) {
const liveTarget = await resolveQueueRefTarget(
queueRef.ref.slice("refs/".length),
);
targetVerified =
liveTarget?.toLowerCase() === mergeGroupSha.toLowerCase();
}
if (!targetVerified) continue;
const finalPull = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pullNumber,
});
const finalHeadSha = finalPull.data?.head?.sha;
if (!fullSha.test(finalHeadSha ?? "")) {
throw new Error("Pull request head response has a malformed commit");
}
if (finalHeadSha.toLowerCase() !== headSha.toLowerCase()) {
continue;
}
await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
Expand All @@ -635,6 +762,7 @@ jobs:
description: `Could not revalidate review for PR #${pullNumber}`,
target_url: pullUrl,
});
seen.add(mergeGroupSha.toLowerCase());
}
return {
headSha,
Expand Down Expand Up @@ -742,7 +870,8 @@ jobs:
if (
!Number.isSafeInteger(pullNumber) ||
pullNumber !== queueEntry.pullNumber ||
sourceHeadSha !== queueEntry.sourceHeadSha
queueEntry.baseSha !==
context.payload.merge_group.base_sha?.toLowerCase()
) {
core.setFailed("Merge queue source does not match its serialized lock");
return;
Expand All @@ -753,6 +882,7 @@ jobs:
repo: context.repo.repo,
pullNumber,
sourceHeadSha,
baseSha: queueEntry.baseSha,
mergeGroupSha: context.payload.merge_group.head_sha,
});
if (result.state === "success") {
Expand Down
Loading
Loading