diff --git a/changelog.d/gsta203-pr8-clear-red-run.md b/changelog.d/gsta203-pr8-clear-red-run.md new file mode 100644 index 000000000..87a4d6e1f --- /dev/null +++ b/changelog.d/gsta203-pr8-clear-red-run.md @@ -0,0 +1 @@ +- **`gstack-pregate clear --run-url` needs a green run.** On a lane with no run profile, a run of the lane's own workflow that failed, was cancelled or timed out now refuses `PREGATE_RECEIPT_INVALID` (`run_not_success`), and one still queued or running refuses `run_in_progress`. The fix line says to rerun the workflow and clear with the green run. The check reads the run's newest attempt, so a job or attempt link is judged by the whole run. A green run still clears as before. docs/pregate.md now names `windows-smoke.yml/windows-smoke` as the free tier's Windows smoke lane, beside the narrower `free-tests.yml/cso-windows-launcher` and `windows-setup-e2e.yml/windows-setup` lanes. `test/pregate-clear-runs.test.ts` failed before the fix. diff --git a/docs/pregate.md b/docs/pregate.md index e3b9393b6..b0c0aa52a 100644 --- a/docs/pregate.md +++ b/docs/pregate.md @@ -759,9 +759,14 @@ workflow file is the lane's own ``. A run of another workflow refuses with `REUSE: refused reason=workflow_mismatch — run attempt ran .github/workflows/.yml, not .github/workflows/.yml`; a run gh cannot read refuses with `api_error`, and a URL that is not an Actions -run URL or id on this repository with `run_ref_invalid`. Only the workflow -file is checked there, not the job, conclusion or tree; that is what a run -profile adds. `test_backend/` lanes and `--bundle-lane` are +run URL or id on this repository with `run_ref_invalid`. The run must also +have finished green (GSTA-203 PR 8): a run that is still queued or running +refuses with `run_in_progress`, and one that concluded anything but `success` +(`failure`, `cancelled`, `timed_out`, …) with `run_not_success`; each fix line +says to rerun the workflow and clear with the green run. The check reads the +run's newest attempt, so a `/job/` or `/attempts/` link is judged by +the whole run, not that job or attempt. The job, tree and age are not +checked there; that is what a run profile adds. `test_backend/` lanes and `--bundle-lane` are unchanged. When the base config or the GitHub origin cannot be read, `--run` fails, and so do `--run-url` and `--bundle-lane` unless the base was read and shows the lane has no profile; then the lane is recorded as before, with a @@ -778,10 +783,15 @@ aggregate, `os` Windows). Each accepts `pull_request` and `workflow_dispatch` runs and reads the `tested-tree` artifact, which both aggregates upload only after their strict verification passes. The main-push reuse job and `--from-run` read them. No lane has a profile, so every macOS and Windows lane -still clears by `--run-url`, checked only for its workflow file. On main the -Windows obligation is `windows-smoke.yml/windows-smoke` (the six-shard +still clears by `--run-url`, checked for its workflow file and a green +conclusion. On main the free tier's Windows smoke lane is +`windows-smoke.yml/windows-smoke`, and a `windows-smoke.yml` run clears it. +Two narrower Windows lanes are owed only when a branch selects their own test +files: `free-tests.yml/cso-windows-launcher` (cleared by a `free-tests.yml` +run) and `windows-setup-e2e.yml/windows-setup` (cleared by a +`windows-setup-e2e.yml` run). The six-shard `windows-free-tests.yml/windows-free-shard` job is `manual`, so it is never an -obligation), and a `windows-smoke.yml` run clears it. A branch cut before +obligation. A branch cut before `windows-smoke.yml` landed still owes `windows-free-tests.yml/windows-free-shard` from its own workflows, and only a `windows-free-tests.yml` run clears that. A pull request cut before these diff --git a/lib/pregate/index.ts b/lib/pregate/index.ts index 29eca7a46..ac1302184 100644 --- a/lib/pregate/index.ts +++ b/lib/pregate/index.ts @@ -219,8 +219,9 @@ export type ClearDecision = { receipt: string; attested: boolean; substitution?: * has no machine-checkable receipt, so its run URL is recorded as attested. * Platform lanes take a run URL or an evidence-bundle lane label. When the run * evidence is readable, a run URL must name a run of this repository whose - * workflow file is the lane's own; a run of another workflow, an unreadable - * run or a URL that is not an Actions run refuses (GSTA-203). + * workflow file is the lane's own and that completed with success; a run of + * another workflow, a red or unfinished run, an unreadable run or a URL that + * is not an Actions run refuses (GSTA-203). * * `--run` (and `--run-url` on a lane the base branch profiles) goes to * clearByRuns, which accepts only verified Actions runs; a profiled lane @@ -265,7 +266,10 @@ export function clearDecision(file: PregateFile, lane: string, input: ClearInput try { run = input.evidence.deps.run(id); } catch (e: any) { return invalid(`api_error — reading run ${id} failed: ${String(e?.message ?? e).slice(0, 300)}`, `check gh auth status and that gh can read ${repo}'s Actions runs, then rerun the same clear`); } const want = `.github/workflows/${target.workflow}`; - if (run?.path !== want) return invalid(`workflow_mismatch — run ${id} attempt ${run?.run_attempt ?? '?'} ran ${run?.path ?? 'an unknown workflow'}, not ${want}`, `clear ${lane} with a run of its own workflow: gh workflow run ${target.workflow} --ref , then gstack-pregate clear --remote ${lane} --run-url `); + if (!run || run.path !== want) return invalid(`workflow_mismatch — run ${id} attempt ${run?.run_attempt ?? '?'} ran ${run?.path ?? 'an unknown workflow'}, not ${want}`, `clear ${lane} with a run of its own workflow: gh workflow run ${target.workflow} --ref , then gstack-pregate clear --remote ${lane} --run-url `); + const green = `then gstack-pregate clear --remote ${lane} --run-url `; + if (run.status !== 'completed') return invalid(`run_in_progress — run ${id} attempt ${run.run_attempt} is ${run.status}`, `wait for run ${id} to finish green (or rerun ${target.workflow} if it fails), ${green}`); + if (run.conclusion !== 'success') return invalid(`run_not_success — run ${id} attempt ${run.run_attempt} concluded ${run.conclusion}`, `rerun ${target.workflow} (gh run rerun ${id}, or gh workflow run ${target.workflow} --ref ), ${green}`); } const receipt = input.runUrl ?? (input.bundleLane ? `bundle-lane:${input.bundleLane}` : undefined); if (!receipt) return { refused: 'usage', message: `${lane} clears with --run-url or --bundle-lane