test(b-gate): nineteen arms that fail because a probe proves nothing, a reason may be empty, and the row's own limit is untrue
RED FIRST, second repair. PM's checkpoint on c1bdb37 measured N2b: row 1 goes from
3 of 17 to 17 of 17 GREEN with fourteen stub doors and ONE unrelated green test used
as the behaviour probe for all of them. The gate only looked up whether a nodeid was
`passed`; nothing bound the probe to its step. EXTERNAL_DOOR promised the probe
"calls the door and reads the artefact it writes" and there was no code for it --
the same class of defect as entry["kind"] one round ago.
20 new arms, 19 RED, every one on an assert about behaviour (43 assert lines, 0
ImportError, 0 AttributeError, 0 collection errors). The twentieth is green by
construction: every held_out entry in the contract does carry a reason today, and
the arm exists so that stops being true loudly.
Each refusal arm carries its own rc-0 control FIRST -- the same probe, the same
surface, accepted -- then breaks exactly one trait. The constructed probe is now
WRITTEN (`_write_probe`), not merely named: the repaired gate reads the probe's own
source, so a nodeid that does not exist cannot be a behaviour.
Red on the three bearing rests:
probe not bound to its step door untouched, step unnamed, nothing asserted,
test missing, one probe shared by two steps, and
N2b against the contract that stands (17 -> k=0)
limit statement untrue the six escapes PM measured as unseen, planted one
at a time, and the "TWO ways remain" sentence
held_out accepts empty a blank reason shrinks the denominator; the summary
counts entries, not reasons; the four steps outside
the run path are counted without being named
Red on five of the nine small rests: pruned manifest (451 of 512 still GREEN), a
non-UTF-8 file counted but never scanned, a symlink out of the tree, a runbook whose
whole content is "x", and the row's ratio read as a coverage measure.
One test-hygiene change, not a product fix: the git-dependent arm now skips itself
without .git instead of failing, so "red in a clean extract" stops growing with each
new arm of that class.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
e71d248f02
commit
40869e272f
1 changed files with 425 additions and 20 deletions
|
|
@ -29,6 +29,8 @@ from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from portfolio_optimiser import workflow
|
from portfolio_optimiser import workflow
|
||||||
from portfolio_optimiser.evals import b_gate as gate
|
from portfolio_optimiser.evals import b_gate as gate
|
||||||
from portfolio_optimiser.evals import v1_gate
|
from portfolio_optimiser.evals import v1_gate
|
||||||
|
|
@ -135,10 +137,41 @@ def _fake_src(root: Path, *, chat_client: bool, symbol: str = "gjoer_noe") -> Pa
|
||||||
|
|
||||||
|
|
||||||
#: En dør er registrert OG bevist med atferd. De konstruerte kildene under bærer begge deler.
|
#: En dør er registrert OG bevist med atferd. De konstruerte kildene under bærer begge deler.
|
||||||
_FAKE_PROBE = "tests/test_konstruert.py::test_doeren_skriver_artefaktet"
|
#: Probefila SKRIVES (``_write_probe``) i stedet for bare å navngis: fra 19.09 leser gaten probens
|
||||||
|
#: EGEN kilde, og en nodeid som ikke finnes er ikke en atferd.
|
||||||
|
_FAKE_PROBE = "tests/test_konstruerte_prober.py::test_doeren_skriver_artefaktet"
|
||||||
_FAKE_PASSED = {_FAKE_PROBE: "passed"}
|
_FAKE_PASSED = {_FAKE_PROBE: "passed"}
|
||||||
|
|
||||||
|
|
||||||
|
def _fake_run_path() -> dict[str, Any]:
|
||||||
|
"""Kjørestien i den konstruerte kilden — bærer pakkenavnet døren måles mot."""
|
||||||
|
return {"module": "driver.py", "scope": "kjor", "package": "fakesrc"}
|
||||||
|
|
||||||
|
|
||||||
|
def _write_probe(
|
||||||
|
root: Path,
|
||||||
|
*,
|
||||||
|
door: str = "fakesrc.driver",
|
||||||
|
needle: str = "gjoer_noe",
|
||||||
|
asserts: bool = True,
|
||||||
|
nodeid: str = _FAKE_PROBE,
|
||||||
|
) -> str:
|
||||||
|
"""En probe som DRIVER døren: kaller kommandoen og asserter på det den gjorde.
|
||||||
|
|
||||||
|
Hvert av de tre trekkene gaten måler kan skrus av her, ett om gangen: hvilken dør den rører
|
||||||
|
(``door``), hvilket steg den navngir (``needle``), og om den asserter i det hele tatt."""
|
||||||
|
file_part, _, name = nodeid.partition("::")
|
||||||
|
body = (
|
||||||
|
"import subprocess\n"
|
||||||
|
"import sys\n\n\n"
|
||||||
|
f"def {name}(tmp_path):\n"
|
||||||
|
f' proc = subprocess.run([sys.executable, "-m", "{door}", "--steg", "{needle}"])\n'
|
||||||
|
+ (" assert proc.returncode == 0\n" if asserts else " return proc\n")
|
||||||
|
)
|
||||||
|
_write(root / file_part, body)
|
||||||
|
return nodeid
|
||||||
|
|
||||||
|
|
||||||
def _one_step(symbol: str = "gjoer_noe") -> list[dict[str, Any]]:
|
def _one_step(symbol: str = "gjoer_noe") -> list[dict[str, Any]]:
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
@ -155,14 +188,16 @@ def _one_step(symbol: str = "gjoer_noe") -> list[dict[str, Any]]:
|
||||||
|
|
||||||
def test_a_step_reachable_from_a_cli_entry_without_a_chat_client_counts(tmp_path: Path) -> None:
|
def test_a_step_reachable_from_a_cli_entry_without_a_chat_client_counts(tmp_path: Path) -> None:
|
||||||
src = _fake_src(tmp_path, chat_client=False)
|
src = _fake_src(tmp_path, chat_client=False)
|
||||||
row = gate.score_toolbox(_one_step(), src, None, _FAKE_PASSED, tmp_path)
|
_write_probe(tmp_path)
|
||||||
assert (row.k, row.n, row.status) == (1, 1, gate.GREEN)
|
row = gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
|
assert (row.k, row.n, row.status) == (1, 1, gate.GREEN), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
def test_m2_a_step_that_needs_a_chat_client_is_not_callable_from_outside(tmp_path: Path) -> None:
|
def test_m2_a_step_that_needs_a_chat_client_is_not_callable_from_outside(tmp_path: Path) -> None:
|
||||||
"""M-2: fjernes chatklient-sjekken, hopper k fra 0 til 1 og denne armen feller mutanten."""
|
"""M-2: fjernes chatklient-sjekken, hopper k fra 0 til 1 og denne armen feller mutanten."""
|
||||||
src = _fake_src(tmp_path, chat_client=True)
|
src = _fake_src(tmp_path, chat_client=True)
|
||||||
row = gate.score_toolbox(_one_step(), src, None, _FAKE_PASSED, tmp_path)
|
_write_probe(tmp_path)
|
||||||
|
row = gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
assert (row.k, row.n, row.status) == (0, 1, gate.RED)
|
assert (row.k, row.n, row.status) == (0, 1, gate.RED)
|
||||||
assert any("chatklient" in x for x in row.exceptions)
|
assert any("chatklient" in x for x in row.exceptions)
|
||||||
|
|
||||||
|
|
@ -182,7 +217,8 @@ def test_m1_the_denominator_follows_the_source_and_is_not_the_config_length(
|
||||||
"entry": {"kind": "module-main", "module": "driver.py", "scope": "main"},
|
"entry": {"kind": "module-main", "module": "driver.py", "scope": "main"},
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
row = gate.score_toolbox(steps, src, None, _FAKE_PASSED, tmp_path)
|
_write_probe(tmp_path)
|
||||||
|
row = gate.score_toolbox(steps, src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
assert row.n == 1, "et steg uten symbol i kilden er ikke et steg"
|
assert row.n == 1, "et steg uten symbol i kilden er ikke et steg"
|
||||||
assert any("borte" in x for x in row.exceptions)
|
assert any("borte" in x for x in row.exceptions)
|
||||||
|
|
||||||
|
|
@ -200,7 +236,8 @@ def test_a_step_the_driver_no_longer_calls_leaves_the_denominator(tmp_path: Path
|
||||||
"entry": {"kind": "module-main", "module": "driver.py", "scope": "main"},
|
"entry": {"kind": "module-main", "module": "driver.py", "scope": "main"},
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
row = gate.score_toolbox(steps, src, None, _FAKE_PASSED, tmp_path)
|
_write_probe(tmp_path)
|
||||||
|
row = gate.score_toolbox(steps, src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
assert row.n == 1
|
assert row.n == 1
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -208,7 +245,8 @@ def test_an_entry_without_a_main_is_no_entry(tmp_path: Path) -> None:
|
||||||
src = _fake_src(tmp_path, chat_client=False)
|
src = _fake_src(tmp_path, chat_client=False)
|
||||||
text = (src / "driver.py").read_text(encoding="utf-8").replace("def main(", "def _main(")
|
text = (src / "driver.py").read_text(encoding="utf-8").replace("def main(", "def _main(")
|
||||||
_write(src / "driver.py", text)
|
_write(src / "driver.py", text)
|
||||||
row = gate.score_toolbox(_one_step(), src, None, _FAKE_PASSED, tmp_path)
|
_write_probe(tmp_path)
|
||||||
|
row = gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
assert (row.k, row.n) == (0, 1)
|
assert (row.k, row.n) == (0, 1)
|
||||||
assert any("main" in x for x in row.exceptions)
|
assert any("main" in x for x in row.exceptions)
|
||||||
|
|
||||||
|
|
@ -739,7 +777,8 @@ def test_a_stub_main_is_not_a_door_without_a_registered_entry(tmp_path: Path) ->
|
||||||
_fake_pyproject(tmp_path, {})
|
_fake_pyproject(tmp_path, {})
|
||||||
steps = _one_step()
|
steps = _one_step()
|
||||||
steps[0]["entry"] = {"kind": "console-script", "module": "driver.py", "scope": "main"}
|
steps[0]["entry"] = {"kind": "console-script", "module": "driver.py", "scope": "main"}
|
||||||
row = gate.score_toolbox(steps, src, None, _FAKE_PASSED, tmp_path)
|
_write_probe(tmp_path)
|
||||||
|
row = gate.score_toolbox(steps, src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
assert (row.k, row.n) == (0, 1)
|
assert (row.k, row.n) == (0, 1)
|
||||||
assert any("pyproject" in x for x in row.exceptions), row.exceptions
|
assert any("pyproject" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
@ -749,21 +788,23 @@ def test_a_registered_door_without_a_passing_behaviour_probe_does_not_count(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""«Kallet med en fixture gir det dokumenterte artefaktet» — et navn er ikke en atferd."""
|
"""«Kallet med en fixture gir det dokumenterte artefaktet» — et navn er ikke en atferd."""
|
||||||
src = _fake_src(tmp_path, chat_client=False)
|
src = _fake_src(tmp_path, chat_client=False)
|
||||||
|
_write_probe(tmp_path)
|
||||||
steps = _one_step()
|
steps = _one_step()
|
||||||
row = gate.score_toolbox(steps, src, None, {}, tmp_path)
|
row = gate.score_toolbox(steps, src, _fake_run_path(), {}, tmp_path)
|
||||||
assert (row.k, row.n) == (0, 1)
|
assert (row.k, row.n) == (0, 1)
|
||||||
assert any("atferdsprobe" in x for x in row.exceptions), row.exceptions
|
assert any("atferdsprobe" in x for x in row.exceptions), row.exceptions
|
||||||
row = gate.score_toolbox(steps, src, None, {_FAKE_PROBE: "skipped"}, tmp_path)
|
row = gate.score_toolbox(steps, src, _fake_run_path(), {_FAKE_PROBE: "skipped"}, tmp_path)
|
||||||
assert row.k == 0, "hoppet over er ikke bestått"
|
assert row.k == 0, "hoppet over er ikke bestått"
|
||||||
row = gate.score_toolbox(steps, src, None, _FAKE_PASSED, tmp_path)
|
row = gate.score_toolbox(steps, src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
assert (row.k, row.n, row.status) == (1, 1, gate.GREEN)
|
assert (row.k, row.n, row.status) == (1, 1, gate.GREEN), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
def test_a_step_without_any_probe_is_never_a_door(tmp_path: Path) -> None:
|
def test_a_step_without_any_probe_is_never_a_door(tmp_path: Path) -> None:
|
||||||
src = _fake_src(tmp_path, chat_client=False)
|
src = _fake_src(tmp_path, chat_client=False)
|
||||||
|
_write_probe(tmp_path)
|
||||||
steps = _one_step()
|
steps = _one_step()
|
||||||
steps[0].pop("probe")
|
steps[0].pop("probe")
|
||||||
row = gate.score_toolbox(steps, src, None, _FAKE_PASSED, tmp_path)
|
row = gate.score_toolbox(steps, src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
assert (row.k, row.n) == (0, 1)
|
assert (row.k, row.n) == (0, 1)
|
||||||
assert any("ingen atferdsprobe" in x for x in row.exceptions), row.exceptions
|
assert any("ingen atferdsprobe" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
@ -795,13 +836,16 @@ def test_the_published_surface_is_the_repo_manifest_and_not_a_handlist() -> None
|
||||||
|
|
||||||
|
|
||||||
def test_the_surface_count_matches_an_independent_count_of_the_manifest() -> None:
|
def test_the_surface_count_matches_an_independent_count_of_the_manifest() -> None:
|
||||||
"""Den ENE armen som trenger repoets egen git-metadata: i et rent uttrekk («not a git
|
"""Den ENE armen som trenger repoets egen git-metadata. I et rent uttrekk («not a git
|
||||||
repository») finnes ikke manifestet å telle mot, og armen er da et uttrekksartefakt — samme
|
repository») finnes ikke manifestet å telle mot, og armen HOPPER OVER seg selv i stedet for å
|
||||||
klasse som de tre som allerede er navngitt. Nevneren pinnes HER, ikke i armen over."""
|
felle: ellers vokser «røde i uttrekket» med hver ny git-avhengig arm, og tallet slutter å
|
||||||
tracked = subprocess.run(
|
være et funn. Nevneren pinnes HER, ikke i armen over."""
|
||||||
["git", "ls-files", "-z"], cwd=_REPO, capture_output=True, text=True, check=True
|
listing = subprocess.run(
|
||||||
).stdout.split("\0")
|
["git", "ls-files", "-z"], cwd=_REPO, capture_output=True, text=True
|
||||||
mine = {t for t in tracked if t}
|
)
|
||||||
|
if listing.returncode != 0:
|
||||||
|
pytest.skip("uttrekk uten git-metadata: manifestet finnes ikke å telle mot")
|
||||||
|
mine = {t for t in listing.stdout.split("\0") if t}
|
||||||
assert len(mine) == _PUBLISHED_TODAY, len(mine)
|
assert len(mine) == _PUBLISHED_TODAY, len(mine)
|
||||||
files, source = gate.published_files(_REPO, _manifest())
|
files, source = gate.published_files(_REPO, _manifest())
|
||||||
assert {str(p.relative_to(_REPO)) for p in files} == mine
|
assert {str(p.relative_to(_REPO)) for p in files} == mine
|
||||||
|
|
@ -1009,3 +1053,364 @@ def test_an_attestation_with_a_bom_is_still_read(tmp_path: Path) -> None:
|
||||||
path = tmp_path / _CONFIG["runbook"]["attestation"]
|
path = tmp_path / _CONFIG["runbook"]["attestation"]
|
||||||
path.write_bytes(b"\xef\xbb\xbf" + path.read_bytes())
|
path.write_bytes(b"\xef\xbb\xbf" + path.read_bytes())
|
||||||
assert gate.score_runbook(_CONFIG["runbook"], tmp_path, now=_NOW).status == gate.GREEN
|
assert gate.score_runbook(_CONFIG["runbook"], tmp_path, now=_NOW).status == gate.GREEN
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================================
|
||||||
|
# REPARASJON 2, 19.09.2026 (PM-sjekkpunktet på c1bdb37): en probe som er bundet til SITT steg,
|
||||||
|
# en grenseerklæring som er sann, og en grunn som ikke kan være tom. Hver arm under feller på en
|
||||||
|
# ASSERT om atferd, og hver nekt-arm bærer sin egen rc-0-kontroll FØRST: en regel som nekter alt
|
||||||
|
# er like ubrukelig som en som nekter ingenting.
|
||||||
|
# =============================================================================================
|
||||||
|
|
||||||
|
#: Den konstruerte doblen brukes i to steg-varianter; symbolene står her fordi armene teller dem.
|
||||||
|
_TWO_STEP_SYMBOLS = ("gjoer_noe", "gjoer_noe_annet")
|
||||||
|
|
||||||
|
|
||||||
|
def _two_step_src(root: Path) -> Path:
|
||||||
|
"""En konstruert kilde med TO steg bak samme dør — der en delt probe blir et spørsmål."""
|
||||||
|
src = root / "fakesrc"
|
||||||
|
_write(
|
||||||
|
src / "steg.py",
|
||||||
|
"def gjoer_noe():\n return 42\n\n\ndef gjoer_noe_annet():\n return 43\n",
|
||||||
|
)
|
||||||
|
_write(
|
||||||
|
src / "driver.py",
|
||||||
|
"from fakesrc.steg import gjoer_noe, gjoer_noe_annet\n\n\n"
|
||||||
|
"def kjor():\n gjoer_noe()\n return gjoer_noe_annet()\n\n\n"
|
||||||
|
"def main(argv=None):\n return kjor()\n\n\n"
|
||||||
|
'if __name__ == "__main__":\n raise SystemExit(main())\n',
|
||||||
|
)
|
||||||
|
return src
|
||||||
|
|
||||||
|
|
||||||
|
def _two_steps(probe_a: str, probe_b: str) -> list[dict[str, Any]]:
|
||||||
|
entry = {"kind": "module-main", "module": "driver.py", "scope": "main"}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"id": "steg-a",
|
||||||
|
"label": "steg a",
|
||||||
|
"module": "steg.py",
|
||||||
|
"symbol": "gjoer_noe",
|
||||||
|
"driver": {"module": "driver.py", "scope": "kjor"},
|
||||||
|
"entry": dict(entry),
|
||||||
|
"probe": [probe_a],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "steg-b",
|
||||||
|
"label": "steg b",
|
||||||
|
"module": "steg.py",
|
||||||
|
"symbol": "gjoer_noe_annet",
|
||||||
|
"driver": {"module": "driver.py", "scope": "kjor"},
|
||||||
|
"entry": dict(entry),
|
||||||
|
"probe": [probe_b],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_probe_that_never_touches_the_door_is_not_a_behaviour_probe(tmp_path: Path) -> None:
|
||||||
|
"""N2 fra sjekkpunktet: en registrert stub-dør + en probe pekt på en URELATERT grønn test tok
|
||||||
|
rad 1 fra 3 til 4 av 17. Det som skilte dem var ingenting — gaten slo bare opp om nodeid-en
|
||||||
|
var ``passed``. Nå leses probens EGEN kilde, og «rører døren» er målt, ikke erklært."""
|
||||||
|
src = _fake_src(tmp_path, chat_client=False)
|
||||||
|
_write_probe(tmp_path)
|
||||||
|
row = gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
|
assert (row.k, row.n, row.status) == (1, 1, gate.GREEN), row.exceptions
|
||||||
|
|
||||||
|
_write_probe(tmp_path, door="fakesrc.enhelt_annen_modul")
|
||||||
|
row = gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
|
assert (row.k, row.n) == (0, 1)
|
||||||
|
assert any("rører ikke døren" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_probe_that_drives_the_door_without_naming_the_step_proves_the_door_only(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""To steg bak SAMME dør er det vanlige tilfellet (``round_builder`` bærer to). En probe som
|
||||||
|
bare kjører kommandoen skiller dem ikke — den må navngi steget den beviser."""
|
||||||
|
src = _fake_src(tmp_path, chat_client=False)
|
||||||
|
_write_probe(tmp_path)
|
||||||
|
assert gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path).k == 1
|
||||||
|
|
||||||
|
_write_probe(tmp_path, needle="et_helt_annet_steg")
|
||||||
|
row = gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
|
assert (row.k, row.n) == (0, 1)
|
||||||
|
assert any("navngir ikke steget" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_probe_that_asserts_nothing_is_not_a_behaviour_probe(tmp_path: Path) -> None:
|
||||||
|
src = _fake_src(tmp_path, chat_client=False)
|
||||||
|
_write_probe(tmp_path)
|
||||||
|
assert gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path).k == 1
|
||||||
|
|
||||||
|
_write_probe(tmp_path, asserts=False)
|
||||||
|
row = gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
|
assert (row.k, row.n) == (0, 1)
|
||||||
|
assert any("asserter" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_probe_whose_test_does_not_exist_is_never_a_door(tmp_path: Path) -> None:
|
||||||
|
"""«passed» for en nodeid som ikke finnes er et måleresultat uten kilde."""
|
||||||
|
src = _fake_src(tmp_path, chat_client=False)
|
||||||
|
_write_probe(tmp_path)
|
||||||
|
assert gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path).k == 1
|
||||||
|
|
||||||
|
(tmp_path / "tests" / "test_konstruerte_prober.py").unlink()
|
||||||
|
row = gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
|
assert (row.k, row.n) == (0, 1)
|
||||||
|
assert any("finnes ikke" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
_write_probe(tmp_path, nodeid="tests/test_konstruerte_prober.py::test_et_annet_navn")
|
||||||
|
row = gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
|
assert (row.k, row.n) == (0, 1), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_one_probe_cannot_prove_two_steps(tmp_path: Path) -> None:
|
||||||
|
"""N2b sin kjerne: ÉN test brukt som atferdsprobe for alle stegene samtidig. En probe som er
|
||||||
|
delt beviser høyst ett av dem, og gaten vet ikke hvilket — altså ingen."""
|
||||||
|
src = _two_step_src(tmp_path)
|
||||||
|
a = _write_probe(tmp_path, needle="gjoer_noe", nodeid="tests/test_a.py::test_a")
|
||||||
|
b = _write_probe(tmp_path, needle="gjoer_noe_annet", nodeid="tests/test_b.py::test_b")
|
||||||
|
outcomes = {a: "passed", b: "passed"}
|
||||||
|
row = gate.score_toolbox(
|
||||||
|
_two_steps(a, b), src, _fake_run_path(), outcomes, tmp_path
|
||||||
|
)
|
||||||
|
assert (row.k, row.n, row.status) == (2, 2, gate.GREEN), row.exceptions
|
||||||
|
|
||||||
|
shared = _write_probe(
|
||||||
|
tmp_path,
|
||||||
|
needle="gjoer_noe",
|
||||||
|
nodeid="tests/test_delt.py::test_delt",
|
||||||
|
)
|
||||||
|
row = gate.score_toolbox(
|
||||||
|
_two_steps(shared, shared), src, _fake_run_path(), {shared: "passed"}, tmp_path
|
||||||
|
)
|
||||||
|
assert row.k == 0, row.exceptions
|
||||||
|
assert all("delt" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_same_unrelated_green_test_cannot_turn_the_real_row1_into_a_door(tmp_path: Path) -> None:
|
||||||
|
"""N2b mot KONTRAKTEN som står: hver av de 17 stegenes probe byttet ut med én urelatert
|
||||||
|
bestått test. Målt 19.09 ga det «17 av 17 GRØNN» med fjorten stubber; her er nevneren
|
||||||
|
repoets egen kontrakt, og ingen av stegene skal telle."""
|
||||||
|
steps = json.loads(json.dumps(_CONFIG["steps"]))
|
||||||
|
unrelated = "tests/test_ledger.py::test_totals_per_project_and_portfolio"
|
||||||
|
for step in steps:
|
||||||
|
step["probe"] = [unrelated]
|
||||||
|
row = gate.score_toolbox(steps, _SRC, _CONFIG["run_path"], {unrelated: "passed"}, _REPO)
|
||||||
|
assert row.n == _STEPS_TODAY
|
||||||
|
assert row.k == 0, row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_door_rule_says_what_it_measures_about_the_probe() -> None:
|
||||||
|
"""A8-klassen igjen: `EXTERNAL_DOOR` lovet at proben «kaller døren og leser artefaktet den
|
||||||
|
skriver», og det fantes ingen kode. Regelen skal si nøyaktig det gaten MÅLER."""
|
||||||
|
rule = gate.EXTERNAL_DOOR.casefold()
|
||||||
|
assert "probens egen kilde" in rule
|
||||||
|
assert "delt" in rule
|
||||||
|
|
||||||
|
|
||||||
|
# --- rad 1: en grunn kan ikke være tom, og de kuraterte stegene navngis ------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_held_out_call_without_a_reason_stays_in_the_denominator(tmp_path: Path) -> None:
|
||||||
|
"""N4/N5: et steg flyttet fra ``steps`` til ``held_out`` tok nevneren 17 → 16 MED ELLER UTEN
|
||||||
|
grunn. Unntakslista er den nye kurateringsflaten; en tom grunn er ingen grunn."""
|
||||||
|
src, spec = _mini_run_path(tmp_path, extra_call=True)
|
||||||
|
steps = [
|
||||||
|
{
|
||||||
|
"id": "alfa",
|
||||||
|
"label": "alfa",
|
||||||
|
"module": "steg.py",
|
||||||
|
"symbol": "alfa",
|
||||||
|
"driver": {"module": "kjor.py", "scope": "run_project"},
|
||||||
|
"entry": {"kind": "module-main", "module": "kjor.py", "scope": "main"},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
spec["held_out"] = [{"symbol": "beta", "reason": "hjelper inne i alfa, ikke et eget steg"}]
|
||||||
|
assert gate.score_toolbox(steps, src, spec, {}, tmp_path).n == 1, "kontroll: en ekte grunn"
|
||||||
|
|
||||||
|
spec["held_out"] = [{"symbol": "beta", "reason": " "}]
|
||||||
|
row = gate.score_toolbox(steps, src, spec, {}, tmp_path)
|
||||||
|
assert row.n == 2, "en tom grunn skal ikke kunne krympe nevneren"
|
||||||
|
assert any("beta" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_row_summary_counts_reasons_and_not_entries(tmp_path: Path) -> None:
|
||||||
|
"""Radens sammendrag sa «29 kall holdt utenfor med grunn» også når grunnen var tom."""
|
||||||
|
src, spec = _mini_run_path(tmp_path, extra_call=True)
|
||||||
|
spec["held_out"] = [{"symbol": "beta", "reason": ""}]
|
||||||
|
row = gate.score_toolbox([], src, spec, {}, tmp_path)
|
||||||
|
assert "0 kall holdt utenfor med grunn" in row.reason, row.reason
|
||||||
|
|
||||||
|
|
||||||
|
def test_every_held_out_entry_in_the_contract_carries_a_non_empty_reason() -> None:
|
||||||
|
held = list(_CONFIG["run_path"]["held_out"])
|
||||||
|
assert held
|
||||||
|
assert all(str(h.get("reason", "")).strip() for h in held), [
|
||||||
|
h["symbol"] for h in held if not str(h.get("reason", "")).strip()
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_steps_outside_the_run_path_are_named_as_having_no_derived_source() -> None:
|
||||||
|
"""N7: et erklært steg UTENFOR kjørestien kan slettes stille — det har ingen kilde å lukke
|
||||||
|
mot. Fire av sytten er slike. De skal stå i radens egen tekst, ikke telles stille."""
|
||||||
|
row = gate.score_toolbox(_CONFIG["steps"], _SRC, _CONFIG["run_path"], {}, _REPO)
|
||||||
|
spec = _CONFIG["run_path"]
|
||||||
|
outside = [
|
||||||
|
str(s["id"])
|
||||||
|
for s in _CONFIG["steps"]
|
||||||
|
if (s["driver"]["module"], s["driver"]["scope"]) != (spec["module"], spec["scope"])
|
||||||
|
]
|
||||||
|
assert outside, "kontrakten har ingen steg utenfor kjørestien — da er denne armen foreldet"
|
||||||
|
for step_id in outside:
|
||||||
|
assert any(step_id in d and "uten utledet kilde" in d for d in row.diagnostics), (
|
||||||
|
step_id,
|
||||||
|
row.diagnostics,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_row_states_that_a_call_moved_into_a_helper_leaves_its_denominator() -> None:
|
||||||
|
"""N8: et po-kall flyttet én etasje ned, fra ``run_project`` til en hjelper den kaller, tar
|
||||||
|
nevneren 41 → 40. Gaten NAVNGIR at kallet er borte, men feller ikke — og grensen er radens
|
||||||
|
egen å uttale."""
|
||||||
|
row = gate.score_toolbox(_CONFIG["steps"], _SRC, _CONFIG["run_path"], {}, _REPO)
|
||||||
|
assert any("hjelper" in a for a in row.attests), row.attests
|
||||||
|
|
||||||
|
|
||||||
|
# --- rad 3: veiene som fortsatt sto åpne, og grenseerklæringen som var usann -------------------
|
||||||
|
|
||||||
|
#: PMs seks MÅLTE omgåelser av aldri-Claude-vakten, base64 av samme grunn som mønstrene: tests/
|
||||||
|
#: er selv en del av flaten raden skanner, og en klartekst-variant her ville vært sitt eget funn.
|
||||||
|
_MEASURED_ESCAPES = (
|
||||||
|
("sdk-anthropic", "aW1wb3J0IGFudGhyb3BpYw=="),
|
||||||
|
("sdk-anthropic-from", "ZnJvbSBhbnRocm9waWMgaW1wb3J0IEFudGhyb3BpYw=="),
|
||||||
|
("node-runner", "bnB4IEBhbnRocm9waWMtYWkvY2xhdWRlLWNvZGUgLXAgImFuYWx5c2VyIg=="),
|
||||||
|
(
|
||||||
|
"node-runner-argv",
|
||||||
|
"c3VicHJvY2Vzcy5ydW4oWyJucHgiLCAiQGFudGhyb3BpYy1haS9jbGF1ZGUtY29kZSIsICItcCIsICJ4Il0p",
|
||||||
|
),
|
||||||
|
("uv-runner", "dXZ4IGNsYXVkZS1jb2RlIC1wICJ4Ig=="),
|
||||||
|
("dynamisk-import", "X19pbXBvcnRfXygiY2xhdWRlX2FnZW50X3NkayIp"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_every_way_the_checkpoint_measured_as_open_and_cheap_to_close_is_caught(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""De seks formene PM plantet og målte som USETTE 19.09: den offisielle ``anthropic``-SDK-en
|
||||||
|
(to skriveformer), ``npx @anthropic-ai/claude-code`` (to former), ``uvx claude-code`` og et
|
||||||
|
dynamisk import-kall. Hver plantes én om gangen, med rc-0-kontroll først og etter."""
|
||||||
|
cfg = json.loads(json.dumps(_CONFIG["no_claude_path"]))
|
||||||
|
cfg["manifest"] = {"sentinel": "src/run.py", "sentinels": ["src/run.py"]}
|
||||||
|
_write(tmp_path / "src" / "run.py", "x = 1\n")
|
||||||
|
assert gate.score_no_claude_path(cfg, tmp_path).status == gate.GREEN, "kontroll: ren flate"
|
||||||
|
for name, snippet in _MEASURED_ESCAPES:
|
||||||
|
planted = tmp_path / "src" / f"{name}.py"
|
||||||
|
_write(planted, gate.decode(snippet) + "\n")
|
||||||
|
row = gate.score_no_claude_path(cfg, tmp_path)
|
||||||
|
assert row.status == gate.RED, name
|
||||||
|
assert any(name in x for x in row.exceptions), (name, row.exceptions)
|
||||||
|
planted.unlink()
|
||||||
|
assert gate.score_no_claude_path(cfg, tmp_path).status == gate.GREEN
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_row_states_the_ways_that_measurably_remain_and_no_longer_claims_two() -> None:
|
||||||
|
"""Radens egen grenseerklæring oppga NØYAKTIG TO veier igjen; målt sto minst SEKS. De fire
|
||||||
|
billige er lukket over; de tre som står skal stå NAVNGITT — og «to veier» skal være borte."""
|
||||||
|
limit = gate.NO_CLAUDE_LIMIT.casefold()
|
||||||
|
assert "to veier står igjen" not in limit
|
||||||
|
assert "base64" in limit
|
||||||
|
assert "miljøvariabel" in limit
|
||||||
|
assert "satt sammen" in limit
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_row_says_its_ratio_is_not_a_coverage_measure() -> None:
|
||||||
|
"""N11: k av n kan polstres med en vakt som bare treffer sin egen kjent-positive. Tallet er
|
||||||
|
et mål på at hver vakt MÅLER, aldri på hvor mye av angrepsflaten som er dekket."""
|
||||||
|
assert "dekningsmål" in gate.NO_CLAUDE_LIMIT
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_file_that_is_not_utf8_is_still_scanned_for_a_command(tmp_path: Path) -> None:
|
||||||
|
"""N9: ``\\xff\\xfe`` + kommandoen ga GRØNN, og fila ble bare telt som «ulesbar». Et fravær
|
||||||
|
som er målt bort er ikke et fravær."""
|
||||||
|
cfg = json.loads(json.dumps(_CONFIG["no_claude_path"]))
|
||||||
|
cfg["manifest"] = {"sentinel": "src/run.py", "sentinels": ["src/run.py"]}
|
||||||
|
_write(tmp_path / "src" / "run.py", "x = 1\n")
|
||||||
|
assert gate.score_no_claude_path(cfg, tmp_path).status == gate.GREEN, "kontroll: ren flate"
|
||||||
|
spec = next(s for s in cfg["patterns"] if s["id"] == "prosess")
|
||||||
|
payload = b"\xff\xfe" + gate.decode(spec["known_positive_b64"]).encode("utf-8") + b"\n"
|
||||||
|
(tmp_path / "src" / "skjult.bin").write_bytes(payload)
|
||||||
|
row = gate.score_no_claude_path(cfg, tmp_path)
|
||||||
|
assert row.status == gate.RED, row.reason
|
||||||
|
assert any("skjult.bin" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_symlink_pointing_out_of_the_tree_is_refused(tmp_path: Path) -> None:
|
||||||
|
"""N10b: symlenker filtreres bort. Peker lenka INN i treet skannes målet selv; peker den UT,
|
||||||
|
bar den en kommando usett."""
|
||||||
|
cfg = json.loads(json.dumps(_CONFIG["no_claude_path"]))
|
||||||
|
cfg["manifest"] = {"sentinel": "src/run.py", "sentinels": ["src/run.py"]}
|
||||||
|
repo = tmp_path / "repo"
|
||||||
|
_write(repo / "src" / "run.py", "x = 1\n")
|
||||||
|
assert gate.score_no_claude_path(cfg, repo).status == gate.GREEN, "kontroll: ren flate"
|
||||||
|
spec = next(s for s in cfg["patterns"] if s["id"] == "prosess")
|
||||||
|
utenfor = tmp_path / "utenfor.py"
|
||||||
|
_write(utenfor, gate.decode(spec["known_positive_b64"]) + "\n")
|
||||||
|
(repo / "src" / "lenke.py").symlink_to(utenfor)
|
||||||
|
row = gate.score_no_claude_path(cfg, repo)
|
||||||
|
assert row.status != gate.GREEN, row.reason
|
||||||
|
assert any("lenke.py" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_pruned_manifest_is_not_measured_instead_of_green(tmp_path: Path) -> None:
|
||||||
|
"""N6: manifestet filtrert fra 512 til 451 filer (hele ``examples/``, ``spikes/``,
|
||||||
|
``contexts/`` og ``main.py``) ga fortsatt «9 av 9 GRØNN». Sentinel-vakten stoppet en TOM
|
||||||
|
flate, ikke en beskåret — én sentinel per område den beskjæringen tok."""
|
||||||
|
cfg = json.loads(json.dumps(_CONFIG["no_claude_path"]))
|
||||||
|
cfg["manifest"] = {
|
||||||
|
"sentinels": ["src/run.py", "main.py", "eksempler/en.md"],
|
||||||
|
"skip_dirs": [".git", "__pycache__"],
|
||||||
|
}
|
||||||
|
for rel in cfg["manifest"]["sentinels"]:
|
||||||
|
_write(tmp_path / rel, "x = 1\n")
|
||||||
|
assert gate.score_no_claude_path(cfg, tmp_path).status == gate.GREEN, "kontroll: hel flate"
|
||||||
|
|
||||||
|
(tmp_path / "eksempler" / "en.md").unlink()
|
||||||
|
row = gate.score_no_claude_path(cfg, tmp_path)
|
||||||
|
assert row.status == gate.NOT_MEASURED, row.reason
|
||||||
|
assert any("eksempler/en.md" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_contract_plants_a_sentinel_in_every_area_the_handlist_missed() -> None:
|
||||||
|
"""De 69 sporede filene utenfor den gamle håndlista lå i fire områder. Hvert av dem bærer nå
|
||||||
|
sin egen sentinel, så en beskåret flate er IKKE MÅLT og ikke grønn."""
|
||||||
|
sentinels = list(_CONFIG["no_claude_path"]["manifest"].get("sentinels", ()))
|
||||||
|
assert sentinels, "kontrakten har ingen sentinel-liste — da måler en beskåret flate grønt"
|
||||||
|
areas = {s.split("/")[0] for s in sentinels}
|
||||||
|
assert {"main.py", "examples", "spikes", "contexts", "src"} <= areas, sorted(areas)
|
||||||
|
for rel in sentinels:
|
||||||
|
assert (_REPO / rel).is_file(), rel
|
||||||
|
|
||||||
|
|
||||||
|
# --- rad 6: en kjørebok må ha et innhold kontrakten navngir ------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_runbook_without_the_sections_the_contract_names_is_not_green(tmp_path: Path) -> None:
|
||||||
|
"""N3: en kjørebok hvis hele innhold er «x», med en korrekt sjekksummet attestering, ga
|
||||||
|
«2 av 2 GRØNN». Tom fil var alt som var avvist."""
|
||||||
|
cfg = _CONFIG["runbook"]
|
||||||
|
sections = list(cfg.get("sections", ()))
|
||||||
|
assert sections, "kontrakten navngir ingen seksjoner — da er «x» en kjørebok"
|
||||||
|
text = "# kjørebok\n\n" + "\n".join(f"## {s}\n\nnoe\n" for s in sections)
|
||||||
|
_write(tmp_path / cfg["path"], text)
|
||||||
|
digest = hashlib.sha256(text.encode("utf-8")).hexdigest()
|
||||||
|
_attestation_lines(tmp_path, digest)
|
||||||
|
row = gate.score_runbook(cfg, tmp_path, now=_NOW)
|
||||||
|
assert (row.k, row.n, row.status) == (2, 2, gate.GREEN), row.exceptions
|
||||||
|
|
||||||
|
_write(tmp_path / cfg["path"], "x\n")
|
||||||
|
digest = hashlib.sha256(b"x\n").hexdigest()
|
||||||
|
_attestation_lines(tmp_path, digest)
|
||||||
|
row = gate.score_runbook(cfg, tmp_path, now=_NOW)
|
||||||
|
assert row.status == gate.NOT_MEASURED, row.exceptions
|
||||||
|
assert any("seksjon" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue