Build ErsatzTV Image / CI toolchain image resolves (pull_request) Successful in 35s
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 57s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 37s
PR Gates / Docs update reminder (pull_request) Successful in 1m0s
PR Gates / decisions lifecycle (pull_request) Successful in 20s
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 17s
review-verdict/h10 Review-verdict: MERGEABLE @ a7d91bf (base: main)
Review verdict / Set review-verdict status (pull_request_target) Successful in 45s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 9m25s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m17s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Skipped
PR Gates / Script lint and tests (ruff + pytest) (pull_request) Successful in 19m27s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 6m4s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 8s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 7s
`docs.no-session-narrative` reaches every durable artifact, but its detector scanned only `docs/**/*.md` and root markdown, and nothing had ever swept the rest. The issue named four sites from one grep and called them a floor. Deriving the population instead — a whitespace-joined sweep over every tracked file outside the detector, for the detector's own phrasings plus the attribution and review-round class #812 found — gave 453 sites in 108 files at `fb5592971`, and a second pass for phrasings the first list missed (hyphenated `round-N`, "an earlier version", "the reviewer proved") added residuals in the same files. Every site was classified with #812's three dispositions (CUT / SEVER / KEEP with its sub-kind) under the who-benefits test; the per-site manifests are on the PR. The rejected designs, tested-and-rejected fixtures, measurements and traps stay; the attribution of who found them and the round in which they were found go. The detector's population grows to `.claude/`, `.gitea/`, `.husky/` and `scripts/` regardless of extension, minus the detector and its own test (whose fixtures ARE the phrasings) and minus `scripts/tests/fixtures/` (test data, including decision-record copies — the same reasoning as the records' own exemption, and what keeps the record's depth measurement true), and `--all` lists tracked REGULAR files only — a symlink's content is its target and a gitlink has none. The #812 argument for leaving `docs/superpowers/**` in the population runs the other way here: `--diff` sees only ADDED lines, and 287 of the 453 sites were under 30 days old — this corpus is where narrative is being added, so the advisory nudge has reach. Density agrees: 56 line-mode hits over the 113 regular files the predicate admits, against 9 over 66 docs files before #812. `web/` and C# stay out on the same measurement (3 of 74 PATTERNS-matching sites, ~4,600 files). The predicate did not grow: PATTERNS matched 74 of 453 sites, and widening the word list to the attribution class is the treadmill the withdrawn parity test ran on. The population oracle is restated over segments with the new arms, the synthetic cross product gains the process heads and non-markdown extensions, a fixture witnesses that a tracked symlink is neither scanned nor counted, a `.py.bak` axis separates a by-name exemption from a `startswith` over the same tuple, and eight mutants (drop the process arm, drop the by-name exemption, exempt by `startswith`, drop or add a prefix, drop the fixtures exemption, list only markdown, drop the symlink filter, test the mode per row instead of per path) each redden it. A pre-existing silent drop in `--diff` goes with it: git tab-terminates a `+++` filename that contains a space, and the kept tab made `is_scanned_path` refuse the file with no notice — fixed, with a positive control and its own mutant. Code is unchanged by construction, measured per file type against `origin/main`: Python modules are AST-equal with docstrings stripped, except `#` lines inside the embedded fixture programs (string literals) of three test modules; workflows differ only in `#` lines inside `run:` block scalars; shell, C#, TypeScript and jq are equal with comment lines stripped. The stated exceptions: the detector and its test, 26 vitest titles that carried review-round or severity labels or a reviewer attribution (call sites whose title changed — every changed title line walked back to its `it(` / `it.each(...)(` anchor, so a `' + '` concatenation counts once), two registry note strings and the mutation manifest's prose fields. scripts/tests: 1565 passed. Web: lint, typecheck, 1319 tests green. Closes #876. Decisions-Edit: yes Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PEcBoFw7ctrf3Nb7R7x7wk
665 lines
37 KiB
Python
665 lines
37 KiB
Python
"""No file population in this repo admits a file git does not track (ersatztv#806).
|
|
|
|
The regression for #778's third defect, hoisted to cover every guard that shares the mechanism
|
|
rather than being copied into each of them. `Path.rglob` enumerated `.husky/_/` — 17 husky shims
|
|
generated by `npm ci`, gitignored and untracked — so `test_remote_state_inventory.py` was RED on
|
|
every developer checkout and GREEN in CI, whose `script-tests` job checks out and pip-installs but
|
|
never runs `npm ci`. A guard that fails everywhere except where it runs trains its readers to ignore
|
|
it, and it did so on the artifact whose entire thesis is population correctness.
|
|
|
|
TWO PROOFS, because they fail differently and either alone leaves a hole.
|
|
|
|
* `test_the_primitive_REALLY_excludes_an_untracked_file` builds a throwaway git repo, commits one
|
|
file, leaves an identical sibling untracked, and runs the real derivation against it. It proves
|
|
the mechanism by EXECUTING it rather than by recognising its shape — no monkeypatching, no
|
|
stand-in for git. Nothing here is a claim about `git ls-files`; it is `git ls-files`.
|
|
* `test_no_derivation_admits_an_untracked_file` narrows the tracked set under each real derivation
|
|
and requires the dropped member to vanish from the population even though the file is still on
|
|
disk and still matches the scope. That is the property stated over the ACTUAL guards, so a
|
|
future refactor that quietly reintroduces a filesystem walk in any one of them fails here rather
|
|
than on somebody's laptop.
|
|
|
|
`DERIVATIONS` is the reason this file is not one near-copy per derivation: a guard that starts deriving a file
|
|
population registers here, and both proofs cover it for free. The register is hand-written and that
|
|
is a SCOPE decision, not a population one — per `testing.guard-derives-population-from-source`, a
|
|
scope mirroring an authoritative source needs its own equality check, and
|
|
`test_every_index_derived_module_is_registered` is it: it reads which modules import the shared
|
|
helper and demands each one appear below, so adding another derivation and forgetting this file is
|
|
red rather than silently uncovered.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ast
|
|
import glob
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from scripts.tests import test_ci_image_pin_population as image_pin
|
|
from scripts.tests import test_ci_status_context_uniqueness as ctx_unique
|
|
from scripts.tests import test_guard_inventory as guard_inventory
|
|
from scripts.tests import test_hook_fire_log as hook_fire
|
|
from scripts.tests import test_image_build_delegates_the_spa_suite as image_build
|
|
from scripts.tests import test_pr_changed_files as pr_changed
|
|
from scripts.tests import test_remote_state_inventory as remote_state
|
|
from scripts.tests import test_workflow_job_guards as job_guards
|
|
from scripts.tests import test_workflow_persist_credentials as persist_creds
|
|
from scripts.tests import tracked_files
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
TESTS_DIR = REPO_ROOT / "scripts" / "tests"
|
|
|
|
|
|
class _EmptyScan:
|
|
"""An exhausted ITERATOR that is also a context manager, standing in for `os.scandir`.
|
|
|
|
Both halves are load-bearing and each was missing in turn. `os.scandir` is used as
|
|
`with os.scandir(...) as it`, so a bare iterator broke the context-manager protocol; and
|
|
`os.walk` does `entry = next(scandir_it)` on the result, so an ITERABLE defining only
|
|
`__iter__` broke that. Either way the enumeration assertion still fired with the right message,
|
|
but the report also carried a TypeError about the harness — and a finding that arrives beside a
|
|
harness error invites doubting the finding rather than the code.
|
|
"""
|
|
|
|
def __iter__(self):
|
|
return self
|
|
|
|
def __next__(self):
|
|
raise StopIteration
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *_exc):
|
|
return False
|
|
|
|
def close(self):
|
|
return None
|
|
|
|
|
|
def _as_relative_strings(members) -> set[str]:
|
|
"""Derivations return either repo-relative strings or absolute `Path`s; compare on one form.
|
|
|
|
Iterating into a set is also what DRAINS a derivation that returns a generator, which the
|
|
enumeration proof depends on — see its call site.
|
|
|
|
An unexpected member type is REPORTED rather than stringified. `str(m)` on anything at all meant
|
|
a derivation yielding, say, nested generators produced plausible-looking members and compared
|
|
equal to nothing, which is a population check passing over data it did not understand.
|
|
"""
|
|
out = set()
|
|
for m in members:
|
|
assert isinstance(m, str | Path), (
|
|
f"a derivation yielded {type(m).__name__} ({m!r}); populations here are repo-relative "
|
|
"strings or absolute Paths, and stringifying anything else would compare a plausible "
|
|
"value against a set that can never contain it."
|
|
)
|
|
out.add(str(Path(m).relative_to(REPO_ROOT)) if isinstance(m, Path) else str(m))
|
|
return out
|
|
|
|
|
|
# (label, callable, a floor below which the derivation has plainly broken). `test_hook_fire_log`
|
|
# floors the same population at the same number for its own coverage assertions; that is not a
|
|
# duplicate guard masking another, because the two protect different consumers from going
|
|
# vacuous — delete this one and THIS file's proofs iterate over nothing while reporting success.
|
|
DERIVATIONS = (
|
|
("test_guard_inventory.derived_guard_files", guard_inventory.derived_guard_files, 25),
|
|
("test_hook_fire_log.hook_scripts", hook_fire.hook_scripts, 10),
|
|
("test_image_build_delegates_the_spa_suite.dockerfiles", image_build.dockerfiles, 4),
|
|
("test_image_build_delegates_the_spa_suite.workflow_files", image_build.workflow_files, 5),
|
|
("test_ci_image_pin_population.workflow_files", image_pin.workflow_files, 5),
|
|
("test_ci_status_context_uniqueness.workflow_files", ctx_unique.workflow_files, 5),
|
|
("test_remote_state_inventory.derived_population", remote_state.derived_population, 40),
|
|
("test_pr_changed_files._workflow_files", pr_changed._workflow_files, 5),
|
|
("test_workflow_persist_credentials.workflow_files", persist_creds.workflow_files, 5),
|
|
("test_workflow_job_guards.workflow_files", job_guards.workflow_files, 5),
|
|
)
|
|
|
|
# The floor matters only to the anti-vacuity test; the two property tests take the pair, so an
|
|
# unused parameter cannot drift into looking like an assertion they make.
|
|
_IDS = [d[0] for d in DERIVATIONS]
|
|
_PAIRS = [(label, derive) for label, derive, _ in DERIVATIONS]
|
|
|
|
# Modules that import the shared derivation WITHOUT deriving a guard population. Kept here, beside
|
|
# DERIVATIONS, so adding one is an edit to this file that a reviewer sees.
|
|
POPULATION_EXEMPT = {
|
|
# Uses the index to assemble a HERMETIC tmp fixture copy; nothing in it asserts membership.
|
|
"test_ci_release_path_scan_job.py": "index-derived fixture copy, not a population",
|
|
# Its population is the two path lists parsed out of ci-image.yml and pr-checks.yml, derived
|
|
# from those documents rather than from the index. It consults the index only to answer a
|
|
# per-member question — does this named path have tracked files beneath it, i.e. is it a
|
|
# DIRECTORY — because `<file>/**` and the pathspec `<file>` select different sets while spelling
|
|
# the same string. Reviewed 2026-08-30.
|
|
"test_ci_image_paths_pin_agreement.py": "index used for a per-member directory test, not a population",
|
|
}
|
|
|
|
_HELPER = "scripts.tests.tracked_files"
|
|
_PACKAGE = ["scripts", "tests"]
|
|
|
|
|
|
# ------------------------------------------------------------------------------------------------
|
|
# ANTI-VACUITY FIRST — every assertion below compares sets, and a derivation that collapsed to
|
|
# nothing would satisfy all of them while proving nothing.
|
|
# ------------------------------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.mark.parametrize(("label", "derive", "floor"), DERIVATIONS, ids=_IDS)
|
|
def test_each_derivation_found_something(label, derive, floor):
|
|
members = _as_relative_strings(derive())
|
|
assert len(members) >= floor, (
|
|
f"{label} derived only {len(members)} members, below its floor of {floor} — the derivation "
|
|
"is broken, not the repo, and every set comparison built on it is vacuous."
|
|
)
|
|
|
|
|
|
# ------------------------------------------------------------------------------------------------
|
|
# PROOF 1 — the primitive, executed against a real git repo rather than described
|
|
# ------------------------------------------------------------------------------------------------
|
|
|
|
|
|
def test_the_primitive_REALLY_excludes_an_untracked_file(tmp_path, monkeypatch):
|
|
"""A tracked and an untracked file, identical in name shape and both on disk. Only one is in.
|
|
|
|
Run rather than reasoned about. This class of defect is produced by arguments about what a
|
|
traversal WOULD enumerate, and such arguments are locally convincing whether or not they are
|
|
right; only executing the traversal distinguishes the two.
|
|
"""
|
|
repo = tmp_path / "repo"
|
|
(repo / ".claude" / "hooks").mkdir(parents=True)
|
|
(repo / ".claude" / "hooks" / "committed.sh").write_text("#!/bin/sh\n")
|
|
(repo / ".claude" / "hooks" / "untracked.sh").write_text("#!/bin/sh\n")
|
|
|
|
def git(*args):
|
|
subprocess.run(["git", "-C", str(repo), *args], check=True, capture_output=True)
|
|
|
|
git("init", "-q")
|
|
git("config", "user.email", "guard@example.invalid")
|
|
git("config", "user.name", "guard")
|
|
git("add", ".claude/hooks/committed.sh")
|
|
git("commit", "-qm", "one tracked hook")
|
|
|
|
monkeypatch.setattr(tracked_files, "REPO_ROOT", repo)
|
|
found = tracked_files.tracked_children(".claude/hooks", ("*.sh",))
|
|
|
|
assert (repo / ".claude" / "hooks" / "untracked.sh").is_file(), (
|
|
"the untracked file must still be on disk, or this proves nothing about the index winning over the filesystem"
|
|
)
|
|
assert found == {".claude/hooks/committed.sh"}, (
|
|
f"the derivation returned {sorted(found)}. A filesystem walk returns both files here; only "
|
|
"the index distinguishes them, and that difference is the entire point of ersatztv#806."
|
|
)
|
|
|
|
|
|
def test_the_primitive_does_not_recurse_into_an_untracked_subdirectory(tmp_path, monkeypatch):
|
|
"""`.husky/_/` in miniature — the shape that made #778 red on every checkout.
|
|
|
|
Even a TRACKED nested file must stay out: `tracked_children` is direct-children-only by design,
|
|
and recursion is what dragged the shims in. Proving it with a tracked file makes the assertion
|
|
about the traversal rather than about the index, so the two properties cannot mask each other.
|
|
"""
|
|
repo = tmp_path / "repo"
|
|
(repo / ".husky" / "_").mkdir(parents=True)
|
|
(repo / ".husky" / "pre-commit").write_text("#!/bin/sh\n")
|
|
(repo / ".husky" / "_" / "husky.sh").write_text("#!/bin/sh\n")
|
|
|
|
def git(*args):
|
|
subprocess.run(["git", "-C", str(repo), *args], check=True, capture_output=True)
|
|
|
|
git("init", "-q")
|
|
git("config", "user.email", "guard@example.invalid")
|
|
git("config", "user.name", "guard")
|
|
git("add", "-A")
|
|
git("commit", "-qm", "husky plus a nested shim, both tracked")
|
|
|
|
monkeypatch.setattr(tracked_files, "REPO_ROOT", repo)
|
|
assert tracked_files.tracked_children(".husky", ("*",)) == {".husky/pre-commit"}, (
|
|
"a nested file entered a flat population — this is the `.husky/_/` shape, and it was red on "
|
|
"every developer checkout the last time it shipped"
|
|
)
|
|
|
|
|
|
def test_the_nested_primitive_separates_a_DIRECTORY_from_a_FILE_and_a_PREFIX(tmp_path, monkeypatch):
|
|
"""`tracked_under` decides directory-ness, and two neighbours are what make that non-trivial.
|
|
|
|
Its caller (`test_ci_image_paths_pin_agreement.py`) uses an empty result to mean "this path is a
|
|
file, not a directory", because `<file>/**` matches nothing while the git pathspec `<file>` tracks
|
|
the file — two spellings that look equal and select differently. A prefix sibling is the other
|
|
trap: `docker/ci` must not collect `docker/cix/...`, or a file-only path would look like a
|
|
directory because of its neighbour's name.
|
|
"""
|
|
repo = tmp_path / "repo"
|
|
(repo / "docker" / "ci").mkdir(parents=True)
|
|
(repo / "docker" / "cix").mkdir(parents=True)
|
|
(repo / "docker" / "ci" / "Dockerfile").write_text("FROM scratch\n")
|
|
(repo / "docker" / "cix" / "Dockerfile").write_text("FROM scratch\n")
|
|
(repo / "docker" / "standalone.txt").write_text("a file, not a directory\n")
|
|
|
|
def git(*args):
|
|
subprocess.run(["git", "-C", str(repo), *args], check=True, capture_output=True)
|
|
|
|
git("init", "-q")
|
|
git("config", "user.email", "guard@example.invalid")
|
|
git("config", "user.name", "guard")
|
|
git("add", "-A")
|
|
git("commit", "-qm", "a directory, a prefix sibling, and a plain file")
|
|
|
|
monkeypatch.setattr(tracked_files, "REPO_ROOT", repo)
|
|
|
|
assert tracked_files.tracked_under("docker/ci") == {"docker/ci/Dockerfile"}, (
|
|
"the prefix sibling docker/cix/ leaked into docker/ci's subtree, so a path would be judged a "
|
|
"directory because of what its NEIGHBOUR is named"
|
|
)
|
|
assert tracked_files.tracked_under("docker/ci/") == {"docker/ci/Dockerfile"}, (
|
|
"a trailing slash changed the answer; it is the same directory"
|
|
)
|
|
assert tracked_files.tracked_under("docker/standalone.txt") == set(), (
|
|
"a tracked FILE reported a subtree, which is the emptiness its caller reads as 'this is a "
|
|
"directory' — the `<file>/**` vs `<file>` false green"
|
|
)
|
|
assert tracked_files.tracked_under("docker/absent") == set(), "an absent path reported a subtree"
|
|
|
|
|
|
def test_an_empty_index_FAILS_LOUDLY_rather_than_reporting_an_empty_population(tmp_path, monkeypatch):
|
|
"""The floor under every floor. A silent empty population is how a completeness guard reports
|
|
total coverage having examined nothing, which is the failure mode this repo has shipped twice
|
|
(#631, #751)."""
|
|
repo = tmp_path / "repo"
|
|
repo.mkdir()
|
|
subprocess.run(["git", "-C", str(repo), "init", "-q"], check=True, capture_output=True)
|
|
|
|
monkeypatch.setattr(tracked_files, "REPO_ROOT", repo)
|
|
with pytest.raises(AssertionError, match="reported nothing"):
|
|
tracked_files.tracked_children(".claude/hooks", ("*.sh",))
|
|
|
|
|
|
# ------------------------------------------------------------------------------------------------
|
|
# PROOF 2 — the property, over the real guards
|
|
# ------------------------------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.mark.parametrize(("label", "derive"), _PAIRS, ids=_IDS)
|
|
def test_no_derivation_admits_an_untracked_file(label, derive):
|
|
"""Narrow the index, leave the disk alone, and require the member to disappear — for EVERY
|
|
member, one at a time.
|
|
|
|
EXHAUSTIVE RATHER THAN ONE VICTIM, and the difference is not thoroughness for its own sake.
|
|
`derived_guard_files` unions four sources; one victim is always drawn from whichever sorts
|
|
first, so a mutant putting only the third source back on a filesystem walk passes while the
|
|
proof reports on all four. A sample cannot see the source it did not draw from — this file
|
|
applying `testing.guard-derives-population-from-source` to itself.
|
|
|
|
The floors cannot substitute, and the numbers say why. Reproduce with:
|
|
|
|
PYTHONPATH=. python3 -c "from scripts.tests import test_guard_inventory as g; \
|
|
print(len(g.derived_guard_files()))"
|
|
|
|
61 members on 2026-08-22; suppressing a single contributor leaves 39 (`scripts/tests`), 48
|
|
(hooks) or 58 (husky), all far above the anti-vacuity floor of 25. The figures move whenever a
|
|
guard is added — they were 60/39/47/57 one commit earlier — so read them as an illustration of
|
|
the GAP, not as values to assert against. A floor tight enough to catch a lost source
|
|
would go red every time a guard is legitimately deleted, which is the wrong instrument.
|
|
|
|
Its own `monkeypatch` context, never the shared fixture instance: the function-scoped fixture is
|
|
the same object `conftest.py`'s autouse `isolate_hook_fire_log` patched, so calling `undo()` on
|
|
it here reverts `ETV_HOOK_FIRE_LOG_DIR` too. Since ersatztv#809 that reverts to the SESSION
|
|
directory `pytest_configure` set rather than to unset, so it no longer re-points anything at the
|
|
PRODUCTION log — but it still collapses THIS test's remaining per-test isolation into the shared
|
|
session dir, and reaching into another fixture's patch object to undo it is the habit worth not
|
|
having either way.
|
|
|
|
WHAT REMOVAL CANNOT SEE, so it is not read as more than it is: a source contributing ONLY
|
|
untracked members has nothing here to remove, and #778's defect was exactly that shape (an
|
|
`rglob` over `.husky/_/` adds 17 untracked members and removes none). That direction is
|
|
`test_no_derivation_ENUMERATES_the_filesystem` below; the two are complements, not duplicates.
|
|
"""
|
|
before = _as_relative_strings(derive())
|
|
assert before, f"{label} derived nothing; there is no victim to remove"
|
|
|
|
real = tracked_files._git_ls_files()
|
|
survivors = []
|
|
absent = []
|
|
with pytest.MonkeyPatch.context() as m:
|
|
for victim in sorted(before):
|
|
if not (REPO_ROOT / victim).is_file():
|
|
absent.append(victim)
|
|
continue
|
|
m.setattr(tracked_files, "_git_ls_files", lambda v=victim: [p for p in real if p != v])
|
|
if victim in _as_relative_strings(derive()):
|
|
survivors.append(victim)
|
|
|
|
assert not absent, (
|
|
f"{label} contains {absent}, which git tracks but are not on disk. The proof below asserts "
|
|
"that the INDEX decides while the file is still present; it cannot mean that for a member "
|
|
"that is missing, so this is reported rather than skipped."
|
|
)
|
|
assert not survivors, (
|
|
f"{label} still contains {survivors} after git stopped tracking them. Every one of those is "
|
|
"still on disk, so the derivation is reading the filesystem for that member and untracked "
|
|
"build output can redden it on a developer checkout while CI stays green (ersatztv#778)."
|
|
)
|
|
|
|
|
|
# The directory-listing APIs a Python file population is realistically written with. NOT every way a
|
|
# process can list a directory — `subprocess.run(["ls"])`, a module-level alias captured before the
|
|
# patch, and any C-level call all walk straight past this, all three verified. That
|
|
# bounds what the check below can claim, and the docstring says so rather than implying a sandbox.
|
|
# Reading a file stays allowed: `derived_guard_files` must read workflow bodies.
|
|
_ENUMERATORS = (
|
|
(Path, "glob"),
|
|
(Path, "rglob"),
|
|
(Path, "iterdir"),
|
|
(Path, "walk"),
|
|
(os, "listdir"),
|
|
(os, "walk"),
|
|
(os, "scandir"),
|
|
(glob, "glob"),
|
|
(glob, "iglob"),
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(("label", "derive"), _PAIRS, ids=_IDS)
|
|
def test_no_derivation_ENUMERATES_the_filesystem(label, derive):
|
|
"""The ADD direction, without arranging any state: a derivation may READ files, but it may not
|
|
LIST a directory while it runs.
|
|
|
|
WHY THE ADD DIRECTION NEEDS ITS OWN TEST. Removing members from the index cannot see a source
|
|
that contributes ONLY untracked members: it adds and never takes away, so nothing of its is
|
|
available to remove. `.husky/_/` is that shape exactly — 17 shims `npm ci` writes, none ever in
|
|
the index — and it is the shape #778 shipped. Scope the claim precisely: a source appending
|
|
`(REPO_ROOT / ".husky" / "_").rglob("*")` leaves the removal proof green ON A MACHINE WHERE THAT
|
|
DIRECTORY IS ABSENT, which is the `script-tests` checkout. Where the shims exist, the appended
|
|
members are present and removal reddens too. The blind spot is an append-only source that yields
|
|
nothing HERE — which is exactly the CI shape, and exactly where a guard going quiet matters.
|
|
|
|
WHY THE PROPERTY IS "DOES NOT ENUMERATE" RATHER THAN "DOES NOT RETURN AN UNTRACKED FILE". Both
|
|
obvious formulations of the latter are machine-dependent, which is the very fault #806 exists to
|
|
remove:
|
|
|
|
* creating real probe files in the checkout and requiring they not enter. That needs a
|
|
`test_*.py` probe to reach the `scripts/tests` scope — a file pytest COLLECTS mid-session —
|
|
and its parametrised names collide across `-n auto` workers and concurrent sessions,
|
|
`finally` does not survive SIGKILL, and a concurrent `git add -A` can stage one. Defects in
|
|
the test, not in the thing tested.
|
|
* neutralising the shared primitive and requiring the derivation to go empty. That misses the
|
|
`.husky/_/` source on any machine where `.husky/_/` does not exist — which is every CI
|
|
checkout of `script-tests`, which never runs `npm ci`. Green where it runs, red only on a
|
|
laptop: the inverted asymmetry again, inside the proof written to abolish it.
|
|
|
|
Watching for the CALL needs no arranged state: an `rglob` issued while deriving is caught even
|
|
where the directory it walks is empty, because the evidence is the call rather than what it
|
|
returned. Reading is untouched, so a derivation may still parse the workflow bodies it scrapes
|
|
for referenced scripts.
|
|
|
|
WHAT IT DOES NOT COVER. The boundary is not "synchronous", which measurement disproves — a
|
|
thread that outlives the `derive()` call but finishes while its result is being drained IS
|
|
caught, as is a `__del__` firing during that drain. The boundary is mechanical rather than
|
|
temporal: a call to one of the SPIES is observed, wherever and
|
|
whenever it happens in this process before the assertion below. "While the patch is active"
|
|
under-claims it — the spy appends to a list that outlives the patch, so a reference captured
|
|
during the window and invoked after it still records. What decides observation is whether the
|
|
call goes through a spy, not when. The result is drained while the patch is installed, so a lazy
|
|
generator is reached.
|
|
|
|
NOT REACHED, because no spy was ever installed on that path. Enumeration HOISTED TO MODULE SCOPE
|
|
runs at import, before this test exists — the likeliest instance rather than a contrivance, since
|
|
`test_ci_image_pin_population.py` already precomputes `_DOC` that way — as does an `atexit` hook,
|
|
and a cached property warmed by the baseline call below. `from os import listdir` binds the real
|
|
function before any patch; `from os import walk` IS caught (it routes through the patched
|
|
`os.scandir`) and so is `from glob import glob` (through the patched `glob.iglob`). And anything
|
|
listing in ANOTHER PROCESS — a deliberate `subprocess.run(["ls"])`, or a forked child. This is a
|
|
regression guard against the shapes that arrive by accident, not a sandbox.
|
|
|
|
It cuts the other way too: any spy call at all reddens this test, so unrelated background thread
|
|
activity touching a patched name during the window would too. Nothing in this suite does that
|
|
today, and the report names the call, so a false red would be diagnosable rather than
|
|
mysterious.
|
|
|
|
It also cannot see a derivation that admits a HARDCODED path without listing anything
|
|
(`if (REPO_ROOT / "x.sh").exists(): add`) — listing is the commonest way to discover an untracked
|
|
member, not the only one. The removal proof above catches that shape, and catches memoisation,
|
|
which this one cannot: the baseline call below warms any cache outside the patch. The two are
|
|
complements.
|
|
"""
|
|
assert derive(), f"{label} derived nothing; this proof needs a baseline"
|
|
|
|
calls: list[str] = []
|
|
|
|
def _spy(what):
|
|
# RECORDS and returns empty rather than raising. Raising made the assertion "did an
|
|
# exception reach us", which a derivation defeats by catching it: a `try: ... except
|
|
# Exception: return set()` around an `rglob` enumerated the filesystem and this test passed,
|
|
# measured. The evidence is the CALL, so the call is what is asserted on.
|
|
def spy(*_args, **_kwargs):
|
|
calls.append(what)
|
|
# Iterator AND context manager: `os.scandir` is used as `with os.scandir(...) as it`,
|
|
# and a bare iterator made the failure report carry a test-induced TypeError about the
|
|
# context manager protocol alongside the real finding. The assertion fired correctly
|
|
# either way, but a report that blames the harness invites doubting the finding.
|
|
return _EmptyScan()
|
|
|
|
return spy
|
|
|
|
failure = None
|
|
with pytest.MonkeyPatch.context() as m:
|
|
for owner, name in _ENUMERATORS:
|
|
m.setattr(owner, name, _spy(f"{getattr(owner, '__name__', owner)}.{name}"), raising=False)
|
|
try:
|
|
# DRAINED inside the context, never `derive()` discarded. A derivation returning a lazy
|
|
# generator does its work when the caller drains it, so discarding the result moved the
|
|
# whole walk outside the patch: a generator yielding the index population and then
|
|
# appending `.husky/_` passed here and admitted 17 untracked shims on a checkout where
|
|
# that directory exists. `_as_relative_strings` is what drains it — it iterates into a
|
|
# set — so the call must stay here rather than being hoisted out or wrapped in something
|
|
# lazier.
|
|
_as_relative_strings(derive())
|
|
except BaseException as exc: # re-raised below, after the evidence has been judged
|
|
failure = exc
|
|
|
|
assert not calls, (
|
|
f"{label} enumerated the filesystem via {sorted(set(calls))} while deriving its population. "
|
|
"Directory listings report build output, generated shims and editor droppings, and differ "
|
|
"between the CI checkout and a developer's, so the member set stops being a property of the "
|
|
f"repo (ersatztv#778, #806)." + (f" It also raised: {failure!r}" if failure is not None else "")
|
|
)
|
|
if failure is not None:
|
|
raise failure
|
|
|
|
|
|
@pytest.mark.parametrize(("label", "derive"), _PAIRS, ids=_IDS)
|
|
def test_every_derived_member_is_tracked(label, derive):
|
|
"""The same property as an invariant over the real tree, which is the form that catches a
|
|
refactor going back to a filesystem walk without also touching this file."""
|
|
tracked = set(tracked_files._git_ls_files())
|
|
stray = sorted(m for m in _as_relative_strings(derive()) if m not in tracked)
|
|
assert not stray, f"{label} contains untracked path(s): {stray}"
|
|
|
|
|
|
# ------------------------------------------------------------------------------------------------
|
|
# THE SCOPE MIRROR ABOVE IS ITSELF CHECKED
|
|
# ------------------------------------------------------------------------------------------------
|
|
|
|
|
|
def _modules_importing_the_helper() -> set[str]:
|
|
"""Which `scripts/tests/test_*.py` import the shared derivation, by PARSING them.
|
|
|
|
`ast` rather than a substring scan, and the distinction is the point rather than tidiness. A
|
|
substring scan over source both EVADES and FALSELY FIRES here: `import scripts.tests.
|
|
tracked_files as tf` and `from scripts.tests import tracked_files as tf` escape a scan for
|
|
`"tracked_files import"`, while a comment merely citing `scripts/tests/tracked_files.py` matches
|
|
a scan for `"tracked_files."` and would redden a correct tree over prose.
|
|
`docs/decisions/records/testing/guard-derives-population-from-source.md` records why patching
|
|
such a predicate does not converge: it is not a parser. Python ships the parser, and a comment
|
|
is not a node at all.
|
|
|
|
WHAT THE PARSE DOES NOT REACH, stated rather than implied by the word "parsing": STATIC import
|
|
statements naming the helper. `importlib.import_module("scripts.tests.tracked_files")`, a
|
|
re-export through `scripts/tests/__init__.py`, and `from scripts.tests import *` are invisible,
|
|
verified by executing each. (`from scripts.tests.tracked_files import *` IS seen — it names the
|
|
module.) Those sit inside the same residual as a module that derives a population without the
|
|
helper at all — the residual named below — and no mechanical check closes it.
|
|
|
|
The FILE LIST is a filesystem walk on purpose, and it is not the defect this file forbids: it is
|
|
a superset check over what pytest itself collects, so an untracked stray `test_x.py` here makes
|
|
the guard MORE demanding, never blind. Using the index would let an unstaged new guard escape
|
|
registration, which is the wrong direction for a check about coverage.
|
|
"""
|
|
found = set()
|
|
for path in sorted(TESTS_DIR.glob("test_*.py")):
|
|
for node in ast.walk(ast.parse(path.read_text())):
|
|
# RESOLVE the name to an absolute module and compare exactly. Testing `base ==
|
|
# "tracked_files"` handled `from .tracked_files import x` but silently missed
|
|
# `from ..tests.tracked_files import x`, which resolves to the same helper — a false
|
|
# NEGATIVE, the direction that lets a module adopt the helper and escape registration.
|
|
# A suffix match instead over-accepts `from unrelated.package import tracked_files`,
|
|
# reddening a correct tree over a module this repo does not own. Resolution is the only
|
|
# form with neither failure: these files live in `scripts.tests`, so level 1 resolves to
|
|
# `scripts.tests` and level 2 to `scripts` — see the guard below for why there is no
|
|
# level 3.
|
|
if isinstance(node, ast.Import):
|
|
hit = any(a.name == _HELPER for a in node.names)
|
|
elif isinstance(node, ast.ImportFrom):
|
|
if node.level > len(_PACKAGE):
|
|
# Beyond the top-level package: Python raises ImportError for this, so it cannot
|
|
# be an import of the helper. Guarded explicitly because `_PACKAGE[:negative]`
|
|
# silently WRAPS — level 4 produced the same prefix as level 2 — which pinned an
|
|
# unimportable form as a valid detection. Valid levels here are exactly 1 and 2:
|
|
# 1 resolves to `scripts.tests`, 2 to `scripts`, and 3 or more is beyond the
|
|
# top-level package, which Python refuses.
|
|
continue
|
|
prefix = _PACKAGE[: len(_PACKAGE) - (node.level - 1)] if node.level else []
|
|
base_parts = prefix + ([node.module] if node.module else [])
|
|
base = ".".join(base_parts)
|
|
hit = base == _HELPER or any(f"{base}.{a.name}" == _HELPER for a in node.names)
|
|
else:
|
|
continue
|
|
if hit:
|
|
found.add(path.name)
|
|
break
|
|
return found
|
|
|
|
|
|
# (source, should the matcher see it). Every row is a form that has actually been mis-classified; the
|
|
# table is here so the next edit to `_modules_importing_the_helper` cannot re-open one silently.
|
|
# A false NEGATIVE lets a module adopt the helper and escape registration; a false POSITIVE reddens
|
|
# a correct tree over a module this repo does not own. Both directions are pinned.
|
|
_IMPORT_FORMS = (
|
|
("import scripts.tests.tracked_files as tf", True),
|
|
("from scripts.tests import tracked_files as tf", True),
|
|
("from scripts.tests.tracked_files import tracked_paths", True),
|
|
("from scripts.tests.tracked_files import *", True),
|
|
("from . import tracked_files", True),
|
|
("from .tracked_files import tracked_paths", True),
|
|
("from ..tests.tracked_files import tracked_paths", True),
|
|
# Beyond the top-level package from `scripts.tests`: Python raises ImportError, so there is
|
|
# nothing to detect. Pinned False so the negative-slicing wrap that once made it look
|
|
# detectable cannot come back.
|
|
("from ...scripts.tests.tracked_files import tracked_paths", False),
|
|
# Witnesses the negative-slice wrap specifically: without the level guard this one
|
|
# resolves through `scripts` and matches.
|
|
("from ....tests.tracked_files import tracked_paths", False),
|
|
("def f():\n from scripts.tests import tracked_files\n return tracked_files", True),
|
|
("from unrelated.package import tracked_files", False),
|
|
("from ..something import tracked_files", False),
|
|
("from .something import tracked_files", False),
|
|
("from ..other.tracked_files import x", False),
|
|
("# see scripts/tests/tracked_files.py for the rationale\nimport re", False),
|
|
# Pins PARSER VISIBILITY, not importability: executing this line really does import the helper.
|
|
# The row records that a static parse cannot see it — a known gap, pinned so it is not a
|
|
# surprise — and closing it would mean updating this row, which is the intended friction.
|
|
("import importlib\nimportlib.import_module('scripts.tests.tracked_files')", False),
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(("source", "expected"), _IMPORT_FORMS, ids=[s.splitlines()[0][:48] for s, _ in _IMPORT_FORMS])
|
|
def test_the_import_matcher_classifies_every_reviewed_form(source, expected, monkeypatch):
|
|
class _Fake:
|
|
name = "test_probe.py"
|
|
|
|
def read_text(self):
|
|
return source
|
|
|
|
def __lt__(self, other):
|
|
return True
|
|
|
|
class _Dir:
|
|
def glob(self, _pattern):
|
|
return [_Fake()]
|
|
|
|
monkeypatch.setattr(sys.modules[__name__], "TESTS_DIR", _Dir())
|
|
seen = "test_probe.py" in _modules_importing_the_helper()
|
|
assert seen is expected, f"the import matcher {'missed' if expected else 'falsely matched'} this form:\n{source}"
|
|
|
|
|
|
def test_every_index_derived_module_is_registered():
|
|
"""`DERIVATIONS` is a hand-written mirror, so it gets an equality check rather than a promise.
|
|
|
|
Without this, adding another index-derived guard and forgetting to register it leaves that guard
|
|
unproven while this file reads as covering them all — a completeness claim standing behind a
|
|
hand-maintained list, which is the defect one altitude up (#773 Family C, and the shape that put
|
|
`MARKED_JOBS` in the record as a residual gap until #787 closed it).
|
|
|
|
THE SCOPE THIS CANNOT SEE, stated because a check described as complete stops being re-examined:
|
|
it detects modules that IMPORT the shared helper. A module deriving a file population some other
|
|
way — shelling out to `git ls-files` itself, or going back to `Path.rglob` — is invisible to it,
|
|
and no mechanical check can close that. `test_guard_inventory.py`'s own header argues the same
|
|
point about flagging filter-shaped guards by token, and #774 concluded there that the honest
|
|
answer is no. What is mechanised here is the case that actually recurs: someone adopts the
|
|
helper and forgets this file.
|
|
|
|
Registration is MODULE-level, not derivation-level, so a second population added inside an
|
|
already-registered module is covered only if it is registered too.
|
|
|
|
`POPULATION_EXEMPT` is the opt-out, and it lives HERE rather than as a marker comment in the
|
|
exempt file because the two directions are not symmetric: a false import-match only reddens,
|
|
while a false EXEMPTION is silent. A marker a file grants itself by containing a token is
|
|
trippable from that file's prose — this file's own error message names the token — so it would
|
|
be a one-line silent kill switch, the shape `test_ci_release_path_scan_job.py` argues against
|
|
for its recursion fence. Listing exemptions beside the registrations makes adding one a visible
|
|
edit here.
|
|
|
|
WHAT NO ASSERTION CAN DECIDE, dated so it is re-examined rather than assumed: whether an
|
|
exemption is still WARRANTED. A stale key and an unexplained one are both caught below, but an
|
|
exempt module that later grows a real derived population stays uncovered and silent. Reviewed
|
|
2026-08-30 — two entries. One uses the helper only to assemble a tmp fixture copy; the other
|
|
consults the index per-member, to decide whether a named path is a directory, and derives its
|
|
population from parsed workflow documents instead. Neither asserts a file-population claim.
|
|
"""
|
|
exempt = {Path(__file__).name} | set(POPULATION_EXEMPT)
|
|
importers = _modules_importing_the_helper()
|
|
registered = {label.split(".", 1)[0] + ".py" for label, _, _ in DERIVATIONS}
|
|
|
|
# ANTI-VACUITY, and only that. A broken parse is caught loudly by `phantom` below — every
|
|
# registered module would go missing at once — so this is the cheaper, more specific signal, not
|
|
# the thing standing between a broken parse and a green run.
|
|
assert len(importers) >= len(DERIVATIONS), (
|
|
f"the import parse found only {sorted(importers)}, fewer modules than DERIVATIONS registers "
|
|
f"({sorted(registered)}) — the parse has broken."
|
|
)
|
|
|
|
stale = sorted(name for name in POPULATION_EXEMPT if name not in importers)
|
|
assert not stale, (
|
|
f"POPULATION_EXEMPT lists {stale}, which the parser no longer sees importing the shared "
|
|
"helper (renamed, "
|
|
"deleted, or the import removed). A stale exemption is worse than none: if the filename is "
|
|
"ever reused, the new module is exempt from birth without anyone deciding that."
|
|
)
|
|
thin = sorted(name for name, why in POPULATION_EXEMPT.items() if not str(why).strip())
|
|
assert not thin, f"POPULATION_EXEMPT entries with no stated reason: {thin}"
|
|
|
|
unregistered = sorted(importers - registered - exempt)
|
|
assert not unregistered, (
|
|
f"{unregistered} import the shared index derivation but are not in DERIVATIONS, so neither "
|
|
"proof in this file covers them. Add a named derivation function and register it, or add "
|
|
"the module to POPULATION_EXEMPT in this file if it imports the helper without deriving a "
|
|
"population."
|
|
)
|
|
phantom = sorted(registered - importers)
|
|
assert not phantom, (
|
|
f"DERIVATIONS registers {phantom}, which no longer import the shared helper. A row for "
|
|
"a derivation that is not there reads as coverage and is not."
|
|
)
|