Commit graph

9 commits

Author SHA1 Message Date
dan-claude-bot
67331ebecd fix: the armed guard also refuses a half-done ceremony (#108, cast#114 review)
The bare-VERSION branch left the top heading unconstrained on purpose — both
ceremony shapes have to stay legal, which is the rig#44 / cast#108 lesson. A
review round on the sibling fix found the gap that asymmetry leaves: a tree
with VERSION bumped, '## Unreleased' still populated on top, and no stamped
section for that version makes the wrong-number test false on its first
clause, short-circuits, and passes. release.yml then refuses at publish time
— after the merge, on main, with the release already half-shipped.

So the bare branch now also requires the section it is about to publish to
exist and be non-empty, asserted by running release-notes.sh itself so the
guard and the publisher cannot drift over what a section is. The message is
distinct from the wrong-number case: a missing stamp is not a misnumbered
one. Matches heavy-duty/rig#67.

test/release.sh constructs the half-ceremony tree and the stamped-but-empty
tree and drives the real script at both — all three new assertions fail
against the previous guard — while the re-armed and un-re-armed ceremony
trees stay green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 20:43:52 +00:00
dan-claude-bot
1a9d96168d fix: the ceremony re-arms CHANGELOG.md, and CI keeps main armed (#108)
Stamping '## Unreleased' away left main with no heading for a PR
authored before the release to land in, so its entry merged cleanly
into the section that just shipped. The ceremony now re-arms, and
changelog-armed.sh enforces it keyed on VERSION -- so the ceremony
PR's own bare-VERSION tree stays legal (rig#44 / cast#108).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 20:30:41 +00:00
dan-claude-bot
03e1a8c5b7 feat: the release re-arms main — the -dev bump folds into the release act
Operator decision: the post-release bump PR is ceremony debris — a
derivable one-liner with no judgment for a review to add. After tag +
publish, the same job computes X.Y.(Z+1)-dev and pushes it to main
directly (a GITHUB_TOKEN push fires no workflows: no recursion, no red
run); if branch protection refuses, the step opens the bump PR itself,
loudly. #98 is the last hand-made bump.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 16:04:22 +00:00
dan-claude-bot
89345f7de3 fix: the merge door rides pushes to main — fork PR tokens are read-only
Round-1 blocker (claude + grok): a pull_request run from a public fork
gets a read-only GITHUB_TOKEN — permissions: cannot raise it — and every
ceremony PR this org merges is cross-repo from the bot fork, so the tag
create would 403 after green asserts, red on main per release. The door
now triggers on push to main (in-repo event, full token); the decide step
keeps the first-parent version interlock, and the release label — still
the operator's declared intent — is read via the API off the merge
commit's PR. A transition with no labeled PR behind it refuses. The doors
split on the pushed ref: tags to the tag door, main to the merge door.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 15:46:03 +00:00
dan-claude-bot
00e36b9436 test: the creating-nothing count follows the -dev-endstate change (5 -> 4)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 15:41:21 +00:00
dan-claude-bot
13aa499dad fix: a -dev endstate is always work — the post-release bump must not run red
The four-state table called '-dev but changed' half a ceremony and
refused — but that state IS the mandatory post-release bump PR
(bare -> X.Y.(Z+1)-dev after every release), which would have put a red
run on main once per release, forever. A tree that ends -dev is by
definition not a release: every such merge is work, and no-ops green
with a NOTICE. The red verdicts now guard only bare endstates.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 15:40:48 +00:00
dan-claude-bot
420e8e7f32 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>
2026-07-19 15:25:52 +00:00
dan-claude-bot
b58ce5524f feat: merging a release-labeled PR is the release (#96)
The 0.7.0 ceremony exposed the gap: the release PR merged with four
approvals and nothing happened, correctly, because publishing hung off a
separate, manual, silent-when-forgotten tag push — the worst failure
shape, no error and no red X. The ship decision already lives in the
release PR, so the merge now IS the release.

release.yml grows a second door: pull_request closed on main, gated on
merged == true AND the hand-set release label (read from the event
payload — no extra permission). Four asserts, in order, each fail-loud
and creating nothing: VERSION at the merge commit is non--dev; VERSION
changed in this PR (merge vs first parent — the -dev interlock that
kills a mislabeled ordinary PR); the version's CHANGELOG.md section
extracts non-empty via the existing release-notes.sh; and no tag or
release exists yet. Then, in the same job, it creates the tag ref at
the merge commit via the API and publishes with gh release create
--verify-tag. Same-job on purpose: a GITHUB_TOKEN-created tag triggers
no workflows (GitHub's anti-recursion), so the tag door can never fire
off it and double-publish, and the no-existing assert covers a manual
tag racing the merge. The tag-push path stays step-for-step identical
as the documented manual fallback and backfill, gated to the push event
so a closed PR never runs it against a branch ref.

CONTRIBUTING.md's Releases section now reads "the maintainer's merge IS
the release", with the manual tag ritual kept as the fallback.
test/release.sh grep-pins the merged+labeled gate, all four asserts,
the same-job tag+publish, and that the tag-push trigger survives — in
the same daemon-free, fail-closed style.

Fixes #96

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 15:19:31 +00:00
dan-claude-bot
646c458e25 test(release): the extraction, the resolution, and every channel — offline (#83)
test/release.sh, wired into CI beside the other suites and never
touching the network: release-notes.sh is driven against a fixture
changelog carrying every boundary (Unreleased must not leak, the
version is matched whole so 0.7.0 never grabs 0.7.0-rc1, a missing
or empty section refuses) AND against the real CHANGELOG.md, so the
header format cannot drift under release.yml. latest_release_tag is
extracted from install.sh and driven against a shim curl serving
canned redirects — including the no-releases redirect and a network
failure. And the three channels are REAL install.sh runs against
throwaway roots with the shim standing in for GitHub: the default
resolves and downloads the tag tarball (never a branch), a pin skips
the probe, BOX_REF=main falls back tag → branch in that order, and a
failed resolution refuses loudly having downloaded nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 22:17:51 +00:00