docs: wire refs guard into ceremony flow

This commit is contained in:
Andriujose 2026-08-03 20:31:34 +00:00
parent dcf72a9af8
commit 66b136efc0
7 changed files with 59 additions and 5 deletions

3
.github/labeler.yml vendored
View file

@ -32,9 +32,12 @@ scope:guards:
- actions/changelog-armed/**
- actions/changelog-monotonic/**
- actions/drill-recorded/**
- actions/refs-not-closing/**
- .github/workflows/refs-guard.yml
- test/changelog-armed.test.sh
- test/changelog-monotonic.test.sh
- test/drill-recorded.test.sh
- test/refs-not-closing.test.sh
scope:labels:
- changed-files:
- any-glob-to-any-file:

View file

@ -182,6 +182,13 @@ triage bug, and the move is to say so on the issue, not to guess.
absent that instruction `Closes #N` remains the default. The exception was
bought the hard way: #143 carried `Closes #137` as doctrine then required,
and the merge closed #137 with its post-merge criterion unmet (#151).
On a `Refs #N` PR, never put a closing keyword (`close`, `closes`,
`closed`, `fix`, `fixes`, `fixed`, `resolve`, `resolves`, `resolved`)
immediately before `#N` anywhere in the body — including the sentence
explaining why the PR does not close it. GitHub reads the whole body by
adjacency, not intent. Put the number first (`#N is closed by hand`) or
omit it (`triage closes the issue by hand`). A code span does not protect
the phrase: a backticked `Closes #199` still closed #199 (#200, #218).
Drafts are invisible to the reviewer panel on
purpose — the draft phase is yours.
- **The issue's acceptance criteria are your definition of done.** Reproduce

View file

@ -34,7 +34,12 @@ In order of authority:
not a defect: the issue directs it, triage owns that close, and a
request-changes on the "missing" keyword enforces the bug the shape
exists to fix — `Closes #137` closed its issue with a post-merge
criterion unmet (#151). Check every
criterion unmet (#151). For a `Refs #N` body, also verify that no closing
keyword immediately precedes `#N` anywhere in the body, even in prose
explaining the hand close or inside a code span: GitHub used those exact
shapes to close #209, #212 and #199 (#200, #218). The safe forms put the
number first (`#N is closed by hand`) or omit it (`triage closes the issue
by hand`). Check every
criterion; a PR that ships less than the issue says is a request-changes
even if the code is beautiful.
2. **The repo's load-bearing constraints** — the rules bought with

View file

@ -44,9 +44,11 @@ runs:
.data.repository.pullRequest
| if . == null then error("pull request was not returned") else .body // "" end
' <<<"$facts" >"$body_file"
jq -er '
jq -r '
.data.repository.pullRequest.closingIssuesReferences
| if .pageInfo.hasNextPage then
| if . == null then
error("closing issue references were not returned")
elif .pageInfo.hasNextPage then
error("more than 100 closing issue references; refusing a partial verdict")
else
.nodes[].number

4
changelog.d/218.md Normal file
View file

@ -0,0 +1,4 @@
### Added
- Pull requests that promise `Refs #N` now fail a read-only, body-edit-aware
guard if GitHub would close N through a keyword or sidebar link (#218).

View file

@ -124,14 +124,41 @@ the machinery at all:
consumer. In particular, `0.1.0` carries `changelog-armed`,
`changelog-monotonic` and `drill-recorded` plus `docs-sync`, but not
`changelog-assembled` or `runner-isolated`.
6. **Labels automation** (optional but recommended): the two callers from
6. **`.github/workflows/refs-guard.yml`** — the body-aware guard is its own
caller because `edited` is load-bearing: #200 gained its accidental
closing keyword after the PR opened, with no push to wake ordinary CI.
It costs the consumer one read-only workflow file and no other machinery:
```yaml
name: Refs guard
on:
pull_request:
types: [opened, edited, reopened, synchronize]
permissions:
contents: read
pull-requests: read
jobs:
refs-not-closing:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: heavy-duty/ceremony/actions/refs-not-closing@<pinned-tag>
```
`refs-not-closing` is **unreleased** until the first tag carrying #218.
Adopt this caller with that ordinary pin bump; never point only this file
at a moving or newer ref.
7. **Labels automation** (optional but recommended): the two callers from
[Labels automation](#labels-automation) — the event-facing labels
caller and the sweep caller (#209) — plus `.github/labels.conf`
(panel + the repo's `scope:*` rows) and `.github/labeler.yml` (the
path→scope globs). Run the sweep caller's `workflow_dispatch` once —
**this bootstraps the taxonomy, `release` label included** — and use it
again whenever an operator needs a full-board sweep immediately.
7. **The artifact hook** (optional): `.github/actions/release-artifact/`
8. **The artifact hook** (optional): `.github/actions/release-artifact/`
per [The artifact hook](#the-artifact-hook). No hook → the source
tarball is the package.
@ -156,6 +183,8 @@ precisely so the machinery is safe to work on
sibling `push:` silently kills a door (rig's review catch).
- [ ] Swap the guard *script* steps in `ci.yml` for the `uses:` steps in
the bootstrap list above (with `fetch-depth: 0` on the checkout).
- [ ] Add `refs-guard.yml` from the bootstrap list with the same ceremony
pin as the release caller and CI guard steps.
- [ ] Replace `labels.yml` with the caller from
[Labels automation](#labels-automation) and add the sweep caller
`labels-sweep.yml` beside it (#209); extract

View file

@ -64,6 +64,10 @@ check "#214 incident replays red" 1 "#212" guard incidents-214 212
body incidents-200 'Refs #199' 'A later edit added `Closes #199`.'
check "#200 incident replays red" 1 "#199" guard incidents-200 199
body multiple 'Refs #5 and Refs #7.' 'Triage closes #5 and fixes #7 by hand.'
check "failure names every intersecting issue" 1 \
"scheduled to close: #5 #7" guard multiple 5 7
for number in 207 191 190 176 165 164; do
body "incident-$number" "Refs #$number"
check "#$number incident replays green" 0 "no Refs target" \