feat(p18): a listing is a WINDOW, and an invented path is refused by name

P18 part A (order 20260914T105139Z). P16 measured S7a-3's ladder against a
delivered corpus for the first time and found two things fixture bases
cannot show.

(1) One level is not bounded by being one level. Measured 14.09 on the four
mounted vegnormal bases: okf.directory_listing on krav/N200 is 169 974 chars
over 1 132 documents, krav/N100 69 250 over 445, krav/N500 39 853 over 269,
and R761's own root 110 912 over 2 728 SUBDIRECTORIES -- 27-113x the
1 500-char ceiling S7a-3 set, riding in every later prompt. That last number
is why the window covers BOTH kinds: a pagination over documents only would
have left the largest measured level unpaginated.

read_dir now answers with a window. offset/limit page directories first then
documents as ONE sequence (two independent windows make "the next ten" a
question with two answers); total is the denominator and is always carried;
limit is CLAMPED to 50, never refused. Default 10 chosen against the ceiling:
one entry is 121-209 chars (median 145) over the four bases. After: n100
1 493, n500 1 453, R761 479, n200 1 537 -- 2.5 % over, stated rather than
tuned away, because the ceiling is a character budget and the window is a
count. Largest single call any caller can make: ~7 600 chars.

filter narrows a level instead of paging it: case-insensitive SUBSTRING over
title + req_number/prosessnr and over a directory path, answering with
total_matches beside total. A substring and not a pattern for
_ground_against_input's reason one rung down -- a form the rule does not know
returns nothing, and an empty listing reads as "the base does not have this".
A filter that matches nothing is an ANSWER (total_matches: 0), never a
refusal. ORDER PREMISE FELLED before building on it: the order asks for a
separate top-level reader "like own_frontmatter" because parse_frontmatter
was last-write-wins -- P15 (f13dc64) already made a top-level key win, so
BundleFile.frontmatter IS the concept's own value and a second reader here
would be the second copy ko-(p) forbids.

(2) 0 of 26 fasit concepts were opened in 32 read_file calls (the order's
"24" is the four runs' DISTINCT paths, re-measured 14.09), and 10 of those
calls named a path the base does not hold. Each reached the model as MAF's
opaque "Error: Function failed." while counting toward the three consecutive
tool errors that end a request. read_file now refuses such a path by name
(BundlePathNotFound, funn-99 returned form) and names the nearest directory
that actually HOLDS documents -- chosen off context_files, never the
filesystem, because a directory can exist on disk and hold no navigated
concept (read_dir would then refuse the very path the refusal handed back)
and because context_files is what drops the type: verdict layer, so a refusal
can never advertise by name the one layer no listing mentions. Narrow by
construction: only an ABSENT path is translated; any other OSError propagates
untouched.

Two existing arms REWRITTEN, neither weakened:
- test_a_nonexistent_sibling_is_still_an_os_error was a tripwire whose own
  docstring said "when it goes red, someone has closed it, and that is a
  decision to be recorded". This is the record. Its narrowness half survives
  as a new arm driving a real PermissionError on a file that IS there.
- test_every_document_is_still_reachable_and_the_counts_add_up became
  STRONGER: the accounting must now page, so the same assertion also proves
  the window is complete and non-overlapping.

tests/test_navigation_window_loadbearing.py: 13 arms. Arms needing the
delivered bases SKIP with the root named (PORTFOLIO_VEGNORMAL_ROOT), as
MAJOR-3's ceiling arm does; the window algebra, the filter negative and the
refusal run over a synthetic base UNCONDITIONALLY, so the file can never be
silently absent in full.

Verification: uv run pytest -q 1672 passed / 5 skipped before the new file
(1670 on cfd9079). ruff check + format clean, mypy clean (38 files). Golden
demo-transcript.stdout BYTE-UNCHANGED, shasum -a 1 of the CONTENT =
ea8c534773acdbe41ae68f2c55724d69aaf8be4f. No version bump, no push.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-14 22:10:43 +02:00
commit 9b47e5aa62
7 changed files with 634 additions and 42 deletions

View file

@ -197,8 +197,11 @@ _INSTRUCTIONS: Final = {
"what exists, read_bundle to open ONE of them and see its top level, read_dir to open a "
"directory that listing named, and read_file to read a document you picked. read_bundle "
"and read_dir return LISTINGS, never the documents — a knowledge base can hold hundreds, "
"so descend to the part that matters instead of asking for all of it. Quote only what "
"read_file gave you, and never guess at content you have not read."
"so descend to the part that matters instead of asking for all of it. A listing is a "
"WINDOW: it reports 'total' for the level and gives you 'limit' of them from 'offset', so "
"when 'total' is large do not page through it — pass read_dir a 'filter' word and read the "
"'total_matches' it reports. Quote only what read_file gave you, and never guess at "
"content you have not read: a path you invent is refused, it does not find a neighbour."
),
HYPOTHESISER_ROLE: (
"You shape ONE candidate cost-saving direction at a time from what the navigator found. "
@ -954,6 +957,28 @@ def _refusal_kind(exc: Exception) -> str:
return type(exc).__name__
def _nearest_listable(bundle_dir: str, path: str, dimension: str | None) -> str:
"""The deepest ANCESTOR of ``path`` that ``read_dir`` 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.
"""
bundle = okf.navigate_bundle(bundle_dir)
reachable = [f for f in bundle.context_files if okf.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 _refused_mapping(exc: Exception) -> dict[str, Any]:
"""A listing tool's refusal: a mapping with no key a successful listing has.
@ -1098,15 +1123,34 @@ def navigator_tools(
"Open ONE directory inside a knowledge base, by base id and the path a previous "
"listing gave you. Answers in the same shape as read_bundle: the directories one level "
"further down, and the concept documents that sit in this one. An unknown path is "
"refused rather than answered as an empty directory."
"refused rather than answered as an empty directory. The answer is a WINDOW: 'total' "
"is how many entries the level holds, 'offset'/'limit' say which of them you were "
"given (limit is capped, so ask for the next page instead of a bigger one). Use "
"'filter' to ask for the entries whose title, requirement number or path contains a "
"word -- e.g. read_dir(bundle_id, 'krav/N100', filter='rundkjoring') answers with the "
"6 of 445 documents about roundabouts and reports total_matches: 6. A filter that "
"matches nothing is an answer (total_matches: 0), not a refusal."
),
)
def read_dir(bundle_id: str, path: str) -> dict[str, Any]:
def read_dir(
bundle_id: str,
path: str,
filter: str | None = None,
offset: int = 0,
limit: int | None = None,
) -> dict[str, Any]:
try:
bundle_dir = _resolve_bundle(index, bundle_id)
bundle = okf.navigate_bundle(bundle_dir)
okf.assert_declared_ids_agree(bundle)
return okf.directory_listing(bundle, path, dimension=dimension)
return okf.directory_listing(
bundle,
path,
dimension=dimension,
filter=filter,
offset=offset,
limit=limit,
)
except _RETURNABLE_REFUSALS as exc:
return _refused_mapping(exc)
@ -1135,6 +1179,23 @@ def navigator_tools(
f"{path!r} in knowledge base {bundle_id!r} is a directory, not a document; "
"use read_dir to list what it holds, then read_file on one of the names it gives"
)
# P18/A3: a path that does not exist, answered as such. MEASURED over P16's four paid runs:
# 10 of 24 ``read_file`` calls named a path the base does not hold (8 distinct -- one is a
# single-character UUID slip, ``4d7f`` for the real ``4e7f``), and each one left the model
# with MAF's opaque "Error: Function failed." while counting toward the three consecutive
# errors that end a request. The nearest EXISTING directory is named because that is the
# one thing the caller can act on: it is the argument for the rung that lists real names.
#
# Narrow BY CONSTRUCTION, and that is the half this replaces rather than weakens: only a
# path that is absent is translated. Any other ``OSError`` -- an unreadable file, a broken
# 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():
raise okf.BundlePathNotFound(
f"knowledge base {bundle_id!r} has no document {path!r}; nearest directory that "
f"holds documents: {_nearest_listable(bundle_dir, path, dimension)!r} — list it "
"with read_dir (it takes a filter) and read_file one of the names it gives"
)
# The verdict layer, refused HOWEVER the path was found (order 20260904T172353Z). No
# listing names it — ``context_files`` drops it at every level, so ``read_bundle`` and
# ``read_dir`` never mention one — but a GUESSED path reached it, and reaching it that way