From b92919d4d0fa00ed778e8f2ea569d814aa3826dd Mon Sep 17 00:00:00 2001 From: dan-claude-bot Date: Sun, 19 Jul 2026 16:31:40 +0000 Subject: [PATCH] fix: pull-requests scope for the door's two PR-API calls; docs catch up to the shipped shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit claude-bot's round-4 blockers: (1) a declared permissions: block zeroes every unspecified scope, so the decide step's label read (commits//pulls) and the bump fallback's gh pr create could only 403 — every genuine ceremony would end red at the label check, the exact failure shape this feature exists to kill, one layer down. labels.yml already carries the precedent; pull-requests: write added with the two consumers named. (2) CONTRIBUTING still prescribed the follow-up bump PR the workflow now performs itself, and the changelog entry described the old PR-base interlock — both now tell the shipped story (event.before interlock, self-re-arm, manual-path bump stays the operator's). Nits taken: fetch-depth 2 for the all-zeros fallback's first parent, re-runs refuse-loudly wording, the bump-window arithmetic comment. Co-Authored-By: Claude Fable 5 --- .github/workflows/release.yml | 21 +++++++++++++++++---- CHANGELOG.md | 29 ++++++++++++++++++----------- CONTRIBUTING.md | 13 ++++++++----- 3 files changed, 43 insertions(+), 20 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7365451..38f826a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,7 +39,11 @@ on: branches: [main] permissions: - contents: write # tag create via the API + gh release create + contents: write # tag create via the API + gh release create + the bump push + # Two consumers (labels.yml precedent — a declared permissions: block + # zeroes every unspecified scope): the decide step's label read + # (commits//pulls) and the bump fallback's `gh pr create --label`. + pull-requests: write jobs: release: @@ -58,6 +62,10 @@ jobs: # merge commit the maintainer shipped, which the tag created # below will name). ref: ${{ github.sha }} + # Depth 2: the pushed head's first parent must be resolvable for + # the decide step's all-zeros fallback (event.before on a + # branch-creation push). + fetch-depth: 2 - uses: actions/setup-node@v4 with: node-version: "22" @@ -159,9 +167,10 @@ jobs: MERGE_SHA: ${{ github.sha }} run: | # Assert 4 — no tag and no release exist for this version. Re-runs - # stay idempotent, and a manual race (an operator who tagged by - # hand between merge and here) fails loudly instead of - # double-publishing. + # of a completed ceremony REFUSE LOUDLY (red, creating nothing — + # the correct direction), and a manual race (an operator who + # tagged by hand between merge and here) fails the same way + # instead of double-publishing. if git ls-remote --exit-code origin "refs/tags/$RELEASE_VERSION" > /dev/null; then echo "tag '$RELEASE_VERSION' already exists — creating nothing (already released, or a manual tag won the race)." >&2 exit 1 @@ -214,6 +223,10 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | + # next is computed from the RELEASE tree (the checkout), then + # applied to whatever main is by the time of the push — if main + # moved in the window, release+1 still lands on the newer head, + # which is the intended arithmetic either way. 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" diff --git a/CHANGELOG.md b/CHANGELOG.md index 2972f4e..b2e769b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,17 +9,24 @@ actually cutting it, and this file starts there. ### Added -- **Merging a release-labeled PR is the release** (#111; box#96's design) — - `release.yml` now also fires when a `release`-labeled PR merges into - main: it asserts fail-loud (non-`-dev` version, version changed in the - PR — the `-dev` interlock, changelog section extracts non-empty, no - existing tag or release), then tags the merge commit, builds the - `cast-X.Y.Z.tgz` asset once, and publishes — the maintainer's merge is - the ship decision, no silent-when-forgotten manual tag step. The - tag-push path stays as the documented fallback and backfill, and both - paths run the same steps so they cannot drift. First-release edge: - 0.1.0 never carried `-dev`, so its ceremony (#110) ships by manual tag; - the automation applies from 0.1.1 on. +- **Merging a release-labeled PR is the release — and the release re-arms + main itself** (#111; box#96's design) — `release.yml` now also fires on + pushes to main (not `pull_request` events: fork-sourced ceremony PRs get + a read-only token there — the round-1 catch). A decide step reads the + version transition from the push (`event.before` → the pushed head) and + answers four states: release-flow *work* merged under the `release` + label — `-dev` endstates, and the post-release window — no-ops green + with a NOTICE; the two genuinely ambiguous bare states refuse loudly; + a true transition then requires a merged, `release`-labeled PR behind + the commit (read via the API — the label is the operator's declared + intent) before the door opens. It then tags the merge commit, builds + the `cast-X.Y.Z.tgz` asset once, publishes — and bumps main to + `X.Y.(Z+1)-dev` itself, direct push with a loud open-a-PR fallback, so + no follow-up bump PR exists on the paved road. The tag-push path stays + as the documented fallback and backfill, and both paths run the same + steps so they cannot drift. First-release edge: 0.1.0 never carried + `-dev`, so its ceremony (#110) ships by manual tag; the automation + applies from 0.1.1 on. ### Fixed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index be9926a..e7adf03 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -69,12 +69,15 @@ on box#83's shape): *Manual fallback and backfill:* push a bare `X.Y.Z` tag on the merge commit yourself — the same workflow runs the same asserts, build, and publish from the tag. -3. **Right after the release, a follow-up PR bumps `package.json` to - `X.Y.(Z+1)-dev`** (and `package-lock.json` with it) — box#90's step of - the family ritual. Installs are versioned by the tree's `package.json` - version, so a `CAST_REF=main` install between releases must land as +3. **The release re-arms main itself**: the same workflow run bumps + `package.json` (and `package-lock.json`) to `X.Y.(Z+1)-dev` and pushes + the commit straight to main — no follow-up PR (it opens one only if + branch protection refuses the direct push, and says so loudly). + Installs are versioned by the tree's `package.json` version, so a + `CAST_REF=main` install between releases must land as `versions/X.Y.(Z+1)-dev`, never as `versions/X.Y.Z` — main's tree must - not impersonate the release it merely descends from. + not impersonate the release it merely descends from. On the *manual* + tag path the bump stays yours: open the one-line PR after publishing. ## Labels — who sets what