feat(verdict-gate): read_file nekter verdict-laget, uansett hvordan stien ble funnet

S2c lot dette staa som en uttalt aerlighetsgrense: ingen listing navngir
type:verdict-laget - context_files dropper det paa hvert nivaa, saa verken
read_bundle, read_dir eller bundle_context nevner en dom - men en GJETTET sti
naadde en, og aa naa den slik gaar utenom den gatede ExpeL-folden som er eneste
sanksjonerte vei fra en tidligere dom inn i en hypotese. Egenskapen var arvet
fra S7a-3 og ble naabar fra debatten ogsaa da S2c ga den navigatoerens fire
verktoey. Operatoerbeslutning 04.09: regelen skal bo ETT sted, i verktoeyet.

Gaten er i read_file, altsaa i én kopi for begge kallere. Ikke i renderingen,
som S2c selv maalte til "et filter i navnet alene" mens denne sprossen fortsatt
serverer bytene; ikke i prompt-tekst, som er raad til en utrodd velger og ikke
en gate.

Predikatet leser DOKUMENTET, ikke gangen. Bundle.verdicts svarer paa "hvilke
dommer naadde navigasjonen", som er riktig spoersmaal for ExpeL-froeene; gatens
spoersmaal er det andre - "faar disse bytene forlate" - og det stilles om en sti
en modell valgte, som ingen listing ga den. En dom ingen index lenker ville seilt
rett gjennom en navigasjons-noeklet sjekk. _VERDICT_TYPE er ÉN kopi av hva et
verdict ER (koe-(p)); tre flater svarer nå paa det.

Gaten staar FOER dimensjonssjekken: laget nektes ubetinget, og dimension=None er
utforskningens egen kall - en gate etter den grenen ville vaert fravaerende fra
nøyaktig den kalleren den ble skrevet for.

VERIFISERT: 1314 passed / 5 skipped (fra 1306/5, +8 = de aatte nye armene, ingen
eksisterende testfil roert). Golden demo-transcript BYTEUENDRET, shasum -a 1 av
INNHOLDET = ea8c534773acdbe41ae68f2c55724d69aaf8be4f. ruff + mypy rene.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-04 20:16:18 +02:00
commit 0a83bb8bc9
2 changed files with 58 additions and 3 deletions

View file

@ -41,6 +41,12 @@ from portfolio_optimiser.ir import CostBaseline, CostBaselineLine
from portfolio_optimiser.retrieval import PathSecurityError, safe_resolve
_INDEX_NAME = "index.md"
#: The declared ``type`` of the layer the gated ExpeL fold owns. ONE copy of "what a verdict IS"
#: (kø-(p)): three surfaces now answer it — ``Bundle.verdicts`` (the seeds Step 1 retrieves),
#: ``context_files`` (the bodies agents may be shown) and ``declares_verdict_type`` (the gate on the
#: bytes leaving ``read_file``). Two copies would be free to disagree about one document, and the
#: disagreement would show up as a judgement reaching a hypothesis around the fold.
_VERDICT_TYPE = "verdict"
_IR_PROJECTION = "validator-input.json"
_COST_BASELINE = "cost-baseline.json"
# Intra-bundle markdown cross-links: ``](target.md)``. A path separator is NOT a rejection reason —
@ -743,7 +749,7 @@ class Bundle:
@property
def verdicts(self) -> list[BundleFile]:
"""Every ``type: verdict`` file (the ExpeL seeds the Step-1 wiring retrieves)."""
return [f for f in self.files if f.type == "verdict"]
return [f for f in self.files if f.type == _VERDICT_TYPE]
@property
def context_files(self) -> list[BundleFile]:
@ -757,7 +763,7 @@ class Bundle:
return [
f
for f in self.files
if posixpath.basename(f.name) != _INDEX_NAME and f.type != "verdict"
if posixpath.basename(f.name) != _INDEX_NAME and f.type != _VERDICT_TYPE
]
@property
@ -1034,6 +1040,23 @@ def in_dimension(file: BundleFile, dimension: str | None) -> bool:
return declared is None or declared == dimension
def declares_verdict_type(path: str | Path) -> bool:
"""Whether the document AT ``path`` declares the ``type: verdict`` layer.
Deliberately keyed on the DOCUMENT rather than on the walk, and that is the whole difference
between this gate and a listing filter. ``Bundle.verdicts`` answers "which judgements did
navigation reach", which is the right question for the ExpeL seeds; the gate's question is the
other one "may these bytes leave" and it is asked about a path a model chose, which no
listing gave it. A verdict file no index links to is absent from the walk and would sail
straight through a navigation-keyed check.
Reads the same frontmatter ``_load_file`` reads, through the same one-scan splitter, so a file
the walk WOULD have classified as a verdict is classified identically here. A missing file
raises where it always did, in the read.
"""
return parse_frontmatter(path).get("type", "") == _VERDICT_TYPE
class BundlePathNotFound(ValueError):
"""A listing was asked for a directory the navigated bundle does not have.