Stress round 6 validated three falsification arms, and every validated approach rested only on run-level declarations nobody can attribute to one approach. declare_requirement now takes a required approach_id (a mandate id or own-proposal; an unknown id is refused naming the valid ones), and a ValidatedProposal whose approach has neither a mandate requirement nor a declaration under its own id becomes validator.Unsupported - a Rejection subclass carrying the validator's own ruling, reported as `unsupported` in coverage, the outcome artefact, the settlement and the judge, and never counted or summed. The rule is active whenever the debate held the declaration tool, the micro base included; the road and pre-pass paths are untouched. Declaration quality is not judged, so the rule can be satisfied by declaring any document the run read. The v1 gate's row 6 probes pass; its artefact half reads IKKE MÅLT because stress round 6 predates approach-addressed declarations, and IKKE MÅLT is never green - it fails the exit code. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
284 lines
13 KiB
Python
284 lines
13 KiB
Python
"""P21 DEL C — a declaration that must have LOOKED, and a refusal that names the neighbours.
|
|
|
|
**C1, and the order asked for a measurement rather than a preference.** P19 DEL A made a direction
|
|
name the requirement that binds it, and the rung works — round 4 produced 13 declarations over six
|
|
runs. Not one named a fasit concept, and 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 been returned by a ``read_dir`` filtered on a word from the
|
|
approach's label"** refuses **13 of 13** — including Søråsen's ``12.11``, which the order names
|
|
as the closest any run came. Measured, ZERO of the 13 declarations were reached through a
|
|
filtered listing at all. A gate that refuses every measured case, right and wrong alike, cannot
|
|
discriminate; it is the vacuous gate's mirror image.
|
|
* **"fewer than k distinct documents opened"** at k=3 refuses **8 of 13** and keeps the five that
|
|
navigated, ``12.11`` among them. 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.
|
|
|
|
The second is built. It is capped by the base's own document count, so a two-document base stays
|
|
declarable rather than becoming a level nobody can declare on.
|
|
|
|
**C2.** Over the same six traces, 18 of 143 path-bearing tool calls named a path the base does not
|
|
hold, and ELEVEN of them are one run walking ``R761/4-3``, ``4.3``, ``4-2``, ``4-1``, ``4-0``,
|
|
``4-5``, ``4-6`` — guessing a chapter-number spelling the corpus does not use, while the real names
|
|
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 was meant. Measured
|
|
after: **16 of 18** refusals now name at least one neighbour, and the two that do not are paths
|
|
whose ancestor holds documents and no subdirectories — an empty list is omitted rather than
|
|
rendered as a sentence with nothing in it.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from portfolio_optimiser import okf
|
|
from portfolio_optimiser.explore import ToolCall, navigator_tools
|
|
|
|
_EXAMPLES = Path(__file__).resolve().parents[1] / "shared" / "examples"
|
|
_TUNNEL = _EXAMPLES / "tunnel-hauglia"
|
|
|
|
|
|
def _wired(bundle_dir: Path = _TUNNEL) -> tuple[dict[str, Any], list[ToolCall], list[Any]]:
|
|
opened: list[ToolCall] = []
|
|
declared: list[Any] = []
|
|
tools = navigator_tools((str(bundle_dir),), opened=opened, requirements=declared)
|
|
return {t.name: t for t in tools}, opened, declared
|
|
|
|
|
|
def _concepts(bundle_dir: Path = _TUNNEL) -> list[str]:
|
|
return [f.name for f in okf.navigate_bundle(str(bundle_dir)).context_files]
|
|
|
|
|
|
# --- C1 -------------------------------------------------------------------------------------
|
|
|
|
|
|
def test_c1_a_declaration_after_one_document_is_refused_with_the_denominator() -> None:
|
|
"""The KNOWN-POSITIVE: exactly the measured failure — open one, declare it.
|
|
|
|
Both numbers are in the refusal, because the pair is the diagnosis: "1 of 5" says something
|
|
"1" alone does not, and a refusal that stated neither would leave the model guessing how much
|
|
more is wanted (``BudgetExceeded``'s kø-(y) rule, one rung down).
|
|
"""
|
|
tools, opened, declared = _wired()
|
|
path = _concepts()[0]
|
|
opened.append(ToolCall(name="read_file", bundle_id="tunnel-hauglia", path=path))
|
|
|
|
answer = tools["declare_requirement"].func(
|
|
approach_id="a1", bundle_id="tunnel-hauglia", path=path, ref="Krav 1.1—1"
|
|
)
|
|
|
|
assert answer["refusal"] == "RequirementNotRead"
|
|
assert "1 distinct document(s)" in answer["refused"]
|
|
assert "of the 5" in answer["refused"], answer["refused"]
|
|
assert "at least 3" in answer["refused"]
|
|
assert declared == [], "a refused declaration must leave no record"
|
|
|
|
|
|
def test_c1_the_same_declaration_after_three_documents_is_accepted() -> None:
|
|
"""The KNOWN-NEGATIVE, and the half that keeps C1 from being a gate that only refuses.
|
|
|
|
Same base, same declared document, same ``ref`` — the ONLY difference is how much of the base
|
|
the run had opened. Without this arm the refusal above would be indistinguishable from one that
|
|
fires on everything, which is exactly what the rule the measurement rejected does.
|
|
"""
|
|
tools, opened, declared = _wired()
|
|
files = _concepts()
|
|
for name in files[:3]:
|
|
opened.append(ToolCall(name="read_file", bundle_id="tunnel-hauglia", path=name))
|
|
|
|
answer = tools["declare_requirement"].func(
|
|
approach_id="a1", bundle_id="tunnel-hauglia", path=files[0], ref="Krav 1.1—1"
|
|
)
|
|
|
|
assert answer["declared"] is True
|
|
assert len(declared) == 1
|
|
|
|
|
|
def test_c1_the_same_path_read_three_times_is_still_one_document() -> None:
|
|
"""DISTINCT paths, not calls. Re-reading one document is not navigation, and a rule counting
|
|
calls would be satisfied by the cheapest possible loop."""
|
|
tools, opened, declared = _wired()
|
|
path = _concepts()[0]
|
|
for _ in range(3):
|
|
opened.append(ToolCall(name="read_file", bundle_id="tunnel-hauglia", path=path))
|
|
|
|
answer = tools["declare_requirement"].func(
|
|
approach_id="a1", bundle_id="tunnel-hauglia", path=path, ref="Krav 1.1—1"
|
|
)
|
|
|
|
assert answer["refusal"] == "RequirementNotRead"
|
|
assert "1 distinct document(s)" in answer["refused"]
|
|
assert declared == []
|
|
|
|
|
|
def test_c1_a_small_base_stays_declarable(tmp_path: Path) -> None:
|
|
"""Capped by the base's OWN size: two documents can be read whole in two.
|
|
|
|
A fixed floor above a base's size would make declaration impossible there — a gate that can
|
|
only refuse, on exactly the small fixtures this repo is built on.
|
|
"""
|
|
base = tmp_path / "mini"
|
|
base.mkdir()
|
|
(base / "index.md").write_text(
|
|
"---\nbundle_id: mini\n---\n\n- [A](a.md) — one.\n- [B](b.md) — two.\n", encoding="utf-8"
|
|
)
|
|
(base / "a.md").write_text("---\ntype: Krav\ntitle: A\n---\n\nbody A\n", encoding="utf-8")
|
|
(base / "b.md").write_text("---\ntype: Krav\ntitle: B\n---\n\nbody B\n", encoding="utf-8")
|
|
|
|
tools, opened, declared = _wired(base)
|
|
for name in ("a.md", "b.md"):
|
|
opened.append(ToolCall(name="read_file", bundle_id="mini", path=name))
|
|
|
|
answer = tools["declare_requirement"].func(
|
|
approach_id="a1", bundle_id="mini", path="a.md", ref="1"
|
|
)
|
|
|
|
assert answer["declared"] is True
|
|
assert len(declared) == 1
|
|
|
|
|
|
def test_c1_the_never_opened_refusal_still_fires_first() -> None:
|
|
"""The P19 gate is UNCHANGED and still the first thing checked: "you never read this" and "you
|
|
have barely read anything" are different facts, and the first is the one the caller can fix
|
|
with one call."""
|
|
tools, opened, declared = _wired()
|
|
files = _concepts()
|
|
for name in files[:3]:
|
|
opened.append(ToolCall(name="read_file", bundle_id="tunnel-hauglia", path=name))
|
|
|
|
answer = tools["declare_requirement"].func(
|
|
approach_id="a1", bundle_id="tunnel-hauglia", path=files[4], ref="Krav 1.1—1"
|
|
)
|
|
|
|
assert answer["refusal"] == "RequirementNotRead"
|
|
assert "is not one of them" in answer["refused"]
|
|
assert declared == []
|
|
|
|
|
|
# --- C2 -------------------------------------------------------------------------------------
|
|
|
|
|
|
def _r761_shaped(root: Path) -> Path:
|
|
"""A base shaped like the delivered R761: numbered chapter directories under one top level.
|
|
|
|
Crafted rather than mounted, for MAJOR-3's reason — R761 lives outside this repository and
|
|
cannot be a test dependency — and shaped from the MEASURED names (``R761/4``, ``R761/41``,
|
|
``R761/42``) so the arm tests the ranking the real corpus provoked.
|
|
"""
|
|
base = root / "proc"
|
|
(base / "R761" / "4").mkdir(parents=True)
|
|
(base / "R761" / "41").mkdir()
|
|
(base / "R761" / "42").mkdir()
|
|
links = "".join(f"- [{d}](R761/{d}/k.md) — chapter {d}.\n" for d in ("4", "41", "42"))
|
|
(base / "index.md").write_text(f"---\nbundle_id: proc\n---\n\n{links}", encoding="utf-8")
|
|
for d in ("4", "41", "42"):
|
|
(base / "R761" / d / "k.md").write_text(
|
|
f"---\ntype: Krav\ntitle: Chapter {d}\nprosessnr: '{d}'\n---\n\nbody {d}\n",
|
|
encoding="utf-8",
|
|
)
|
|
return base
|
|
|
|
|
|
def test_c2_a_guessed_chapter_spelling_is_answered_with_the_real_names(tmp_path: Path) -> None:
|
|
"""The order's known-positive: ``R761/4-3`` must come back with ``R761/4…`` candidates.
|
|
|
|
``R761/4`` is FIRST, because the ranking is longest-common-prefix with the segment that failed
|
|
— which is the one thing that distinguishes "the names at this level" from "the name you were
|
|
reaching for".
|
|
"""
|
|
base = _r761_shaped(tmp_path)
|
|
bundle = okf.navigate_bundle(str(base))
|
|
|
|
neighbours = okf.nearest_subdirectories(bundle, "R761/4-3")
|
|
|
|
assert neighbours[0] == "R761/4"
|
|
assert set(neighbours) == {"R761/4", "R761/41", "R761/42"}
|
|
|
|
|
|
def test_c2_the_read_file_refusal_carries_them(tmp_path: Path) -> None:
|
|
"""The seam: the message the MODEL sees. A helper nothing calls would leave every arm above
|
|
green and the measured defect untouched."""
|
|
base = _r761_shaped(tmp_path)
|
|
tools, _opened, _declared = _wired(base)
|
|
|
|
answer = tools["read_file"].func(bundle_id="proc", path="R761/4-3/k.md")
|
|
|
|
assert answer.startswith("REFUSED (BundlePathNotFound)")
|
|
assert "'R761'" in answer
|
|
assert "subdirectories include R761/4, R761/41, R761/42" in answer
|
|
|
|
|
|
def test_c2_the_read_dir_refusal_carries_them_too(tmp_path: Path) -> None:
|
|
"""The OTHER rung, and the reason both were changed: a caller that guesses a directory gets the
|
|
same help as one that guesses a document. One question about one base, one answer."""
|
|
base = _r761_shaped(tmp_path)
|
|
tools, _opened, _declared = _wired(base)
|
|
|
|
answer = tools["read_dir"].func(bundle_id="proc", path="R761/4-3")
|
|
|
|
assert answer["refusal"] == "BundlePathNotFound"
|
|
assert "R761/4, R761/41, R761/42" in answer["refused"]
|
|
|
|
|
|
def test_c2_an_ancestor_with_no_subdirectories_names_none(tmp_path: Path) -> None:
|
|
"""Omission, never an empty clause. MEASURED: 2 of the 18 guessed paths land here — their
|
|
ancestor holds documents and no directories — and a trailing "its subdirectories include "
|
|
with nothing after it is a sentence that says nothing."""
|
|
base = _r761_shaped(tmp_path)
|
|
tools, _opened, _declared = _wired(base)
|
|
|
|
answer = tools["read_file"].func(bundle_id="proc", path="R761/4/missing.md")
|
|
|
|
assert answer.startswith("REFUSED (BundlePathNotFound)")
|
|
assert "subdirectories include" not in answer
|
|
|
|
|
|
def test_c2_every_name_it_hands_back_resolves(tmp_path: Path) -> None:
|
|
"""The property that makes the help worth having (``_index_excerpt``'s rule): a path that never
|
|
was is worse than no path. Each suggestion is fed straight back to ``read_dir``."""
|
|
base = _r761_shaped(tmp_path)
|
|
tools, _opened, _declared = _wired(base)
|
|
bundle = okf.navigate_bundle(str(base))
|
|
|
|
for name in okf.nearest_subdirectories(bundle, "R761/4-3"):
|
|
listing = tools["read_dir"].func(bundle_id="proc", path=name)
|
|
assert "refused" not in listing, f"{name} does not resolve: {listing}"
|
|
|
|
|
|
def test_c2_the_verdict_layer_is_never_advertised_by_name(tmp_path: Path) -> None:
|
|
"""Built from ``context_files``, never ``files``: the one layer no listing mentions must not
|
|
appear in a REFUSAL either, which would be the same leak wearing an apology."""
|
|
base = tmp_path / "withverdict"
|
|
(base / "judged").mkdir(parents=True)
|
|
(base / "open").mkdir()
|
|
(base / "index.md").write_text(
|
|
"---\nbundle_id: wv\n---\n\n- [A](open/a.md) — one.\n- [V](judged/v.md) — two.\n",
|
|
encoding="utf-8",
|
|
)
|
|
(base / "open" / "a.md").write_text(
|
|
"---\ntype: Krav\ntitle: A\n---\n\nbody\n", encoding="utf-8"
|
|
)
|
|
(base / "judged" / "v.md").write_text(
|
|
"---\ntype: verdict\ntitle: V\n---\n\nprior judgement\n", encoding="utf-8"
|
|
)
|
|
bundle = okf.navigate_bundle(str(base))
|
|
|
|
neighbours = okf.nearest_subdirectories(bundle, "nope")
|
|
|
|
assert neighbours == ("open",), neighbours
|
|
|
|
|
|
@pytest.mark.parametrize("path", ["", "a"])
|
|
def test_c2_a_top_level_guess_names_the_top_level_directories(tmp_path: Path, path: str) -> None:
|
|
"""The fallback: no ancestor at all means the base's own top level, which ``directory_listing``
|
|
always answers. ``krav`` and ``r761`` were both measured as top-level guesses on R761."""
|
|
base = _r761_shaped(tmp_path)
|
|
bundle = okf.navigate_bundle(str(base))
|
|
|
|
assert okf.nearest_subdirectories(bundle, path) == ("R761",)
|