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
|
|
@ -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}"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue