test(785): pin the workflow's TRIGGERS, and sweep the retracted figure by subject
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 11s
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 21s
PR Gates / Docs update reminder (pull_request) Successful in 19s
PR Gates / decisions lifecycle (pull_request) Successful in 30s
review-verdict/h10 Review-verdict: MERGEABLE @ 55a7df2 (base: main)
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 18s
Review verdict / Set review-verdict status (pull_request_target) Successful in 19s
PR Gates / Script tests (pytest) (pull_request) Successful in 4m12s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m48s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m25s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Skipped
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 5m51s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 9s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 7s

Fifth cold review returned MERGEABLE, having reproduced all five of the previous commit's
measurements exactly and verified by execution that all twelve parametrised cases decide for
their labelled reason rather than incidentally. Three residual items, none of which was
coverage claimed and not delivered.

The `on:` trigger was an uncovered channel. `_active_runs` started at `jobs:`, so a workflow
whose triggers no longer include `pull_request` left a perfectly healthy `decisions-guard`
job gating nothing while the proof stayed green. Nothing claimed to cover it, which is why
it was LOW rather than a false coverage claim — but it is two lines and it is now the ninth
disablement case.

`on` parses to the BOOLEAN True in YAML 1.1, not the string `"on"`, so the key is looked up
both ways. Verified which branch is live rather than assuming: pyyaml produces `True` here,
so a lookup of `"on"` alone would have yielded an empty trigger set and fired the assertion
on a correct workflow — a false red that the anti-vacuity check (the unmodified workflow
must still hold) is what catches.

`script.replace("python3", sys.executable)` was unbounded, having been `count=1` on a single
extracted line before the withdrawal. It rewrote every occurrence including inside a path, so
a step spelled `/usr/bin/python3` would have become nonsense and failed with a red blaming
the catalog guard. Now substituted only where `python3` is a bare command word.

And the retracted "1,488 records" figure survived in a second place — the leak guard's
docstring — because the previous sweep went by file rather than by subject. Replaced with the
reproduction and the figure that survives it, the same wording the inventory carries.
`stale-comments-sweep-by-subject-not-verb`.

Two channels remain uncovered and are deliberately not guessed at: a job `if:` whose
expression is false only at run time, and `continue-on-error` as a non-literal expression.
`_falsey`'s docstring already declines these as undecidable outside the runner, and no row,
test name or docstring claims them.

Refs: #785
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-21 21:29:00 +02:00
co-authored by Claude Opus 5
parent 52ed319c8a
commit 55a7df226e
2 changed files with 24 additions and 3 deletions
+21 -1
View File
@@ -169,6 +169,18 @@ def _active_runs(workflow: Path | None = None) -> list[str]:
stepping around it. No shell parsing, and the ambiguous cases resolve by execution.
"""
doc = yaml.safe_load((workflow or WORKFLOW).read_text())
# The workflow's TRIGGERS, before its jobs. A `decisions-guard` job that is perfectly healthy
# gates nothing if the workflow stopped running on pull requests, and starting at `jobs:` cannot
# see that. Note `on` parses to the boolean True in YAML 1.1 (the Norway problem's cousin), so
# the key is looked up both ways rather than assumed.
triggers = (doc or {}).get("on", (doc or {}).get(True)) or {}
names = set(triggers) if isinstance(triggers, dict) else {triggers} if isinstance(triggers, str) else set(triggers)
assert "pull_request" in names, (
"pr-checks.yml no longer runs on `pull_request`, so NO gate in it — including the catalog "
f"guard — fires on a PR. Triggers found: {sorted(str(n) for n in names)}"
)
jobs = (doc or {}).get("jobs") or {}
job = jobs.get("decisions-guard")
assert job is not None, (
@@ -250,7 +262,11 @@ def _ci_check_command(workflow: Path | None = None) -> list[str]:
"the catalog step's script interpolates an Actions expression, which cannot be expanded "
f"outside the runner — this proof would be executing something else: {script!r}"
)
return ["bash", "-c", script.replace("python3", sys.executable)]
# Substitute the interpreter only where `python3` is a bare command word. A plain
# `str.replace` rewrites EVERY occurrence, including inside a path — `/usr/bin/python3` would
# become `/usr/bin/<venv>/bin/python3` and fail with ENOENT, a red blaming the catalog guard for
# something this line did.
return ["bash", "-c", re.sub(r"(?<![\w/])python3\b", sys.executable, script)]
def _proof_holds(workflow: Path, repo: Path) -> bool:
@@ -345,6 +361,10 @@ _STEP = (
1,
),
),
(
"the WORKFLOW no longer runs on pull requests",
lambda s: s.replace("on:\n pull_request:", "on:\n workflow_dispatch:", 1),
),
(
"the command survives only as HEREDOC DATA",
lambda s: s.replace(
@@ -63,8 +63,9 @@ def _env() -> dict:
to the REAL `$HOME/.cache/ersatztv/hook-fire/` log instead of the fixture's tmp dir. That is not
untidiness: it is #776's defect reintroduced in the file that is meant to prove #776's hooks,
and it corrupts the `hook-fire-log.sh report` surface this repo cites as the observability claim
for every guard still graded NONE. Measured before the fix: 1,488 records for the two fake
session ids below in the production log.
for every guard still graded NONE. The reproduction, rather than a figure whose evidence has
since been deleted: reintroduce the snapshot and run this file, then count records for the two
synthetic session ids below — 58 per run, on macOS and Linux alike.
"""
return {**os.environ, "CLAUDE_PROJECT_DIR": str(REPO_ROOT)}