Skip to content

Commit 036e579

Browse files
fix(ci): bind semantic review to workflow run head
1 parent c4106f5 commit 036e579

2 files changed

Lines changed: 26 additions & 16 deletions

File tree

.github/workflows/semantic-review.yml

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,13 @@ jobs:
4747
throw new Error(`ambiguous workflow_run pull request bindings: ${runPRs.length}`);
4848
}
4949
let prNumber = Number(runPRs[0]?.number || 0);
50-
let eventBaseSha = runPRs[0]?.base?.sha || "";
50+
const eventBaseSha = runPRs[0]?.base?.sha || "";
5151
const eventHeadSha = runPRs[0]?.head?.sha || "";
52-
const targetHeadSha = eventHeadSha || run.head_sha;
52+
const targetHeadSha = run.head_sha;
5353
if (!/^[a-f0-9]{40}$/i.test(targetHeadSha)) throw new Error("invalid PR head sha");
54+
if (eventHeadSha && eventHeadSha.toLowerCase() !== targetHeadSha.toLowerCase()) {
55+
core.notice("PR quality summary using workflow_run head_sha because workflow_run pull request head differs from the CI run head");
56+
}
5457
5558
const factsArtifactPattern = /^quality-gate-facts-([a-f0-9]{40})-([a-f0-9]{40})$/i;
5659
const { data: artifactData } = await github.rest.actions.listWorkflowRunArtifacts({
@@ -71,11 +74,11 @@ jobs:
7174
if (artifactHeadSha.toLowerCase() !== targetHeadSha.toLowerCase()) {
7275
artifactError = "facts artifact head sha does not match verified PR head sha";
7376
factsArtifactName = "";
74-
} else if (eventBaseSha && parsedBaseSha.toLowerCase() !== eventBaseSha.toLowerCase()) {
75-
artifactError = "facts artifact base sha does not match workflow_run pull request base sha";
76-
factsArtifactName = "";
7777
} else {
7878
artifactBaseSha = parsedBaseSha;
79+
if (eventBaseSha && parsedBaseSha.toLowerCase() !== eventBaseSha.toLowerCase()) {
80+
core.notice("PR quality summary using facts artifact base because workflow_run pull request base differs from the CI facts artifact base");
81+
}
7982
}
8083
}
8184
if (!prNumber) {
@@ -123,7 +126,7 @@ jobs:
123126
core.setOutput("stale", "true");
124127
return;
125128
}
126-
const baseSha = eventBaseSha || artifactBaseSha || pr.base.sha;
129+
const baseSha = artifactBaseSha || eventBaseSha || pr.base.sha;
127130
if (!/^[a-f0-9]{40}$/i.test(baseSha)) throw new Error("invalid PR base sha");
128131
if ((eventBaseSha || artifactBaseSha) && pr.base.sha !== baseSha) {
129132
core.notice("PR quality summary skipped: workflow_run is stale for this PR base");
@@ -255,10 +258,13 @@ jobs:
255258
throw new Error(`ambiguous workflow_run pull request bindings: ${runPRs.length}`);
256259
}
257260
let prNumber = Number(runPRs[0]?.number || 0);
258-
let eventBaseSha = runPRs[0]?.base?.sha || "";
261+
const eventBaseSha = runPRs[0]?.base?.sha || "";
259262
const eventHeadSha = runPRs[0]?.head?.sha || "";
260-
const targetHeadSha = eventHeadSha || run.head_sha;
263+
const targetHeadSha = run.head_sha;
261264
if (!/^[a-f0-9]{40}$/i.test(targetHeadSha)) throw new Error("invalid PR head sha");
265+
if (eventHeadSha && eventHeadSha.toLowerCase() !== targetHeadSha.toLowerCase()) {
266+
core.notice("semantic review using workflow_run head_sha because workflow_run pull request head differs from the CI run head");
267+
}
262268
263269
const factsArtifactPattern = /^quality-gate-facts-([a-f0-9]{40})-([a-f0-9]{40})$/i;
264270
const { data: artifactData } = await github.rest.actions.listWorkflowRunArtifacts({
@@ -279,11 +285,11 @@ jobs:
279285
if (artifactHeadSha.toLowerCase() !== targetHeadSha.toLowerCase()) {
280286
artifactError = "facts artifact head sha does not match verified PR head sha";
281287
factsArtifactName = "";
282-
} else if (eventBaseSha && parsedBaseSha.toLowerCase() !== eventBaseSha.toLowerCase()) {
283-
artifactError = "facts artifact base sha does not match workflow_run pull request base sha";
284-
factsArtifactName = "";
285288
} else {
286289
artifactBaseSha = parsedBaseSha;
290+
if (eventBaseSha && parsedBaseSha.toLowerCase() !== eventBaseSha.toLowerCase()) {
291+
core.notice("semantic review using facts artifact base because workflow_run pull request base differs from the CI facts artifact base");
292+
}
287293
}
288294
}
289295
if (!prNumber) {
@@ -331,7 +337,7 @@ jobs:
331337
core.setOutput("stale", "true");
332338
return;
333339
}
334-
const baseSha = eventBaseSha || artifactBaseSha || pr.base.sha;
340+
const baseSha = artifactBaseSha || eventBaseSha || pr.base.sha;
335341
if (!/^[a-f0-9]{40}$/i.test(baseSha)) throw new Error("invalid PR base sha");
336342
if ((eventBaseSha || artifactBaseSha) && pr.base.sha !== baseSha) {
337343
core.notice("semantic review skipped: workflow_run is stale for this PR base");

scripts/semantic-review-workflow.test.sh

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,10 @@ fi
179179
require_in_step "$summary_verify_step" 'workflowPath !== ".github/workflows/ci.yml"' "PR quality summary must verify the triggering workflow path"
180180
require_in_step "$summary_verify_step" 'run.event !== "pull_request"' "PR quality summary must only handle pull_request workflow_run events"
181181
require_in_step "$summary_verify_step" 'run.repository.id !== context.payload.repository.id' "PR quality summary must verify workflow_run repository id"
182+
require_in_step "$summary_verify_step" 'const targetHeadSha = run.head_sha' "PR quality summary must use the CI run head SHA as the verified PR head"
183+
require_in_step "$summary_verify_step" 'eventHeadSha && eventHeadSha.toLowerCase() !== targetHeadSha.toLowerCase()' "PR quality summary should tolerate mutable workflow_run PR head metadata"
182184
require_in_step "$summary_verify_step" 'factsArtifactPattern' "PR quality summary should use the base-bound facts artifact name when available"
185+
require_in_step "$summary_verify_step" 'const baseSha = artifactBaseSha || eventBaseSha || pr.base.sha' "PR quality summary must prefer the CI-time artifact base SHA"
183186
require_in_step "$summary_verify_step" 'core.setOutput("artifact_error"' "PR quality summary must expose artifact binding failures"
184187
require_in_step "$summary_artifact_step" 'factsArtifactName' "PR quality summary artifact step must use the verified facts artifact binding"
185188
require_in_step "$summary_extract_facts_step" 'SEMANTIC_REVIEW_DECISION_OUT' "PR quality summary artifact verifier must write an infrastructure decision on verifier failure"
@@ -198,8 +201,9 @@ require_in_step "$verify_step" 'workflowPath !== ".github/workflows/ci.yml"' "se
198201
require_in_step "$verify_step" 'run.repository.id !== context.payload.repository.id' "semantic-review must verify workflow_run repository id"
199202
require_in_step "$verify_step" 'run.event !== "pull_request"' "semantic-review must only handle pull_request workflow_run events"
200203
require_in_step "$verify_step" 'run.conclusion !== "success"' "semantic-review must only consume successful CI runs"
201-
require_in_step "$verify_step" 'const eventHeadSha = runPRs[0]?.head?.sha || ""' "semantic-review must prefer workflow_run PR head when GitHub provides it"
202-
require_in_step "$verify_step" 'const targetHeadSha = eventHeadSha || run.head_sha' "semantic-review target PR head must come from the workflow_run event"
204+
require_in_step "$verify_step" 'const eventHeadSha = runPRs[0]?.head?.sha || ""' "semantic-review should inspect workflow_run PR head metadata"
205+
require_in_step "$verify_step" 'const targetHeadSha = run.head_sha' "semantic-review target PR head must come from the completed CI run"
206+
require_in_step "$verify_step" 'eventHeadSha && eventHeadSha.toLowerCase() !== targetHeadSha.toLowerCase()' "semantic-review should tolerate mutable workflow_run PR head metadata"
203207
require_in_step "$verify_step" 'factsArtifactPattern' "semantic-review must use a base-bound facts artifact name"
204208
require_in_step "$verify_step" 'listWorkflowRunArtifacts' "semantic-review must read the workflow_run artifacts before resolving fallback base SHA"
205209
require_in_step "$verify_step" 'artifactHeadSha.toLowerCase() !== targetHeadSha.toLowerCase()' "semantic-review must not let the artifact choose a different PR head"
@@ -210,8 +214,8 @@ require_in_step "$verify_step" 'commit_sha: targetHeadSha' "semantic-review fall
210214
require_in_step "$verify_step" 'github.rest.pulls.list' "semantic-review must have a pull-list fallback when commit association is empty"
211215
require_in_step "$verify_step" 'candidatePRs.length > 1' "semantic-review must fail closed when commit-to-PR fallback is ambiguous"
212216
require_in_step "$verify_step" 'pr.head.sha !== targetHeadSha' "semantic-review must skip stale PR heads"
213-
require_in_step "$verify_step" 'eventBaseSha && parsedBaseSha.toLowerCase() !== eventBaseSha.toLowerCase()' "semantic-review must reject mismatched event and artifact base SHAs"
214-
require_in_step "$verify_step" 'const baseSha = eventBaseSha || artifactBaseSha' "semantic-review fallback must use the CI-time artifact base SHA"
217+
require_in_step "$verify_step" 'eventBaseSha && parsedBaseSha.toLowerCase() !== eventBaseSha.toLowerCase()' "semantic-review should tolerate mutable workflow_run PR base metadata"
218+
require_in_step "$verify_step" 'const baseSha = artifactBaseSha || eventBaseSha || pr.base.sha' "semantic-review must prefer the CI-time artifact base SHA"
215219
require_in_step "$verify_step" 'pr.base.sha !== baseSha' "semantic-review must skip stale PR bases"
216220
require_in_step "$verify_step" 'core.setOutput("run_id"' "semantic-review must pass verified workflow run id to publisher"
217221
require_in_step "$verify_step" 'core.setOutput("head_repo_id"' "semantic-review must pass verified head repo id"

0 commit comments

Comments
 (0)