From 589854ae0b927464a69c3b50be2f51ac4eaea91a Mon Sep 17 00:00:00 2001 From: dan-claude-bot Date: Sun, 19 Jul 2026 13:48:41 +0000 Subject: [PATCH] fix: the release suite accepts the ceremony's own tree (#44) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test/release.sh demanded a literal '## Unreleased' heading extracting non-empty with '#32' in it — all false by construction on the very tree the release PR produces, so the first real 'release: 0.1.0' PR turned CI red and the ceremony blocked itself. Both fork rehearsals missed it: they tag a branch, which runs release.yml and never ci.yml. The guard now asserts its actual purpose — the TOP section, whatever its name, extracts non-empty via the exact function release.yml runs — and passes on both legitimate tree states (verified on main's shape and on a stamped copy). Fixes #44 Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 12 ++++++++++++ test/release.sh | 22 ++++++++++++++++------ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07367b9..4ed2a8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,18 @@ on the way to cutting its first release, and this file starts there. ### Fixed +- **The release suite accepts the ceremony's own tree** (#44) — + `test/release.sh` demanded a literal `## Unreleased` heading in the real + `CHANGELOG.md`, extracting non-empty and containing `#32`. All three are + false by construction on the `release: X.Y.Z` tree the ceremony's own PR + produces (it stamps that heading into `## X.Y.Z — date`), so the first + real release PR turned CI red and the flow blocked itself — invisible to + both fork rehearsals, which tag a branch (`release.yml` runs; `ci.yml` + never does). The guard now asserts what it was for: whatever the TOP + `## ` section is — `Unreleased` between releases, the stamped version on + and right after one — the exact `changelog_section` the workflow runs + extracts it non-empty. The rotting issue-number grep is gone. + - **Headless credential prompts refuse loudly instead of dying silently** (#42) — the interactive credential prompts (`TS_AUTHKEY` in `bootstrap`, `RUNNER_TOKEN` in `runner install`, `RUNNER_REMOVE_TOKEN` in diff --git a/test/release.sh b/test/release.sh index 7e3ead3..f223b9d 100644 --- a/test/release.sh +++ b/test/release.sh @@ -89,12 +89,22 @@ check "changelog: an unknown version yields NOTHING (the refusal signal)" 0 "" \ check "changelog: a date-stamped heading never matches by date" 0 "" \ absent "$FIXCH" 2026-07-18 -# ...and the SHIPPED changelog fits the extractor: an Unreleased section the -# release PR will stamp, extractable by the exact function release.yml runs. -check "CHANGELOG.md: has an Unreleased section" 0 "" \ - grep -qx "## Unreleased" "$ROOT/CHANGELOG.md" -check "CHANGELOG.md: Unreleased extracts non-empty (the format fits the tool)" \ - 0 "#32" changelog_section "$ROOT/CHANGELOG.md" Unreleased +# ...and the SHIPPED changelog fits the extractor. The real file has two +# legitimate states, and the old check knew only one (#44, found the day the +# first release PR turned CI red): BETWEEN releases there is an `## Unreleased` +# section feature PRs append to; on a `release: X.Y.Z` tree — and on main +# right after it, until the next feature PR — that section IS the stamped +# `## X.Y.Z — date`. Demanding the literal heading (or, worse, an issue +# number inside it) made the release PR of the ceremony unshippable by +# construction. What the guard is FOR is format drift: whatever the top +# section is called, the exact function release.yml runs must extract it +# non-empty. +# shellcheck disable=SC2016 # the $-refs are the inner bash -c's, deliberately +check "CHANGELOG.md: has a top section (Unreleased or a stamped release)" 0 "" \ + bash -c '[ -n "$(grep -m1 "^## " "$1")" ]' _ "$ROOT/CHANGELOG.md" +# shellcheck disable=SC2016 # same: positional args resolve inside the inner shell +check "CHANGELOG.md: the top section extracts non-empty (the format fits the tool)" 0 "" \ + bash -c '. "$2/.github/scripts/release-lib.sh"; [ -n "$(changelog_section "$1" "$(grep -m1 "^## " "$1" | awk "{print \$2}")")" ]' _ "$ROOT/CHANGELOG.md" "$ROOT" # --- release.yml: the pins --------------------------------------------------- # The workflow itself runs only on a tag push upstream, so pin its