llm-ingestion-okf/tests/test_parent_reaches_reader.py
Kjell Tore Guttormsen a5cd7c5688 test(consume): parent reaches the reader -- payload, body link, checker
K3-21 A, red. Round 20 wrote `parent:` on 675 of 710 heading-only sections
and no reader could see it: `okf consume` did not read the key, the payload
did not carry it, the body held no link, and the pointer was a `segment_id`
a reader cannot open without enumerating the bundle.

Held here: a heading-only body carries ONE bundle-relative link to its
enclosing section (SPEC SS 5.1, SS 6.1); an excerpt carries `parent` as the
resolved concept id and title, never the raw id; an unresolvable pointer is
named `parent_unresolved`; a pointer resolves inside its own document; the
checker has 17 rules and refuses a parent a reader cannot follow, with four
known-negatives; the contract and the skill template name the field.

11 of 12 red on e717b1c; the one green is the guard that a payload with no
`parent` meets the rule as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 12:20:23 +02:00

245 lines
9.6 KiB
Python

"""`parent` reaches the reader: the concept body, the payload, and the checker.
Round 20 gave a heading-only section a `parent:` key naming the nearest
ancestor that holds text (`okf build --shell-parent`), and no reader could see
it. `okf consume` did not read the key, so the payload did not carry it; the
body held no link, so a reader who opened the file found a `segment_id` it had
no way to follow without enumerating the bundle, which the consumption
contract's SS 9.2 forbids.
Two expressions of one relation, each on its own ground in the canonical spec:
- **The body carries a link.** SS 5.1: "Lineage is expressed through links, not
a dedicated field." SS 6.1: the bundle-relative form, beginning with `/`, "is
the recommended form", and the kind of relationship "is conveyed by the
surrounding prose, not by the link itself". Written only where the body is
its heading alone -- a body holding text already has something to read, and
the segmented goldens' declared parents are bodies holding text.
- **The payload carries the pointer resolved.** SS 4.1 makes `parent:` a
permitted extension. The excerpt names the concept the pointer resolves to
and that concept's title, never the raw `segment_id`: a reader holding
`p1977` cannot open anything.
`sts-inherit.xml` is hand-written in an invented setting and carries no
sentence from any source.
"""
from __future__ import annotations
import re
from pathlib import Path
from typing import Any
import pytest
from llm_ingestion_okf import cli, consume, contract_check, skill
PROJECT_ROOT = Path(__file__).resolve().parents[1]
FIXTURE = Path(__file__).parent / "fixtures" / "sts-inherit.xml"
TEMPLATE = PROJECT_ROOT / "skills" / "okf-consume-template" / "SKILL.md"
CONTRACT = PROJECT_ROOT / "docs" / "consumption-contract.md"
#: shell title -> the section its link and its excerpt name. The same pairs
#: `tests/test_shell_parent.py` holds for the plan.
LINKED = {
"Rekkverk": "Vedlikehold av utstyr",
"Utskifting": "Vedlikehold av utstyr",
"Utskifting av list": "Utskifting av enkeltdeler",
"Utskifting av stolpe": "Utskifting av enkeltdeler",
}
#: The one line a linked body gains, in the spec's recommended form.
LINK = re.compile(r"^Enclosing section: \[(?P<title>[^\]]+)\]\((?P<target>/[^)\s]+\.md)\)$")
def _build(tmp_path: Path, name: str, files: dict[str, bytes], *extra: str) -> Path:
inbox = tmp_path / f"{name}-inbox"
inbox.mkdir()
for filename, data in files.items():
(inbox / filename).write_bytes(data)
bundle = tmp_path / name
assert (
cli.main(
[
"build",
str(inbox),
"--bundle",
str(bundle),
"--bundle-id",
"inherit-fixture",
"--okf-version",
"0.2",
*extra,
]
)
== 0
)
return bundle
def _flagged(tmp_path: Path) -> Path:
return _build(tmp_path, "flagged", {FIXTURE.name: FIXTURE.read_bytes()}, "--shell-parent")
def _concepts(bundle: Path) -> dict[str, tuple[str, dict[str, str], str]]:
"""title -> (concept id, frontmatter, body), for one document's concepts."""
found: dict[str, tuple[str, dict[str, str], str]] = {}
for path in sorted(bundle.rglob("*.md")):
if path.name in ("index.md", "log.md"):
continue
head, _, body = path.read_text(encoding="utf-8").partition("\n---\n")
values = dict(line.split(": ", 1) for line in head.split("\n")[1:] if ": " in line)
concept_id = path.relative_to(bundle).as_posix()[: -len(".md")]
found[values["title"]] = (concept_id, values, body)
return found
# --- The body ------------------------------------------------------------------
def test_a_shell_s_body_links_once_to_its_enclosing_section(tmp_path: Path) -> None:
bundle = _flagged(tmp_path)
concepts = _concepts(bundle)
for title, (_, _, body) in concepts.items():
links = [match for line in body.split("\n") if (match := LINK.match(line))]
if title not in LINKED:
assert links == [], title
continue
assert len(links) == 1, title
ancestor_id = concepts[LINKED[title]][0]
assert links[0]["title"] == LINKED[title]
assert links[0]["target"] == f"/{ancestor_id}.md"
# Not broken: the target is a concept of this bundle.
assert (bundle / links[0]["target"].lstrip("/")).is_file()
# --- The payload ---------------------------------------------------------------
def test_an_excerpt_carries_the_parent_it_can_follow(tmp_path: Path) -> None:
bundle = _flagged(tmp_path)
concepts = _concepts(bundle)
payload = consume.build_payload(bundle, question="Utskifting av list")
by_title = {excerpt["title"]: excerpt for excerpt in payload["excerpts"]} # type: ignore[attr-defined]
shell = by_title["Utskifting av list"]
assert shell["parent"] == {
"concept_id": concepts["Utskifting av enkeltdeler"][0],
"title": "Utskifting av enkeltdeler",
}
# Conditional, never empty: an excerpt whose concept names no parent
# carries neither member.
for title, excerpt in by_title.items():
if title not in LINKED:
assert "parent" not in excerpt and "parent_unresolved" not in excerpt, title
def test_a_pointer_that_resolves_to_nothing_is_named_and_not_dropped(tmp_path: Path) -> None:
bundle = _flagged(tmp_path)
concepts = _concepts(bundle)
shell_id, values, _ = concepts["Utskifting av list"]
path = bundle / f"{shell_id}.md"
path.write_text(
path.read_text(encoding="utf-8").replace(f"parent: {values['parent']}\n", "parent: p999\n"),
encoding="utf-8",
)
payload = consume.build_payload(bundle, question="Utskifting av list")
shell = next(e for e in payload["excerpts"] if e["title"] == "Utskifting av list") # type: ignore[attr-defined]
assert "parent" not in shell
assert shell["parent_unresolved"] is True
def test_a_pointer_resolves_inside_its_own_document(tmp_path: Path) -> None:
"""Segment ids are per plan, so `p1` exists once in EVERY document. A
pointer resolved across the bundle would be ambiguous in two documents and
wrong in one."""
other = (
FIXTURE.read_bytes()
.replace(b"Fikstur for arvet kontekst", b"Annen fikstur")
.replace(b"alt utstyr langs vegen", b"utstyr i tunnel")
)
bundle = _build(
tmp_path,
"two",
{"a.xml": FIXTURE.read_bytes(), "b.xml": other},
"--shell-parent",
)
payload = consume.build_payload(
bundle, question="Utskifting av list og stolpe i rekkverk", k=50
)
carried = [e for e in payload["excerpts"] if "parent" in e] # type: ignore[attr-defined]
documents = {str(e["concept_id"]).split("/")[0] for e in carried}
assert documents == {"a", "b"}
for excerpt in carried:
assert str(excerpt["parent"]["concept_id"]).split("/")[0] == str(
excerpt["concept_id"]
).split("/")[0]
# --- The checker ---------------------------------------------------------------
def _pair(tmp_path: Path) -> tuple[str, dict[str, Any]]:
bundle = _flagged(tmp_path)
text, _ = skill.render(bundle, out=bundle.parent / "unwritten")
payload = consume.build_payload(bundle, question="Utskifting av list")
return text, dict(payload)
def test_the_checker_has_seventeen_rules_and_accepts_a_followable_parent(tmp_path: Path) -> None:
text, payload = _pair(tmp_path)
assert any("parent" in excerpt for excerpt in payload["excerpts"])
report = contract_check.check(text, payload)
assert report.findings == ()
assert report.rules_evaluated == len(contract_check.RULES) == 17
assert "17 rules" in report.render()
@pytest.mark.parametrize(
"parent",
[
# The raw segment id round 20 wrote: nothing a reader can open.
"p4",
# A concept id naming nothing in this bundle.
{"concept_id": "nowhere/in-this-bundle", "title": "Utskifting av enkeltdeler"},
# No title, so nothing a citation can be made of.
{"concept_id": "SELF-PARENT"},
# A section naming itself.
"SELF",
],
)
def test_known_negative_a_parent_a_reader_cannot_follow_is_a_finding(
tmp_path: Path, parent: object
) -> None:
text, payload = _pair(tmp_path)
shell = next(e for e in payload["excerpts"] if "parent" in e)
if parent == "SELF":
parent = {"concept_id": shell["concept_id"], "title": shell["title"]}
elif isinstance(parent, dict) and parent.get("concept_id") == "SELF-PARENT":
parent = {"concept_id": shell["parent"]["concept_id"]}
shell["parent"] = parent
found = [finding.code for finding in contract_check.check(text, payload).findings]
assert found == ["parent_unfollowable"]
def test_a_payload_carrying_no_parent_meets_the_rule_as_before(tmp_path: Path) -> None:
text, payload = _pair(tmp_path)
for excerpt in payload["excerpts"]:
excerpt.pop("parent", None)
assert contract_check.check(text, payload).findings == ()
# --- The documents a reader is told by -----------------------------------------
def test_the_contract_names_the_field_and_what_the_checker_reads() -> None:
section = CONTRACT.read_text(encoding="utf-8").split("## 8. The payload shape")[1]
section = section.split("## 9.")[0]
assert '"parent"' in section
assert "`parent_unresolved`" in section
assert "are not read by the checker" not in section
def test_the_skill_template_tells_the_reader_what_parent_is() -> None:
text = TEMPLATE.read_text(encoding="utf-8")
assert "`parent`" in text
assert "`parent_unresolved`" in text