okf build --accounting PATH inventories every source document before extraction, in the gate's per-format vocabulary, and after the run gives each element one fate (carried / pointer / coded rejection), written as JSON and summarised in log.md. "carried" is checked against the written concept bodies, so a gate that drops a line is found (test). Exit 1 on anything unaccounted or double-booked. Opt-in: +744 s (+19 %) on the 43-document reference corpus, and that corpus fails the check on 24 real losses (22 images on text-less PDF pages, 2 docx Title paragraphs). Changed without the flag: - okf build exits 1 when it extracted documents and persisted none. Door B and corpus.measure are unchanged. One test relied on exit 0. - An image file carried through a persisted document is its own K1b column, no longer also extractor_unknown. The set is what the resolver actually carried (ExtractedDocument.files), never a byte match. tools/okf_accounting_gate.py (checks untouched) is green on all six rows, R761 110 of 110 under both gates. Report: docs/2026-09-17-innholdsregnskapet-bygget.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
331 lines
13 KiB
Python
331 lines
13 KiB
Python
"""`okf build` runs a NAMED gate, and the bundle says which one (F1).
|
||
|
||
Reported from outside on 2026-09-15 by `claude-code-llm-wiki`, verified here
|
||
before a line moved: `corpus.measure` wired an unconditional approve-everything
|
||
stub (`corpus._gate`, returning the persist disposition for any text) into
|
||
`process_inbox`, and no `add_argument` call anywhere in the package named a
|
||
gate. So the only path people actually use -- `okf build`, and `okf project`
|
||
through it -- screened nothing, while `pyproject.toml` makes the guard a
|
||
MANDATORY runtime dependency and the README recommends exactly the composition
|
||
that was never reachable. A permissive stub that approves everything is the
|
||
shape this repository's own README names as the danger; it was on our own door.
|
||
|
||
Three separate things are pinned, because they fail in three different ways:
|
||
|
||
- **the gate runs.** A document the real guard refuses is not persisted. Under
|
||
the stub it was, which is the defect itself; `CARRIER` is measured to fail
|
||
secure in BOTH presets (an invisible carrier is an any-tier finding), so this
|
||
test cannot pass by picking a lenient tier.
|
||
- **the gate is named in the artifact.** SS 9's `log.md` already carries `N`
|
||
because it is the one fact about a run the bundle cannot otherwise recover.
|
||
Which gate screened the bytes is the same class of fact: without it a bundle
|
||
built with `--gate none` is indistinguishable from one the guard cleared, and
|
||
that indistinguishability is the whole trust trap, not the stub alone.
|
||
- **the seam composes with segmentation.** Measured before the fix:
|
||
`grep -rl inbox_gate tests/` gave ONE file (`tests/test_guard_adapter.py`)
|
||
and `grep -c segment` in it gave 0, while the nine files passing
|
||
`segmentation=`/`segmentations=` all injected a local warn-stub. The
|
||
composition the README recommends -- `process_inbox(segmentations=...,
|
||
gate=inbox_gate)` -- had no test at all, which is how the defect survived.
|
||
|
||
The default is `guard-trusted-source`, set by the operator 2026-09-15 on a
|
||
measurement rather than on caution: over the 453 concept bodies of the pinned
|
||
default bundle, `PRESET_TRUSTED_SOURCE` returns the persist disposition on
|
||
453 of 453 (the artifact survives the move), while `PRESET_USER_UPLOAD` holds
|
||
1 of them and would take one of the 39 source documents out of the bundle.
|
||
Pointing `okf build` at your own folder is not an untrusted upload; Door B's
|
||
library default stays `PRESET_USER_UPLOAD` because an inbox drop is.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
from llm_ingestion_okf import cli, corpus
|
||
from llm_ingestion_okf.inbox import process_inbox
|
||
from llm_ingestion_okf.profiles import SEGMENTED_OKF_V0_2
|
||
from llm_ingestion_okf.propose import build_plan
|
||
from llm_ingestion_okf.segmentation import parse_segmentation_plan
|
||
|
||
BUNDLE_ID = "cli-gate-fixture"
|
||
OKF_VERSION = "0.2"
|
||
INGESTED_AT = "2026-09-15T00:00:00Z"
|
||
|
||
# Measured against the pinned guard 1.4.0, both presets, before this suite was
|
||
# written: `fail_secure` under `PRESET_TRUSTED_SOURCE` AND under
|
||
# `PRESET_USER_UPLOAD` ("any-tier: invisible carrier"). A fixture only one tier
|
||
# refuses would make this suite pass for the wrong reason.
|
||
CARRIER = "# Kostnader\n\nEn merknad med et nullbreddetegn i seg.\n"
|
||
BENIGN = "# Kostnader\n\nKvartalstall for plattformgruppen, uten funn.\n"
|
||
|
||
|
||
def _inbox(root: Path, documents: dict[str, str]) -> Path:
|
||
inbox = root / "inbox"
|
||
inbox.mkdir(parents=True, exist_ok=True)
|
||
for name, body in documents.items():
|
||
(inbox / name).write_text(body, encoding="utf-8", newline="")
|
||
return inbox
|
||
|
||
|
||
def _build(inbox: Path, bundle: Path, *extra: str) -> int:
|
||
return cli.main(
|
||
[
|
||
"build",
|
||
str(inbox),
|
||
"--bundle",
|
||
str(bundle),
|
||
"--bundle-id",
|
||
BUNDLE_ID,
|
||
"--okf-version",
|
||
OKF_VERSION,
|
||
*extra,
|
||
]
|
||
)
|
||
|
||
|
||
def _concepts(bundle: Path) -> list[Path]:
|
||
return [
|
||
path
|
||
for path in sorted(bundle.rglob("*.md"))
|
||
if path.name not in {"index.md", corpus.LOG_NAME}
|
||
]
|
||
|
||
|
||
# --- the gate runs ---------------------------------------------------------
|
||
|
||
|
||
def test_build_refuses_a_document_the_real_guard_refuses(tmp_path: Path) -> None:
|
||
"""The defect itself, as a red test: the stub persisted this document.
|
||
|
||
`CARRIER` carries a zero-width space inside a word. The guard refuses it in
|
||
any trust tier, so a build that persists it has not consulted the guard --
|
||
whatever the dependency table says.
|
||
"""
|
||
pytest.importorskip("llm_ingestion_guard")
|
||
inbox = _inbox(tmp_path, {"carrier.md": CARRIER})
|
||
bundle = tmp_path / "bundle"
|
||
# 1, not 0, since the content accounting: a build that extracted a document
|
||
# and persisted none is not a success, however every refusal was coded.
|
||
assert _build(inbox, bundle) == 1
|
||
assert _concepts(bundle) == [], "a document the guard refuses must not reach the bundle"
|
||
|
||
|
||
def test_build_persists_a_document_the_real_guard_clears(tmp_path: Path) -> None:
|
||
"""The known-positive beside it: the gate must not simply refuse everything.
|
||
|
||
A gate that blocked every document would pass the test above and destroy
|
||
the command. Both halves are needed, on the same default, in the same run.
|
||
"""
|
||
pytest.importorskip("llm_ingestion_guard")
|
||
inbox = _inbox(tmp_path, {"benign.md": BENIGN})
|
||
bundle = tmp_path / "bundle"
|
||
assert _build(inbox, bundle) == 0
|
||
assert _concepts(bundle), "a clean document must still reach the bundle"
|
||
|
||
|
||
def test_gate_none_is_reachable_and_still_permissive(tmp_path: Path) -> None:
|
||
"""`--gate none` keeps the old behaviour, deliberately and by name.
|
||
|
||
The stub is not deleted -- the corpus harness reproduces published numbers
|
||
with it, and a caller measuring segmentation alone has a legitimate reason
|
||
to take the gate out of the picture. What changes is that asking for it is
|
||
now an act, and the bundle records it.
|
||
"""
|
||
inbox = _inbox(tmp_path, {"carrier.md": CARRIER})
|
||
bundle = tmp_path / "bundle"
|
||
assert _build(inbox, bundle, "--gate", "none") == 0
|
||
assert _concepts(bundle), "--gate none is the documented permissive path"
|
||
|
||
|
||
def test_user_upload_preset_is_reachable(tmp_path: Path) -> None:
|
||
"""The stricter tier is a flag value, not a fork of the adapter.
|
||
|
||
Door B's own default stays `PRESET_USER_UPLOAD`; the CLI exposes it so a
|
||
caller whose folder IS an untrusted drop does not have to write their own
|
||
entry point to reach the tier this library already ships.
|
||
"""
|
||
pytest.importorskip("llm_ingestion_guard")
|
||
inbox = _inbox(tmp_path, {"benign.md": BENIGN})
|
||
bundle = tmp_path / "bundle"
|
||
assert _build(inbox, bundle, "--gate", "guard-user-upload") == 0
|
||
assert _concepts(bundle)
|
||
|
||
|
||
def test_an_unknown_gate_name_is_refused(tmp_path: Path) -> None:
|
||
"""A misspelled tier must not fall back to the permissive one.
|
||
|
||
Falling back would reproduce the defect with an extra step: the caller
|
||
believes they asked for the guard, and the run approves everything.
|
||
"""
|
||
inbox = _inbox(tmp_path, {"benign.md": BENIGN})
|
||
bundle = tmp_path / "bundle"
|
||
with pytest.raises(SystemExit):
|
||
_build(inbox, bundle, "--gate", "guard-trusted")
|
||
assert not bundle.exists() or not _concepts(bundle)
|
||
|
||
|
||
# --- the gate is named in the artifact -------------------------------------
|
||
|
||
|
||
def test_the_log_names_the_gate_that_screened_the_bundle(tmp_path: Path) -> None:
|
||
"""SS 9's log carries the gate for the same reason it carries `N`.
|
||
|
||
A consumer holding a bundle cannot otherwise tell a screened one from an
|
||
unscreened one, and that is the trust trap in its pure form -- a stub is
|
||
only dangerous because nothing downstream can see it.
|
||
"""
|
||
pytest.importorskip("llm_ingestion_guard")
|
||
inbox = _inbox(tmp_path, {"benign.md": BENIGN})
|
||
bundle = tmp_path / "bundle"
|
||
assert _build(inbox, bundle) == 0
|
||
log = (bundle / corpus.LOG_NAME).read_text(encoding="utf-8")
|
||
assert corpus.GATE_GUARD_TRUSTED_SOURCE in log
|
||
|
||
|
||
def test_the_log_says_when_nothing_was_screened(tmp_path: Path) -> None:
|
||
"""`--gate none` must be legible as "nothing was screened", not as silence.
|
||
|
||
An omitted line reads as "no gate section in this log format"; a present
|
||
line naming the permissive stub reads as what it is.
|
||
"""
|
||
inbox = _inbox(tmp_path, {"benign.md": BENIGN})
|
||
bundle = tmp_path / "bundle"
|
||
assert _build(inbox, bundle, "--gate", "none") == 0
|
||
log = (bundle / corpus.LOG_NAME).read_text(encoding="utf-8")
|
||
assert corpus.GATE_NONE in log
|
||
assert "NOTHING WAS SCREENED" in log
|
||
|
||
|
||
def test_every_gate_name_renders_a_log_line() -> None:
|
||
"""No gate name may render a log without a gate line.
|
||
|
||
A name added later without a description would produce a bundle that
|
||
declares its gate as the empty string, which reads as an absent field.
|
||
"""
|
||
for name in corpus.GATE_NAMES:
|
||
report = corpus.CorpusReport(
|
||
corpus="corpus",
|
||
ingested_at=INGESTED_AT,
|
||
n=0,
|
||
extracted=0,
|
||
gated=0,
|
||
persisted=0,
|
||
substantive=0,
|
||
degenerate=0,
|
||
rejected=0,
|
||
seconds_total=0.0,
|
||
converter_path="none",
|
||
converter_version="none",
|
||
codes=(),
|
||
unaccounted=(),
|
||
gate=name,
|
||
)
|
||
assert f"**Gate**: {name}" in report.render_log()
|
||
|
||
|
||
# --- the seam composes with segmentation -----------------------------------
|
||
|
||
|
||
def test_segmentation_and_the_real_guard_compose(tmp_path: Path) -> None:
|
||
"""`process_inbox(segmentations=..., gate=inbox_gate)`, the README's own form.
|
||
|
||
Measured before this test existed: no test in the repository ran a real
|
||
guard verdict and a segmentation plan in the same call. The composition is
|
||
what the README recommends to consumers, so leaving it untested left the
|
||
recommendation unverified -- and it is the exact call `okf build` now makes.
|
||
"""
|
||
guard_adapter = pytest.importorskip("llm_ingestion_okf.guard_adapter")
|
||
document = (
|
||
"# 1 Innledning\n\nDette dokumentet beskriver krav til seksjonering.\n\n"
|
||
"# 2 Omfang\n\nOmfanget er hele anlegget og alle tilhoerende systemer.\n"
|
||
)
|
||
inbox = _inbox(tmp_path, {"alpha.md": document})
|
||
bundle = tmp_path / "bundle"
|
||
source = inbox / "alpha.md"
|
||
plans = {
|
||
"alpha.json": parse_segmentation_plan(
|
||
build_plan(
|
||
source,
|
||
document,
|
||
source.read_bytes(),
|
||
okf_type="reference",
|
||
proposed_at=INGESTED_AT,
|
||
path_prefix="alpha",
|
||
)
|
||
)
|
||
}
|
||
result = process_inbox(
|
||
inbox,
|
||
bundle,
|
||
INGESTED_AT,
|
||
okf_type="reference",
|
||
gate=guard_adapter.inbox_gate,
|
||
profile=SEGMENTED_OKF_V0_2,
|
||
root_frontmatter_values={"okf_version": OKF_VERSION, "bundle_id": BUNDLE_ID},
|
||
segmentations=plans,
|
||
)
|
||
assert len(result.persisted) == 1
|
||
assert not result.rejected and not result.quarantined
|
||
assert len(_concepts(bundle)) > 1, "the plan's segments must be what the guard screened"
|
||
|
||
|
||
def test_segmentation_and_the_real_guard_refuse_together(tmp_path: Path) -> None:
|
||
"""The known-negative for the composition above, on the same call shape.
|
||
|
||
One refused segment body refuses the whole file, which is Door B's stated
|
||
rule; without this half the test above would pass against a gate that never
|
||
said no.
|
||
"""
|
||
guard_adapter = pytest.importorskip("llm_ingestion_okf.guard_adapter")
|
||
document = (
|
||
"# 1 Innledning\n\nDette dokumentet beskriver krav til seksjonering.\n\n"
|
||
"# 2 Omfang\n\nEn merknad med et nullbreddetegn i seg.\n"
|
||
)
|
||
inbox = _inbox(tmp_path, {"alpha.md": document})
|
||
bundle = tmp_path / "bundle"
|
||
source = inbox / "alpha.md"
|
||
plans = {
|
||
"alpha.json": parse_segmentation_plan(
|
||
build_plan(
|
||
source,
|
||
document,
|
||
source.read_bytes(),
|
||
okf_type="reference",
|
||
proposed_at=INGESTED_AT,
|
||
path_prefix="alpha",
|
||
)
|
||
)
|
||
}
|
||
result = process_inbox(
|
||
inbox,
|
||
bundle,
|
||
INGESTED_AT,
|
||
okf_type="reference",
|
||
gate=guard_adapter.inbox_gate,
|
||
profile=SEGMENTED_OKF_V0_2,
|
||
root_frontmatter_values={"okf_version": OKF_VERSION, "bundle_id": BUNDLE_ID},
|
||
segmentations=plans,
|
||
)
|
||
assert not result.persisted
|
||
assert result.rejected, "a carrier in one segment refuses the file"
|
||
|
||
|
||
# --- the adapter's second tier ---------------------------------------------
|
||
|
||
|
||
def test_the_trusted_source_adapter_is_the_same_shape(tmp_path: Path) -> None:
|
||
"""The second tier is a second three-line adapter, not a parameter.
|
||
|
||
`guard_adapter`'s own docstring says a caller needing another tier writes
|
||
their own three-line adapter; this is that adapter, shipped, so the CLI
|
||
does not have to reach past the seam into the guard's presets.
|
||
"""
|
||
guard_adapter = pytest.importorskip("llm_ingestion_okf.guard_adapter")
|
||
clean = guard_adapter.inbox_gate_trusted_source(BENIGN)
|
||
assert clean.disposition == "warn"
|
||
assert clean.sanitized_text == BENIGN
|
||
held = guard_adapter.inbox_gate_trusted_source(CARRIER)
|
||
assert held.disposition != "warn", "an any-tier finding is refused at every tier"
|
||
assert held.reasons, "the guard's audit trail is carried across verbatim"
|