"""B-gatens egne tester: hver rad KAN bli grønn og KAN bli rød — og kan ikke FAKES grønn. Rammen er operatørbeslutningen 19.09.2026: i utvikling og test LEDER Claude Code, og portfolio-optimiser er verktøykassen. po kaller ALDRI Claude. Gaten måler avstanden dit, rad for rad, uten et eneste modellkall. Tre ting gjør testene load-bearing i stedet for grønne-men-døde: * **Nevneren telles uavhengig HER.** Rad 1 og rad 2 får ikke sine tall fra gatens egne funksjoner: testen parser kilden selv (``ast``) eller importerer modulen og teller, og sammenligner. En gate som hardkoder nevneren felles av arm ``M-1``, som kjører den mot en KONSTRUERT kilde der svaret er et annet enn repoets. * **Hver vakt har en kjent-positiv.** Et mønster som ikke treffer sin egen kjent-positive er ikke en måling, og rad 3 nekter å telle det (``M-3``). Et fravær («0 treff») uttales med nevner. * **Rad 6 kan ikke bli grønn av en fil gaten selv skriver** (``M-5``): armen kjører hele ``evaluate`` og krever at attesteringsfila fortsatt ikke finnes etterpå. """ from __future__ import annotations import ast import base64 import hashlib import json import re import subprocess import sys from datetime import datetime, timezone from pathlib import Path from typing import Any from portfolio_optimiser import workflow from portfolio_optimiser.evals import b_gate as gate from portfolio_optimiser.evals import v1_gate _REPO = Path(__file__).resolve().parents[1] _SRC = _REPO / "src" / "portfolio_optimiser" _CONFIG = gate.load_config() #: Klokka rad 6 leses mot, injisert: den ENE sjekken som ser på tiden skal ikke flytte seg. _NOW = datetime(2026, 9, 19, 12, 0, tzinfo=timezone.utc) #: Repoets egne tall ved skriving (19.09.2026), talt av armene under mot KILDEN. De står her for #: at en stille endring i kjørestien skal vise seg som en rød test og ikke som et nytt tall. _STEPS_TODAY = 17 _EXTERNAL_TODAY = 3 _ROLES_TODAY = ("proposer", "checker") _PATTERNS_TODAY = 9 def _write(path: Path, text: str) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(text, encoding="utf-8") def _probes(outcome: str = "missing") -> Any: return lambda nodeids: {n: outcome for n in nodeids} def _row(rows: list[v1_gate.Row], key: str) -> v1_gate.Row: return next(r for r in rows if r.key == key) # --------------------------------------------------------------------------------------------- # Rad 1 — verktøykassen er komplett # --------------------------------------------------------------------------------------------- def _independently_counted_steps() -> tuple[int, int]: """(steg som oppløses i kilden, steg med et kallsted) — talt HER, av testens egen parser. Kilden er ``src/portfolio_optimiser/``; ingen av gatens funksjoner røres.""" resolved = called = 0 for step in _CONFIG["steps"]: tree = ast.parse((_SRC / step["module"]).read_text(encoding="utf-8")) defs = { n.name for n in tree.body if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)) } if step["symbol"] in defs: resolved += 1 driver = ast.parse((_SRC / step["driver"]["module"]).read_text(encoding="utf-8")) scopes = [ n for n in ast.walk(driver) if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef)) and n.name == step["driver"]["scope"] ] names = { c.func.id if isinstance(c.func, ast.Name) else c.func.attr for s in scopes for c in ast.walk(s) if isinstance(c, ast.Call) and isinstance(c.func, (ast.Name, ast.Attribute)) } if step["symbol"] in names: called += 1 return resolved, called def test_the_step_denominator_equals_an_independent_count_of_the_source() -> None: resolved, called = _independently_counted_steps() assert resolved == called == _STEPS_TODAY, "kjørestien har endret seg — oppdater nevneren" row = gate.score_toolbox(_CONFIG["steps"], _SRC, _CONFIG["run_path"], {}, _REPO) assert row.n == _STEPS_TODAY def test_row1_today_is_red_with_three_of_seventeen_steps_callable_from_outside() -> None: passing = {n: "passed" for step in _CONFIG["steps"] for n in step.get("probe", ())} row = gate.score_toolbox(_CONFIG["steps"], _SRC, _CONFIG["run_path"], passing, _REPO) assert (row.k, row.n, row.status) == (_EXTERNAL_TODAY, _STEPS_TODAY, gate.RED) named = {x.split(":")[0] for x in row.exceptions} assert "rundebinding" not in named and "gate" not in named assert "validering" in named and "utboks" in named assert "prepass-artefakt" in named and "parse-feil" in named def _fake_src(root: Path, *, chat_client: bool, symbol: str = "gjoer_noe") -> Path: """En konstruert kilde med ETT steg: en inngang ``main`` som når steget. ``chat_client=True`` legger en chatklient på veien dit — steget er da fortsatt kallbart, men ikke uten en modell, og skal ikke telle.""" src = root / "fakesrc" body = " return create_chat_client()\n" if chat_client else " return 1\n" _write(src / "steg.py", f"def {symbol}():\n return 42\n") _write( src / "driver.py", "from fakesrc.steg import " + symbol + "\n\n" "def _hjelper():\n" + body + "\n\n" "def kjor():\n" f" _hjelper()\n return {symbol}()\n\n\n" "def main(argv=None):\n return kjor()\n\n\n" 'if __name__ == "__main__":\n raise SystemExit(main())\n', ) return src #: 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" _FAKE_PASSED = {_FAKE_PROBE: "passed"} def _one_step(symbol: str = "gjoer_noe") -> list[dict[str, Any]]: return [ { "id": "steg", "label": "ett steg", "module": "steg.py", "symbol": symbol, "driver": {"module": "driver.py", "scope": "kjor"}, "entry": {"kind": "module-main", "module": "driver.py", "scope": "main"}, "probe": [_FAKE_PROBE], } ] 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) row = gate.score_toolbox(_one_step(), src, None, _FAKE_PASSED, tmp_path) assert (row.k, row.n, row.status) == (1, 1, gate.GREEN) 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.""" src = _fake_src(tmp_path, chat_client=True) row = gate.score_toolbox(_one_step(), src, None, _FAKE_PASSED, tmp_path) assert (row.k, row.n, row.status) == (0, 1, gate.RED) assert any("chatklient" in x for x in row.exceptions) def test_m1_the_denominator_follows_the_source_and_is_not_the_config_length( tmp_path: Path, ) -> None: """M-1: en hardkodet nevner overlever repoet, men ikke en kilde med ett steg mindre.""" src = _fake_src(tmp_path, chat_client=False) steps = _one_step() + [ { "id": "borte", "label": "et steg som ikke finnes i kilden", "module": "steg.py", "symbol": "finnes_ikke", "driver": {"module": "driver.py", "scope": "kjor"}, "entry": {"kind": "module-main", "module": "driver.py", "scope": "main"}, } ] row = gate.score_toolbox(steps, src, None, _FAKE_PASSED, tmp_path) assert row.n == 1, "et steg uten symbol i kilden er ikke et steg" assert any("borte" in x for x in row.exceptions) def test_a_step_the_driver_no_longer_calls_leaves_the_denominator(tmp_path: Path) -> None: src = _fake_src(tmp_path, chat_client=False) _write(src / "steg.py", "def gjoer_noe():\n return 42\n\n\ndef ubrukt():\n return 0\n") steps = _one_step() + [ { "id": "ubrukt", "label": "definert, men ikke i kjørestien", "module": "steg.py", "symbol": "ubrukt", "driver": {"module": "driver.py", "scope": "kjor"}, "entry": {"kind": "module-main", "module": "driver.py", "scope": "main"}, } ] row = gate.score_toolbox(steps, src, None, _FAKE_PASSED, tmp_path) assert row.n == 1 def test_an_entry_without_a_main_is_no_entry(tmp_path: Path) -> None: src = _fake_src(tmp_path, chat_client=False) text = (src / "driver.py").read_text(encoding="utf-8").replace("def main(", "def _main(") _write(src / "driver.py", text) row = gate.score_toolbox(_one_step(), src, None, _FAKE_PASSED, tmp_path) assert (row.k, row.n) == (0, 1) assert any("main" in x for x in row.exceptions) # --------------------------------------------------------------------------------------------- # Rad 2 — det modellen leverte, kan leveres utenfra # --------------------------------------------------------------------------------------------- def test_the_role_denominator_is_read_from_workflow_and_matches_the_imported_module() -> None: assert workflow._MAKER_CHECKER_ROLES == _ROLES_TODAY # uavhengig telling: modulen selv assert gate.run_roles(_SRC, _CONFIG["roles"]["source"]) == _ROLES_TODAY def test_the_role_denominator_follows_a_changed_source(tmp_path: Path) -> None: src = tmp_path / "fakesrc" _write(src / "workflow.py", '_MAKER_CHECKER_ROLES = ("a", "b", "c")\n') assert gate.run_roles(src, _CONFIG["roles"]["source"]) == ("a", "b", "c") def test_a_role_counts_only_when_every_named_probe_passes() -> None: evidence = _CONFIG["roles"]["evidence"] outcomes = {n: "passed" for ns in evidence.values() for n in ns} row = gate.score_roles(_CONFIG["roles"], outcomes, _SRC) assert (row.k, row.n, row.status) == (2, 2, gate.GREEN) outcomes[evidence["checker"][0]] = "failed" row = gate.score_roles(_CONFIG["roles"], outcomes, _SRC) assert (row.k, row.n, row.status) == (1, 2, gate.RED) def test_row2_today_is_red_because_the_named_probes_do_not_exist_yet() -> None: row = gate.score_roles(_CONFIG["roles"], {}, _SRC) assert (row.k, row.n, row.status) == (0, 2, gate.RED) assert all("missing" in x for x in row.exceptions) def test_m4_row2_demands_byte_identical_artefacts_not_merely_present_ones() -> None: """M-4: byttes kravet til «finnes», står kontrakten igjen uten den ene setningen som skiller en ekte likhet fra to filer med samme navn.""" assert "byte-identisk" in gate.BYTE_IDENTICAL_RULE row = gate.score_roles(_CONFIG["roles"], {}, _SRC) assert any("byte-identisk" in a for a in row.attests) # --------------------------------------------------------------------------------------------- # Rad 3 — po har ingen vei til Claude # --------------------------------------------------------------------------------------------- def _patterns() -> list[dict[str, str]]: return list(_CONFIG["no_claude_path"]["patterns"]) def test_every_pattern_hits_its_own_known_positive_and_misses_its_known_negative() -> None: """Fravær er et måleresultat: en vakt som ikke kan treffe, måler ikke null — den måler ingenting. Nevner: alle mønstrene i kontrakten.""" assert len(_patterns()) == _PATTERNS_TODAY for spec in _patterns(): rx = re.compile(gate.decode(spec["pattern_b64"])) assert rx.search(gate.decode(spec["known_positive_b64"])), spec["id"] assert not rx.search(gate.decode(spec["known_negative_b64"])), spec["id"] def _clean_surface(tmp_path: Path) -> dict[str, Any]: """En ren, konstruert flate med sin egen sentinel — nevneren er aldri 0 her.""" cfg = json.loads(json.dumps(_CONFIG["no_claude_path"])) cfg["manifest"] = {"sentinel": "src/rent.py", "skip_dirs": [".git", "__pycache__"]} _write(tmp_path / "src" / "rent.py", "print('ingenting her')\n") return cfg def test_m3_the_denominator_is_the_number_of_patterns_that_can_measure(tmp_path: Path) -> None: """M-3: fjernes et mønster, faller nevneren — og et mønster som ikke treffer sin egen kjent-positive telles ikke som en vakt.""" cfg = _clean_surface(tmp_path) row = gate.score_no_claude_path(cfg, tmp_path) assert (row.k, row.n, row.status) == (_PATTERNS_TODAY, _PATTERNS_TODAY, gate.GREEN) cfg["patterns"][0]["pattern_b64"] = base64.b64encode(b"finnes-aldri-xyzzy").decode() row = gate.score_no_claude_path(cfg, tmp_path) assert (row.k, row.n, row.status) == (_PATTERNS_TODAY - 1, _PATTERNS_TODAY, gate.RED) assert any("kjent-positiv" in x for x in row.exceptions) cfg["patterns"] = cfg["patterns"][1:] row = gate.score_no_claude_path(cfg, tmp_path) assert row.n == _PATTERNS_TODAY - 1 def test_a_planted_path_to_claude_turns_the_row_red_for_that_pattern(tmp_path: Path) -> None: cfg = _clean_surface(tmp_path) assert gate.score_no_claude_path(cfg, tmp_path).status == gate.GREEN, "rc-0-kontrollen først" for spec in cfg["patterns"]: _write( tmp_path / "src" / f"{spec['id']}.py", gate.decode(spec["known_positive_b64"]) + "\n" ) row = gate.score_no_claude_path(cfg, tmp_path) assert (row.k, row.status) == (0, gate.RED) assert len(row.exceptions) == _PATTERNS_TODAY def test_markdown_counts_only_inside_fenced_blocks(tmp_path: Path) -> None: """«docs med kjørbare kommandoer»: en pakkenavn-omtale i brødtekst er ikke en vei til Claude; den samme linja i en kodeblokk er en kommando noen kan kjøre.""" cfg = _clean_surface(tmp_path) spec = next(s for s in cfg["patterns"] if s["id"] == "maf-anthropic") line = gate.decode(spec["known_positive_b64"]) _write(tmp_path / "docs" / "prosa.md", f"- vurdert og forkastet: `{line}` (aldri tatt inn)\n") assert gate.score_no_claude_path(cfg, tmp_path).status == gate.GREEN _write(tmp_path / "docs" / "kjorbar.md", f"```bash\n{line}\n```\n") row = gate.score_no_claude_path(cfg, tmp_path) assert row.status == gate.RED assert any("kjorbar.md" in x for x in row.exceptions) def test_row3_on_the_real_published_surface_is_green_today() -> None: row = gate.score_no_claude_path(_CONFIG["no_claude_path"], _REPO) assert (row.k, row.n, row.status) == (_PATTERNS_TODAY, _PATTERNS_TODAY, gate.GREEN), ( row.exceptions ) def test_the_surface_names_its_denominator_in_files_not_in_prose() -> None: files, _ = gate.published_files(_REPO, _manifest()) assert len(files) > 100, "en tom flate ville gitt 0 treff uten å måle noe" assert any(f.name == "run.py" for f in files) # --------------------------------------------------------------------------------------------- # Rad 4 — ingen modellkall i verktøykasse-modus # --------------------------------------------------------------------------------------------- def test_row4_is_red_today_and_names_all_three_checks() -> None: row = gate.score_no_model_calls(_CONFIG["no_model_calls"], {}, _SRC) assert (row.k, row.n, row.status) == (0, 3, gate.RED) assert len(row.exceptions) == 3 def test_row4_counts_only_checks_whose_probes_pass() -> None: checks = _CONFIG["no_model_calls"]["checks"] outcomes = {n: "passed" for c in checks.values() for n in c["probe"]} row = gate.score_no_model_calls(_CONFIG["no_model_calls"], outcomes, _SRC) assert row.status == gate.GREEN outcomes[checks["fullfører"]["probe"][0]] = "skipped" row = gate.score_no_model_calls(_CONFIG["no_model_calls"], outcomes, _SRC) assert (row.k, row.status) == (2, gate.RED) def test_row4_states_what_the_budget_guard_is_in_b_instead_of_pretending() -> None: row = gate.score_no_model_calls(_CONFIG["no_model_calls"], {}, _SRC) assert any("budsjett" in a.casefold() for a in row.attests) assert "valgt" in gate.BUDGET_IN_B.casefold() # --------------------------------------------------------------------------------------------- # Rad 5 — Foundry-veien urørt # --------------------------------------------------------------------------------------------- def test_the_foundry_path_is_intact_in_the_source_today() -> None: ok, missing = gate.foundry_intact(_CONFIG["foundry"], _SRC) assert (ok, missing) == (True, ()) def test_the_foundry_check_fails_when_the_azure_profile_is_gone(tmp_path: Path) -> None: src = tmp_path / "fakesrc" _write(src / "backends.py", "class Profile:\n LOCAL = 'local'\n") _write(src / "run.py", "def run_project(client_factory=None):\n return 1\n") ok, missing = gate.foundry_intact(_CONFIG["foundry"], src) assert ok is False assert any("AZURE" in m for m in missing) def test_row5_is_red_until_the_schema_comparison_probe_passes() -> None: structural = len(_CONFIG["foundry"]["profile"]["members"]) + 2 row = gate.score_foundry(_CONFIG["foundry"], {}, _SRC) assert (row.k, row.n, row.status) == (structural, structural + 1, gate.RED) nodeid = _CONFIG["foundry"]["evidence"]["samme-skjema"][0] row = gate.score_foundry(_CONFIG["foundry"], {nodeid: "passed"}, _SRC) assert (row.k, row.n, row.status) == (structural + 1, structural + 1, gate.GREEN) # --------------------------------------------------------------------------------------------- # Rad 6 — kjøreboka finnes # --------------------------------------------------------------------------------------------- def test_row6_is_not_measured_while_the_runbook_is_missing(tmp_path: Path) -> None: row = gate.score_runbook(_CONFIG["runbook"], tmp_path) assert (row.k, row.n, row.status) == (0, 2, gate.NOT_MEASURED) assert row.failing is True def test_row6_is_still_not_measured_without_the_operators_attestation(tmp_path: Path) -> None: _write(tmp_path / _CONFIG["runbook"]["path"], "# kjørebok\n\nsteg 1 …\n") row = gate.score_runbook(_CONFIG["runbook"], tmp_path) assert (row.k, row.n, row.status) == (1, 2, gate.NOT_MEASURED) def test_row6_goes_green_only_with_every_required_key(tmp_path: Path) -> None: cfg = _CONFIG["runbook"] digest = _runbook(tmp_path) attest = tmp_path / cfg["attestation"] _write(attest, f"kjørebok: {cfg['path']}\nsjekksum: {digest}\nkjørt: operatøren\n") row = gate.score_runbook(cfg, tmp_path, now=_NOW) assert row.status == gate.NOT_MEASURED assert any("dato" in x for x in row.exceptions) _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) def test_a_duplicated_key_in_the_attestation_is_red(tmp_path: Path) -> None: cfg = _CONFIG["runbook"] digest = _runbook(tmp_path) _attestation_lines(tmp_path, digest) path = tmp_path / cfg["attestation"] path.write_text(path.read_text(encoding="utf-8") + "kjørt: en annen\n", encoding="utf-8") row = gate.score_runbook(cfg, tmp_path, now=_NOW) assert row.status == gate.NOT_MEASURED assert any("to ganger" in x for x in row.exceptions) def test_m5_the_gate_never_writes_the_attestation_itself(tmp_path: Path) -> None: """M-5: kan gaten skrive fila den leser, er rad 6 en sløyfe som bekrefter seg selv.""" _write(tmp_path / _CONFIG["runbook"]["path"], "# kjørebok\n") attest = tmp_path / _CONFIG["runbook"]["attestation"] rows = gate.evaluate(config=_CONFIG, repo_root=tmp_path, src=_SRC, probe_runner=_probes()) assert not attest.exists(), "gaten skrev operatørens attestering — da beviser rad 6 ingenting" assert _row(rows, "kjørebok").status == gate.NOT_MEASURED assert any("aldri" in a for a in _row(rows, "kjørebok").attests) # --------------------------------------------------------------------------------------------- # Hele gaten # --------------------------------------------------------------------------------------------- def test_the_gate_is_red_today_and_every_row_is_red_for_a_measured_reason() -> None: rows = gate.evaluate(config=_CONFIG, repo_root=_REPO, src=_SRC, probe_runner=_probes()) assert gate.exit_code(rows) == 1 assert [r.key for r in rows] == [ "verktøykasse", "roller", "ingen-claude-vei", "ingen-modellkall", "foundry", "kjørebok", ] for row in rows: assert row.status in {gate.GREEN, gate.RED, gate.NOT_MEASURED} if row.status != gate.GREEN: assert row.k is not None and row.n is not None, f"{row.key} mangler k av N" assert row.exceptions, f"{row.key} er ikke grønn uten å si hvorfor" def test_the_probe_runner_separates_a_test_that_exists_from_one_that_does_not() -> None: """Kjent-positiv for selve probe-mekanismen: «missing» over hele lista ville vært et måleresultat uten nevner. Nevneren er to nodeid-er — én som finnes i suiten og én som ikke gjør det — og mekanismen må skille dem. Proben peker med vilje UT av denne fila: en nodeid herfra ville fått barne-pytest til å kjøre denne testen igjen, i det uendelige.""" lever = "tests/test_ledger.py::test_totals_per_project_and_portfolio" dod = "tests/test_ledger.py::test_finnes_ikke_xyzzy" outcomes = v1_gate.run_probes([lever, dod], _REPO) assert outcomes[lever] == "passed" assert outcomes[dod] == "missing" def test_render_names_every_row_the_budget_choice_and_the_runbook_rule() -> None: rows = gate.evaluate(config=_CONFIG, repo_root=_REPO, src=_SRC, probe_runner=_probes()) text = gate.render(rows) for row in rows: assert row.title in text assert gate.BUDGET_IN_B in text assert gate.RUNBOOK_RULE in text def test_the_command_runs_offline_and_exits_one() -> None: proc = subprocess.run( [sys.executable, "-m", "portfolio_optimiser.evals.b_gate", "--json"], cwd=_REPO, capture_output=True, text=True, ) assert proc.returncode == 1, proc.stderr payload = json.loads(proc.stdout) assert payload["exit"] == 1 assert len(payload["rows"]) == 6 def test_a_bad_attestation_path_is_usage_error_not_a_silent_green(tmp_path: Path) -> None: proc = subprocess.run( [sys.executable, "-m", "portfolio_optimiser.evals.b_gate", "--attest", str(tmp_path / "x")], cwd=_REPO, capture_output=True, text=True, ) assert proc.returncode == 2 # ============================================================================================= # REPARASJONEN 19.09.2026 (PM-sjekkpunktet på 207337c): nevnerne utledet av KILDEN, en dør som # må være REGISTRERT og bevist med atferd, hele den publiserte flaten, og en attestering som kan # felles. Hver arm under feller på en ASSERT om atferd, aldri ved innsamling. # ============================================================================================= #: po-funksjoner ``run.py::run_project`` faktisk kaller — talt av armens EGEN parser under. #: 41 = PMs 39 deterministiske + de to som krever en chatklient og holdes utenfor med grunn. _RUN_PATH_CALLS_TODAY = 41 #: utboks-skrivere: definert i outbox.py / kalt i kjørestien. PMs tall, talt om igjen her. _WRITERS_DEFINED = 10 _WRITERS_IN_RUN_PATH = 7 #: publiserte filer i git-manifestet (uttrekk og arbeidstre gir SAMME tall — det var hele poenget #: med å slutte å telle filtreet: 435 i arbeidstreet var to gitignorerte .local.md-filer). _PUBLISHED_TODAY = 512 _UNDECODABLE_TODAY = 1 def _manifest() -> dict[str, Any]: manifest = _CONFIG["no_claude_path"].get("manifest") assert manifest is not None, "kontrakten utleder ikke flaten av repo-manifestet" return dict(manifest) def _run_path_spec() -> dict[str, Any]: spec = _CONFIG.get("run_path") assert spec is not None, "kontrakten navngir ingen kjørested — nevneren har da ingen kilde" return dict(spec) def _independently_counted_run_path() -> dict[str, str]: """po-funksjonene ``run_project`` kaller, talt av TESTENS egen parser — ingen gate-kode rørt. Regelen: et navn importert fra en ``portfolio_optimiser``-undermodul og kalt i scopet, eller et attributt på en modul importert som ``from portfolio_optimiser import X``. Klasser og unntak (stor forbokstav) er ikke steg; private hjelpere i run.py heller ikke.""" tree = ast.parse((_SRC / "run.py").read_text(encoding="utf-8")) symbols: dict[str, str] = {} aliases: set[str] = set() for node in ast.walk(tree): if not isinstance(node, ast.ImportFrom): continue module = node.module or "" if module == "portfolio_optimiser": aliases |= {a.asname or a.name for a in node.names} elif module.startswith("portfolio_optimiser."): for alias in node.names: symbols[alias.asname or alias.name] = module.split(".")[-1] + ".py" scope = next( n for n in ast.walk(tree) if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef)) and n.name == "run_project" ) found: dict[str, str] = {} for call in ast.walk(scope): if not isinstance(call, ast.Call): continue func = call.func if isinstance(func, ast.Name) and func.id in symbols and func.id[:1].islower(): found[func.id] = symbols[func.id] elif ( isinstance(func, ast.Attribute) and isinstance(func.value, ast.Name) and func.value.id in aliases and func.attr[:1].islower() ): found[func.attr] = f"{func.value.id}.py" return found def test_the_run_path_denominator_is_read_from_the_source_not_from_the_contract() -> None: """Rad 1s nevner kom fra gatens EGEN b_gate.json. Nå leses kjørestien av kilden, og testen teller den om igjen selv.""" mine = _independently_counted_run_path() assert len(mine) == _RUN_PATH_CALLS_TODAY, sorted(mine) theirs = gate.run_path_calls(_SRC, _run_path_spec()) assert set(theirs) == set(mine), sorted(set(mine) ^ set(theirs)) def test_every_call_in_the_run_path_is_either_a_declared_step_or_held_out_with_a_reason() -> None: """Den ene egenskapen en kuratert liste ikke har: ingenting kan utelates STILLE.""" calls = set(_independently_counted_run_path()) declared = {str(s["symbol"]) for s in _CONFIG["steps"]} held = { str(h["symbol"]): str(h.get("reason", "")) for h in _run_path_spec().get("held_out", ()) } assert not (calls - declared - set(held)), sorted(calls - declared - set(held)) assert all(held.values()), [s for s, why in held.items() if not why] assert len(calls & declared) + len(held) == _RUN_PATH_CALLS_TODAY def test_the_outbox_writers_the_run_path_uses_are_all_declared_steps() -> None: """PMs 7 av 10, talt om igjen her: artefaktfamilien er ikke tre filer, den er sju.""" outbox_tree = ast.parse((_SRC / "outbox.py").read_text(encoding="utf-8")) writers = { n.name for n in outbox_tree.body if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef)) and n.name.startswith("write_") } assert len(writers) == _WRITERS_DEFINED in_path = writers & set(_independently_counted_run_path()) assert len(in_path) == _WRITERS_IN_RUN_PATH, sorted(in_path) declared = {str(s["symbol"]) for s in _CONFIG["steps"]} assert not (in_path - declared), sorted(in_path - declared) def _mini_run_path(root: Path, *, extra_call: bool) -> tuple[Path, dict[str, Any]]: """En konstruert kilde med en kjøresti som kaller ett eller to po-steg.""" src = root / "minisrc" _write(src / "steg.py", "def alfa():\n return 1\n\n\ndef beta():\n return 2\n") body = " alfa()\n" + (" beta()\n" if extra_call else "") _write( src / "kjor.py", f"from minisrc.steg import alfa, beta\n\n\nasync def run_project():\n{body} return 0\n", ) return src, {"module": "kjor.py", "scope": "run_project", "package": "minisrc"} def test_an_undeclared_call_in_the_run_path_raises_the_denominator_and_is_named( tmp_path: Path, ) -> None: """M-6: nevneren kan ikke krympe stille — et kall ingen erklærte teller som et steg UTEN dør.""" 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"}, } ] row = gate.score_toolbox(steps, src, spec, {}, tmp_path) assert row.n == 2, "beta kalles i kjørestien og er verken erklært eller holdt utenfor" assert any("beta" in x for x in row.exceptions) def test_a_held_out_call_leaves_the_denominator_but_is_named_with_its_reason( tmp_path: Path, ) -> None: src, spec = _mini_run_path(tmp_path, extra_call=True) spec["held_out"] = [{"symbol": "beta", "reason": "hjelper inne i alfa, ikke et eget steg"}] 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"}, } ] row = gate.score_toolbox(steps, src, spec, {}, tmp_path) assert row.n == 1 assert any("beta" in d and "hjelper inne i alfa" in d for d in row.diagnostics), row.diagnostics def test_removing_a_declared_step_does_not_shrink_the_denominator(tmp_path: Path) -> None: """A2 fra sjekkpunktet: «utboks» fjernet fra steps ga «3 av 12» uten en klage.""" src, spec = _mini_run_path(tmp_path, extra_call=True) row = gate.score_toolbox([], src, spec, {}, tmp_path) assert row.n == 2, "begge kallene står igjen i kjørestien selv om kontrakten glemte dem" assert len(row.exceptions) == 2 # --- døren: registrert, av en art gaten kan verifisere, og bevist med atferd ------------------- def _fake_pyproject(root: Path, scripts: dict[str, str]) -> None: lines = "\n".join(f'{name} = "{target}"' for name, target in scripts.items()) _write( root / "pyproject.toml", f'[project]\nname = "fake"\nversion = "0"\n\n[project.scripts]\n{lines}\n\n[tool.x]\ny = 1\n', ) def test_an_entry_kind_the_gate_cannot_verify_is_never_a_door(tmp_path: Path) -> None: src = _fake_src(tmp_path, chat_client=False) ok, why = gate.registered_entry( src, tmp_path, "fakesrc", {"kind": "magi", "module": "driver.py", "scope": "main"} ) assert ok is False and "magi" in why assert "magi" not in gate.ENTRY_KINDS def test_a_console_script_is_a_door_only_when_the_manifest_registers_it(tmp_path: Path) -> None: src = _fake_src(tmp_path, chat_client=False) entry = {"kind": "console-script", "module": "driver.py", "scope": "main"} _fake_pyproject(tmp_path, {}) ok, why = gate.registered_entry(src, tmp_path, "fakesrc", entry) assert ok is False and "pyproject" in why _fake_pyproject(tmp_path, {"fake-kommando": "fakesrc.driver:main"}) assert gate.registered_entry(src, tmp_path, "fakesrc", entry) == (True, "") def test_a_module_main_is_a_door_only_with_the_dunder_guard(tmp_path: Path) -> None: src = _fake_src(tmp_path, chat_client=False) entry = {"kind": "module-main", "module": "driver.py", "scope": "main"} assert gate.registered_entry(src, tmp_path, "fakesrc", entry) == (True, "") stripped = (src / "driver.py").read_text(encoding="utf-8") _write(src / "driver.py", stripped[: stripped.index("if __name__")]) ok, why = gate.registered_entry(src, tmp_path, "fakesrc", entry) assert ok is False and "__main__" in why def test_a_subcommand_is_a_door_only_when_the_parser_registers_that_name(tmp_path: Path) -> None: src = _fake_src(tmp_path, chat_client=False) entry = { "kind": "subcommand", "module": "driver.py", "scope": "main", "command": "naviger-pakke", } assert gate.registered_entry(src, tmp_path, "fakesrc", entry)[0] is False _write( src / "driver.py", (src / "driver.py").read_text(encoding="utf-8") + '\n\ndef _parser(sub):\n sub.add_parser("naviger-pakke")\n', ) assert gate.registered_entry(src, tmp_path, "fakesrc", entry) == (True, "") def test_a_stub_main_is_not_a_door_without_a_registered_entry(tmp_path: Path) -> None: """A1 fra sjekkpunktet, felt: en modul med bare `def main(): return ()` tok rad 1 fra 3 til 4 av 13. Navn alene er aldri grønt.""" src = _fake_src(tmp_path, chat_client=False) _fake_pyproject(tmp_path, {}) steps = _one_step() steps[0]["entry"] = {"kind": "console-script", "module": "driver.py", "scope": "main"} row = gate.score_toolbox(steps, src, None, _FAKE_PASSED, tmp_path) assert (row.k, row.n) == (0, 1) assert any("pyproject" in x for x in row.exceptions), row.exceptions def test_a_registered_door_without_a_passing_behaviour_probe_does_not_count( tmp_path: Path, ) -> None: """«Kallet med en fixture gir det dokumenterte artefaktet» — et navn er ikke en atferd.""" src = _fake_src(tmp_path, chat_client=False) steps = _one_step() row = gate.score_toolbox(steps, src, None, {}, tmp_path) assert (row.k, row.n) == (0, 1) assert any("atferdsprobe" in x for x in row.exceptions), row.exceptions row = gate.score_toolbox(steps, src, None, {_FAKE_PROBE: "skipped"}, tmp_path) assert row.k == 0, "hoppet over er ikke bestått" row = gate.score_toolbox(steps, src, None, _FAKE_PASSED, tmp_path) assert (row.k, row.n, row.status) == (1, 1, gate.GREEN) def test_a_step_without_any_probe_is_never_a_door(tmp_path: Path) -> None: src = _fake_src(tmp_path, chat_client=False) steps = _one_step() steps[0].pop("probe") row = gate.score_toolbox(steps, src, None, _FAKE_PASSED, tmp_path) assert (row.k, row.n) == (0, 1) assert any("ingen atferdsprobe" in x for x in row.exceptions), row.exceptions def test_the_door_rule_claims_only_kinds_the_gate_can_verify() -> None: """A8: EXTERNAL_DOOR påsto at en MCP-registrert dør telles; «kind» ble aldri lest.""" assert gate.ENTRY_KINDS assert "mcp" not in gate.EXTERNAL_DOOR.casefold().replace("mcp ville", "") assert all(kind in gate.EXTERNAL_DOOR for kind in gate.ENTRY_KINDS) assert '"kind"' in Path(gate.__file__).read_text(encoding="utf-8") # --- rad 3: hele den publiserte flaten, og de indirekte veiene --------------------------------- def test_the_published_surface_is_the_repo_manifest_and_not_a_handlist() -> None: """A10: main.py, examples/, spikes/ og contexts/ er publisert, men lå utenfor roots. Armen holder i BEGGE verdener — arbeidstreet (git-manifestet) og et rent uttrekk (filtreet), som er nøyaktig det publiserte. Den nøyaktige nevneren pinnes av armen under, som er den ene som trenger repoets egen git-metadata.""" files, _source = gate.published_files(_REPO, _manifest()) names = {str(p.relative_to(_REPO)) for p in files} assert "main.py" in names and "CLAUDE.md" in names and "llms.txt" in names assert any(n.startswith("examples/") for n in names) assert any(n.startswith("spikes/") for n in names) assert any(n.startswith("contexts/") for n in names) assert len(files) > 400, len(files) 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 repository») finnes ikke manifestet å telle mot, og armen er da et uttrekksartefakt — samme klasse som de tre som allerede er navngitt. Nevneren pinnes HER, ikke i armen over.""" tracked = subprocess.run( ["git", "ls-files", "-z"], cwd=_REPO, capture_output=True, text=True, check=True ).stdout.split("\0") mine = {t for t in tracked if t} assert len(mine) == _PUBLISHED_TODAY, len(mine) files, source = gate.published_files(_REPO, _manifest()) assert {str(p.relative_to(_REPO)) for p in files} == mine assert "git-manifestet" == source def test_a_surface_without_its_sentinel_is_not_measured_instead_of_green(tmp_path: Path) -> None: """A4: «6 av 6 GRØNN over 0 publiserte filer». Et fravær uten nevner er ikke et bevis.""" cfg = json.loads(json.dumps(_CONFIG["no_claude_path"])) cfg.pop("roots", None) cfg["manifest"] = {"sentinel": "src/portfolio_optimiser/run.py"} row = gate.score_no_claude_path(cfg, tmp_path) assert row.status == gate.NOT_MEASURED, row.reason assert any("sentinel" in x for x in row.exceptions), row.exceptions assert "0 publiserte filer" in row.reason def test_the_row_names_which_manifest_it_read() -> None: """Nevneren står i radens EGEN tekst, sammen med hvor lista kom fra og hvor mange filer som ikke lot seg avkode — «0 treff» uten nevner er ikke et bevis.""" row = gate.score_no_claude_path(_CONFIG["no_claude_path"], _REPO) files, source = gate.published_files(_REPO, _manifest()) assert f"over {len(files)} publiserte filer" in row.reason assert source in row.reason assert f"{_UNDECODABLE_TODAY} ulesbare" in row.reason def test_the_surface_falls_back_to_the_tree_when_there_is_no_git_metadata(tmp_path: Path) -> None: """Et rent uttrekk (`git archive | tar -x`) har ingen .git — og ER nøyaktig det publiserte.""" _write(tmp_path / "src" / "portfolio_optimiser" / "run.py", "x = 1\n") _write(tmp_path / "main.py", "y = 2\n") _write(tmp_path / "__pycache__" / "skrot.pyc", "nei\n") files, source = gate.published_files(tmp_path, {"sentinel": "src/portfolio_optimiser/run.py"}) names = {str(p.relative_to(tmp_path)) for p in files} assert names == {"src/portfolio_optimiser/run.py", "main.py"} assert "uttrekk" in source #: PMs fem realistiske kall-varianter, base64-kodet av samme grunn som mønstrene i kontrakten: #: tests/ er selv en del av den publiserte flaten raden skanner, og en klartekst-variant her #: ville registrert seg som sitt eget funn. _INDIRECT_VARIANTS = ( ("direkte", "c3VicHJvY2Vzcy5ydW4oWyJjbGF1ZGUiLCAiLXAiLCAieCJdKQ=="), ("absolutt-sti", "c3VicHJvY2Vzcy5ydW4oWyIvdXNyL2xvY2FsL2Jpbi9jbGF1ZGUiLCAiLXAiLCAieCJdKQ=="), ("liste-i-variabel", "Y21kID0gWyJjbGF1ZGUiLCAiLXAiLCAieCJdCnN1YnByb2Nlc3MucnVuKGNtZCk="), ( "konstant", "Q0xBVURFX0JJTiA9ICJjbGF1ZGUiCnN1YnByb2Nlc3MucnVuKFtDTEFVREVfQklOLCAiLXAiLCAieCJdKQ==", ), ("shell-streng", "c3VicHJvY2Vzcy5ydW4oc2hsZXguc3BsaXQoImNsYXVkZSAtcCB4Iikp"), ) def test_every_indirect_way_of_starting_claude_is_caught(tmp_path: Path) -> None: """A9: 4 av 5 realistiske varianter slapp forbi. Hver plantes som kjent-positiv, én om gangen, og hver skal felle raden.""" cfg = json.loads(json.dumps(_CONFIG["no_claude_path"])) cfg.pop("roots", None) cfg["manifest"] = {"sentinel": "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 _INDIRECT_VARIANTS: 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_limit_its_patterns_cannot_reach() -> None: row = gate.score_no_claude_path(_CONFIG["no_claude_path"], _REPO) assert any("satt sammen" in a or "miljøvariabel" in a for a in row.attests), row.attests # --- rad 4, 5 og 6: hver nevner med en navngitt kilde ----------------------------------------- def test_row4_counts_only_checks_whose_named_source_resolves(tmp_path: Path) -> None: """M=3 var en konstant i gatens egen konfig. Nå er hver sjekk bundet til et symbol i kilden.""" cfg = json.loads(json.dumps(_CONFIG["no_model_calls"])) row = gate.score_no_model_calls(cfg, {}, _SRC) assert (row.k, row.n, row.status) == (0, 3, gate.RED) for name, check in cfg["checks"].items(): assert isinstance(check, dict) and "source" in check, name tree = ast.parse((_SRC / check["source"]["module"]).read_text(encoding="utf-8")) names = { n.name for n in tree.body if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef)) } | { t.id for n in tree.body if isinstance(n, ast.Assign) for t in n.targets if isinstance(t, ast.Name) } assert check["source"]["symbol"] in names, name src = tmp_path / "tom" src.mkdir() row = gate.score_no_model_calls(cfg, {}, src) assert row.n == 0, "en sjekk uten kilde i koden teller ikke med i nevneren" assert len(row.exceptions) == 3 def test_row5_counts_each_structural_requirement_on_its_own(tmp_path: Path) -> None: """M=2 blandet profil, fabrikk og injeksjonssøm til ÉN enhet. Nå telles hvert krav for seg, og hvert av dem er et symbol i kilden.""" row = gate.score_foundry(_CONFIG["foundry"], {}, _SRC) members = len(_CONFIG["foundry"]["profile"]["members"]) evidence = len(_CONFIG["foundry"]["evidence"]) assert row.n == members + 2 + evidence assert (row.k, row.status) == (members + 2, gate.RED) nodeid = _CONFIG["foundry"]["evidence"]["samme-skjema"][0] row = gate.score_foundry(_CONFIG["foundry"], {nodeid: "passed"}, _SRC) assert row.status == gate.GREEN def test_row5_denominator_follows_the_named_members(tmp_path: Path) -> None: cfg = json.loads(json.dumps(_CONFIG["foundry"])) cfg["profile"]["members"].append("FINNES_IKKE") row = gate.score_foundry(cfg, {}, _SRC) assert row.n == len(cfg["profile"]["members"]) + 2 + len(cfg["evidence"]) assert any("FINNES_IKKE" in x for x in row.exceptions) def test_row6_denominator_comes_from_the_artefacts_the_contract_names(tmp_path: Path) -> None: artefacts = _CONFIG["runbook"].get("artefacts") assert artefacts is not None, "M=2 var en konstant i koden, ikke noe kontrakten navngir" row = gate.score_runbook(_CONFIG["runbook"], tmp_path) assert row.n == len(artefacts) # --- rad 6: attesteringen kan felles ---------------------------------------------------------- def _runbook(root: Path) -> str: text = "# kjørebok\n\nsteg 1: naviger pakken\n" _write(root / _CONFIG["runbook"]["path"], text) return hashlib.sha256(text.encode("utf-8")).hexdigest() def _attestation_lines(root: Path, digest: str, **over: str) -> None: fields = { "kjørebok": _CONFIG["runbook"]["path"], "sjekksum": digest, "kjørt": "operatøren", "dato": "2026-09-19", } fields.update(over) _write( root / _CONFIG["runbook"]["attestation"], "".join(f"{k}: {v}\n" for k, v in fields.items()), ) def test_a_valid_attestation_is_the_only_thing_that_turns_row6_green(tmp_path: Path) -> None: """rc-0-kontrollen FØRST: en gate som nekter alt er like ubrukelig som en som nekter noe.""" digest = _runbook(tmp_path) _attestation_lines(tmp_path, digest) row = gate.score_runbook(_CONFIG["runbook"], tmp_path, now=_NOW) assert (row.k, row.n, row.status) == (2, 2, gate.GREEN), row.exceptions def test_an_attestation_that_names_another_runbook_is_refused(tmp_path: Path) -> None: """A7: «kjørebok: x» ga 2 av 2 GRØNN.""" digest = _runbook(tmp_path) _attestation_lines(tmp_path, digest, **{"kjørebok": "x"}) row = gate.score_runbook(_CONFIG["runbook"], tmp_path, now=_NOW) assert row.status == gate.NOT_MEASURED assert any("kjørebok" in x for x in row.exceptions) def test_an_attestation_whose_checksum_does_not_match_the_runbook_is_refused( tmp_path: Path, ) -> None: """En attestering av en ANNEN versjon av kjøreboka er ikke en attestering av denne.""" _runbook(tmp_path) _attestation_lines(tmp_path, "0" * 64) row = gate.score_runbook(_CONFIG["runbook"], tmp_path, now=_NOW) assert row.status == gate.NOT_MEASURED assert any("sjekksum" in x for x in row.exceptions) def test_an_attestation_dated_in_the_future_is_refused(tmp_path: Path) -> None: """A7: framtidsdato 3026-01-01 ga GRØNN. Samme regel som v1-gaten, gjenbrukt.""" digest = _runbook(tmp_path) _attestation_lines(tmp_path, digest, dato="3026-01-01") row = gate.score_runbook(_CONFIG["runbook"], tmp_path, now=_NOW) assert row.status == gate.NOT_MEASURED assert any("framtiden" in x for x in row.exceptions) def test_an_attestation_without_a_real_date_is_refused(tmp_path: Path) -> None: digest = _runbook(tmp_path) _attestation_lines(tmp_path, digest, dato="x") row = gate.score_runbook(_CONFIG["runbook"], tmp_path, now=_NOW) assert row.status == gate.NOT_MEASURED assert any("ISO" in x or "dato" in x for x in row.exceptions) def test_an_attestation_with_a_bom_is_still_read(tmp_path: Path) -> None: """v1-gaten TÅLER BOM — en editor som skriver den er ikke operatørens feil.""" digest = _runbook(tmp_path) _attestation_lines(tmp_path, digest) path = tmp_path / _CONFIG["runbook"]["attestation"] path.write_bytes(b"\xef\xbb\xbf" + path.read_bytes()) assert gate.score_runbook(_CONFIG["runbook"], tmp_path, now=_NOW).status == gate.GREEN