open-standards/scripts/pr-touches-paths-selftest.sh
Transaction Science a544054098
All checks were successful
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 1h4m33s
standards workspaces build + test / aac-rs (pull_request) Successful in 15m41s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 5m23s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 4m7s
standards workspaces build + test / map-rs (pull_request) Successful in 5m41s
standards workspaces build + test / mesh-rs (pull_request) Successful in 2m16s
standards workspaces build + test / openpay (pull_request) Successful in 13m43s
standards workspaces build + test / arl-rs (pull_request) Successful in 1m13s
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m55s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m30s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 1m13s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 1m17s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m18s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m16s
standards workspaces build + test / sense-rs (pull_request) Successful in 1m5s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 3m37s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 4s
workflow hygiene / every workflow supersedes its own queue (pull_request) Successful in 4s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 2m59s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 2m47s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 4m54s
wai-pack / wai-pack · linux (pull_request) Successful in 1m48s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 3m0s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 24s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 2m0s
wai-pack / wai-pack · macos (pull_request) Successful in 2m11s
ci: a rebase ran every path-filtered workflow, because paths: matches the push delta
Forgejo matches a workflow's `paths:` filter against the **push delta**, not the
pull request's diff. A rebase moves a branch across every commit main has
gained, so its delta touches nearly every path and every path-filtered workflow
fires.

Measured on this repo, as a clean natural experiment — five PRs, all changing
only `jcp/` or `mesh/`:

| PRs | how pushed | workflows triggered |
|---|---|---|
| #189, #190 | fresh branch, one push | **2** |
| #165, #177, #183 | force-push after rebase | **15** |

Fifteen includes JPEG-AI whole decode, `wai-pack` on three platforms, and
cross-architecture byte-equality on three — none of which a change to
`jcp/jcp-rs/crates/jcp-grant/src/checkpoint.rs` can affect. Rebasing three PRs
put the queue 65 runs deep behind one macOS runner, which delays everyone's real
jobs, and there is no way to rebase without it: a merge of main into the branch
has the same delta.

So the `paths:` filters still gate the *trigger*, and a new `relevant` job gates
the *work*, from the pull request's own diff. It reads the path list out of the
workflow file it guards, so there is one list rather than a third copy to drift
from the two `on:` blocks. On a push to main there is no pull-request context,
the shas are empty, and the gate answers `true` — main runs everything, as
before.

**It fails open, by construction.** A gate that decides whether tests run is
load-bearing in the dangerous direction: a wrong `false` silently skips a whole
workflow, which is the "passes but tested nothing" failure. Every way the script
can fail to decide — a missing workflow file, a filter it cannot parse, an empty
or unresolvable sha, too few arguments, an empty diff — prints `true`. The
self-test pins all six of those, both directions of the real decision, and that
`**` spans a path separator while `*` does not.

Writing that self-test caught one of its own cases: the single-star case first
created a top-level `.md` alongside the nested one, so the glob matched
legitimately and the case proved nothing. It now changes only the nested file,
and asserts separately that the glob does match at its own level — so it cannot
pass by never matching anything.

Applied to `wai-pack.yml` alone, not all eighteen. What a *skipped* job reports
as a commit status on this forge is the open question, and a required context
that never reports would make PRs unmergeable. That failure is at least loud
rather than silent, but it is worth learning on the most expensive workflow —
six job instances across three platforms — before changing the rest. Validated
against real branches first: every WAI workflow answers `false` for #165's
jcp-only diff and `true` for a WAI branch, and `standards-build.yml` correctly
answers `true` for #165 because it builds every workspace.

The hygiene workflow runs the self-test, so the gate cannot regress silently,
and its own `paths:` filter now lists the scripts it checks.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-08 11:40:23 -04:00

70 lines
3.1 KiB
Shell
Executable file

#!/usr/bin/env bash
# Self-test for pr-touches-paths.py, run by the workflow-hygiene job.
#
# A gate that decides whether tests run is itself load-bearing: a wrong `false`
# silently skips a whole workflow. These cases pin both directions and, most
# importantly, that every way of failing fails OPEN.
set -euo pipefail
cd "$(dirname "$0")/.."
G=scripts/pr-touches-paths.py
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
fail=0
check() { # name expected actual
if [ "$2" = "$3" ]; then printf ' ok %s\n' "$1"
else printf ' FAIL %s: expected %s, got %s\n' "$1" "$2" "$3"; fail=1; fi
}
# A throwaway repo, so the diffs are real git diffs and not a mock.
repo="$tmp/r"; mkdir -p "$repo/wai/wai-pack" "$repo/jcp"
git -C "$repo" init -q
git -C "$repo" config user.email t@e; git -C "$repo" config user.name t
echo x > "$repo/jcp/SPEC.md"; echo y > "$repo/wai/wai-pack/lib.rs"
git -C "$repo" add -A; git -C "$repo" commit -qm base
BASE=$(git -C "$repo" rev-parse HEAD)
echo changed > "$repo/jcp/SPEC.md"
git -C "$repo" add -A; git -C "$repo" commit -qm jcp-only
JCP=$(git -C "$repo" rev-parse HEAD)
echo changed > "$repo/wai/wai-pack/lib.rs"
git -C "$repo" add -A; git -C "$repo" commit -qm wai-too
WAI=$(git -C "$repo" rev-parse HEAD)
wf="$tmp/w.yml"
cat > "$wf" <<'Y'
on:
pull_request:
paths:
- 'wai/wai-pack/**'
Y
run() { ( cd "$repo" && python3 "$OLDPWD/$G" "$1" "$2" "$3" ); }
check "a jcp-only diff does not touch a wai filter" false "$(run "$wf" "$BASE" "$JCP")"
check "a diff that touches the filter runs" true "$(run "$wf" "$BASE" "$WAI")"
# Every failure mode must fail OPEN.
check "a missing workflow file" true "$(run "$tmp/nope.yml" "$BASE" "$JCP")"
check "a workflow with no filter" true "$(printf 'on:\n pull_request:\n' > "$tmp/nofilter.yml"; run "$tmp/nofilter.yml" "$BASE" "$JCP")"
check "an empty base sha" true "$(run "$wf" "" "$JCP")"
check "a sha git cannot resolve" true "$(run "$wf" "$BASE" deadbeefdeadbeefdeadbeefdeadbeefdeadbeef)"
check "too few arguments" true "$( cd "$repo" && python3 "$OLDPWD/$G" "$wf" )"
check "an empty diff" true "$(run "$wf" "$JCP" "$JCP")"
# `**` spans separators; `*` does not.
cat > "$tmp/star.yml" <<'Y'
on:
pull_request:
paths:
- 'jcp/*.md'
Y
# Only the NESTED file changes, so `jcp/*.md` must not match it. (A top-level
# .md in the same commit would match legitimately and prove nothing — which is
# how this case failed the first time it was written.)
mkdir -p "$repo/jcp/sub"; echo z > "$repo/jcp/sub/x.md"
git -C "$repo" add -A >/dev/null; git -C "$repo" commit -qm sub >/dev/null
SUB=$(git -C "$repo" rev-parse HEAD)
check "a single star does not span a separator" false "$(run "$tmp/star.yml" "$WAI" "$SUB")"
# And it does match a file at that level, so the glob is not simply never matching.
echo z > "$repo/jcp/top.md"
git -C "$repo" add -A >/dev/null; git -C "$repo" commit -qm top >/dev/null
check "a single star matches at its own level" true "$(run "$tmp/star.yml" "$SUB" "$(git -C "$repo" rev-parse HEAD)")"
[ "$fail" = 0 ] && echo "pr-touches-paths self-test: all cases pass" || { echo "pr-touches-paths self-test FAILED"; exit 1; }