feat(p21): a declaration that must have LOOKED, and a refusal that names the neighbours
C1. Round 4 produced 13 declarations over six runs and NOT ONE named a fasit concept. The distinct documents opened before each were 1,1,1,1,1,1,1,2,5,5,6,13,13: seven declared the base's FIRST requirement after opening exactly ONE document. The order offered two rules and asked which discriminates. Replayed against the real listings: "the declared document must have come back from a read_dir filtered on a word from the approach's label" refuses 13 of 13 -- including Soraasen's 12.11, the closest any run came -- because ZERO of the 13 were reached through a filtered listing at all. A gate that refuses every measured case, right and wrong alike, cannot discriminate. "fewer than k distinct documents opened" at k=3 refuses 8 of 13 and keeps the five that navigated. k=3, 4 and 5 refuse the SAME eight -- the distribution has a gap between 2 and 5 -- so the threshold is not on a cliff, and 3 is the lowest of that plateau. DISTINCT paths, not calls, and capped by the base's own size so a small base stays declarable. C2. Over the same traces 18 of 143 path-bearing calls named a path the base does not hold, ELEVEN of them one run walking R761/4-3, 4.3, 4-2, 4-1, 4-0, 4-5, 4-6 while the real names are R761/4, R761/41, R761/42. The refusal already named the nearest listable ancestor; now it also names up to five of that rung's own subdirectories, ranked by longest common prefix with the segment that failed. ONE copy shared by both refusal sites, built from context_files through in_dimension, so every name handed back resolves and the verdict layer can never be advertised in an apology. MEASURED after: 16 of 18. Load-bearing MEASURED, four mutations all red against the WHOLE suite, green control 1863/5, golden byte-unchanged: C3(i) the declaration gate detached (2 red) . C3(ii) the neighbour list empty (6) . C3(iii) built from files (1, the verdict arm alone) . C3(iv) count CALLS instead of distinct documents (1, the repetition arm alone). Three existing arms REWRITTEN, not weakened: all three read one document and declared, which is the measured failure class exactly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
7b4f85d77c
commit
ac0bfdba27
8 changed files with 534 additions and 28 deletions
|
|
@ -1289,6 +1289,89 @@ def _matches_filter(file: BundleFile, needle: str) -> bool:
|
|||
return any(needle in value.casefold() for value in hay)
|
||||
|
||||
|
||||
#: P21/C2 — how many neighbouring directories a "no such path" refusal may name. A refusal's job is
|
||||
#: to hand back the one thing the caller can act on, not to re-list the level: five is enough to
|
||||
#: show the SHAPE of the names this base uses (``4``/``41``/``42`` rather than ``4-3``), and the
|
||||
#: full level is one ``read_dir`` away.
|
||||
_NEIGHBOUR_LIMIT: Final = 5
|
||||
|
||||
|
||||
def nearest_listable_directory(bundle: Bundle, path: str, *, dimension: str | None = None) -> str:
|
||||
"""The deepest ANCESTOR of ``path`` that ``directory_listing`` will actually answer for.
|
||||
|
||||
Chosen off the NAVIGATED ``context_files`` rather than off the filesystem, for two reasons that
|
||||
are the same reason: a directory can exist on disk and hold no navigated concept (nothing links
|
||||
it), in which case ``read_dir`` refuses it and the refusal would have handed the caller a path
|
||||
that does not resolve — ``_index_excerpt``'s rule one rung up, a path that never was is worse
|
||||
than no path. And ``context_files`` is the property that drops the ``type: verdict`` layer, so a
|
||||
refusal can never advertise by name the one layer no listing mentions.
|
||||
|
||||
Falls back to ``""``, the base's own top level, which ``directory_listing`` always answers.
|
||||
"""
|
||||
reachable = [f for f in bundle.context_files if in_dimension(f, dimension)]
|
||||
segments = path.strip("/").split("/")
|
||||
for depth in range(len(segments) - 1, 0, -1):
|
||||
candidate = "/".join(segments[:depth])
|
||||
if any(f.name.startswith(candidate + "/") for f in reachable):
|
||||
return candidate
|
||||
return ""
|
||||
|
||||
|
||||
def nearest_subdirectories(
|
||||
bundle: Bundle,
|
||||
path: str,
|
||||
*,
|
||||
dimension: str | None = None,
|
||||
limit: int = _NEIGHBOUR_LIMIT,
|
||||
) -> tuple[str, ...]:
|
||||
"""The directories a caller who named a path this base does not hold could have meant (P21/C2).
|
||||
|
||||
**The measured defect.** Over round 4's six traces, 16 of 105 ``read_dir`` calls and 2 of 38
|
||||
``read_file`` calls named a path the base does not hold, and eleven of those were one run
|
||||
walking ``R761/4-3``, ``R761/4.3``, ``R761/4-2``, ``R761/4-1``, ``R761/4-0``, ``R761/4-5``,
|
||||
``R761/4-6`` — guessing at a chapter-number spelling the corpus does not use, while the real
|
||||
neighbours are ``R761/4``, ``R761/41``, ``R761/42``. The refusal already named the nearest
|
||||
LISTABLE ancestor, which is the right rung; what it could not say is which of that rung's names
|
||||
the caller was reaching for.
|
||||
|
||||
**Every name it returns RESOLVES.** Built from ``context_files`` and through the SAME
|
||||
``in_dimension`` predicate the listing uses: a suggestion read off the filesystem could name a
|
||||
directory ``read_dir`` then refuses, and one built from ``files`` could name the ``type:
|
||||
verdict`` layer by path — advertising in a refusal the one layer no listing mentions.
|
||||
|
||||
**Ranked by longest common prefix with the segment that failed, then shortest, then name.** A
|
||||
ranking cannot refuse anything — this is help text on a refusal — so its failure direction is
|
||||
benign: at worst it names five real directories that are not the one meant. Prefix ranking is
|
||||
what puts ``4`` ahead of ``41`` for ``4-3``; with no common prefix at all every candidate ties
|
||||
and the order degrades to "the shortest names at this level", which is an honest "here is what
|
||||
IS here".
|
||||
"""
|
||||
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 ""
|
||||
|
||||
children: set[str] = set()
|
||||
for file in bundle.context_files:
|
||||
if not in_dimension(file, dimension) or not file.name.startswith(prefix):
|
||||
continue
|
||||
rest = file.name[len(prefix) :].split("/")
|
||||
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))
|
||||
return tuple(f"{prefix}{name}" for name in ranked[:limit])
|
||||
|
||||
|
||||
def directory_listing(
|
||||
bundle: Bundle,
|
||||
path: str = "",
|
||||
|
|
@ -1404,9 +1487,16 @@ def directory_listing(
|
|||
f"{path!r} in knowledge base {bundle.dir!r} is a document, not a directory; "
|
||||
f"use read_file to read {named.name!r} whole"
|
||||
)
|
||||
# P21/C2: name the rung the caller could have meant. The SAME two helpers the read_file
|
||||
# refusal uses (kø-(p)) — one question about one base must not have two answers — and both
|
||||
# are built from ``context_files`` through ``in_dimension``, so every name handed back is
|
||||
# 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 ""
|
||||
raise BundlePathNotFound(
|
||||
f"knowledge base {bundle.dir!r} has no directory {path!r}; it holds no concept "
|
||||
"document under that path"
|
||||
f"document under that path. Nearest directory that does: {ancestor!r}{nearby}"
|
||||
)
|
||||
# A1: the WINDOW. Clamped, never refused — a caller asking for more than the maximum asked for
|
||||
# a listing, and the bound is this rung's job to keep, not the caller's to remember. A negative
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue