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:
Kjell Tore Guttormsen 2026-09-16 01:17:39 +02:00
commit f8709ec228
5 changed files with 373 additions and 14 deletions

View file

@ -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 "

View file

@ -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}"