test(frozen-pin): an added or removed file, and listing order, pinned by tests; drift named apart from missing

K5 survived a full run (a mutant ignoring an extra or removed file left the suite green) and K4
(sort removed) was caught only by corpus data. Now pinned with no corpus: the add arm, the remove
arm, and a shuffled directory listing digesting the same. The gate says "pin-drift" for a drifted
copy instead of "artefakter mangler"; the unread "store" key is gone from frozen_bundles.json; the
forbidden-path scan covers md/yaml/yml/toml and proves a known positive per suffix.

Five mutants killed in a scratch clone (drop-last-entry 8 failed, no-sort, no-names, drift-as-missing,
scan-suffixes-narrowed 1 failed each).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-18 18:49:54 +02:00
commit 68079469c3
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
3 changed files with 108 additions and 17 deletions

View file

@ -86,7 +86,6 @@ def _store(tmp_path: Path, name: str = "n500-2024", body: str = "one") -> tuple[
pin.write_text(
json.dumps(
{
"store": str(store),
"bundles": {name: {"directory": directory, "sha256": digest, "files": files}},
}
),
@ -127,6 +126,48 @@ def test_the_digest_covers_every_byte_and_every_name(tmp_path: Path) -> None:
assert fb.digest_bundle(renamed)[0] != fb.digest_bundle(_bundle(tmp_path / "d"))[0]
def test_an_added_or_removed_file_is_drift_even_when_no_kept_byte_changes(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""The two arms nothing pinned (measured 2026-09-18: a mutant that ignored an extra or a
missing file left 197 tests green). Every surviving file is byte-identical to the pin here
only the SET of files moved, and the set is what a copy is."""
store, pin = _store(tmp_path)
_use(monkeypatch, store, pin)
base = fb.bundle_dir("n500-2024") # control: green before either edit
extra = base / "krav" / "b.md"
extra.write_text("---\ntype: Krav\ntitle: B\n---\n", encoding="utf-8")
with pytest.raises(fb.FrozenBundleDrift):
fb.bundle_dir("n500-2024")
extra.unlink()
assert fb.bundle_dir("n500-2024") == base # the edit, not the fixture, was the cause
(base / "krav" / "a.md").unlink()
with pytest.raises(fb.FrozenBundleDrift) as exc:
fb.bundle_dir("n500-2024")
assert "1 filer" in str(exc.value) # what disk holds now: one file of the pinned two
def test_the_digest_does_not_depend_on_directory_listing_order(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Determinism with no corpus mounted: the same tree listed in ANOTHER order digests the same.
Only the corpus tests used to catch a removed sort, and they skip where no corpus exists."""
base = _bundle(tmp_path / "b")
for extra in ("z.md", "m.md", "krav/q.md"):
(base / extra).write_text(extra, encoding="utf-8")
baseline = fb.digest_bundle(base)
listed = list(base.rglob("*"))
assert len(listed) > 3
real = Path.rglob
for order in (list(reversed(listed)), listed[1:] + listed[:1]):
monkeypatch.setattr(Path, "rglob", lambda self, pattern, _o=order: iter(_o))
assert fb.digest_bundle(base) == baseline
monkeypatch.setattr(Path, "rglob", real)
def test_a_matching_copy_resolves_to_the_pinned_directory(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
@ -239,6 +280,13 @@ def test_the_pinned_directory_name_carries_the_short_digest() -> None:
assert pin.files > 0
def test_the_pin_file_carries_no_key_nobody_reads() -> None:
"""``store`` sat in the pin file with no reader: the store is ``DEFAULT_STORE`` / the env var,
and a second, silent spelling of it would drift from the first."""
keys = set(json.loads(fb.PIN_FILE.read_text(encoding="utf-8")))
assert keys == {"source", "renewal", "bundles"}
def test_the_bundle_itself_is_never_tracked_here() -> None:
"""Vegnormal corpora must not reach a public remote: only the pin is tracked."""
tracked = (_REPO / "src" / "portfolio_optimiser" / "frozen_bundles.json").read_text("utf-8")
@ -250,19 +298,49 @@ def test_the_bundle_itself_is_never_tracked_here() -> None:
# ---------------------------------------------------------------------------------------------
#: Every text form a path to that build directory could be written in. ``.py/.json/.txt`` alone
#: left prose-and-config (md, yaml, toml) outside the denominator, unmeasured.
_SUFFIXES = {".py", ".json", ".txt", ".md", ".yaml", ".yml", ".toml"}
def _forbidden_hits(root: Path, tops: tuple[str, ...]) -> tuple[list[str], int]:
"""(hits, files scanned). The count is the denominator: a scan of zero files is not green."""
hits: list[str] = []
scanned = 0
for top in tops:
for path in sorted((root / top).rglob("*")):
if not path.is_file() or path.suffix not in _SUFFIXES:
continue
scanned += 1
for n, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
if any(p.search(line) for p, _ in _FORBIDDEN):
hits.append(f"{path.relative_to(root)}:{n}: {line.strip()}")
return hits, scanned
def test_the_forbidden_path_scan_finds_a_known_positive_in_every_scanned_suffix(
tmp_path: Path,
) -> None:
"""Shown, not assumed: plant each spelling in a file of EACH suffix and see the scan report
exactly those files. A suffix the scan skips would read clean here and be red in this test."""
suffixes = (".py", ".json", ".txt", ".md", ".yaml", ".yml", ".toml") # literal, not _SUFFIXES
for suffix in suffixes:
for i, (_, sample) in enumerate(_FORBIDDEN):
planted = tmp_path / "src" / f"planted{i}{suffix}"
planted.parent.mkdir(exist_ok=True)
planted.write_text(f"before\n{sample}\nafter\n", encoding="utf-8")
hits, scanned = _forbidden_hits(tmp_path, ("src",))
assert scanned == len(suffixes) * len(_FORBIDDEN)
assert len(hits) == scanned, hits
def test_no_measurement_reads_the_other_repos_build_directory() -> None:
"""Both spellings, each with a known-positive control: a pattern that cannot match anything
is a gate that can only be green."""
for pattern, known_positive in _FORBIDDEN:
assert pattern.search(known_positive), pattern.pattern
hits = [
f"{path.relative_to(_REPO)}:{n}: {line.strip()}"
for top in _SCANNED
for path in sorted((_REPO / top).rglob("*"))
if path.is_file() and path.suffix in {".py", ".json", ".txt"}
for n, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1)
if any(p.search(line) for p, _ in _FORBIDDEN)
]
hits, scanned = _forbidden_hits(_REPO, _SCANNED)
assert scanned > 0
assert hits == [], "\n".join(hits)
@ -297,9 +375,12 @@ def test_a_drifted_copy_fails_the_gate_with_the_reason_said(
evidence, stress_root = _evidence(tmp_path)
m = gate.measure_stress(evidence, _REPO, stress_root, None)
assert "avviker fra pin" in m.missing
assert "avviker fra pin" in m.drift
assert m.missing == "" # drift is NOT "artefakter mangler" — the two are named apart
row = gate.score_undeclared(["p"], {"p": "passed"}, m, "s")
assert (row.k, row.status, row.failing) == (None, gate.NOT_MEASURED, True)
assert "pin-drift" in row.reason and "artefakter mangler" not in row.reason
assert "pin-drift" in gate.score_named(m, "s").reason
assert gate.exit_code([row]) == 1
@ -312,9 +393,10 @@ def test_a_missing_copy_is_not_measured_and_never_green(
monkeypatch.setenv(fb.STORE_ENV, str(tmp_path / "gone"))
m = gate.measure_stress(evidence, _REPO, stress_root, None)
assert m.missing and m.validated == 0
assert m.missing and not m.drift and m.validated == 0
row = gate.score_undeclared(["p"], {"p": "passed"}, m, "s")
assert (row.status, row.failing) == (gate.NOT_MEASURED, True)
assert "artefakter mangler" in row.reason and "pin-drift" not in row.reason
assert gate.exit_code([row]) == 1