`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>
270 lines
10 KiB
Python
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
|