portfolio-optimiser/tests/test_frozen_bundles_loadbearing.py
Kjell Tore Guttormsen 68079469c3
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>
2026-09-18 18:49:54 +02:00

442 lines
19 KiB
Python

"""The measurements read a FROZEN copy of the vegnormal bases, pinned by sha256 — never another
repository's live build directory.
Measured 2026-09-17 17:43: ``vegnormal-okf`` rebuilt ``build/ferdig/r761-2025`` while this
repository's gate pointed straight at it. Rows 6-7 went "IKKE MÅLT" and five tests fell, for a
change no one here made. The failure mode was never falsehood — the gate says IKKE MÅLT and exits
non-zero, never green — it was that two projects shared a directory neither owns, so what this
repository MEASURES could change without a commit here.
The fix is a copy outside both repositories plus a pin this repository tracks. The pin is the whole
point: a copy with no pin is the same shared directory one move further away. So the three states
are separated by construction, and each has its own arm below:
* the copy matches the pin -> it resolves, and that is the only green path;
* the copy is GONE -> ``FrozenBundleMissing`` (an ``OSError``): the gate says
IKKE MÅLT and fails the exit code exactly as it did before this change; the delivered-corpus
tests SKIP, which is MAJOR-3's ceiling rule (a hard error would break ``uv run pytest`` in the
handover archive, where no corpus is mounted);
* the copy DIFFERS from the pin -> ``FrozenBundleDrift`` (a ``ValueError``): loud, named, and
NEVER a skip. A drifted copy is not an unreadable measurement, it is a measurement of the wrong
corpus, which is the one thing that produces a silently wrong number.
The two exception classes are deliberately unrelated: a caller that catches "missing" to skip must
not swallow "drift". ``test_drift_is_not_a_missing_copy`` is that gate.
"""
from __future__ import annotations
import json
import re
import shutil
from pathlib import Path
from typing import Any
import pytest
from portfolio_optimiser import frozen_bundles as fb
from portfolio_optimiser.evals import v1_gate as gate
from portfolio_optimiser.stress import read_bundle_declarations
_REPO = Path(__file__).resolve().parent.parent
#: Joined at run time on purpose: this file NAMES the forbidden path spellings, and a literal
#: would make the gate below red against its own source. (Implicit concatenation is not enough —
#: ``ruff format`` folds ``"a" "b"`` back into one literal, measured here.)
_OTHER_REPO = "-".join(("vegnormal", "okf"))
#: The two spellings a path to that repository's build directory takes in this codebase. Both were
#: present before this change (3 + 4 hits, the known positives recorded in the order's evidence).
#: Each is paired with the line that PROVES it can match: a pattern that matches nothing makes a
#: gate that can only be green, and the two spellings do not match each other's sample.
_FORBIDDEN = (
(
re.compile(re.escape(_OTHER_REPO + "/build")),
f'ROOT = Path("~/repos/{_OTHER_REPO}/build/ferdig")',
),
(
re.compile("[\"']" + re.escape(_OTHER_REPO) + "[\"']"),
'ROOT = Path.home() / "repos" / "' + _OTHER_REPO + '" / "build" / "ferdig"',
),
)
#: Prose that documents history is explicitly allowed by the order; only paths are forbidden.
_SCANNED = ("src", "tests", "contexts")
# ---------------------------------------------------------------------------------------------
# helpers
# ---------------------------------------------------------------------------------------------
def _bundle(root: Path, body: str = "one") -> Path:
root.mkdir(parents=True, exist_ok=True)
(root / "index.md").write_text("# base\n\n- [a](krav/a.md)\n", encoding="utf-8")
(root / "krav").mkdir(exist_ok=True)
(root / "krav" / "a.md").write_text(
f"---\ntype: Krav\ntitle: A\n---\n\n{body}\n", encoding="utf-8"
)
return root
def _store(tmp_path: Path, name: str = "n500-2024", body: str = "one") -> tuple[Path, Path]:
"""A frozen store holding ONE pinned bundle, and the pin file that names it."""
store = tmp_path / "store"
digest, files = fb.digest_bundle(_bundle(tmp_path / "src-of-truth", body))
directory = f"{name}-{digest[: fb.SHORT]}"
_bundle(store / directory, body)
pin = tmp_path / "pin.json"
pin.write_text(
json.dumps(
{
"bundles": {name: {"directory": directory, "sha256": digest, "files": files}},
}
),
encoding="utf-8",
)
return store, pin
def _use(monkeypatch: pytest.MonkeyPatch, store: Path, pin: Path) -> None:
monkeypatch.setenv(fb.STORE_ENV, str(store))
monkeypatch.setattr(fb, "PIN_FILE", pin)
def _touch_one_byte(base: Path) -> None:
doc = base / "krav" / "a.md"
doc.write_text(doc.read_text(encoding="utf-8").replace("one", "ONE"), encoding="utf-8")
# ---------------------------------------------------------------------------------------------
# (a)-(b) the digest
# ---------------------------------------------------------------------------------------------
def test_the_digest_covers_every_byte_and_every_name(tmp_path: Path) -> None:
"""Stable across recomputation; moved by one byte AND by a rename that changes no byte —
without the name in the hash, a corpus reshuffled under the same bytes would pin clean."""
base = _bundle(tmp_path / "b")
first, files = fb.digest_bundle(base)
assert (first, files) == fb.digest_bundle(base)
assert files == 2
_touch_one_byte(base)
changed, _ = fb.digest_bundle(base)
assert changed != first
renamed = _bundle(tmp_path / "c")
(renamed / "krav" / "a.md").rename(renamed / "krav" / "b.md")
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:
store, pin = _store(tmp_path)
_use(monkeypatch, store, pin)
resolved = fb.bundle_dir("n500-2024")
assert resolved.parent == store
assert (resolved / "index.md").is_file()
# ---------------------------------------------------------------------------------------------
# (c)-(e) the three states
# ---------------------------------------------------------------------------------------------
def test_one_changed_byte_is_drift_named_with_both_digests(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
store, pin = _store(tmp_path)
_use(monkeypatch, store, pin)
base = fb.bundle_dir("n500-2024") # green before the edit — the control
expected = json.loads(pin.read_text(encoding="utf-8"))["bundles"]["n500-2024"]["sha256"]
_touch_one_byte(base)
with pytest.raises(fb.FrozenBundleDrift) as exc:
fb.bundle_dir("n500-2024")
message = str(exc.value)
assert "avviker fra pin" in message
assert "n500-2024" in message
assert expected[: fb.SHORT] in message # what was pinned
assert fb.digest_bundle(base)[0][: fb.SHORT] in message # what is on disk
def test_a_missing_copy_is_missing_and_stays_an_oserror(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""``measure_stress`` already catches ``OSError`` -> IKKE MÅLT + exit 1; that path is unchanged."""
store, pin = _store(tmp_path)
_use(monkeypatch, store, pin)
shutil.rmtree(store / fb.load_pins()["n500-2024"].directory)
with pytest.raises(fb.FrozenBundleMissing) as exc:
fb.bundle_dir("n500-2024")
assert isinstance(exc.value, OSError)
assert "n500-2024" in str(exc.value)
def test_drift_is_not_a_missing_copy(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""A caller that catches "missing" in order to SKIP must never swallow drift: skipping on a
wrong corpus is the silent failure this whole change exists to remove."""
assert not issubclass(fb.FrozenBundleDrift, fb.FrozenBundleMissing)
assert not issubclass(fb.FrozenBundleMissing, fb.FrozenBundleDrift)
assert not isinstance(fb.FrozenBundleDrift("x"), OSError)
store, pin = _store(tmp_path)
_use(monkeypatch, store, pin)
_touch_one_byte(fb.bundle_dir("n500-2024"))
with pytest.raises(fb.FrozenBundleDrift):
try:
fb.bundle_dir("n500-2024")
except fb.FrozenBundleMissing: # pragma: no cover - the defect this arm forbids
pytest.fail("drift was answered as a missing copy")
def test_an_unknown_name_is_refused_by_name(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
store, pin = _store(tmp_path)
_use(monkeypatch, store, pin)
with pytest.raises(fb.FrozenBundleMissing) as exc:
fb.bundle_dir("n100-2023")
assert "n100-2023" in str(exc.value) and "n500-2024" in str(exc.value)
def test_an_explicit_override_is_unpinned_and_the_operator_named_it(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""``--bundle-root`` / ``PORTFOLIO_VEGNORMAL_ROOT`` stays an escape hatch: the operator who
names a live mount gets it, pin or no pin. Absent it, the frozen store answers."""
store, pin = _store(tmp_path)
_use(monkeypatch, store, pin)
live = tmp_path / "live"
_bundle(live / "n500-2024", body="something else entirely")
assert fb.bundle_dir("n500-2024", override=live) == live / "n500-2024"
monkeypatch.setenv(fb.OVERRIDE_ENV, str(live))
assert fb.bundle_dir("n500-2024") == live / "n500-2024"
# ---------------------------------------------------------------------------------------------
# (f)-(g) the tracked pin
# ---------------------------------------------------------------------------------------------
def test_the_pin_names_every_base_the_context_sets_declare() -> None:
declared = {
d["name"]
for path in sorted((_REPO / "contexts").glob("*/bundle.txt"))
for d in read_bundle_declarations(path)
}
assert declared, "no context set declares a base — the denominator would be vacuous"
assert declared <= set(fb.load_pins())
def test_the_pinned_directory_name_carries_the_short_digest() -> None:
"""A stale copy is visible in ``ls``, not only to the verifier."""
pins = fb.load_pins()
assert pins, "the pin file names no bundle"
for name, pin in pins.items():
assert pin.directory == f"{name}-{pin.sha256[: fb.SHORT]}"
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")
assert "index.md" not in tracked
# ---------------------------------------------------------------------------------------------
# (h) nothing reads the other repository's build directory any more
# ---------------------------------------------------------------------------------------------
#: 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, scanned = _forbidden_hits(_REPO, _SCANNED)
assert scanned > 0
assert hits == [], "\n".join(hits)
# ---------------------------------------------------------------------------------------------
# (i)-(j) the gate
# ---------------------------------------------------------------------------------------------
def _evidence(tmp_path: Path) -> tuple[dict[str, Any], Path]:
stress_root = tmp_path / "stress"
(stress_root / "o").mkdir(parents=True)
return {
"label": "s",
"root": "scratchpad",
"runs": [
{
"context": "contexts/tunnel-hauglia-2027",
"outbox": "o",
"run_id": "r",
"bundle": None,
}
],
}, stress_root
def test_a_drifted_copy_fails_the_gate_with_the_reason_said(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
store, pin = _store(tmp_path)
_use(monkeypatch, store, pin)
_touch_one_byte(fb.bundle_dir("n500-2024"))
evidence, stress_root = _evidence(tmp_path)
m = gate.measure_stress(evidence, _REPO, stress_root, None)
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
def test_a_missing_copy_is_not_measured_and_never_green(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
store, pin = _store(tmp_path)
_use(monkeypatch, store, pin)
evidence, stress_root = _evidence(tmp_path)
monkeypatch.setenv(fb.STORE_ENV, str(tmp_path / "gone"))
m = gate.measure_stress(evidence, _REPO, stress_root, None)
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
# ---------------------------------------------------------------------------------------------
# (k) the delivered-corpus tests: missing SKIPS, drift FAILS
# ---------------------------------------------------------------------------------------------
@pytest.mark.parametrize(
"module,helper",
[
("test_context_sets_loadbearing", "_bundle_dir"),
("test_navigation_window_loadbearing", "_delivered"),
("test_inert_identifier_loadbearing", "_base"),
("test_requirement_number_gate_loadbearing", "_base"),
],
)
def test_the_corpus_tests_skip_when_absent_but_fail_on_drift(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, module: str, helper: str
) -> None:
mod = __import__(module)
resolve = getattr(mod, helper)
store, pin = _store(tmp_path)
_use(monkeypatch, store, pin)
assert resolve("n500-2024").is_dir() # control: the matching copy resolves
_touch_one_byte(fb.bundle_dir("n500-2024"))
# A skip is caught EXPLICITLY, never left to ``pytest.raises``: measured against the mutation
# that makes drift a subclass of missing, these four arms SKIPPED instead of failing (5 -> 9
# skipped over the whole suite) and stayed green — a gate that cannot see the one defect it
# exists for.
try:
resolve("n500-2024")
except fb.FrozenBundleDrift:
pass
except pytest.skip.Exception as exc:
raise AssertionError(f"{module}.{helper} SKIPPED a drifted copy: {exc}") from None
else:
raise AssertionError(f"{module}.{helper} accepted a drifted copy")
monkeypatch.setenv(fb.STORE_ENV, str(tmp_path / "gone"))
with pytest.raises(pytest.skip.Exception):
resolve("n500-2024")