forked from heavy-duty/ceremony
fix: align marker guard with release oracle
This commit is contained in:
parent
7200c8da49
commit
d87d76d64b
3 changed files with 58 additions and 18 deletions
26
.github/scripts/marker-check.sh
vendored
26
.github/scripts/marker-check.sh
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue