forked from heavy-duty/box
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: ["**"]
|
tags: ["**"]
|
||||||
|
|
||||||
permissions:
|
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:
|
jobs:
|
||||||
# The merge door (#96), riding pushes to main (see the trigger comment:
|
# The merge door (#96), riding pushes to main (see the trigger comment:
|
||||||
|
|
@ -120,7 +124,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
bash .github/scripts/release-notes.sh "$(cat VERSION)" > "$RUNNER_TEMP/notes.md"
|
bash .github/scripts/release-notes.sh "$(cat VERSION)" > "$RUNNER_TEMP/notes.md"
|
||||||
cat "$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'
|
if: steps.decide.outputs.ceremony == 'yes'
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
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
|
### Added
|
||||||
|
|
||||||
- **Merging the release PR IS the release** (#96) — the 0.7.0 ceremony ended
|
- **Merging the release PR IS the release — and the release re-arms main
|
||||||
in an absence: the release PR merged with four approvals and nothing
|
itself** (#96) — the 0.7.0 ceremony ended in an absence: the release PR
|
||||||
happened, correctly, because publishing hung off a separate, manual,
|
merged with four approvals and nothing happened, correctly, because
|
||||||
silent-when-forgotten tag push — a failure shape with no error and no red
|
publishing hung off a separate, manual, silent-when-forgotten tag push —
|
||||||
X. The ship decision already lives in the release PR (the one PR whose
|
a failure shape with no error and no red X. The ship decision already
|
||||||
whole diff is "the version leaves `-dev`"), so `release.yml` now fires on
|
lives in the release PR (the one PR whose whole diff is "the version
|
||||||
a merged, `release`-labeled PR into main: the label is the intent, the
|
leaves `-dev`"), so `release.yml` now fires on pushes to main
|
||||||
version transition is the interlock — `VERSION` at the merge commit must
|
(fork-sourced ceremony PRs get a read-only token on `pull_request`
|
||||||
be non-`-dev` AND must have changed in this PR, so a mislabeled ordinary
|
events), reading the transition from the push itself: `event.before` to
|
||||||
PR fails loudly and creates nothing — the notes must extract from the
|
the pushed head. A decide step answers four states — release-flow *work*
|
||||||
changelog, and no tag or release may exist yet. Then, in the same job, it
|
merged under the `release` label (`-dev` endstates, the post-release
|
||||||
tags the merge commit via the API and publishes; same-job on purpose,
|
window) no-ops green with a NOTICE; the two genuinely ambiguous bare
|
||||||
because a `GITHUB_TOKEN`-created tag triggers no workflows, which is also
|
states refuse loudly; a true transition then requires a merged,
|
||||||
what makes double-publish impossible. The tag-push path stays unchanged as
|
`release`-labeled PR behind the commit (read via the API — the label is
|
||||||
the documented manual fallback and backfill (it shipped 0.7.0 itself).
|
the operator's declared intent) before anything is created. Then, in the
|
||||||
`test/release.sh` grep-pins the merged+labeled gate, all four asserts, and
|
same job, it tags the merge commit via the API, publishes — and bumps
|
||||||
the same-job tag+publish in the same daemon-free, fail-closed style.
|
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
|
## 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
|
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
|
way (release.yml asserts the tag names the tree's own `VERSION`) — for
|
||||||
backfills, or the day the merge path is red.
|
backfills, or the day the merge path is red.
|
||||||
3. **Immediately after: bump `main`'s `VERSION` to `X.Y.(Z+1)-dev`.** Not
|
3. **The release re-arms main itself**: the same workflow run bumps
|
||||||
cosmetic — the versioned layout names install trees after `VERSION`, so a
|
`VERSION` to `X.Y.(Z+1)-dev` and pushes the commit straight to main —
|
||||||
`main` install without the bump would land in `versions/X.Y.Z` and
|
no follow-up PR (it opens one only if branch protection refuses the
|
||||||
impersonate the release you just cut.
|
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
|
## Labels — who sets what
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue