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 (package.json + lock, via npm, never regex) with no
judgment for a review to add. After tag + build + 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. Merge-door only —
the manual tag fallback does not rewrite main.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
dan-claude-bot 2026-07-19 16:04:55 +00:00
parent 84bd9f341f
commit 121aa6bd5b
2 changed files with 41 additions and 0 deletions

View file

@ -199,3 +199,35 @@ jobs:
gh release create "$RELEASE_VERSION" --verify-tag \ gh release create "$RELEASE_VERSION" --verify-tag \
--title "$RELEASE_VERSION" --notes-file "$RUNNER_TEMP/notes.md" \ --title "$RELEASE_VERSION" --notes-file "$RUNNER_TEMP/notes.md" \
"$RUNNER_TEMP/cast-$RELEASE_VERSION.tgz" "$RUNNER_TEMP/cast-$RELEASE_VERSION.tgz"
# The post-release bump, folded into the release act (#111 followup —
# operator decision: a mechanical one-liner deserves no PR of its
# own). X.Y.(Z+1)-dev is arithmetic, not judgment: derived, committed
# straight to main with this job's token. A GITHUB_TOKEN push fires
# no workflows (anti-recursion), so the bump triggers neither the
# merge path nor a red run; should branch protection ever refuse the
# direct push, the step opens the bump PR itself and says so, loudly.
# Merge-door only (the decide gate): the manual tag path stays a
# fallback and does not rewrite main.
- name: bump main to the next -dev — the release re-arms main itself
if: github.ref == 'refs/heads/main' && steps.decide.outputs.ceremony == 'yes'
env:
GH_TOKEN: ${{ github.token }}
run: |
next="$(node -p 'const v = require("./package.json").version.split("."); v[2] = String(Number(v[2]) + 1) + "-dev"; v.join(".")')"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git fetch origin main
git checkout -B main origin/main
npm pkg set version="$next"
npm install --package-lock-only --ignore-scripts
git add package.json package-lock.json
git commit -m "chore: bump main to $next — a dev install must not impersonate $RELEASE_VERSION"
if ! git push origin main; then
echo "direct push refused (branch protection?) — opening the bump PR instead" >&2
git checkout -b "chore/bump-$next"
git push origin "chore/bump-$next"
gh pr create -R "$GITHUB_REPOSITORY" --head "chore/bump-$next" \
--title "chore: bump main to $next" \
--body "The post-release re-arm, opened by release.yml because the direct push was refused." \
--label release
fi

View file

@ -194,6 +194,15 @@ describe("release.yml", () => {
expect(RY).not.toContain("pull_request:"); expect(RY).not.toContain("pull_request:");
}); });
it("the release re-arms main itself — the -dev bump folds into the release act", () => {
// Operator decision (#111 followup): the post-release bump PR was
// ceremony debris. Direct push with the job's token, PR fallback when
// branch protection refuses, merge-door only.
expect(RY).toContain("bump main to the next -dev");
expect(RY).toContain("opening the bump PR instead");
expect(RY).toContain("npm install --package-lock-only");
});
it("asserts tag == package.json version, and the assert precedes the create", () => { it("asserts tag == package.json version, and the assert precedes the create", () => {
expect(RY).toContain('require("./package.json").version'); expect(RY).toContain('require("./package.json").version');
expect(RY).toContain("creating nothing"); expect(RY).toContain("creating nothing");