llm-ingestion-okf/tests/test_accounting_gate.py
Kjell Tore Guttormsen 0b00de4408 test(accounting): content-accounting gate for okf build, written red
Capability loop step 3, no capability. tools/okf_accounting_gate.py asks,
per README file type, how many of the elements a SOURCE holds okf build
books as carried / pointer / coded rejection, with unaccounted and
double-booked both required to be 0. Exit 1 today on rows 2, 3, 4 and 6.

The fasit is tools/okf_witness.py (stdlib + pdfplumber + poppler, no
package import; tested on the live import graph), committed as
tests/fixtures/accounting/*inventory.json over one fixture per type.

Measured: no source inventory (0 of 13); two graphics/ files carried
through documents AND counted extractor_unknown (50 on R761 under
--gate none); a refused document logged "0 carried of 0 found"; R761
refused whole because guard 1.4.0 treats its 71 U+00AD soft hyphens as an
invisible carrier (asked of the security repo). The two R761 witnesses
agree once STS labels are counted by role, not tag.

Report: docs/2026-09-17-innholdsregnskapet-rod-gate.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 15:40:37 +02:00

425 lines
15 KiB
Python

"""The content-accounting gate's own tests (`tools/okf_accounting_gate.py`).
The gate is RED today by design: `okf build` has no source inventory and no
per-element accounting. These tests are GREEN and prove three things.
1. The fasit is independent. `tools/okf_witness.py` imports no
`llm_ingestion_okf` module -- checked on the live import graph of a process
that ran every witness, with a control that shows the check fires -- and
the committed inventories are exactly what the witness counts today, pinned
again to hand counts on four documents.
2. Every row CAN turn green and CAN turn red, each boundary driven from both
sides with synthetic build output (the door the capability must open).
3. Run against the real `okf build` at this commit, the gate sees the three
defects it was ordered for: no inventory, a file carried AND rejected, and
a rejected document logged as "0 carried of 0 found".
"""
from __future__ import annotations
import json
import subprocess
import sys
from pathlib import Path
from typing import Any
import pytest
TOOLS = Path(__file__).resolve().parents[1] / "tools"
sys.path.insert(0, str(TOOLS))
import okf_accounting_gate as gate # noqa: E402
import okf_witness as witness # noqa: E402
TABLE = [
".csv",
".docx",
".htm",
".html",
".json",
".md",
".odt",
".pdf",
".pptx",
".rtf",
".txt",
".xlsx",
".xml",
]
# --- 1. the fasit ------------------------------------------------------------
_IMPORT_PROBE = """
import sys
sys.path.insert(0, {tools!r})
{preload}
import okf_witness as w
from pathlib import Path
fixtures = Path({fixtures!r})
w.witness_inbox(fixtures / "corpus")
w.witness_inbox(fixtures / "rejected")
w.count_sts_json((fixtures / "witness" / "prosess-84-sts.twin.json").read_bytes())
w.pdf_poppler(fixtures / "corpus" / "prosess-84-tabell.pdf")
print(sorted(m for m in sys.modules if m.split(".")[0] == "llm_ingestion_okf"))
"""
def _package_modules_after_witness(preload: str) -> list[str]:
script = _IMPORT_PROBE.format(tools=str(TOOLS), fixtures=str(gate.FIXTURES), preload=preload)
out = subprocess.run(
[sys.executable, "-c", script], capture_output=True, text=True, check=True
).stdout
result: list[str] = json.loads(out.strip().splitlines()[-1].replace("'", '"'))
return result
def test_the_witness_imports_no_module_of_the_package_it_judges() -> None:
pytest.importorskip("pdfplumber")
assert _package_modules_after_witness("") == []
def test_the_import_check_fires_when_the_package_is_loaded() -> None:
pytest.importorskip("pdfplumber")
assert "llm_ingestion_okf" in _package_modules_after_witness("import llm_ingestion_okf")
@pytest.mark.parametrize(
("inbox", "committed"),
[(gate.CORPUS, gate.INVENTORY), (gate.REJECTED, gate.REJECTED_INVENTORY)],
)
def test_the_committed_fasit_is_what_the_witness_counts(inbox: Path, committed: Path) -> None:
pytest.importorskip("pdfplumber")
assert witness.witness_inbox(inbox) == gate.load_inventory(committed)
def test_the_fixture_corpus_covers_every_readme_file_type() -> None:
inventory = gate.load_inventory(gate.INVENTORY)
assert gate.readme_types() == TABLE
assert {entry["suffix"] for entry in inventory["documents"].values()} == set(TABLE)
# Counted by hand from the fixture bytes, not by the witness.
HAND_COUNTS = {
"notat.md": {
"code_block": 1,
"heading": 2,
"image": 1,
"paragraph": 2,
"table": 1,
"table_row": 2,
},
"side.htm": {"cell": 4, "heading": 2, "image": 0, "list_item": 2, "paragraph": 1, "table": 1},
"krav-rikt-tekstformat.rtf": {"cell": 56, "image": 0, "paragraph": 3, "table_row": 24},
"prosess-84-sts.xml": {
"cell": 0,
"footnote": 0,
"image": 2,
"list_item": 0,
"paragraph": 2,
"section": 2,
"section_label": 2,
"table": 0,
"table_label": 0,
"title": 2,
},
}
@pytest.mark.parametrize("name", sorted(HAND_COUNTS))
def test_the_witness_matches_a_hand_count(name: str) -> None:
inventory = witness.witness_file(gate.CORPUS, gate.CORPUS / name)
assert inventory.elements == HAND_COUNTS[name]
def test_a_fenced_heading_is_not_a_heading_to_the_witness() -> None:
elements, _ = witness.count_markdown("# Real\n\n```bash\n# not one\n```\n")
assert elements["heading"] == 1
assert elements["code_block"] == 1
def test_the_sts_image_reference_resolves_through_the_graphics_directory() -> None:
inventory = witness.witness_file(gate.CORPUS, gate.STS_FIXTURE)
assert [ref.target for ref in inventory.images] == [
"graphics/tabell-84-2.png",
"graphics/figur-84-1.png",
]
def test_a_reference_above_the_document_resolves_to_nothing(tmp_path: Path) -> None:
(tmp_path / "secret.png").write_bytes(b"x")
(tmp_path / "docs").mkdir()
document = tmp_path / "docs" / "a.html"
assert witness.resolve_local(tmp_path, document, "../secret.png") is None
def test_the_witness_refuses_a_doctype() -> None:
with pytest.raises(witness.WitnessRefused):
witness.count_sts_xml(b'<!DOCTYPE x [<!ENTITY a "b">]><standard/>')
# --- 2. every row can go both ways -------------------------------------------
def _inventory() -> dict[str, Any]:
return {
"documents": {
"a.md": {"suffix": ".md", "elements": {"heading": 2, "image": 1}, "images": []},
},
"files": {"graphics/x.png": {"pointed_at_by": ["a.md"]}},
}
def _build(
*,
accounting: dict[str, Any] | None = None,
sources: set[str] | None = None,
assets: set[str] | None = None,
log: str = "",
exit_code: int = 0,
) -> gate.Build:
return gate.Build(
exit_code=exit_code,
log=log,
accounting=accounting,
source_files={"a.md"} if sources is None else sources,
asset_prefixes=assets or set(),
)
def _declared(heading: int = 2, image: int = 1, fate: str = "rejected") -> dict[str, Any]:
return {
"accounting_version": 1,
"documents": [
{
"source_file": "a.md",
"status": "persisted",
"code": None,
"inventory": {"heading": 2, "image": 1},
"fates": {
"heading": {"carried": heading},
"image": {"pointer": image},
},
}
],
"files": [{"source_file": "graphics/x.png", "fate": fate, "code": "extractor_unknown"}],
}
def _corpus(tmp_path: Path) -> Path:
(tmp_path / "graphics").mkdir()
(tmp_path / "graphics" / "x.png").write_bytes(b"png bytes")
return tmp_path
def test_row1_is_green_when_every_type_has_a_fasit() -> None:
inventory = {
"documents": {f"f{s}": {"suffix": s, "elements": {}} for s in TABLE},
"files": {},
}
row = gate.row1(TABLE, inventory, inventory)
assert (row.k, row.m, row.status) == (13, 13, gate.GREEN)
def test_row1_is_red_when_one_type_lacks_a_fasit() -> None:
inventory = {
"documents": {f"f{s}": {"suffix": s, "elements": {}} for s in TABLE[:-1]},
"files": {},
}
row = gate.row1(TABLE, inventory, inventory)
assert (row.k, row.m, row.status) == (12, 13, gate.RED)
def test_row1_is_red_when_the_committed_fasit_is_stale() -> None:
inventory = {"documents": {"f.md": {"suffix": ".md", "elements": {"heading": 1}}}}
fresh = {"documents": {"f.md": {"suffix": ".md", "elements": {"heading": 2}}}}
row = gate.row1([".md"], inventory, fresh)
assert (row.k, row.status) == (0, gate.RED)
def test_row2_is_red_without_the_door() -> None:
row = gate.row2([".md"], _inventory(), _build(), door=False)
assert (row.k, row.m, row.status) == (0, 1, gate.RED)
def test_row2_is_green_when_the_declared_inventory_equals_the_witness() -> None:
row = gate.row2([".md"], _inventory(), _build(accounting=_declared()), door=True)
assert (row.k, row.m, row.status) == (1, 1, gate.GREEN)
def test_row2_is_red_when_the_declared_inventory_is_one_off() -> None:
declared = _declared()
declared["documents"][0]["inventory"]["heading"] = 3
row = gate.row2([".md"], _inventory(), _build(accounting=declared), door=True)
assert (row.k, row.status) == (0, gate.RED)
def test_row3_is_green_when_every_element_and_file_has_one_fate(tmp_path: Path) -> None:
units = gate.account(_inventory(), _build(accounting=_declared()), _corpus(tmp_path))
row = gate.row3(units, door=True)
assert (row.k, row.m, row.status) == (2, 2, gate.GREEN)
@pytest.mark.parametrize(("heading", "u", "d"), [(1, 1, 0), (3, 0, 1)])
def test_row3_is_red_one_element_either_side(tmp_path: Path, heading: int, u: int, d: int) -> None:
units = gate.account(
_inventory(), _build(accounting=_declared(heading=heading)), _corpus(tmp_path)
)
document = units[0]
assert (document.unaccounted, document.double) == (u, d)
assert gate.row3(units, door=True).status == gate.RED
def test_row3_is_red_when_no_fate_is_declared(tmp_path: Path) -> None:
units = gate.account(_inventory(), _build(), _corpus(tmp_path))
assert units[0].unaccounted == 3
assert gate.row3(units, door=False).status == gate.RED
def test_a_file_carried_through_a_document_and_rejected_is_double_booked(tmp_path: Path) -> None:
corpus = _corpus(tmp_path)
carried = {gate._sha12(corpus / "graphics" / "x.png")}
units = gate.account(_inventory(), _build(accounting=_declared(), assets=carried), corpus)
assert (units[1].unaccounted, units[1].double) == (0, 1)
def test_a_file_carried_through_a_document_and_declared_carried_is_clean(tmp_path: Path) -> None:
corpus = _corpus(tmp_path)
carried = {gate._sha12(corpus / "graphics" / "x.png")}
build = _build(accounting=_declared(fate="carried"), assets=carried)
assert gate.account(_inventory(), build, corpus)[1].clean
def test_a_file_declared_carried_without_its_bytes_is_unaccounted(tmp_path: Path) -> None:
build = _build(accounting=_declared(fate="carried"))
unit = gate.account(_inventory(), build, _corpus(tmp_path))[1]
assert (unit.unaccounted, unit.double) == (1, 0)
def test_an_unpointed_file_sharing_bytes_with_a_carried_one_is_not_carried(
tmp_path: Path,
) -> None:
corpus = _corpus(tmp_path)
(corpus / "graphics" / "twin.png").write_bytes(b"png bytes")
inventory = _inventory()
inventory["files"]["graphics/twin.png"] = {"pointed_at_by": []}
carried = {gate._sha12(corpus / "graphics" / "x.png")}
units = gate.account(inventory, _build(assets=carried), corpus)
assert [(u.name, u.double) for u in units[1:]] == [
("graphics/twin.png", 0),
("graphics/x.png", 1),
]
def test_without_the_door_double_booking_is_derived_from_conservation(tmp_path: Path) -> None:
corpus = _corpus(tmp_path)
carried = {gate._sha12(corpus / "graphics" / "x.png")}
assert gate.account(_inventory(), _build(assets=carried), corpus)[1].double == 1
assert gate.account(_inventory(), _build(), corpus)[1].clean
_HONEST_LOG = (
"* **Images**: 0 carried of 1 found, written to `assets/`.\n"
"* a.md: 3 elements found in the source, 0 carried: document rejected `fail_secure`\n"
)
def _rejected_inventory() -> dict[str, Any]:
inventory = _inventory()
inventory["documents"]["a.md"]["images"] = [{"kind": "local"}]
return inventory
def test_row4_is_green_when_the_log_names_what_the_rejected_document_held() -> None:
row = gate.row4(_rejected_inventory(), _build(sources=set(), log=_HONEST_LOG))
assert (row.k, row.m, row.status) == (1, 1, gate.GREEN)
@pytest.mark.parametrize(
"log",
[
_HONEST_LOG.replace("0 carried of 1 found", "0 carried of 0 found"),
_HONEST_LOG.replace("3 elements", "2 elements"),
_HONEST_LOG.replace(" `fail_secure`", ""),
],
)
def test_row4_is_red_when_the_log_understates_the_rejected_document(log: str) -> None:
row = gate.row4(_rejected_inventory(), _build(sources=set(), log=log))
assert (row.k, row.status) == (0, gate.RED)
def test_row4_cannot_be_green_when_nothing_was_rejected() -> None:
row = gate.row4(_rejected_inventory(), _build(log=_HONEST_LOG))
assert (row.m, row.status) == (0, gate.RED)
def test_row5_is_green_when_the_witnesses_agree() -> None:
row = gate.row5([("pair", gate.compare({"p": 3}, {"p": 3}))], [])
assert (row.k, row.m, row.status) == (1, 1, gate.GREEN)
def test_row5_is_red_with_both_numbers_when_they_disagree_by_one() -> None:
row = gate.row5([("pair", gate.compare({"p": 3}, {"p": 4}))], [])
assert row.status == gate.RED
assert row.details == ["pair: p: 3 vs 4"]
def test_row5_is_red_when_a_witness_is_missing() -> None:
assert gate.row5([("pair", gate.compare({"p": 3}, None))], []).status == gate.RED
def test_row6_without_its_source_is_red_locally_and_skipped_in_ci(tmp_path: Path) -> None:
missing = tmp_path / "absent"
local = gate.row6(missing, ci=False)
ci = gate.row6(missing, ci=True)
assert (local.status, local.fails) == (gate.RED, True)
assert (ci.status, ci.fails) == (gate.SKIPPED, False)
assert "source missing" in ci.reason
def test_the_proposed_exceptions_are_not_applied() -> None:
assert gate.APPROVED_EXCEPTIONS == frozenset()
assert "NOT APPROVED" in gate.render([])
def test_bad_usage_exits_two() -> None:
with pytest.raises(SystemExit) as exc:
gate.main(["--no-such-flag"])
assert exc.value.code == 2
# --- 3. the real build at this commit ----------------------------------------
@pytest.fixture(scope="module")
def real_rows() -> list[gate.Row]:
pytest.importorskip("pdfplumber")
pytest.importorskip("pypandoc")
if gate.door_available():
pytest.skip("the accounting door exists; these rows describe the build before it")
return gate.evaluate(r761=None, ci=True, consume=False)
def test_the_real_gate_is_red_on_rows_two_three_and_four(real_rows: list[gate.Row]) -> None:
assert [r.number for r in real_rows if r.fails] == [2, 3, 4]
assert [(r.number, r.status) for r in real_rows if not r.fails] == [
(1, gate.GREEN),
(5, gate.GREEN),
(6, gate.SKIPPED),
]
def test_the_real_gate_sees_the_graphics_carried_and_rejected(real_rows: list[gate.Row]) -> None:
row3 = real_rows[2]
assert "d = 2 double-booked" in row3.reason
doubled = [d for d in row3.details if "AND rejected" in d]
assert [d.split(":")[0] for d in doubled] == [
"file graphics/figur-84-1.png",
"file graphics/tabell-84-2.png",
]
def test_the_real_gate_sees_the_rejected_document_logged_as_empty(
real_rows: list[gate.Row],
) -> None:
assert "log says 0 carried of 0 found; the source declares 1" in "\n".join(real_rows[3].details)