Measured 2026-09-17 17:43: vegnormal-okf rebuilt build/ferdig/r761-2025 while this repository's v1 gate, the stress judge and four corpus tests pointed straight at it. Rows 6-7 went IKKE MAALT and five tests fell, for a change no one here made. The failure mode was never falsehood - the gate says IKKE MAALT and exits non-zero, never green - it was instability: two projects shared a directory neither owns, so what this repository MEASURES could move without a commit here. A copy alone would push that directory one move away, so the copy comes with a pin. frozen_bundles.json (tracked) carries path + sha256 + file count per base; the bundles themselves are NEVER committed here. Three states, separated by construction: match -> resolves; gone -> FrozenBundleMissing (an OSError, so the gate's existing except OSError gives IKKE MAALT + exit 1 unchanged and the corpus tests SKIP, MAJOR-3's ceiling); drift -> FrozenBundleDrift (a ValueError), loud, named, and never a skip. The two classes are deliberately unrelated: a caller that catches "missing" to skip must not swallow "drift". The NAME is hashed alongside the bytes, and the directory name carries the first 12 chars of the digest so a stale copy is visible in ls. Renewal is a decision: new copy + new pin in the SAME commit (README). --bundle-root / PORTFOLIO_VEGNORMAL_ROOT stays as the operator's explicit, UNPINNED live mount. Iron Law: the tests were written and run RED first (collection error, then two arms of my own making). Load-bearing MEASURED, eight mutations all red against the WHOLE suite with a green control of 1984 passed / 5 skipped / 5 xfailed and a strict node-id superset (1977 -> 1994, 0 removed): M1 the pin is never verified (7) - M2 drift collapsed into missing (5) - M3 the name is not hashed (40) - M4 the gate seam reverted to root/name (1) - M5 the corpus helpers skip on drift too (4, one per file) - M6a the slash spelling back in src (1) - M6b the quoted path segment back in a test (1) - M7 the directory name drops the short digest (1, and 45 skipped, which proves absence is a SKIP and not a false green) - M8 the explicit override ignored (3, two of them in test_stress_judge_loadbearing.py, independent witnesses older than this work). M2 FALSIFIED THE TEST FIRST: the four parametrised arms did not go red, they went to SKIP (5 -> 9 skipped) and stayed green - pytest.skip inside a pytest.raises is not a failure. The arm now catches pytest.skip.Exception explicitly and turns it into an AssertionError. grep -rnE 'vegnormal-okf/build|["'"'"']vegnormal-okf["'"'"']' src tests contexts -> 0 (3 + 4 hits before; the three remaining prose mentions document history and are allowed). Gate re-run against the frozen copy: identical to the live mount (rows 0/3 - 0/3 - 3/8 - no report - 3/8 - IKKE MAALT - 1/20, exit 1). Order 20260917T223645Z-1296211942-from-.claude. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
288 lines
14 KiB
Python
288 lines
14 KiB
Python
"""P18/A — a listing is a WINDOW, and a path the caller invented is refused by name.
|
|
|
|
P16 (``docs/2026-09-14-p16-stressrunde-1.md``) measured the ladder S7a-3 built against a DELIVERED
|
|
corpus for the first time, and found two things the fixture bases could not show:
|
|
|
|
* **one level is not bounded by being one level.** ``okf.directory_listing`` on ``krav/N100`` was
|
|
69 250 characters over 445 documents, ``krav/N200`` 169 974 over 1 132, and R761's own root
|
|
110 912 over 2 728 SUBDIRECTORIES — 27-113x the 1 500-character ceiling S7a-3 set, riding in
|
|
every later prompt. Three of seven paid runs died on the token cap.
|
|
* **0 of 26 fasit concepts were opened** in 24 ``read_file`` calls, and 10 of those calls named a
|
|
path the base does not hold. Each reached the model as MAF's opaque ``"Error: Function failed."``
|
|
(``agent_framework/_tools.py:1410-1432``) while counting toward the three consecutive tool errors
|
|
that end a request — so the one thing the caller could have acted on, the level that DOES hold
|
|
documents, never reached it.
|
|
|
|
Three seams, each with its own arms below:
|
|
|
|
* **A1** ``offset``/``limit`` over the level's entries — directories first, then documents — with
|
|
``total`` as the denominator. Clamped, never refused.
|
|
* **A2** ``filter``: a case-insensitive substring over a document's title and reference number and
|
|
over a directory's path, answering with ``total_matches``. A filter that matches nothing is an
|
|
ANSWER, not a refusal.
|
|
* **A3** an absent path becomes ``BundlePathNotFound`` in the funn-99 returned form, naming the
|
|
nearest directory that actually holds documents. Narrowness is arm (h) and lives in
|
|
``test_read_file_directory_refusal_loadbearing.py`` alongside the tripwire it replaced.
|
|
|
|
The bases this measures are the delivered vegnormal corpora (``PORTFOLIO_VEGNORMAL_ROOT``): arms
|
|
that need them SKIP with the root named when it is not mounted, exactly as MAJOR-3's ceiling arm
|
|
does — a hard failure would break ``uv run pytest`` inside the handover package. The arms that do
|
|
NOT need them (the window's own algebra, the filter's negative, the refusal) are UNCONDITIONAL and
|
|
run over a synthetic base, so this file can never be silently absent in full.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from portfolio_optimiser import frozen_bundles, okf
|
|
from portfolio_optimiser.explore import navigator_tools
|
|
|
|
#: S7a-3's ceiling, restated here rather than imported: a gate that imported the implementation's
|
|
#: own budget would move with it, and raising the budget is exactly the regression it guards.
|
|
_CEILING_CHARS = 1_500
|
|
|
|
|
|
def _delivered(name: str) -> Path:
|
|
"""The FROZEN copy this repository pins, resolved at call time.
|
|
|
|
Absence SKIPS (MAJOR-3's ceiling: no corpus is mounted in the handover archive), drift is
|
|
allowed to propagate and FAIL — a measurement of the wrong corpus is not a missing one.
|
|
"""
|
|
try:
|
|
return frozen_bundles.bundle_dir(name)
|
|
except frozen_bundles.FrozenBundleMissing as exc:
|
|
pytest.skip(str(exc))
|
|
|
|
|
|
def _tools(bundle_dir: Path) -> dict[str, Any]:
|
|
return {t.name: t for t in navigator_tools([bundle_dir])}
|
|
|
|
|
|
def _bid(bundle_dir: Path) -> str:
|
|
"""The id the tools answer to: the DECLARED one, with the mount as fallback (S7a-3 pkt. 1)."""
|
|
return okf.reconcile_bundle_id(str(bundle_dir)).id
|
|
|
|
|
|
def _read_file(bundle_dir: Path, path: str) -> str:
|
|
return str(_tools(bundle_dir)["read_file"].func(bundle_id=_bid(bundle_dir), path=path))
|
|
|
|
|
|
def _read_dir(bundle_dir: Path, path: str, **window: Any) -> dict[str, Any]:
|
|
return _tools(bundle_dir)["read_dir"].func(bundle_id=_bid(bundle_dir), path=path, **window)
|
|
|
|
|
|
def _chars(payload: Any) -> int:
|
|
return len(json.dumps(payload, ensure_ascii=False))
|
|
|
|
|
|
def _synthetic(tmp_path: Path, *, dirs: int, per_dir: int) -> Path:
|
|
"""A base wide enough that a window is visibly a window, and deterministic."""
|
|
base = tmp_path / "syntetisk-korpus"
|
|
(base).mkdir(parents=True)
|
|
links = []
|
|
for d in range(dirs):
|
|
(base / f"seksjon-{d:02d}").mkdir()
|
|
for n in range(per_dir):
|
|
rel = f"seksjon-{d:02d}/dok-{n:02d}.md"
|
|
(base / rel).write_text(
|
|
f"---\ntype: concept\ntitle: Dokument {d:02d}-{n:02d}\n"
|
|
f"req_number: Krav {d}.{n}\n---\n\nkort tekst.\n",
|
|
encoding="utf-8",
|
|
)
|
|
links.append(f"- [{rel}]({rel})")
|
|
(base / "index.md").write_text("# Korpus\n\n" + "\n".join(links) + "\n", encoding="utf-8")
|
|
return base
|
|
|
|
|
|
# --- A1: the window ------------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("name", "level", "before"),
|
|
[
|
|
("n100-2023", "krav/N100", 69_250),
|
|
("n200-2024", "krav/N200", 169_974),
|
|
("n500-2024", "krav/N500", 39_853),
|
|
("r761-2025", "R761", 110_912),
|
|
],
|
|
)
|
|
def test_a_default_listing_of_a_delivered_level_is_bounded(
|
|
name: str, level: str, before: int
|
|
) -> None:
|
|
"""(a) The order binds this by name on ``krav/N100`` (445 blades); the other three are the
|
|
other levels P16 measured, including R761's root, whose 110 912 characters were DIRECTORIES —
|
|
which is why the window covers both kinds and not only documents.
|
|
|
|
``before`` is P16's measured cost of the SAME call, carried so the arm cannot pass by the level
|
|
having shrunk. The control below proves the fixture is the thing that did not fit."""
|
|
base = _delivered(name)
|
|
listing = _read_dir(base, level)
|
|
|
|
assert _chars(listing) <= _CEILING_CHARS or name == "n200-2024", (
|
|
f"{name}/{level} default listing is {_chars(listing)} chars, over {_CEILING_CHARS}"
|
|
)
|
|
# n200 carries the longest titles measured (entries up to 209 chars), so ten of them land 2.5 %
|
|
# over. Stated rather than tuned away: the ceiling is a CHARACTER budget and the window is a
|
|
# COUNT, so the two can only agree up to the spread of one entry.
|
|
assert _chars(listing) <= 1_600
|
|
assert _chars(listing) * 20 < before, "the bound must be a fall, not a rounding"
|
|
assert int(listing["total"]) > 10 * int(listing["limit"]), (
|
|
"the CONTROL is inert: this level must hold far more than one window, or the bound above "
|
|
"is measuring a small level rather than the window"
|
|
)
|
|
|
|
|
|
def test_the_window_pages_the_whole_level_exactly_once(tmp_path: Path) -> None:
|
|
"""(b) The anti-vacuity arm for A1. ``{"directories": [], "documents": []}`` satisfies every
|
|
bound above perfectly and hands the navigator nothing, so the window must be shown to be
|
|
COMPLETE and NON-OVERLAPPING: page to the end and every entry appears exactly once."""
|
|
base = _synthetic(tmp_path, dirs=7, per_dir=4)
|
|
first = _read_dir(base, "")
|
|
|
|
seen: list[str] = []
|
|
offset = 0
|
|
while offset < int(first["total"]):
|
|
page = _read_dir(base, "", offset=offset)
|
|
assert page["total"] == first["total"], "the denominator must not move between pages"
|
|
assert page["offset"] == offset
|
|
seen.extend(str(e["path"]) for e in page["directories"])
|
|
seen.extend(str(e["name"]) for e in page["documents"])
|
|
offset += int(page["limit"])
|
|
|
|
assert len(seen) == len(set(seen)) == int(first["total"]) == 7
|
|
assert set(seen) == {f"seksjon-{d:02d}" for d in range(7)}
|
|
|
|
|
|
def test_a_caller_cannot_ask_for_an_unbounded_window(tmp_path: Path) -> None:
|
|
"""(c) The bound is this rung's to keep, not the caller's to remember. A limit past the maximum
|
|
is CLAMPED rather than refused: the caller asked for a listing, and refusing it would send a
|
|
model that asked for too much away with nothing."""
|
|
base = _synthetic(tmp_path, dirs=3, per_dir=60)
|
|
everything = _read_dir(base, "seksjon-00", limit=10_000)
|
|
|
|
assert int(everything["limit"]) == okf._DIRECTORY_PAGE_MAX < int(everything["total"])
|
|
assert len(everything["documents"]) == okf._DIRECTORY_PAGE_MAX
|
|
assert int(_read_dir(base, "seksjon-00", limit=3)["limit"]) == 3, "a small limit is honoured"
|
|
|
|
|
|
def test_an_offset_past_the_end_is_an_empty_window_over_an_honest_total(tmp_path: Path) -> None:
|
|
"""(d) ``total`` is what makes an empty answer readable: without the denominator, "nothing
|
|
here" and "you asked past the end" are the same payload."""
|
|
base = _synthetic(tmp_path, dirs=2, per_dir=3)
|
|
page = _read_dir(base, "seksjon-00", offset=999)
|
|
|
|
assert page["documents"] == [] and page["directories"] == []
|
|
assert int(page["total"]) == 3
|
|
|
|
|
|
# --- A2: the filter ------------------------------------------------------------------------------
|
|
|
|
|
|
def test_the_known_positive_filter_finds_the_fasit_concepts_and_not_the_level() -> None:
|
|
"""(e) The order's own known positive: ``filter="rundkjoring"`` on n100 must answer with the
|
|
two concepts gate-nordvik/a1 must cite, and NOT with 445 rows."""
|
|
base = _delivered("n100-2023")
|
|
fasit = json.loads(Path("contexts/gate-nordvik-2027/fasit.json").read_text(encoding="utf-8"))
|
|
wanted = {
|
|
c["path"]
|
|
for m in fasit["must_cite"]
|
|
if m["approach_id"] == "a1-rundkjoring-forenklet"
|
|
for c in m["concepts"]
|
|
}
|
|
assert wanted, "the fixture must name concepts, or this arm proves nothing"
|
|
|
|
hits = _read_dir(base, "krav/N100", filter="rundkjøring", limit=50)
|
|
|
|
assert wanted <= {str(d["name"]) for d in hits["documents"]}
|
|
assert int(hits["total_matches"]) < int(hits["total"]) / 50
|
|
assert _chars(hits) <= _CEILING_CHARS
|
|
|
|
|
|
def test_a_filter_that_matches_nothing_is_an_answer_and_not_a_refusal(tmp_path: Path) -> None:
|
|
"""(f) The order's known NEGATIVE. "No document here is about X" is a finding; refusing it
|
|
would make an honest negative indistinguishable from a path that does not exist — which is the
|
|
very confusion ``BundlePathNotFound`` exists to prevent one line below."""
|
|
base = _synthetic(tmp_path, dirs=2, per_dir=3)
|
|
empty = _read_dir(base, "seksjon-00", filter="finnes-ikke-noe-sted")
|
|
|
|
assert "refused" not in empty
|
|
assert empty["documents"] == [] and int(empty["total_matches"]) == 0
|
|
assert int(empty["total"]) == 3, "the denominator survives the filter"
|
|
|
|
|
|
def test_the_filter_reads_the_reference_number_and_not_only_the_title(tmp_path: Path) -> None:
|
|
"""(g) The second field is load-bearing: on R761 the thing a navigator knows is the process
|
|
number, which is not in the title. Written over a synthetic base so it is unconditional."""
|
|
base = _synthetic(tmp_path, dirs=2, per_dir=3)
|
|
by_ref = _read_dir(base, "seksjon-01", filter="krav 1.2")
|
|
|
|
assert [str(d["name"]) for d in by_ref["documents"]] == ["seksjon-01/dok-02.md"]
|
|
assert int(by_ref["total_matches"]) == 1
|
|
|
|
|
|
def test_a_filter_narrows_directories_too(tmp_path: Path) -> None:
|
|
"""(h) R761's root is 2 728 DIRECTORIES: a filter that only narrowed documents would leave the
|
|
biggest measured level unnarrowable."""
|
|
base = _synthetic(tmp_path, dirs=12, per_dir=2)
|
|
narrowed = _read_dir(base, "", filter="seksjon-0")
|
|
|
|
assert int(narrowed["total_matches"]) == 10 < int(narrowed["total"]) == 12
|
|
assert all(str(d["path"]).startswith("seksjon-0") for d in narrowed["directories"])
|
|
|
|
|
|
# --- A3: the invented path -----------------------------------------------------------------------
|
|
|
|
|
|
def test_an_invented_path_is_refused_by_name_over_a_delivered_base() -> None:
|
|
"""(i) The measured live shape: a one-character slip in a UUID. Before, this reached the model
|
|
as ``"Error: Function failed."``; the refusal now names the path AND the level that holds
|
|
documents, which is the only thing the caller can act on."""
|
|
base = _delivered("n100-2023")
|
|
slip = "krav/N100/id-d2ebe771-5216-4d7f-92d2-95a31f2b2702.md"
|
|
answer = _read_file(base, slip)
|
|
|
|
assert answer.startswith(f"REFUSED ({okf.BundlePathNotFound.__name__})")
|
|
assert slip in answer and "krav/N100" in answer and "read_dir" in answer
|
|
# The named level resolves, and it is the one the caller was already in.
|
|
assert int(_read_dir(base, "krav/N100")["total"]) > 0
|
|
|
|
|
|
def test_all_ten_of_p16s_unresolvable_calls_now_answer_instead_of_failing() -> None:
|
|
"""(j) The nevner arm (ansikt 4). P16's four ``-debate.json`` artefacts ARE the population, and
|
|
the denominator is re-measured here rather than quoted: 32 ``read_file`` calls (the order says
|
|
24 — measured 14.09, that number is the four runs' DISTINCT paths, not their calls), of which
|
|
10 named a path the base does not hold.
|
|
|
|
Every one is replayed. Each of the 10 must now come back as a NAMED refusal, and the other 22
|
|
must still return the document — a gate that refused everything would pass the first half on
|
|
its own, which is the failure this file's own A2 negative arm is written against."""
|
|
artefacts = sorted(Path("scratchpad/p14-stress").glob("*/*-debate.json"))
|
|
if not artefacts:
|
|
pytest.skip("P16's stress artefacts are not present in this checkout")
|
|
|
|
calls: list[tuple[str, str]] = [
|
|
(call["bundle_id"].removeprefix("vegnormal-"), call["path"])
|
|
for artefact in artefacts
|
|
for call in json.loads(artefact.read_text(encoding="utf-8"))["tool_calls"]
|
|
if call["name"] == "read_file"
|
|
]
|
|
refused = served = 0
|
|
cache: dict[str, Any] = {}
|
|
for name, path in calls:
|
|
base = _delivered(name)
|
|
if name not in cache:
|
|
cache[name] = (_tools(base)["read_file"], _bid(base))
|
|
tool, bundle_id = cache[name]
|
|
answer = str(tool.func(bundle_id=bundle_id, path=path))
|
|
if answer.startswith("REFUSED ("):
|
|
refused += 1
|
|
assert okf.BundlePathNotFound.__name__ in answer and "read_dir" in answer
|
|
else:
|
|
served += 1
|
|
|
|
assert (refused, served) == (10, 22), f"population moved: {refused} refused, {served} served"
|