diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f9216835..ef89fdfa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,10 +11,11 @@ permissions: contents: read concurrency: - # Only duplicate runs for the exact PR base/head revision cancel each other. - # A later revision must not kill an earlier cold-cache producer, and every - # protected-main SHA keeps an independent producer run. - group: ci-${{ github.workflow }}-${{ github.event_name == 'pull_request' && format('pr-{0}-{1}-{2}', github.event.pull_request.number, github.event.pull_request.base.sha, github.event.pull_request.head.sha) || format('push-{0}', github.sha) }} + # Keep only the latest revision of a pull request: a stale run must not + # compete with its replacement for hosted runners. A replacement that sees + # a cold baseline cache recomputes it authoritatively. Protected-main pushes + # remain keyed by exact SHA so every potential merge base has a producer. + group: ci-${{ github.workflow }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || format('push-{0}', github.sha) }} cancel-in-progress: true jobs: diff --git a/docs/ci-pr-gates.md b/docs/ci-pr-gates.md index 20500e12..32bfeb7a 100644 --- a/docs/ci-pr-gates.md +++ b/docs/ci-pr-gates.md @@ -312,11 +312,12 @@ the same dedicated cache profile path because GitHub includes that path in the cache version; the runtime-facing candidate and baseline filenames remain separate. Near-miss reuse is forbidden — the caches carry no prefix restore keys, because a neighbouring commit's profile would compare the candidate -against the wrong baseline. CI concurrency is keyed by PR number plus exact -event base/head SHA. Duplicate runs for that exact revision may cancel each -other, but a later revision cannot kill an earlier cold-cache producer. Main -runs use the pushed SHA, so a newer main push cannot cancel a predecessor's -producer. +against the wrong baseline. PR concurrency is keyed by PR number, so a later +revision cancels the stale run instead of letting obsolete test matrices +compete with the replacement for hosted runners. If cancellation interrupts a +cold-cache fallback, the latest run recomputes the same exact merge-base +profile authoritatively. Main concurrency remains keyed by pushed SHA, so a +newer main push cannot cancel a predecessor's producer. Every supported main advancement path has an exact-SHA producer. The required `Test` context rejects GitHub workflow-skip directives in PR and auto-merge diff --git a/test/scripts/coverage_workflow_contract_test.go b/test/scripts/coverage_workflow_contract_test.go index facc8b8d..5395595b 100644 --- a/test/scripts/coverage_workflow_contract_test.go +++ b/test/scripts/coverage_workflow_contract_test.go @@ -136,19 +136,19 @@ func TestCoverageWorkflowShardsAndBaselineCache(t *testing.T) { } admission := string(data) for _, want := range []string{ - "group: ci-${{ github.workflow }}-${{ github.event_name == 'pull_request' && format('pr-{0}-{1}-{2}', github.event.pull_request.number, github.event.pull_request.base.sha, github.event.pull_request.head.sha) || format('push-{0}', github.sha) }}", + "group: ci-${{ github.workflow }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || format('push-{0}', github.sha) }}", "cancel-in-progress: true", } { if !strings.Contains(admission, want) { - t.Errorf("CI workflow missing exact-revision producer contract %q", want) + t.Errorf("CI workflow missing latest-PR/exact-main producer contract %q", want) } } for _, forbidden := range []string{ + "format('pr-{0}-{1}-{2}'", "github.event.pull_request.number || github.ref", - "github.event.pull_request.number || github.sha", } { if strings.Contains(admission, forbidden) { - t.Errorf("CI producers must not share the coarse concurrency identity %q", forbidden) + t.Errorf("CI workflow retains a stale concurrency identity %q", forbidden) } }