diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37a6882..c17c424 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,10 @@ jobs: # The pin rules (issue #9; #1 D3): a stale CEREMONY_SELF_REF fails # CI here, not a consumer's release. run: bash .github/scripts/self-ref-check.sh + - name: Documentation availability markers + # Five stale markers survived the tags that shipped their machinery + # (#221); #238 makes the release candidate reject that drift. + run: bash .github/scripts/marker-check.sh - name: Tests env: # The npm-backed version_write case may skip locally when npm is diff --git a/changelog.d/238.md b/changelog.d/238.md new file mode 100644 index 0000000..6805c0b --- /dev/null +++ b/changelog.d/238.md @@ -0,0 +1,4 @@ +### Added + +- Guard documentation availability markers against missing issue citations + and release candidates that already ship the cited work (#238). diff --git a/test/marker-check.test.sh b/test/marker-check.test.sh new file mode 100644 index 0000000..7017e84 --- /dev/null +++ b/test/marker-check.test.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# Contract tests for .github/scripts/marker-check.sh (issue #238). The guard +# is driven against tracked fixture trees; set -u, not -e, because failures +# are behavior for the harness to inspect. +set -u + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +# shellcheck source=test/harness.sh +. "$ROOT/test/harness.sh" + +CHECK="$ROOT/.github/scripts/marker-check.sh" +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT + +fixture() { + local name="$1" version="$2" + mkdir -p "$TMP/$name/docs" "$TMP/$name/changelog.d" + git -C "$TMP/$name" init -q + printf '%s\n' "$version" >"$TMP/$name/VERSION" + printf '# Changelog\n\n## 0.5.0 — 2026-08-03\n\n- Shipped (#221).\n' \ + >"$TMP/$name/CHANGELOG.md" +} + +run_check() { + git -C "$TMP/$1" add . + bash "$CHECK" "$TMP/$1" +} + +fixture wrapped 0.6.0-dev +cat >"$TMP/wrapped/docs/CONSUMERS.md" <<'EOF' +The new guard remains **unreleased** +(#238) until the next tag. +EOF +check "a wrapped local citation is accepted" 0 "agree with the tree" \ + run_check wrapped + +fixture uncited 0.6.0-dev +printf 'The new guard remains **unreleased** for now.\n' \ + >"$TMP/uncited/docs/CONSUMERS.md" +check "an uncited marker fails on a dev tree with file and line" 1 \ + "docs/CONSUMERS.md:1" run_check uncited + +fixture shipped 0.6.0 +printf 'The new guard remains **unreleased** (#224).\n' \ + >"$TMP/shipped/docs/CONSUMERS.md" +cat >"$TMP/shipped/CHANGELOG.md" <<'EOF' +# Changelog + +## 0.6.0 — 2026-08-03 + +- The guard shipped (#224). + +## 0.5.0 — 2026-08-02 + +- Older work (#999). +EOF +check "a release rejects a marker cited by its top section" 1 \ + "docs/CONSUMERS.md:1: **unreleased** (#224)" run_check shipped + +fixture not-shipped 0.6.0 +printf 'Future work remains **unreleased** (#999).\n' \ + >"$TMP/not-shipped/docs/CONSUMERS.md" +cp "$TMP/shipped/CHANGELOG.md" "$TMP/not-shipped/CHANGELOG.md" +check "a release keeps a marker absent from its top section" 0 \ + "agree with the tree" run_check not-shipped + +fixture dev-shipped 0.6.0-dev +printf 'Future work remains **unreleased** (#224).\n' \ + >"$TMP/dev-shipped/docs/CONSUMERS.md" +cp "$TMP/shipped/CHANGELOG.md" "$TMP/dev-shipped/CHANGELOG.md" +check "a dev tree does not compare markers with shipped sections" 0 \ + "agree with the tree" run_check dev-shipped + +fixture cross-repo 0.6.0 +printf 'Crew work remains **unreleased** (crew#293).\n' \ + >"$TMP/cross-repo/docs/CONSUMERS.md" +cat >"$TMP/cross-repo/CHANGELOG.md" <<'EOF' +# Changelog + +## 0.6.0 — 2026-08-03 + +- Local work shipped (#293). +EOF +check "a cross-repo citation is valid and ignored by release comparison" 0 \ + "agree with the tree" run_check cross-repo + +fixture exclusions 0.6.0-dev +printf '# Notes\n\n## Unreleased\n' >"$TMP/exclusions/NOTES.md" +printf -- '- A fragment may say **unreleased** without being documentation.\n' \ + >"$TMP/exclusions/changelog.d/999.md" +check "headings and changelog fragments do not trip the guard" 0 \ + "agree with the tree" run_check exclusions + +summary