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 "