feat(p22): a refusal names the DOCUMENTS when the ancestor has no subdirectories
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 -- read_file against a document the base does not hold went 2 of 38 to 7 of 52 -- and 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 round 5's six read_file misses, THREE land on such an ancestor: krav/N100 with 445 documents, and R761/1 with exactly ONE -- which two separate guesses in one run were both reaching for. The other three have subdirectories and were already answered. okf.nearest_documents is the sibling of nearest_subdirectories, never a widening of it: never both clauses, and the subdirectory branch stays FIRST, which is what keeps every C2 refusal byte-identical. Built from context_files and through the same in_dimension predicate the listing uses, so a refusal can never advertise the type: verdict layer by path, and every name it hands back resolves -- measured by feeding each one back into read_file, not by asserting the list is non-empty. A MUTATION FOUND THE RANKING UNWITNESSED, and that is recorded rather than dropped: replacing _shared_prefix with a plain reverse sort left the whole suite green. The bound, the source and the resolve property were all gated; the ORDER was not. For R761/1 that costs nothing, but a level of a delivered corpus can hold 445, and then which five it names is the whole value of the clause. The new arm builds a level where the closest name is also the LONGEST, so a length rule puts it last and an alphabetical one puts another first -- only the prefix rule puts it first. Load-bearing MEASURED (tests/test_document_neighbours_loadbearing.py, 10 arms), seven mutations all red against the WHOLE suite + green control 1891/5 (from 1881/5, superset, 0 removed) and golden demo-transcript.stdout BYTE-UNCHANGED (shasum -a 1 of the CONTENT = ea8c534773acdbe41ae68f2c55724d69aaf8be4f): C1 detach the document branch in read_file (5 red) - C2 detach it in read_dir (1) - C3 build from files (1) - C4 ignore the dimension (1) - C5 no bound (1) - C6 both clauses at once (1) - C7 a second ranking rule (1, after the test was fixed; green before, which is the finding). Honesty limits, stated: the foreign-dimension arm was VACUOUSLY green before this change (nothing was named, so nothing could leak) and is gated only now -- C4 is what makes it real; no LIVE model has read the new clause (DEL D is the measurement); and the clause is help text, not a gate -- it cannot make a guessed path right, only cheaper to correct. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
9072359606
commit
f8709ec228
5 changed files with 373 additions and 14 deletions
36
CLAUDE.md
36
CLAUDE.md
|
|
@ -3054,6 +3054,42 @@ Python ≥3.10. MAF (`agent-framework-core` 1.16.0, `-orchestrations` 1.1.1 —
|
|||
(structured-output-grensens klasse) — DEL D er maalingen; rapporten kan ikke si at et krav ER
|
||||
riktig, bare at det ikke deler ett ord med retningen; og funn 4 (`named` 1/20) er denne saken
|
||||
sett fra den andre siden, saa den maales av DEL D og ikke av en egen soem.
|
||||
- **Naar forfaren ikke har underkataloger, navngir nekten DOKUMENTENE den holder (P22 DEL C,
|
||||
16.09):** P21/C2 lot en nekt for en fravaerende sti navngi forfarens UNDERKATALOGER, og den kjoepte
|
||||
hva den var bygget for — `read_dir` mot et nivaa basen ikke holder gikk 16/104 → 8/128. Den gjorde
|
||||
INGENTING for dokumenter: `read_file` mot et dokument basen ikke holder gikk **2/38 → 7/52**.
|
||||
Grunnen er strukturell, ikke tilfeldig: den naermeste listbare forfaren til en GJETTET dokumentsti
|
||||
holder ofte dokumenter og ingen underkataloger, og da ble naboklausulen utelatt — BEVISST, fordi en
|
||||
tom liste er en setning uten innhold. **MAALT over runde 5s seks `read_file`-bom: TRE lander paa en
|
||||
slik forfar** (`krav/N100` med 445 dokumenter; `R761/1` med NOEYAKTIG ETT — som to separate
|
||||
gjetninger i samme kjoering, `R761/1/1-1.md` og `R761/1/R761-1-1_id-...md`, begge strakte seg
|
||||
etter), og tre har underkataloger og var alt besvart. `okf.nearest_documents` er soesknet til
|
||||
`nearest_subdirectories`, ALDRI en utvidelse av den: **aldri begge klausuler** (forfaren er ETT
|
||||
nivaa, og aa navngi dens dokumenter naar den ogsaa har underkataloger besvarer et annet spoersmaal
|
||||
enn det kalleren stilte), og underkatalog-grenen staar FOERST, hvilket er dét som holder hver
|
||||
C2-nekt byte-identisk (M C6 → 1 roed). **Bygget fra `context_files`, ALDRI `files`** — en liste
|
||||
lest av filsystemet ville reklamert, i en NEKT, for `type: verdict`-laget som ingen listing nevner
|
||||
og `read_file` straks avviser blankt: samme lekkasje i unnskyldningens klaer (M C3 → 1 roed) — og
|
||||
gjennom SAMME `in_dimension`-predikat listingen bruker (M C4 → 1 roed). **Hvert navn RESOLVERER**,
|
||||
maalt ved aa mate hvert av dem tilbake inn i `read_file`, aldri ved aa asserte at lista er
|
||||
ikke-tom. Bundet til fem (M C5 → 1 roed), og **ÉN hjelper for BEGGE nekt-steder** (`read_dir`s egen
|
||||
og `read_file`s — kø-(p): ett spoersmaal om én base maa ikke ha to svar; M C1 → 5 roede, M C2 → 1).
|
||||
**`_shared_prefix` er den ene rangeringsregelen, delt av begge soesken**, og det staar her fordi en
|
||||
MUTASJON FANT DEN UVITNET: aa bytte den mot en ren revers-sortering lot HELE suiten staa groenn
|
||||
(1890/5) — bindingen, kilden og resolver-egenskapen var alle gatet, og REKKEFOELGEN var det ikke.
|
||||
For `R761/1` koster det ingenting (ett dokument, ett svar), men et nivaa i et levert korpus kan
|
||||
holde 445, og da ER hvilke fem den navngir hele verdien av klausulen. Den nye armen bygger et nivaa
|
||||
der det naermeste navnet ogsaa er det LENGSTE, saa en lengde-regel legger det sist og en
|
||||
alfabetisk legger et annet foerst — bare prefiks-regelen legger det foerst (M C7 → 1 roed etter
|
||||
rettelsen, groenn foer). Load-bearing MAALT
|
||||
(`tests/test_document_neighbours_loadbearing.py`, 10 armer), **sju mutasjoner alle roede mot HELE
|
||||
suiten** + groenn kontroll **1891/5** (fra 1881/5, supersett, 0 fjernet) og golden BYTE-UENDRET
|
||||
(`shasum -a 1` av INNHOLDET = `ea8c534773acdbe41ae68f2c55724d69aaf8be4f`). **AErlighets-grenser,
|
||||
uttalt:** armen for fremmed dimensjon var VAKUOEST GROENN foer endringen (ingenting ble navngitt,
|
||||
saa ingenting kunne lekke) og er gatet foerst naa — M C4 er dét som gjoer den ekte; ingen LEVENDE
|
||||
modell har lest den nye klausulen (structured-output-grensens klasse), DEL D er maalingen; og
|
||||
klausulen er hjelpetekst, ikke en gate — den kan ikke gjoere en gjettet sti riktig, bare billigere
|
||||
aa rette.
|
||||
- **STATE.md er local-only** (gitignored). Voyage session-state er efemert; STATE.md er kanonisk kontinuitet.
|
||||
- Prosess: Voyage-plugin (`/trekbrief → /trekplan → /trekexecute → /trekreview`) per større fase.
|
||||
|
||||
|
|
|
|||
|
|
@ -796,7 +796,11 @@ when the seam is detached, so the loop cannot silently degrade into theater.
|
|||
none of the thirteen named a right one. The refusal is a turn the model can correct, and it
|
||||
carries both numbers. A path the base does not hold is likewise refused by name, and the refusal
|
||||
now lists up to five subdirectories of the nearest directory that does hold documents — every one
|
||||
of them a path `read_dir` will answer for.
|
||||
of them a path `read_dir` will answer for. When that directory has no subdirectories at all, the
|
||||
refusal names up to five of the DOCUMENTS it holds instead, on the same rule and with the same
|
||||
property: each name resolves. (Measured over one stress round, three of six guessed document
|
||||
paths landed on such a directory and got no suggestion at all — including two separate guesses,
|
||||
in one run, at the name of the single document that level holds.)
|
||||
|
||||
Every tool call recorded in `{run_id}-exploration.json` and `{run_id}-debate.json` says HOW the
|
||||
level was asked for — `filter`, `offset` and `limit` beside the tool name, the base and the path
|
||||
|
|
|
|||
|
|
@ -1087,7 +1087,9 @@ def _refusal_kind(exc: Exception) -> str:
|
|||
return type(exc).__name__
|
||||
|
||||
|
||||
def _neighbours(bundle_dir: str, path: str, dimension: str | None) -> tuple[str, tuple[str, ...]]:
|
||||
def _neighbours(
|
||||
bundle_dir: str, path: str, dimension: str | None
|
||||
) -> tuple[str, tuple[str, ...], tuple[str, ...]]:
|
||||
"""``(nearest listable ancestor, up to five of its subdirectories)`` for a path that is absent.
|
||||
|
||||
ONE navigation for both halves, and BOTH read off ``okf`` rather than reimplemented here: this
|
||||
|
|
@ -1099,6 +1101,7 @@ def _neighbours(bundle_dir: str, path: str, dimension: str | None) -> tuple[str,
|
|||
return (
|
||||
okf.nearest_listable_directory(bundle, path, dimension=dimension),
|
||||
okf.nearest_subdirectories(bundle, path, dimension=dimension),
|
||||
okf.nearest_documents(bundle, path, dimension=dimension),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -1383,14 +1386,22 @@ def navigator_tools(
|
|||
# link -- still propagates untouched, because a refusal is a statement about the CALLER's
|
||||
# path and a failure to read something that IS there is not one.
|
||||
if not resolved.exists():
|
||||
ancestor, neighbours = _neighbours(bundle_dir, path, dimension)
|
||||
ancestor, neighbours, documents = _neighbours(bundle_dir, path, dimension)
|
||||
# P21/C2: the nearest listable ancestor AND up to five of its own subdirectories. The
|
||||
# ancestor alone says which rung to go back to; the neighbours say which names that
|
||||
# rung actually uses — measured, one run spent eleven calls walking ``R761/4-3``,
|
||||
# ``4.3``, ``4-2``, ``4-1``, ``4-0``, ``4-5``, ``4-6`` while the real names were
|
||||
# ``R761/4``, ``R761/41``, ``R761/42``. Omitted when the ancestor has no
|
||||
# subdirectories: an empty list would be a sentence with nothing in it.
|
||||
nearby = f" (its subdirectories include {', '.join(neighbours)})" if neighbours else ""
|
||||
# P22 DEL C: subdirectories when that rung has any, otherwise the DOCUMENTS it
|
||||
# holds - measured, three of round 5's six misses landed on an ancestor with no
|
||||
# subdirectories, and the clause was omitted for all three. Never both, and the
|
||||
# subdirectory branch stays first, which is what keeps every C2 refusal unchanged.
|
||||
nearby = ""
|
||||
if neighbours:
|
||||
nearby = f" (its subdirectories include {', '.join(neighbours)})"
|
||||
elif documents:
|
||||
nearby = f" (it holds the documents {', '.join(documents)})"
|
||||
raise okf.BundlePathNotFound(
|
||||
f"knowledge base {bundle_id!r} has no document {path!r}; nearest directory that "
|
||||
f"holds documents: {ancestor!r}{nearby} — list it "
|
||||
|
|
|
|||
|
|
@ -1317,6 +1317,21 @@ def nearest_listable_directory(bundle: Bundle, path: str, *, dimension: str | No
|
|||
return ""
|
||||
|
||||
|
||||
def _shared_prefix(name: str, missing: str) -> int:
|
||||
"""How many leading characters ``name`` shares with the path segment that failed.
|
||||
|
||||
The ONE ranking rule behind both neighbour helpers (ko-(p)). Two copies of "which of these
|
||||
names did the caller mean" would be free to rank one base's level two ways in two refusals
|
||||
about the same call.
|
||||
"""
|
||||
shared = 0
|
||||
for a, b in zip(name, missing):
|
||||
if a != b:
|
||||
break
|
||||
shared += 1
|
||||
return shared
|
||||
|
||||
|
||||
def nearest_subdirectories(
|
||||
bundle: Bundle,
|
||||
path: str,
|
||||
|
|
@ -1360,18 +1375,56 @@ def nearest_subdirectories(
|
|||
if len(rest) > 1:
|
||||
children.add(rest[0])
|
||||
|
||||
def _shared(name: str) -> int:
|
||||
n = 0
|
||||
for a, b in zip(name, missing):
|
||||
if a != b:
|
||||
break
|
||||
n += 1
|
||||
return n
|
||||
|
||||
ranked = sorted(children, key=lambda n: (-_shared(n), len(n), n))
|
||||
ranked = sorted(children, key=lambda n: (-_shared_prefix(n, missing), len(n), n))
|
||||
return tuple(f"{prefix}{name}" for name in ranked[:limit])
|
||||
|
||||
|
||||
def nearest_documents(
|
||||
bundle: Bundle,
|
||||
path: str,
|
||||
*,
|
||||
dimension: str | None = None,
|
||||
limit: int = _NEIGHBOUR_LIMIT,
|
||||
) -> tuple[str, ...]:
|
||||
"""The concept documents sitting directly in the nearest listable ancestor of an absent path.
|
||||
|
||||
**The measured gap (P22 DEL C).** P21/C2 named the ancestor's SUBDIRECTORIES, and it moved
|
||||
``read_dir`` against a level the base does not hold from 16 of 104 to 8 of 128. It did nothing
|
||||
for documents: ``read_file`` against a document the base does not hold went 2 of 38 to 7 of 52,
|
||||
because the nearest listable ancestor of a guessed DOCUMENT path often holds documents and no
|
||||
subdirectories at all - and then the neighbour clause was omitted, deliberately, since an empty
|
||||
list is a sentence with nothing in it. Measured over round 5's six ``read_file`` misses: three
|
||||
have an ancestor with no subdirectories (``krav/N100`` with 445 documents, and ``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). The other three have subdirectories and
|
||||
are already answered by ``nearest_subdirectories``.
|
||||
|
||||
**Every name it returns RESOLVES**, the same property and by the same construction as its
|
||||
sibling: built from ``context_files`` and through the SAME ``in_dimension`` predicate the
|
||||
listing uses, so a name handed back in a refusal is one ``read_file`` will then serve - and can
|
||||
never be a ``type: verdict`` document, the one layer no listing mentions.
|
||||
|
||||
**Ranked by the same rule**, through the same ``_shared_prefix`` helper rather than a second
|
||||
copy of it (ko-(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 is benign.
|
||||
"""
|
||||
ancestor = nearest_listable_directory(bundle, path, dimension=dimension)
|
||||
prefix = f"{ancestor}/" if ancestor else ""
|
||||
depth = len(prefix.split("/")) - 1 if prefix else 0
|
||||
segments = path.strip("/").split("/")
|
||||
missing = segments[depth] if len(segments) > depth else ""
|
||||
|
||||
here = [
|
||||
file.name
|
||||
for file in bundle.context_files
|
||||
if in_dimension(file, dimension)
|
||||
and file.name.startswith(prefix)
|
||||
and "/" not in file.name[len(prefix) :]
|
||||
]
|
||||
ranked = sorted(here, key=lambda n: (-_shared_prefix(n[len(prefix) :], missing), len(n), n))
|
||||
return tuple(ranked[:limit])
|
||||
|
||||
|
||||
def directory_listing(
|
||||
bundle: Bundle,
|
||||
path: str = "",
|
||||
|
|
@ -1493,7 +1546,17 @@ def directory_listing(
|
|||
# one this same function would then answer for.
|
||||
neighbours = nearest_subdirectories(bundle, path, dimension=dimension)
|
||||
ancestor = nearest_listable_directory(bundle, path, dimension=dimension)
|
||||
nearby = f"; its subdirectories include {', '.join(neighbours)}" if neighbours else ""
|
||||
# P22 DEL C: subdirectories when that rung has any, otherwise the DOCUMENTS it holds. Never
|
||||
# both - the ancestor is one level, and naming its documents when it also has
|
||||
# subdirectories would answer a different question than the one the caller asked. The
|
||||
# branch order is what keeps every C2 refusal byte-identical.
|
||||
nearby = ""
|
||||
if neighbours:
|
||||
nearby = f"; its subdirectories include {', '.join(neighbours)}"
|
||||
else:
|
||||
documents = nearest_documents(bundle, path, dimension=dimension)
|
||||
if documents:
|
||||
nearby = f"; it holds the documents {', '.join(documents)}"
|
||||
raise BundlePathNotFound(
|
||||
f"knowledge base {bundle.dir!r} has no directory {path!r}; it holds no concept "
|
||||
f"document under that path. Nearest directory that does: {ancestor!r}{nearby}"
|
||||
|
|
|
|||
245
tests/test_document_neighbours_loadbearing.py
Normal file
245
tests/test_document_neighbours_loadbearing.py
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
"""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",
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue