fix: align marker guard with release oracle

This commit is contained in:
Andriujose 2026-08-03 22:56:02 +00:00
parent 7200c8da49
commit d87d76d64b
3 changed files with 58 additions and 18 deletions

View file

@ -3,7 +3,10 @@
# outlived the releases that shipped their machinery. A release candidate must # outlived the releases that shipped their machinery. A release candidate must
# therefore reject a marker its assembled changelog makes false, while every # therefore reject a marker its assembled changelog makes false, while every
# tree rejects an untraceable marker. Cross-repo citations are traceable but # tree rejects an untraceable marker. Cross-repo citations are traceable but
# are not compared with this repository's changelog. # are not compared with this repository's changelog; a marker for this repo's
# own issue uses bare #N, never a self-qualified repository citation (#238 D8).
# CHANGELOG.md is the release oracle and immutable shipped prose, so it and the
# fragments that feed it are excluded from the documentation scan (#238 D5).
# #
# Usage: marker-check.sh [tree-dir] (default: the repository root) # Usage: marker-check.sh [tree-dir] (default: the repository root)
set -euo pipefail set -euo pipefail
@ -26,25 +29,36 @@ trap 'rm -f "$marker_records"' EXIT
mapfile -d '' markdown_files < <(git -C "$tree" ls-files -z -- '*.md') mapfile -d '' markdown_files < <(git -C "$tree" ls-files -z -- '*.md')
for relative in "${markdown_files[@]}"; do for relative in "${markdown_files[@]}"; do
case "$relative" in case "$relative" in
changelog.d/*) continue ;; CHANGELOG.md|changelog.d/*) continue ;;
esac esac
if ! awk -v file="$relative" ' if ! awk -v file="$relative" '
{ lines[NR] = $0 } function without_inline_code(text, before, after) {
while (match(text, /`[^`]*`/)) {
before = substr(text, 1, RSTART - 1)
after = substr(text, RSTART + RLENGTH)
text = before after
}
return text
}
{
lines[NR] = $0
scan_lines[NR] = without_inline_code($0)
}
END { END {
token = "**unreleased**" token = "**unreleased**"
citation_re = "^[[:space:]]*\\((([[:alnum:]_.-]+/)?[[:alnum:]_.-]+)?#[0-9]+\\)" citation_re = "^[[:space:]]*\\((([[:alnum:]_.-]+/)?[[:alnum:]_.-]+)?#[0-9]+\\)"
bad = 0 bad = 0
for (line_no = 1; line_no <= NR; line_no++) { for (line_no = 1; line_no <= NR; line_no++) {
remaining = lines[line_no] remaining = scan_lines[line_no]
offset = 0 offset = 0
while ((at = index(remaining, token)) != 0) { while ((at = index(remaining, token)) != 0) {
rest = substr(remaining, at + length(token)) rest = substr(remaining, at + length(token))
candidate = rest candidate = rest
next_line = line_no + 1 next_line = line_no + 1
while (candidate ~ /^[[:space:]]*$/ && next_line <= NR) { while (candidate ~ /^[[:space:]]*$/ && next_line <= NR) {
candidate = candidate " " lines[next_line] candidate = candidate " " scan_lines[next_line]
next_line++ next_line++
} }
@ -60,7 +74,7 @@ for relative in "${markdown_files[@]}"; do
} }
offset += at + length(token) - 1 offset += at + length(token) - 1
remaining = substr(lines[line_no], offset + 1) remaining = substr(scan_lines[line_no], offset + 1)
} }
} }
exit bad exit bad

View file

@ -113,14 +113,14 @@ the machinery at all:
self-hosted runner still wants it: the guard's value is the day self-hosted runner still wants it: the guard's value is the day
somebody adds one. somebody adds one.
This guide documents `main`. New machinery is marked with the lowercase This guide documents `main`. A marker is the literal token
word `unreleased` in bold, immediately followed by its issue citation `**unreleased**` immediately followed by its issue citation (for example,
(for example, `(#238)`); whitespace between them may include a line break. `(#238)`); whitespace between them may include a line break. A citation is
A citation is mandatory, because a marker the guard cannot trace is a mandatory, because a marker the guard cannot trace is a marker it cannot
marker it cannot prove false. Cross-repo citations such as `(crew#293)` prove false. A marker for this repository's own issue uses bare `#N`.
satisfy that traceability rule but are not compared with this repository's Cross-repo citations such as `(crew#293)` satisfy the traceability rule but
release section. The ceremony-only `marker-check.sh` guard enforces both are not compared with this repository's release section. The ceremony-only
rules. The release PR that ships the machinery clears, in that same PR, `marker-check.sh` guard enforces these rules. The release PR that ships the machinery clears, in that same PR,
every marker its own assembled section makes false: the section cites its every marker its own assembled section makes false: the section cites its
issues, each marker cites the same issue, and the release PR's diff is the issues, each marker cites the same issue, and the release PR's diff is the
one place both halves are visible at once (#221). If an action does not exist at the one place both halves are visible at once (#221). If an action does not exist at the
@ -153,7 +153,7 @@ the machinery at all:
- uses: heavy-duty/ceremony/actions/refs-not-closing@<pinned-tag> - uses: heavy-duty/ceremony/actions/refs-not-closing@<pinned-tag>
``` ```
`refs-not-closing` is **unreleased** until the first tag carrying #218. `refs-not-closing` is **unreleased** (#218) until the first tag carrying it.
Adopt this caller with that ordinary pin bump; never point only this file Adopt this caller with that ordinary pin bump; never point only this file
at a moving or newer ref. at a moving or newer ref.
7. **Labels automation** (optional but recommended): the two callers from 7. **Labels automation** (optional but recommended): the two callers from
@ -592,7 +592,7 @@ mirror), `--check` re-diffs it in CI on every PR, so a hand edit or a stale
pin goes red instead of quietly governing. pin goes red instead of quietly governing.
`RELEASES.md` joins that mirror with the first tag carrying ceremony#248. `RELEASES.md` joins that mirror with the first tag carrying ceremony#248.
It is **unreleased** until that tag exists: consumers add It is **unreleased** (#248) until that tag exists: consumers add
`.ceremony/RELEASES.md` only with the ordinary pin bump and re-sync, never by `.ceremony/RELEASES.md` only with the ordinary pin bump and re-sync, never by
copying it ahead of their pinned doctrine set. copying it ahead of their pinned doctrine set.

View file

@ -84,11 +84,37 @@ EOF
check "a cross-repo citation is valid and ignored by release comparison" 0 \ check "a cross-repo citation is valid and ignored by release comparison" 0 \
"agree with the tree" run_check cross-repo "agree with the tree" run_check cross-repo
fixture self-qualified 0.6.0
printf 'Ceremony work remains **unreleased** (ceremony#248).\n' \
>"$TMP/self-qualified/docs/CONSUMERS.md"
cat >"$TMP/self-qualified/CHANGELOG.md" <<'EOF'
# Changelog
## 0.6.0 — 2026-08-03
- Ceremony work shipped (#248).
EOF
check "a self-qualified citation is ignored; local markers must use bare #N" 0 \
"agree with the tree" run_check self-qualified
fixture exclusions 0.6.0-dev fixture exclusions 0.6.0-dev
printf '# Notes\n\n## Unreleased\n' >"$TMP/exclusions/NOTES.md" cat >"$TMP/exclusions/NOTES.md" <<'EOF'
# Notes
## Unreleased
The marker token is `**unreleased**`.
EOF
printf -- '- A fragment may say **unreleased** without being documentation.\n' \ printf -- '- A fragment may say **unreleased** without being documentation.\n' \
>"$TMP/exclusions/changelog.d/999.md" >"$TMP/exclusions/changelog.d/999.md"
check "headings and changelog fragments do not trip the guard" 0 \ cat >"$TMP/exclusions/CHANGELOG.md" <<'EOF'
# Changelog
## 0.5.0 — 2026-08-03
- Shipped prose may discuss **unreleased** markers without becoming one.
EOF
check "headings, inline mentions, changelog entries, and fragments are excluded" 0 \
"agree with the tree" run_check exclusions "agree with the tree" run_check exclusions
summary summary