diff --git a/.github/actions/report/README.md b/.github/actions/report/README.md index 03eb48a..7ef1c92 100644 --- a/.github/actions/report/README.md +++ b/.github/actions/report/README.md @@ -10,7 +10,7 @@ as SVG/HTML in an uploaded artifact. - run: cargo test env: TERMLENS_ARTIFACT_DIR: ${{ runner.temp }}/termlens -- uses: vyncint/termlens/.github/actions/report@v0.10.1 +- uses: vyncint/termlens/.github/actions/report@v0.11.1 if: failure() ``` diff --git a/.github/scripts/check-report-pin.sh b/.github/scripts/check-report-pin.sh new file mode 100755 index 0000000..006c08a --- /dev/null +++ b/.github/scripts/check-report-pin.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# The `report` action is pinned by tag in three hand-maintained files, and +# the workspace version is a fourth copy of the same number. Three releases +# bumped two of the three and missed the action's own README (#386); this +# makes the pin checked rather than maintained, in the shape +# `check-candidate-statement.sh` already uses for the other promise made in +# more than one place. +# +# The three files are checked as a set rather than one by one: a listed file +# with no pin at all fails the same way a stale one does, so a copy that +# quietly disappeared is caught, and the file list is a single place to add +# the next one to. +# +# `REPORT_PIN_VERSION` overrides the workspace version, for the selftest +# only (`tools/report-pin-gate-selftest/`); CI never sets it. +# +# Usage: check-report-pin.sh [file ...] +# (default: the three files that carry the pin) +set -euo pipefail + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" + +version="${REPORT_PIN_VERSION:-$(sed -n 's/^version *= *"\(.*\)"$/\1/p' "$root/Cargo.toml" | head -1)}" +if [ -z "$version" ]; then + echo "::error::Cargo.toml has no workspace version" + exit 1 +fi + +files=("$@") +if [ "${#files[@]}" -eq 0 ]; then + files=(README.md .github/actions/report/README.md skills/termlens/SKILL.md) +fi + +status=0 +refs=0 +for file in "${files[@]}"; do + if [ ! -f "$file" ]; then + echo "::error::\"$file\" is not a file" + status=1 + continue + fi + found=0 + while IFS= read -r ref; do + [ -z "$ref" ] && continue + found=$((found + 1)) + refs=$((refs + 1)) + if [ "$ref" != "report@v$version" ]; then + echo "::error::$file pins \`$ref\` but the workspace version is $version" + status=1 + fi + done <<<"$(grep -oE 'actions/report@v[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.-]+)?' "$file" \ + | sed 's|^actions/||' | sort -u || true)" + if [ "$found" -eq 0 ]; then + echo "::error::$file carries no \`actions/report@vX.Y.Z\` pin" + status=1 + fi +done + +if [ "$status" -eq 0 ]; then + echo "report action pin: $refs ref(s) across ${#files[@]} file(s), every one at report@v$version" +fi +exit "$status" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 171c4a9..f612916 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -186,6 +186,10 @@ jobs: # misspelled org repository and a deleted in-repo target are both # asserted to go red, so today's green run means something. - run: tools/link-gate-selftest/run.sh + # …and the same for the report-action pin gate (#386): a stale ref, a + # listed file that lost its ref, and one stale ref among two are all + # asserted to go red. + - run: tools/report-pin-gate-selftest/run.sh # The emit fixture's documented steps against the ones it implements # (#318). It lives in this job because it needs no toolchain — it is # two greps and a `comm` — and the fixture is what almost every @@ -294,6 +298,11 @@ jobs: # header and docs/STABILITY.md, in the same words (#328, #334). Three # copies of one promise drift; this is the check that stops it. - run: .github/scripts/check-candidate-statement.sh + # The `report` action's tag is pinned in three hand-maintained files + # and the workspace version is a fourth copy of the same number; two + # releases bumped two of the three and missed the action's own README + # (#386). This is the check that keeps the four in step. + - run: .github/scripts/check-report-pin.sh deny: name: deny diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cce2cd6..813036a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,6 +53,7 @@ cargo build -p termlens-cli # then the CLI's docume RUSTDOCFLAGS='-D warnings' cargo doc --no-deps RUSTDOCFLAGS='-D warnings' cargo doc --no-deps --all-features .github/scripts/check-candidate-statement.sh # README, CHANGELOG and STABILITY state the candidate in the same words +.github/scripts/check-report-pin.sh # the three report@v refs agree with the workspace version cargo deny check # cargo install cargo-deny pipx run zizmor==1.29.0 --persona=pedantic .github/workflows/ # workflow audit; needs GH_TOKEN for the online checks cargo +1.85 check --workspace --exclude ratatui-app --locked --all-targets # the MSRV: `rust-version` in Cargo.toml (the ratatui fixture needs 1.88) @@ -61,6 +62,7 @@ cargo +1.85 check -p termlens --no-default-features --all-targets --locked cargo clippy --workspace --all-targets --all-features --target x86_64-pc-windows-msvc -- -D warnings # the Windows build, from any host: `rustup target add x86_64-pc-windows-msvc` once tools/semver-gate-selftest/run.sh # the semver gate can fail (cargo install cargo-semver-checks)… tools/link-gate-selftest/run.sh # …and so can the link gate, with no extra tooling +tools/report-pin-gate-selftest/run.sh # …and the report-pin gate, likewise .github/scripts/check-semver.sh 0.11.1 # …and the public API is compatible with the last published release ``` diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 35766e7..8b52c00 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -71,8 +71,12 @@ gh run watch # ubuntu, macos, windows × 5 shar # 1. Bump the version (workspace.package.version in root Cargo.toml), and # the same number in crates/termlens-cli/Cargo.toml's `termlens = { version -# = … }` — a path dependency publishes by its version. +# = … }` — a path dependency publishes by its version. The `report@vX.Y.Z` +# action pin lives in three hand-maintained files, and releases bumped two +# of the three twice before the gate that catches it existed (#386); +# `check-report-pin.sh` fails whenever one disagrees with this number. $EDITOR Cargo.toml crates/termlens-cli/Cargo.toml +$EDITOR README.md .github/actions/report/README.md skills/termlens/SKILL.md cargo check --workspace # refreshes Cargo.lock # 2. Move the CHANGELOG section. A `- **Breaking:**` bullet moves with it, diff --git a/tools/report-pin-gate-selftest/absent.md b/tools/report-pin-gate-selftest/absent.md new file mode 100644 index 0000000..ed1e3cc --- /dev/null +++ b/tools/report-pin-gate-selftest/absent.md @@ -0,0 +1,6 @@ +# Fixture: a file that lost its pin + +A listed file carrying no `actions/report@vX.Y.Z` ref is drift too; without +this the gate would pass a file whose pin someone deleted. + +- uses: vyncint/termlens/.github/actions/report diff --git a/tools/report-pin-gate-selftest/matching.md b/tools/report-pin-gate-selftest/matching.md new file mode 100644 index 0000000..bcbb36e --- /dev/null +++ b/tools/report-pin-gate-selftest/matching.md @@ -0,0 +1,6 @@ +# Fixture: a matching pin + +The ref below must equal the workspace version — 9.9.9 as the selftest +injects it — exactly as the three real files' refs must. + +- uses: vyncint/termlens/.github/actions/report@v9.9.9 diff --git a/tools/report-pin-gate-selftest/run.sh b/tools/report-pin-gate-selftest/run.sh new file mode 100755 index 0000000..fb079b4 --- /dev/null +++ b/tools/report-pin-gate-selftest/run.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Proves the report-action pin gate can fail, before it is trusted to pass +# (#386). +# +# Four fixtures model the ways the pin drifts: a matching ref, a stale one, +# a listed file that lost its ref entirely, and a file with two refs where +# only one was bumped. They are written against an injected version so they +# do not go stale with the workspace every release; the last assertion drops +# the injection and runs the repository's own three files against the real +# workspace version, which is what a green CI run means. +# +# Usage: tools/report-pin-gate-selftest/run.sh +set -euo pipefail + +cd "$(dirname "${BASH_SOURCE[0]}")" +root="$(cd ../.. && pwd)" +gate="$root/.github/scripts/check-report-pin.sh" +status=0 + +# `