diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b24bdc1866..c8e74ea146 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -192,6 +192,15 @@ jobs: exit 0 fi + # Promotions run the release tooling of the source commit, so the + # source must already understand the nightly channel. (Literal match + # of release.sh's channel case arm; if that line is reformatted this + # fails closed and should be updated alongside it.) + if ! git show "${sha}:scripts/release.sh" | grep -qF 'canary|nightly'; then + echo "Error: source commit $sha predates nightly release tooling; promote a newer canary." >&2 + exit 1 + fi + echo "proceed=true" >> "$GITHUB_OUTPUT" echo "sha=$sha" >> "$GITHUB_OUTPUT" echo "canary_version=$canary_version" >> "$GITHUB_OUTPUT" @@ -380,6 +389,15 @@ jobs: exit 1 fi + # Promotions run the release tooling of the source commit, so the + # source must already understand the beta channel. (Literal match of + # release.sh's channel case arm; if that line is reformatted this + # fails closed and should be updated alongside it.) + if ! git show "${sha}:scripts/release.sh" | grep -qF 'canary|nightly|beta|stable)'; then + echo "Error: source commit $sha predates beta release tooling; promote a newer nightly whose source contains the beta channel." >&2 + exit 1 + fi + echo "sha=$sha" >> "$GITHUB_OUTPUT" echo "nightly_version=$nightly_version" >> "$GITHUB_OUTPUT" { diff --git a/doc/RELEASING.md b/doc/RELEASING.md index 1559601ec7..a77ca18729 100644 --- a/doc/RELEASING.md +++ b/doc/RELEASING.md @@ -148,6 +148,10 @@ Betas are manual promotions. Dispatch it does not exist or already shipped as a beta - the publish waits for approval in the **`npm-beta` environment** — its required reviewers are the promotion gate +- promotions run the release tooling of the source commit, so the source + nightly must postdate the beta channel's introduction; the selection job + rejects older sources with a clear error (in practice every nightly cut + after the beta tooling merged qualifies) - the same commit is republished as `YYYY.MDD.P-beta.N` under the npm dist-tag `beta`, tagged `beta/vYYYY.MDD.P-beta.N`, and `docker.yml` is dispatched at that tag to publish the `:beta` images diff --git a/scripts/__tests__/release-verify-workflow.test.mjs b/scripts/__tests__/release-verify-workflow.test.mjs index 29ffad1469..3e33d0b254 100644 --- a/scripts/__tests__/release-verify-workflow.test.mjs +++ b/scripts/__tests__/release-verify-workflow.test.mjs @@ -38,6 +38,15 @@ test("onboard smoke container binds beyond loopback so the mapped port is reacha assert.match(dockerfile, /onboard --yes --bind lan/); }); +test("promotion selection guards against sources that predate their channel tooling", () => { + const releaseWorkflow = readWorkflow("release.yml"); + + // Promotions run the source commit's release.sh, so selection must reject + // sources whose tooling does not know the target channel yet. + assert.match(releaseWorkflow, /git show "\$\{sha\}:scripts\/release\.sh" \| grep -qF 'canary\|nightly'/); + assert.match(releaseWorkflow, /git show "\$\{sha\}:scripts\/release\.sh" \| grep -qF 'canary\|nightly\|beta\|stable\)'/); +}); + test("release smoke workflow extends the container readiness budget for CI", () => { const smokeWorkflow = readWorkflow("release-smoke.yml"); const harness = readFileSync(path.join(repoRoot, "scripts/docker-onboard-smoke.sh"), "utf8");