fix: pull-requests scope for the door's two PR-API calls; docs catch up
The permission-starvation blocker found on the cast twin (claude-bot, cast#112 round 4) is identical here: a declared permissions: block zeroes every unspecified scope, so the decide step's label read and the bump fallback's gh pr create could only 403 — every genuine ceremony would end red at the label check. pull-requests: write added with the consumers named. CONTRIBUTING step 3 and the changelog entry now tell the shipped story: push-to-main door, event.before interlock, self-re-arm with the manual path's bump staying the operator's. Re-runs wording nit taken. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
03e1a8c5b7
commit
b89ed144fe
3 changed files with 38 additions and 23 deletions
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
|
|
@ -40,7 +40,11 @@ on:
|
|||
tags: ["**"]
|
||||
|
||||
permissions:
|
||||
contents: write # create the tag ref + gh release create
|
||||
contents: write # create the tag ref + gh release create + the bump push
|
||||
# Two consumers (a declared permissions: block zeroes every unspecified
|
||||
# scope): the decide step's label read (commits/<sha>/pulls) and the bump
|
||||
# fallback's `gh pr create --label`.
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
# The merge door (#96), riding pushes to main (see the trigger comment:
|
||||
|
|
@ -120,7 +124,7 @@ jobs:
|
|||
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)
|
||||
- name: nothing may exist yet — no tag, no release (re-runs refuse loudly)
|
||||
if: steps.decide.outputs.ceremony == 'yes'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
|
|
|||
41
CHANGELOG.md
41
CHANGELOG.md
|
|
@ -7,23 +7,30 @@ which records not just what changed but what each drill run proved.
|
|||
|
||||
### Added
|
||||
|
||||
- **Merging the release PR IS the release** (#96) — the 0.7.0 ceremony ended
|
||||
in an absence: the release PR merged with four approvals and nothing
|
||||
happened, correctly, because publishing hung off a separate, manual,
|
||||
silent-when-forgotten tag push — a failure shape with no error and no red
|
||||
X. The ship decision already lives in the release PR (the one PR whose
|
||||
whole diff is "the version leaves `-dev`"), so `release.yml` now fires on
|
||||
a merged, `release`-labeled PR into main: the label is the intent, the
|
||||
version transition is the interlock — `VERSION` at the merge commit must
|
||||
be non-`-dev` AND must have changed in this PR, so a mislabeled ordinary
|
||||
PR fails loudly and creates nothing — the notes must extract from the
|
||||
changelog, and no tag or release may exist yet. Then, in the same job, it
|
||||
tags the merge commit via the API and publishes; same-job on purpose,
|
||||
because a `GITHUB_TOKEN`-created tag triggers no workflows, which is also
|
||||
what makes double-publish impossible. The tag-push path stays unchanged as
|
||||
the documented manual fallback and backfill (it shipped 0.7.0 itself).
|
||||
`test/release.sh` grep-pins the merged+labeled gate, all four asserts, and
|
||||
the same-job tag+publish in the same daemon-free, fail-closed style.
|
||||
- **Merging the release PR IS the release — and the release re-arms main
|
||||
itself** (#96) — the 0.7.0 ceremony ended in an absence: the release PR
|
||||
merged with four approvals and nothing happened, correctly, because
|
||||
publishing hung off a separate, manual, silent-when-forgotten tag push —
|
||||
a failure shape with no error and no red X. The ship decision already
|
||||
lives in the release PR (the one PR whose whole diff is "the version
|
||||
leaves `-dev`"), so `release.yml` now fires on pushes to main
|
||||
(fork-sourced ceremony PRs get a read-only token on `pull_request`
|
||||
events), reading the transition from the push itself: `event.before` to
|
||||
the pushed head. A decide step answers four states — release-flow *work*
|
||||
merged under the `release` label (`-dev` endstates, 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 anything is created. Then, in the
|
||||
same job, it tags the merge commit via the API, 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. Same-job on
|
||||
purpose: a `GITHUB_TOKEN`-created tag triggers no workflows, which is
|
||||
also what makes double-publish impossible. The tag-push path stays
|
||||
unchanged as the documented manual fallback and backfill (it shipped
|
||||
0.7.0 itself). `test/release.sh` grep-pins the gate, every decide
|
||||
verdict, the single `on.push` key, and the same-job tag+publish+re-arm
|
||||
in the same daemon-free, fail-closed style.
|
||||
|
||||
## 0.7.0 — 2026-07-19
|
||||
|
||||
|
|
|
|||
|
|
@ -70,10 +70,14 @@ A release is a PR, and merging it ships it
|
|||
commit bare `X.Y.Z` by hand and pushing the tag still publishes the same
|
||||
way (release.yml asserts the tag names the tree's own `VERSION`) — for
|
||||
backfills, or the day the merge path is red.
|
||||
3. **Immediately after: bump `main`'s `VERSION` to `X.Y.(Z+1)-dev`.** Not
|
||||
cosmetic — the versioned layout names install trees after `VERSION`, so a
|
||||
`main` install without the bump would land in `versions/X.Y.Z` and
|
||||
impersonate the release you just cut.
|
||||
3. **The release re-arms main itself**: the same workflow run bumps
|
||||
`VERSION` 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). Not cosmetic — the versioned layout
|
||||
names install trees after `VERSION`, so a `main` install without the
|
||||
bump would land in `versions/X.Y.Z` and impersonate the release just
|
||||
cut. On the *manual* tag path the bump stays yours: open the one-line
|
||||
PR after publishing.
|
||||
|
||||
## Labels — who sets what
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue