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:
parent
e7d1a31ecf
commit
0a83bb8bc9
2 changed files with 58 additions and 3 deletions
|
|
@ -215,7 +215,20 @@ class ExplorationError(RuntimeError):
|
||||||
|
|
||||||
|
|
||||||
class VerdictLayerRefused(ValueError):
|
class VerdictLayerRefused(ValueError):
|
||||||
"""PLACEHOLDER for the red observation — declared, not yet enforced anywhere."""
|
"""A navigator asked ``read_file`` for a document belonging to the ``type: verdict`` layer.
|
||||||
|
|
||||||
|
Prior expert judgements reach a hypothesis through ONE door — the gated ExpeL fold inside
|
||||||
|
``run_project``, keyed on the candidate (S3.2) and folded before generation. Everything else in
|
||||||
|
the bundle is context an agent may navigate; a verdict is not, because a run that reads its own
|
||||||
|
corpus of past judgements as ordinary knowledge has routed around the gate that decides which
|
||||||
|
of them are relevant, and self-contamination is exactly what målbilde §4 excludes the layer to
|
||||||
|
prevent.
|
||||||
|
|
||||||
|
A ``ValueError``, the ``DimensionScopeRefused``/``BundlePathNotFound`` precedent: the caller is
|
||||||
|
a model choosing a path, so the refusal lands on the CLI's refusal tuple and hosting's 400 arm
|
||||||
|
rather than the crash channel. Like the dimension scope it is a refused read inside a run that
|
||||||
|
is otherwise fine, never an exploration that cannot be honoured as configured.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class DimensionScopeRefused(ValueError):
|
class DimensionScopeRefused(ValueError):
|
||||||
|
|
@ -1017,6 +1030,25 @@ def navigator_tools(
|
||||||
# model-chosen path is untrusted input by definition, so it goes through the same gate the
|
# model-chosen path is untrusted input by definition, so it goes through the same gate the
|
||||||
# navigation walk uses rather than a second, laxer check.
|
# navigation walk uses rather than a second, laxer check.
|
||||||
resolved = Path(safe_resolve(bundle_dir, path))
|
resolved = Path(safe_resolve(bundle_dir, path))
|
||||||
|
# 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
|
||||||
|
# walks around the gated ExpeL fold that is the only sanctioned route from a past judgement
|
||||||
|
# into a hypothesis. Measured on the fixture base before the gate: all 2 883 characters.
|
||||||
|
#
|
||||||
|
# The rule lives HERE, in the tool, and therefore in exactly one place for both callers —
|
||||||
|
# the exploration (``--explore``) and, since S2c, the debate. Not in the rendering, which
|
||||||
|
# S2c measured to be "a filter in name only" while this rung still serves the bytes; not in
|
||||||
|
# prompt text, which is advice to an untrusted chooser rather than a gate. It is BEFORE the
|
||||||
|
# dimension check because the layer is refused unconditionally: ``dimension=None`` is the
|
||||||
|
# exploration's own call and admits every dimension, so a gate placed after that branch
|
||||||
|
# would be absent from precisely the caller it was written for.
|
||||||
|
if okf.declares_verdict_type(resolved):
|
||||||
|
raise VerdictLayerRefused(
|
||||||
|
f"document {path!r} in knowledge base {bundle_id!r} is a prior expert verdict; "
|
||||||
|
"judgements reach a hypothesis only through the gated ExpeL fold, never by being "
|
||||||
|
"read as bundle knowledge"
|
||||||
|
)
|
||||||
if dimension is not None:
|
if dimension is not None:
|
||||||
# The SECOND half of the scope (§4.1a). A listing that hides a document while this rung
|
# The SECOND half of the scope (§4.1a). A listing that hides a document while this rung
|
||||||
# still serves it by path is a filter in name only, and the caller here is a model that
|
# still serves it by path is a filter in name only, and the caller here is a model that
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,12 @@ from portfolio_optimiser.ir import CostBaseline, CostBaselineLine
|
||||||
from portfolio_optimiser.retrieval import PathSecurityError, safe_resolve
|
from portfolio_optimiser.retrieval import PathSecurityError, safe_resolve
|
||||||
|
|
||||||
_INDEX_NAME = "index.md"
|
_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"
|
_IR_PROJECTION = "validator-input.json"
|
||||||
_COST_BASELINE = "cost-baseline.json"
|
_COST_BASELINE = "cost-baseline.json"
|
||||||
# Intra-bundle markdown cross-links: ``](target.md)``. A path separator is NOT a rejection reason —
|
# Intra-bundle markdown cross-links: ``](target.md)``. A path separator is NOT a rejection reason —
|
||||||
|
|
@ -743,7 +749,7 @@ class Bundle:
|
||||||
@property
|
@property
|
||||||
def verdicts(self) -> list[BundleFile]:
|
def verdicts(self) -> list[BundleFile]:
|
||||||
"""Every ``type: verdict`` file (the ExpeL seeds the Step-1 wiring retrieves)."""
|
"""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
|
@property
|
||||||
def context_files(self) -> list[BundleFile]:
|
def context_files(self) -> list[BundleFile]:
|
||||||
|
|
@ -757,7 +763,7 @@ class Bundle:
|
||||||
return [
|
return [
|
||||||
f
|
f
|
||||||
for f in self.files
|
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
|
@property
|
||||||
|
|
@ -1034,6 +1040,23 @@ def in_dimension(file: BundleFile, dimension: str | None) -> bool:
|
||||||
return declared is None or declared == dimension
|
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):
|
class BundlePathNotFound(ValueError):
|
||||||
"""A listing was asked for a directory the navigated bundle does not have.
|
"""A listing was asked for a directory the navigated bundle does not have.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue