fix(ci): keep cancellation ownership in the calling workflow

Remove redundant ref-based concurrency from reusable provider and website workflows. Their old groups could cancel qualification from another CI tier even though the callers used separate groups. Disposable hosted jobs now inherit caller scheduling; direct manual dispatches remain independent.

Validate every called CI workflow has no independent cancellation group. Validation: 100 focused scope/gate tests and actionlint 1.7.7.
This commit is contained in:
morluto 2026-10-13 17:15:42 +08:00
parent 0c7f4dfbd9
commit db3c9f084b
11 changed files with 27 additions and 35 deletions

View File

@ -12,10 +12,6 @@ on:
permissions:
contents: read
concurrency:
group: real-binary-layout-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
NODE_OPTIONS: --max-old-space-size=768
HUSKY: "0"

View File

@ -4,10 +4,6 @@ on:
workflow_call:
workflow_dispatch:
concurrency:
group: real-browser-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
HUSKY: "0"

View File

@ -7,10 +7,6 @@ on:
permissions:
contents: read
concurrency:
group: real-evm-interface-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
NODE_OPTIONS: --max-old-space-size=768
HUSKY: "0"

View File

@ -7,10 +7,6 @@ on:
permissions:
contents: read
concurrency:
group: javascript-recovery-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
HUSKY: "0"
NODE_OPTIONS: "--max-old-space-size=768"

View File

@ -7,10 +7,6 @@ on:
permissions:
contents: read
concurrency:
group: real-recorded-crash-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
NODE_OPTIONS: --max-old-space-size=768
HUSKY: "0"

View File

@ -7,10 +7,6 @@ on:
permissions:
contents: read
concurrency:
group: real-web-network-captures-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
HUSKY: "0"
NODE_OPTIONS: --max-old-space-size=768

View File

@ -4,9 +4,6 @@ on:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: real-web-runtime-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
HUSKY: "0"
jobs:

View File

@ -4,9 +4,6 @@ on:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: real-web-source-map-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
HUSKY: "0"
jobs:

View File

@ -7,10 +7,6 @@ on:
permissions:
contents: read
concurrency:
group: website-check-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
check:
name: Website checks

View File

@ -776,7 +776,10 @@ should require this aggregate, `Static checks`, and `Portable tests`. Fast runs
cancel superseded feedback for the same PR or main branch. Scheduled/manual
qualification has a separate concurrency group: active work finishes and only
the latest pending run is retained. Release qualification is grouped by its
exact source SHA and cannot be cancelled by newer main pushes. Publication
exact source SHA and cannot be cancelled by newer main pushes. Provider and
website lanes inherit their caller's scheduling; reusable
workflows have no independent cancellation group. Direct manual lane dispatches
run independently on disposable hosted runners. Publication
requires that qualification to succeed and consumes its exact verified tarball,
checking source SHA, workflow run, run attempt, package/Node version and SHA-256
before npm publication. Earlier attempts from the same workflow run are accepted

View File

@ -64,6 +64,29 @@ it("makes the required gate wait for every CI lane, including future additions",
);
});
it("leaves cancellation ownership with CI so child lanes cannot cancel another tier", async () => {
const { jobs } = await workflow();
const children = Object.values(jobs).flatMap((job) =>
typeof job.uses === "string" && job.uses.startsWith("./.github/workflows/")
? [job.uses]
: [],
);
expect(children.length).toBeGreaterThan(0);
for (const path of children) {
const child = z
.object({ concurrency: z.unknown().optional() })
.parse(
parse(
await readFile(new URL(`../../../${path}`, import.meta.url), "utf8"),
),
);
expect(
child.concurrency,
`${path} must use caller scheduling`,
).toBeUndefined();
}
});
it("keeps fast feedback separate from complete qualification and release proof", async () => {
const ci = z
.object({