llm-ingestion-okf/tests/test_folder_of_bundles.py
Kjell Tore Guttormsen 21f9241712 feat(check): the checker and the contract read a folder's reply
`okf check --payload` takes the reply to one call over a folder as well
as a single payload: every bundle's payload is held to all 19 rules on
its own, a finding is named with its bundle, one every payload carries
alike is reported once, an answer labelled with a bundle its payload
does not describe is `answer_misattributed`, and a reply with no answer
is `payload_invalid`. No rule is added, and a single payload's report is
unchanged. Contract SS 2.5.4 names the folder run and SS 8.11 fixes the
reply; the known-positive moves to 24 620 / delta 592.

The skill text follows: the working method's steps 1 and 4 name the
folder, and the generic skill says to use the server's tools first where
they are registered, with the skill as the supplement. The folder is an
instruction in both generators, never a path: the bundle's parent
written absolute named this checkout, and the test holding generated
commands to no repository path fell on it.

v1.1 order F, part F4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 10:50:14 +02:00

270 lines
10 KiB
Python

"""One folder, every bundle under it: the command line's own door (v1.1 F).
The server has read a FOLDER of bundles since `okf mcp --root`; the generic
skill read one bundle at a time, the one it was pointed at, and could not see
the others. These tests hold the two command-line entries the skill now uses
-- `okf card <folder>` and `okf consume <folder>` -- to the server's OWN
functions: one source, two doors. Both corpora are invented here.
"""
from __future__ import annotations
import json
import subprocess
import sys
from pathlib import Path
import pytest
from llm_ingestion_okf import mcp_server
from llm_ingestion_okf.cli import build
BAKERY = {
"surdeig.md": (
"# Surdeig\n\n"
"## Heving\n\n"
"Surdeigen hever i tolv timer ved romtemperatur før den formes.\n\n"
"## Steking\n\n"
"Brødet stekes i førti minutter på to hundre og tretti grader.\n"
),
}
GARDEN = {
"tomater.md": (
"# Tomater\n\n"
"## Vanning\n\n"
"Tomatene vannes hver morgen, og jorda skal aldri tørke helt ut.\n\n"
"## Oppbinding\n\n"
"Plantene bindes opp til en stokk når de er tretti centimeter høye.\n"
),
}
def _bundle(tmp_path: Path, folder: Path, name: str, documents: dict[str, str]) -> Path:
source = tmp_path / f"src-{name}"
source.mkdir()
for file_name, text in documents.items():
(source / file_name).write_text(text, encoding="utf-8")
target = folder / name
build(source, target, bundle_id=name, okf_version="0.2")
return target
@pytest.fixture(scope="module")
def folder(tmp_path_factory: pytest.TempPathFactory) -> Path:
tmp_path = tmp_path_factory.mktemp("folder-of-bundles")
root = tmp_path / "samlinger"
root.mkdir()
_bundle(tmp_path, root, "bakeri", BAKERY)
_bundle(tmp_path, root, "hage", GARDEN)
return root
def _okf(*argv: str) -> subprocess.CompletedProcess[str]:
return subprocess.run(
[sys.executable, "-m", "llm_ingestion_okf.cli", *argv],
capture_output=True,
text=True,
check=False,
)
def _surface(folder: Path) -> mcp_server.Surface:
return mcp_server.build_surface(bundle=None, roots=[folder])
# --- F1: the overview -------------------------------------------------------
def test_the_card_of_a_folder_names_every_bundle_under_it(folder: Path) -> None:
run = _okf("card", str(folder))
assert run.returncode == 0, run.stderr
overview = json.loads(run.stdout)
assert [card["bundle_id"] for card in overview["cards"]] == ["bakeri", "hage"]
assert [entry["bundle_id"] for entry in overview["bundles"]] == ["bakeri", "hage"]
assert overview["unreadable"] == []
assert overview["shape"] == "one-to-many"
# Each card carries the map, which is what the working method reads first.
assert all(card["map"] for card in overview["cards"])
def test_the_card_of_a_folder_is_the_servers_own_listing_and_description(folder: Path) -> None:
"""One source: the bytes the command prints are the server's two replies."""
surface = _surface(folder)
listing = mcp_server.call_list(surface, {})
described = mcp_server.call_describe(surface, {})
overview = json.loads(_okf("card", str(folder)).stdout)
assert overview["bundles"] == listing["bundles"]
assert overview["unreadable"] == listing["unreadable"]
assert overview["cards"] == described["cards"]
def test_the_card_of_one_bundle_is_unchanged(folder: Path) -> None:
"""Pointed at one bundle, the command prints that bundle's card, as before."""
bundle = folder / "bakeri"
run = _okf("card", str(bundle))
assert run.returncode == 0, run.stderr
card = json.loads(run.stdout)
assert card == mcp_server.card(bundle.resolve(), profile=mcp_server.okf_consume.DEFAULT_PROFILE)
assert "cards" not in card
def test_a_folder_holding_no_bundle_is_refused_rather_than_empty(tmp_path: Path) -> None:
empty = tmp_path / "tom"
empty.mkdir()
run = _okf("card", str(empty))
assert run.returncode == 1
assert "bundle_none_served" in run.stderr
def test_a_broken_bundle_under_the_folder_is_reported(folder: Path, tmp_path: Path) -> None:
root = tmp_path / "med-feil"
root.mkdir()
for name in ("bakeri", "hage"):
(root / name).symlink_to(folder / name) # never followed: not listed
broken = root / "odelagt"
broken.mkdir()
(broken / "index.md").write_text("---\ntitle: x\n---\n", encoding="utf-8")
real = root / "ekte"
real.mkdir()
for source in (folder / "hage").rglob("*"):
target = real / source.relative_to(folder / "hage")
if source.is_dir():
target.mkdir(parents=True, exist_ok=True)
else:
target.write_bytes(source.read_bytes())
overview = json.loads(_okf("card", str(root)).stdout)
assert [card["bundle_id"] for card in overview["cards"]] == ["hage"]
assert overview["unreadable"] == [
{"directory": "odelagt", "reason": "index.md declares no bundle_id"}
]
# --- F2: one call across the folder ------------------------------------------
QUESTIONS = ("hvor lenge hever surdeigen", "hvor ofte vannes tomatene")
def _ask(folder: Path, *extra: str) -> subprocess.CompletedProcess[str]:
argv = ["consume", str(folder)]
for question in QUESTIONS:
argv += ["--question", question]
return _okf(*argv, *extra)
def test_one_call_over_a_folder_answers_from_every_bundle(folder: Path) -> None:
run = _ask(folder)
assert run.returncode == 0, run.stderr
reply = json.loads(run.stdout)
assert reply["asked"] == ["bakeri", "hage"]
assert reply["questions"] == list(QUESTIONS)
by_bundle = {answer["bundle_id"]: answer["payload"] for answer in reply["answers"]}
assert set(by_bundle) == {"bakeri", "hage"}
# Every excerpt names the bundle it came from, and it is the right one.
for bundle_id, payload in by_bundle.items():
assert payload["excerpts"], bundle_id
assert {excerpt["bundle_id"] for excerpt in payload["excerpts"]} == {bundle_id}
delivered = {
answer["bundle_id"]: " ".join(excerpt["text"] for excerpt in answer["payload"]["excerpts"])
for answer in reply["answers"]
}
assert "tolv timer" in delivered["bakeri"]
assert "hver morgen" in delivered["hage"]
def test_one_call_over_a_folder_is_the_servers_own_ask(folder: Path) -> None:
"""No ranking of its own: the bytes are `okf_ask`'s with no bundle named."""
reply = json.loads(_ask(folder).stdout)
assert reply == mcp_server.call_ask(_surface(folder), {"questions": list(QUESTIONS)})
def test_naming_one_bundle_under_the_folder_asks_only_that_one(folder: Path) -> None:
reply = json.loads(_ask(folder, "--bundle-id", "hage").stdout)
assert reply["asked"] == ["hage"]
assert reply == mcp_server.call_ask(
_surface(folder), {"questions": list(QUESTIONS), "bundle_id": "hage"}
)
def test_an_unknown_bundle_name_is_refused(folder: Path) -> None:
run = _ask(folder, "--bundle-id", "finnes-ikke")
assert run.returncode == 1
assert "bundle_unknown" in run.stderr
def test_a_reading_flag_the_server_does_not_take_is_refused_over_a_folder(folder: Path) -> None:
"""A flag that would be silently dropped is refused: the folder door reads
exactly as the server reads, and a flag it ignored would make the caller
believe in a cut that never happened."""
run = _ask(folder, "--no-source-quota")
assert run.returncode == 2
assert "--no-source-quota" in run.stderr
def test_bundle_id_on_one_bundle_is_refused(folder: Path) -> None:
run = _okf("consume", str(folder / "hage"), "--question", "vanning", "--bundle-id", "hage")
assert run.returncode == 2
assert "--bundle-id" in run.stderr
def test_one_bundle_is_read_as_before(folder: Path) -> None:
"""Pointed at one bundle, the payload is the single-bundle payload."""
run = _okf("consume", str(folder / "hage"), "--question", "vanning")
assert run.returncode == 0, run.stderr
payload = json.loads(run.stdout)
assert "answers" not in payload
assert payload["bundle"]["bundle_id"] == "hage"
# --- F4: the checker reads the folder's reply --------------------------------
def _check(tmp_path: Path, reply: object) -> subprocess.CompletedProcess[str]:
from llm_ingestion_okf import skill
skill_path = tmp_path / "SKILL.md"
skill_path.write_text(skill.render_generic(), encoding="utf-8")
payload_path = tmp_path / "reply.json"
payload_path.write_text(json.dumps(reply, ensure_ascii=False), encoding="utf-8")
return _okf("check", "--skill", str(skill_path), "--payload", str(payload_path))
def _reply(folder: Path) -> dict[str, object]:
return mcp_server.call_ask(_surface(folder), {"questions": list(QUESTIONS)})
def test_the_generic_skill_is_conformant_on_a_folders_reply(folder: Path, tmp_path: Path) -> None:
run = _check(tmp_path, _reply(folder))
assert run.returncode == 0, run.stdout
assert run.stdout.startswith("conformant: ")
assert "over 2 payloads" in run.stdout
def test_an_answer_labelled_with_another_bundle_is_a_finding(folder: Path, tmp_path: Path) -> None:
reply = _reply(folder)
answers = reply["answers"]
assert isinstance(answers, list)
answers[0]["bundle_id"] = "hage"
run = _check(tmp_path, reply)
assert run.returncode == 1
assert "answer_misattributed" in run.stdout
def test_a_defect_in_one_answer_is_named_with_its_bundle(folder: Path, tmp_path: Path) -> None:
reply = _reply(folder)
answers = reply["answers"]
assert isinstance(answers, list)
del answers[1]["payload"]["contract"]
run = _check(tmp_path, reply)
assert run.returncode == 1
findings = [line for line in run.stdout.splitlines() if line.startswith(" ")]
assert findings == [
line for line in findings if line.startswith(" contract_unversioned: [hage]")
]
assert len(findings) == 1
def test_a_reply_with_no_answer_is_a_finding_not_a_pass(tmp_path: Path) -> None:
run = _check(tmp_path, {"asked": [], "answers": []})
assert run.returncode == 1
assert "payload_invalid" in run.stdout