"""P22 DEL C — when the nearest listable ancestor holds documents and no subdirectories, name them. P21/C2 made a refusal for an absent path name the ancestor's SUBDIRECTORIES, and it bought what it was built for: ``read_dir`` against a level the base does not hold went from 16 of 104 to 8 of 128. It did nothing for documents. Re-measured at the head of okt 126 over round 5's six debate traces, ``read_file`` against a document the base does not hold went from 2 of 38 to **7 of 52** under the ``context_files`` definition (2 of 38 to 6 of 52 under the judge's filesystem one — P21 declared both definitions, and this file uses the ``context_files`` one because that is the property the refusal is built from). The reason is structural: the nearest listable ancestor of a guessed DOCUMENT path often holds documents and no subdirectories, and then the neighbour clause was omitted — deliberately, because an empty list is a sentence with nothing in it. Measured over those six misses, THREE land on such an ancestor (``krav/N100`` with 445 documents; ``R761/1`` with exactly ONE, which two separate guesses — ``R761/1/1-1.md`` and ``R761/1/R761-1-1_id-...md`` — were both reaching for) and three have subdirectories and were already answered. Same source and same property as its sibling: built from ``context_files`` through ``in_dimension``, so **every name it gives back resolves** — arm (c) measures that by feeding each one back, rather than asserting the list is non-empty. """ from __future__ import annotations import json from pathlib import Path from typing import Any from portfolio_optimiser import okf from portfolio_optimiser.explore import navigator_tools _EXAMPLES = Path(__file__).resolve().parents[1] / "shared" / "examples" _NEIGHBOUR_MAX = 5 def _write_tree(base: Path, *, dirs: int, per_dir: int, top: int = 2) -> Path: """A base shaped like an ingested corpus: a root holding documents AND subdirectories, each subdirectory holding documents and NOTHING else. The two branches this file separates.""" base.mkdir(parents=True) root_lines = [f"# {base.name}", ""] for t in range(top): root_lines.append(f"- [Toppdokument {t}](topp-{t}.md)") (base / f"topp-{t}.md").write_text( f"---\ntype: concept\ntitle: Toppdokument {t}\n---\n\nkort tekst.\n", encoding="utf-8" ) for d in range(dirs): sub = base / f"kategori-{d:02d}" sub.mkdir() root_lines.append(f"- [kategori-{d:02d} (index)](kategori-{d:02d}/index.md)") sub_lines = [f"# kategori-{d:02d}", ""] for i in range(per_dir): sub_lines.append(f"- [Dokument {i}](dok-{i:02d}.md)") (sub / f"dok-{i:02d}.md").write_text( f"---\ntype: concept\ntitle: Dokument {d:02d}-{i:02d}\n---\n\nkort tekst.\n", encoding="utf-8", ) (sub / "index.md").write_text( "---\ntype: index\n---\n\n" + "\n".join(sub_lines) + "\n", encoding="utf-8" ) (base / "index.md").write_text( "---\ntype: index\n---\n\n" + "\n".join(root_lines) + "\n", encoding="utf-8" ) return base def _tools(base: Path) -> dict[str, Any]: return {t.name: t for t in navigator_tools((str(base),))} def _read_file(base: Path, path: str) -> str: return str(_tools(base)["read_file"].func(bundle_id=base.name, path=path)) def _read_dir(base: Path, path: str) -> Any: return _tools(base)["read_dir"].func(bundle_id=base.name, path=path) # --- (a) the KNOWN-POSITIVE: a level with documents and no subdirectories ------------------------ def test_a_missing_document_is_answered_with_the_names_that_level_holds(tmp_path: Path) -> None: """RED before DEL C: the ancestor ``kategori-00`` holds documents and no subdirectories, so P21/C2's clause was omitted and the refusal said only which rung to go back to. It now names the documents that rung holds.""" base = _write_tree(tmp_path / "korpus", dirs=3, per_dir=4) refusal = _read_file(base, "kategori-00/dok-99-gjettet.md") assert refusal.startswith("REFUSED ("), refusal assert "it holds the documents" in refusal, refusal assert "kategori-00/dok-00.md" in refusal, refusal def test_a_level_with_exactly_one_document_names_it(tmp_path: Path) -> None: """The measured ``R761/1`` case, which is the sharpest one in the round-5 trace: TWO separate guesses at one document's name, in one run, at a level that holds exactly that one document. Naming it answers both in a single step.""" base = _write_tree(tmp_path / "korpus", dirs=1, per_dir=1) for guess in ("kategori-00/1-1.md", "kategori-00/kat-1-1_id-abc.md"): refusal = _read_file(base, guess) assert "kategori-00/dok-00.md" in refusal, (guess, refusal) # --- (b) the KNOWN-NEGATIVE: a level that HAS subdirectories is answered as before --------------- def test_a_level_with_subdirectories_still_names_the_subdirectories(tmp_path: Path) -> None: """The control, and the half that keeps DEL C from being a rewrite of C2. The root holds BOTH documents and subdirectories; the subdirectory branch stays first, so every C2 refusal is unchanged — and the two clauses are never both present, because the ancestor is one level and naming its documents when it also has subdirectories answers a different question.""" base = _write_tree(tmp_path / "korpus", dirs=3, per_dir=4) refusal = _read_file(base, "finnes-ikke.md") assert "its subdirectories include" in refusal, refusal assert "it holds the documents" not in refusal, refusal # --- (c) every name it gives back RESOLVES ------------------------------------------------------- def test_every_named_document_can_then_be_read(tmp_path: Path) -> None: """The ``_index_excerpt`` property, measured rather than assumed: a refusal that hands back a path the next call refuses is worse than one that hands back nothing. Each named document is fed straight back into ``read_file``.""" base = _write_tree(tmp_path / "korpus", dirs=2, per_dir=4) refusal = _read_file(base, "kategori-01/dok-99.md") named = refusal.split("it holds the documents ")[1].split(")")[0].split(", ") assert named, refusal for path in named: answer = _read_file(base, path.strip()) assert not answer.startswith("REFUSED ("), f"{path!r} does not resolve: {answer}" # --- (d) the bound ------------------------------------------------------------------------------ def test_the_document_list_is_bounded(tmp_path: Path) -> None: """A level of a delivered corpus can hold hundreds — ``krav/N100`` holds 445. The list is the same fixed five its sibling uses, for the same reason: this is help text on a refusal, and its price must not be set by how much the level contains.""" base = _write_tree(tmp_path / "korpus", dirs=1, per_dir=40) refusal = _read_file(base, "kategori-00/dok-99-gjettet.md") named = refusal.split("it holds the documents ")[1].split(")")[0].split(", ") assert len(named) == _NEIGHBOUR_MAX, named def test_the_closest_name_comes_first(tmp_path: Path) -> None: """The RANKING, and it is here because a mutation found it unwitnessed. Replacing ``_shared_prefix`` with a plain reverse sort left the whole suite green: the bound, the source and the resolve property were all gated, and the ORDER was not. For the measured ``R761/1`` case that costs nothing — one document, one answer — but a level of a delivered corpus can hold 445, and then which five it names is the whole value of the clause. The rule is the sibling's, through the SAME ``_shared_prefix`` helper rather than a second copy (kø-(p)): longest common prefix with the segment that failed, then shortest, then name. A ranking on help text cannot refuse anything, so its failure direction stays benign — at worst it names five real documents that are not the one meant. """ base = tmp_path / "korpus" base.mkdir(parents=True) names = ["asfalt-slitelag-2024.md", "asfalt-dekke.md", "betong.md", "grus.md"] lines = [f"# {base.name}", ""] for name in names: lines.append(f"- [{name}]({name})") (base / name).write_text( f"---\ntype: concept\ntitle: {name}\n---\n\nkort tekst.\n", encoding="utf-8" ) (base / "index.md").write_text( "---\ntype: index\n---\n\n" + "\n".join(lines) + "\n", encoding="utf-8" ) refusal = _read_file(base, "asfalt-slitelag.md") named = refusal.split("it holds the documents ")[1].split(")")[0].split(", ") # It shares the longest prefix AND is the longest name, so a length-only rule puts it last and # a reverse-alphabetical one puts ``grus.md`` first. Only the prefix rule puts it first. assert named[0] == "asfalt-slitelag-2024.md", named assert named[1] == "asfalt-dekke.md", named # --- (e) the other call site --------------------------------------------------------------------- def test_read_dir_gets_the_same_answer(tmp_path: Path) -> None: """ONE helper for both refusal sites (kø-(p)): ``read_dir``'s own absent-path refusal and ``read_file``'s answer the same question about the same base, and two copies would be free to give a caller two answers about one level.""" base = _write_tree(tmp_path / "korpus", dirs=1, per_dir=4) answer = _read_dir(base, "kategori-00/finnes-ikke") blob = json.dumps(answer, ensure_ascii=False) assert "it holds the documents" in blob, blob assert "kategori-00/dok-00.md" in blob, blob # --- (f)/(g) built from context_files, through in_dimension -------------------------------------- def test_the_verdict_layer_is_never_named_in_a_refusal(tmp_path: Path) -> None: """Built from ``context_files``, never ``files``. A list read off the filesystem would advertise, in a REFUSAL, the one layer no listing mentions and ``read_file`` then refuses outright — the same leak in an apology's clothing.""" base = _write_tree(tmp_path / "korpus", dirs=1, per_dir=2) verdict = base / "kategori-00" / "dom.md" verdict.write_text( "---\ntype: verdict\ntitle: En tidligere dom\n---\n\nkropp.\n", encoding="utf-8" ) index = base / "kategori-00" / "index.md" index.write_text(index.read_text(encoding="utf-8") + "- [Dom](dom.md)\n", encoding="utf-8") refusal = _read_file(base, "kategori-00/dok-99.md") assert "dom.md" not in refusal, refusal assert "kategori-00/dok-00.md" in refusal, refusal def test_a_foreign_dimension_document_is_never_named(tmp_path: Path) -> None: """Through the SAME ``in_dimension`` predicate the listing uses. Naming a document the run would refuse to open a moment later is S2c's "a filter in name only", one rung over.""" base = _write_tree(tmp_path / "korpus", dirs=1, per_dir=1) foreign = base / "kategori-00" / "fremmed.md" foreign.write_text( "---\ntype: concept\ndimension: asfalt\ntitle: Fremmed\n---\n\nkropp.\n", encoding="utf-8" ) index = base / "kategori-00" / "index.md" index.write_text( index.read_text(encoding="utf-8") + "- [Fremmed](fremmed.md)\n", encoding="utf-8" ) tools = {t.name: t for t in navigator_tools((str(base),), dimension="energi")} refusal = str(tools["read_file"].func(bundle_id=base.name, path="kategori-00/dok-99.md")) assert "fremmed.md" not in refusal, refusal # --- (h) the shipped nested golden is untouched -------------------------------------------------- def test_the_shipped_nested_golden_still_navigates_unchanged() -> None: """A commons-owned base, read the way the nav-goldens read it: the helper is additive, and the property the byte-exact goldens rest on is that nothing about navigation moved.""" bundle = okf.navigate_bundle(str(_EXAMPLES / "nav-golden-hierarchy" / "bundle")) assert [f.name for f in bundle.context_files] == [ "overview.md", "a/doc-a.md", "a/b/doc-b.md", ]