"""A section whose body is its heading alone points at the ancestor holding its text. THE SHAPE, in the mechanism rather than in a corpus. A process code states its lettered points once, on the section that owns them, and every section nested below inherits them; the nested section itself carries a title and nothing else. Built faithfully, it becomes a concept whose body is one heading line -- measured on one 2 761-concept standard, 710 such concepts -- and a reader handed one has no way to reach the text it inherits: the directory tree is two levels deep, so the parent is not in the path either. The rule, behind `--shell-parent` and OFF by default: a plan entry whose span holds no line but its heading gets `parent_id` naming the NEAREST preceding entry at a smaller level whose own span holds text. An empty ancestor is passed over, and a shell with no ancestor holding text gets no parent. Nothing is copied: the door writes the existing `parent:` key and not one borrowed line. It reads the PLAN -- level and order -- and never the row, so the same outline reaches the same parents through a route that is not NISO-STS. `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 llm_ingestion_okf import cli, extract, propose FIXTURE = Path(__file__).parent / "fixtures" / "sts-inherit.xml" #: Every flag `okf build` turns on by default. BUILD_DEFAULTS = dict( outline_run=3, table_grid=True, unit_fold=True, keep_table_heading=True, sheet_section_rows=True, drop_wrapped_outline=True, outline_gate=True, first_span_from_zero=True, close_span_gaps=True, contents_name=True, ) #: shell title -> title of the nearest ancestor holding text, or None. EXPECTED = { "Rekkverk": "Vedlikehold av utstyr", # Its parent `Rekkverk` is a shell too, so the pointer goes one further up. "Utskifting": "Vedlikehold av utstyr", "Utskifting av list": "Utskifting av enkeltdeler", "Utskifting av stolpe": "Utskifting av enkeltdeler", # A shell whose only ancestor is empty, and one with no ancestor at all. "Kapittel uten tekst": None, "Underkapittel uten tekst": None, # Sections holding text never get one. "Vedlikehold av utstyr": None, "Utskifting av enkeltdeler": None, } def _plan(source: Path, **flags: object) -> dict: # type: ignore[type-arg] data = FIXTURE.read_bytes() text = extract.extract_text(FIXTURE.name, data) return propose.build_plan( source, text, data, okf_type="reference", proposed_at="2026-01-01T00:00:00Z", **BUILD_DEFAULTS, **flags, # type: ignore[arg-type] ) def _parents(plan: dict) -> dict[str, str | None]: # type: ignore[type-arg] titles = {entry["segment_id"]: entry["title"] for entry in plan["entries"]} return { entry["title"]: titles[entry["parent_id"]] if "parent_id" in entry else None for entry in plan["entries"] } def test_a_shell_points_at_the_nearest_ancestor_holding_text() -> None: assert _parents(_plan(FIXTURE, shell_parent=True)) == EXPECTED def test_nothing_moves_without_the_flag() -> None: plain = _plan(FIXTURE) assert not [entry for entry in plain["entries"] if "parent_id" in entry] flagged = _plan(FIXTURE, shell_parent=True) for entry in flagged["entries"]: entry.pop("parent_id", None) assert flagged == plain def test_the_rule_reads_the_plan_and_not_the_row(tmp_path: Path) -> None: """The same outline through the bookmark arm's route: the same parents.""" data = FIXTURE.read_bytes() text = extract.extract_text(FIXTURE.name, data) source = tmp_path / "same-outline.md" source.write_text(text, encoding="utf-8") plan = propose.build_plan( source, text, source.read_bytes(), okf_type="reference", proposed_at="2026-01-01T00:00:00Z", outline_marks=extract.xml_outline(FIXTURE.name, data), shell_parent=True, **BUILD_DEFAULTS, # type: ignore[arg-type] ) assert {entry["derived"][1] for entry in plan["entries"]} == {propose.RULE_PDF_OUTLINE} assert _parents(plan) == EXPECTED def _frontmatter(bundle: Path) -> dict[str, dict[str, str]]: by_title: dict[str, dict[str, str]] = {} for path in bundle.rglob("*.md"): if path.name in ("index.md", "log.md"): continue head = path.read_text(encoding="utf-8").split("\n---\n", 1)[0] values = dict(line.split(": ", 1) for line in head.split("\n")[1:] if ": " in line) by_title[values["title"]] = values return by_title def _build(tmp_path: Path, name: str, *extra: str) -> Path: inbox = tmp_path / "inbox" if not inbox.exists(): inbox.mkdir() (inbox / FIXTURE.name).write_bytes(FIXTURE.read_bytes()) 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 test_the_door_writes_parent_as_the_ancestor_s_segment_id(tmp_path: Path) -> None: concepts = _frontmatter(_build(tmp_path, "flagged", "--shell-parent")) for title, ancestor in EXPECTED.items(): if ancestor is None: assert "parent" not in concepts[title] else: assert concepts[title]["parent"] == concepts[ancestor]["segment_id"] def test_a_build_without_the_flag_is_the_build_with_the_opt_out(tmp_path: Path) -> None: default = _build(tmp_path, "default") opted_out = _build(tmp_path, "opted-out", "--no-shell-parent") flagged = _build(tmp_path, "flagged", "--shell-parent") files = sorted(p.relative_to(default) for p in default.rglob("*") if p.is_file()) assert files == sorted(p.relative_to(opted_out) for p in opted_out.rglob("*") if p.is_file()) for relative in files: assert (default / relative).read_bytes() == (opted_out / relative).read_bytes() # The flag adds one `parent:` line to each shell with an ancestor, and -- # since K3-21 -- one link line to that shell's body # (`tests/test_parent_reaches_reader.py`). The index, a projection of the # frontmatter, shows the same key as a facet on that shell's entry. Nothing # else moves. added = linked = 0 for relative in files: before = (default / relative).read_text(encoding="utf-8").split("\n") text = (flagged / relative).read_text(encoding="utf-8") if relative.name == "index.md": after = text.split("\n") assert [re.sub(r"parent: p\d+\?; ", "", line) for line in after] == before continue text, links = re.subn(r"\n\nEnclosing section: \[[^\]]+\]\(/[^)\s]+\)\n\Z", "\n", text) linked += links after = text.split("\n") assert [line for line in after if not line.startswith("parent: ")] == before added += sum(1 for line in after if line.startswith("parent: ")) assert added == linked == sum(1 for ancestor in EXPECTED.values() if ancestor is not None)