test(b-gate): 31 arms against the gate's own denominators, 30 red on an assert about behaviour

The PM checkpoint on 207337c judged the gate DELVIS: row 1's M=13 is a curated list in the
gate's OWN b_gate.json (the run path has 41 po-calls, 7 of 10 outbox writers), rows 4, 5 and 6
have denominators with no source at all, 6 of 10 cheat-attacks got through, and the
never-Claude guard sees 433 of 512 published files.

This commit is the red half. Every arm fails on an ASSERT about behaviour, never at collection:
the four names that do not exist yet (ENTRY_KINDS, run_path_calls, registered_entry,
published_files) are stubbed here with DELIBERATELY wrong values — everything is a door, the
run path calls nothing, the surface is empty — so each arm measures the defect rather than the
absence of a symbol.

30 av 31 red on assert. The one that is green is the rc-0 control
(test_a_valid_attestation_is_the_only_thing_that_turns_row6_green): a valid attestation must
turn row 6 green both before and after, or the row refuses everything, which proves as little
as refusing nothing. All 34 pre-existing arms stay green — measured, not assumed.

The planted claude-invocations are base64 in the test file for the same reason the contract's
patterns are: tests/ is itself part of the surface row 3 scans, and a cleartext variant here
would register as its own finding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-19 19:14:17 +02:00
commit 332eb5965b
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
2 changed files with 576 additions and 7 deletions

View file

@ -20,10 +20,12 @@ 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
@ -34,6 +36,8 @@ 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.
@ -481,3 +485,507 @@ def test_a_bad_attestation_path_is_usage_error_not_a_silent_green(tmp_path: Path
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 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)[0] is False
_write(
src / "driver.py",
(src / "driver.py").read_text(encoding="utf-8")
+ '\n\nif __name__ == "__main__":\n raise SystemExit(main())\n',
)
assert gate.registered_entry(src, tmp_path, "fakesrc", entry) == (True, "")
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 <symbol>()` 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, {}, 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)
_write(
src / "driver.py",
(src / "driver.py").read_text(encoding="utf-8")
+ '\n\nif __name__ == "__main__":\n raise SystemExit(main())\n',
)
steps = _one_step()
steps[0]["entry"] = {"kind": "module-main", "module": "driver.py", "scope": "main"}
steps[0]["probe"] = ["tests/test_noe.py::test_doeren_skriver_artefaktet"]
row = gate.score_toolbox(steps, src, None, {}, tmp_path)
assert (row.k, row.n) == (0, 1)
assert any("probe" in x for x in row.exceptions), row.exceptions
passed = {"tests/test_noe.py::test_doeren_skriver_artefaktet": "passed"}
row = gate.score_toolbox(steps, src, None, 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)
_write(
src / "driver.py",
(src / "driver.py").read_text(encoding="utf-8")
+ '\n\nif __name__ == "__main__":\n raise SystemExit(main())\n',
)
steps = _one_step()
steps[0]["entry"] = {"kind": "module-main", "module": "driver.py", "scope": "main"}
row = gate.score_toolbox(steps, src, None, {}, 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."""
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) == _PUBLISHED_TODAY, len(files)
assert "git" in source
def test_the_surface_count_matches_an_independent_count_of_the_manifest() -> None:
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}
files, _ = gate.published_files(_REPO, _manifest())
assert {str(p.relative_to(_REPO)) for p in files} == mine
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:
row = gate.score_no_claude_path(_CONFIG["no_claude_path"], _REPO)
assert str(_PUBLISHED_TODAY) in row.reason
assert "git" 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