fix: the release label's two meanings part ways in a decide step
LABELS.md gives 'release' to release-flow WORK as well as to the ceremony PR — the PR that added the merge door included. The old assert pair turned every such merge into a red run on main. The fused decide step reads VERSION against the merge commit's first parent and answers all states: -dev unchanged = work, green NOTICE no-op; bare unchanged but already released = work in the post-release window, same no-op; -dev-but-changed and bare-unchanged-never-released = half-ceremonies, refused loudly; bare-and-changed = the ceremony. Later steps gate on its output. Five new pins in test/release.sh cover each verdict and the gating. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b58ce5524f
commit
420e8e7f32
2 changed files with 55 additions and 13 deletions
52
.github/workflows/release.yml
vendored
52
.github/workflows/release.yml
vendored
|
|
@ -56,32 +56,57 @@ jobs:
|
|||
# base.sha can be stale; the merge commit's first parent cannot.)
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
fetch-depth: 2
|
||||
- name: VERSION at the merge commit must be a release, not -dev
|
||||
run: |
|
||||
ver="$(cat VERSION)"
|
||||
case "$ver" in
|
||||
*-dev)
|
||||
echo "VERSION is '$ver' — still -dev, so this merge is not a release ceremony, whatever its label says — creating nothing." >&2
|
||||
exit 1 ;;
|
||||
esac
|
||||
- name: VERSION must have CHANGED in this PR — the -dev interlock
|
||||
# Only the release PR moves VERSION off -dev. A mislabeled ordinary
|
||||
# PR merged while main already carries a release version dies here,
|
||||
# loudly, instead of re-releasing whatever VERSION says.
|
||||
# The decide step — the version asserts fused, because the `release`
|
||||
# label carries TWO legitimate meanings (LABELS.md: "release flow and
|
||||
# version/packaging work"): the ceremony PR that ships a version, and
|
||||
# ordinary work ON the release machinery — the PR that added this very
|
||||
# job included. The version tells them apart, in four states:
|
||||
# -dev, unchanged → work under the label: green NOTICE
|
||||
# no-op, not a red run per infra PR
|
||||
# -dev, changed → a bump that forgot to leave -dev:
|
||||
# half a ceremony, refuse
|
||||
# bare, unchanged, released → work merged in the post-release
|
||||
# window (ceremony landed, the -dev
|
||||
# bump has not): green NOTICE no-op
|
||||
# bare, unchanged, UNreleased→ the label says ship but this PR did
|
||||
# not mint the version: refuse to guess
|
||||
# bare, changed → the ceremony: proceed
|
||||
- name: 'decide: ceremony, or release-flow work under the label?'
|
||||
id: decide
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
ver="$(cat VERSION)"
|
||||
base="$(git show HEAD^1:VERSION)"
|
||||
case "$ver" in
|
||||
*-dev)
|
||||
if [ "$base" = "$ver" ]; then
|
||||
echo "VERSION '$ver' did not change in this PR (the base commit already carried it) — this is not the release PR — creating nothing." >&2
|
||||
echo "NOTICE: VERSION '$ver' is -dev and unchanged by this PR — release-flow work under the release label, not a ceremony. Nothing to publish."
|
||||
echo "ceremony=no" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
echo "VERSION changed ('$base' -> '$ver') but is still -dev — half a ceremony; a release PR ships a bare X.Y.Z — creating nothing." >&2
|
||||
exit 1 ;;
|
||||
esac
|
||||
if [ "$base" = "$ver" ]; then
|
||||
if gh release view "$ver" --json name >/dev/null 2>&1; then
|
||||
echo "NOTICE: VERSION '$ver' is already released and unchanged by this PR — release-flow work merged in the post-release window (before the -dev bump). Nothing to publish."
|
||||
echo "ceremony=no" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
echo "VERSION '$ver' is bare, unchanged by this PR, and never released — the label says ship but this PR did not mint the version. Refusing to guess — creating nothing." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "ceremony=yes" >> "$GITHUB_OUTPUT"
|
||||
- name: release notes — the version's own CHANGELOG.md section
|
||||
if: steps.decide.outputs.ceremony == 'yes'
|
||||
# release-notes.sh fails loudly on a missing/empty section, which
|
||||
# fails the release here — before anything is created.
|
||||
run: |
|
||||
bash .github/scripts/release-notes.sh "$(cat VERSION)" > "$RUNNER_TEMP/notes.md"
|
||||
cat "$RUNNER_TEMP/notes.md"
|
||||
- name: nothing may exist yet — no tag, no release (idempotency)
|
||||
if: steps.decide.outputs.ceremony == 'yes'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
|
|
@ -95,6 +120,7 @@ jobs:
|
|||
exit 1
|
||||
fi
|
||||
- name: tag the merge commit, then publish — one job, on purpose
|
||||
if: steps.decide.outputs.ceremony == 'yes'
|
||||
# Same job as the asserts: the GITHUB_TOKEN-created tag triggers no
|
||||
# workflows (GitHub's anti-recursion), so the tag door cannot fire
|
||||
# off it — this step is the release's only chance to publish.
|
||||
|
|
|
|||
|
|
@ -150,6 +150,22 @@ check "release.yml: BOTH doors publish bound to an existing tag (--verify-tag)"
|
|||
grep -cF -- '--verify-tag' "$RY"
|
||||
check "release.yml: tag + publish share one job (the anti-recursion shape)" 0 "" \
|
||||
grep -qF 'anti-recursion' "$RY"
|
||||
# The decide step tells the label's two meanings apart (LABELS.md gives
|
||||
# `release` to release-flow WORK as well as to the ceremony PR — the PR
|
||||
# that added the merge door included): work under the label no-ops GREEN —
|
||||
# in the -dev steady state and in the post-release window (bare, unchanged,
|
||||
# already released) — while every half-ceremony refuses. Pin each verdict
|
||||
# and the gating output.
|
||||
check "release.yml: decide — dev-tree work no-ops green (not a red run per infra PR)" 0 "" \
|
||||
grep -qF "release-flow work under the release label, not a ceremony" "$RY"
|
||||
check "release.yml: decide — a half-ceremony (-dev but changed) refuses" 0 "" \
|
||||
grep -qF "half a ceremony" "$RY"
|
||||
check "release.yml: decide — post-release-window work no-ops green" 0 "" \
|
||||
grep -qF "release-flow work merged in the post-release window" "$RY"
|
||||
check "release.yml: decide — bare, unchanged, never released refuses to guess" 0 "" \
|
||||
grep -qF "Refusing to guess" "$RY"
|
||||
check "release.yml: decide gates every later merge-door step on ceremony=yes" 0 "3" \
|
||||
grep -cF "if: steps.decide.outputs.ceremony == 'yes'" "$RY"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# latest_release_tag — extracted from install.sh (the source-the-pure-function
|
||||
|
|
|
|||
Loading…
Reference in a new issue