feat(row6): a proposal whose approach declared no requirement is unsupported

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>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-17 16:40:54 +02:00
commit 938a1ca30e
23 changed files with 718 additions and 115 deletions

View file

@ -215,7 +215,8 @@ _INSTRUCTIONS: Final = {
"BINDS it: pass read_dir a 'filter' word taken from the approach's own label — "
"filter='rundkjoring' finds the level's requirements about roundabouts, and one of them is "
"the 'Krav 4.1.2-1' you are looking for — read it with read_file, then call "
"declare_requirement with the base id, that path and the requirement's own number. The "
"declare_requirement with the base id, that path, the requirement's own number and the "
"short label of the direction as approach_id. The "
"reply gives back the document's own title and number: if they are not about your measure, "
"you declared the wrong requirement and should filter again. A direction with no "
"requirement behind it is a guess. "
@ -371,6 +372,21 @@ class DeclaredRequirement:
bundle_id: str
path: str
ref: str
#: WHICH approach the declaration is for (row 6). A requirement bound at run level cannot be
#: attributed to one approach — the judge labelled such a declaration ``run`` — so the rule that
#: a validated proposal must rest on its own approach's declaration needs the address on the
#: record itself. In a commissioned run it is one of the mandate's ids or ``own-proposal``; the
#: exploration, which mints its directions after declaring, records the label verbatim.
approach_id: str
class UnknownApproach(ValueError):
"""A declaration named an approach this run was not commissioned with (row 6).
Returned as a refused TURN, never raised out of the run (the ``RequirementNotRead`` rule): the
refusal NAMES the valid ids, and naming them is the correction a declaration filed under an
id no approach carries would be recorded against nothing and could never satisfy the rule.
"""
class RequirementNotRead(ValueError):
@ -1068,6 +1084,7 @@ def _index_excerpt(body: str) -> tuple[str, bool]:
_RETURNABLE_REFUSALS: Final = (
ExplorationError,
RequirementNotRead,
UnknownApproach,
okf.BundleIdMismatch,
okf.BundlePathNotFound,
okf.DocumentPathRefused,
@ -1186,6 +1203,7 @@ def navigator_tools(
opened: list[ToolCall] | None = None,
requirements: list[DeclaredRequirement] | None = None,
labels: Sequence[str] = (),
approach_ids: Sequence[str] | None = None,
) -> list[FunctionTool]:
"""The navigator's tools: survey the catalogue, open one base, read one document — and, when
the caller offers the two sinks, DECLARE the requirement that binds a direction.
@ -1241,6 +1259,11 @@ def navigator_tools(
the run's own read trace and a second record of it would be free to disagree with the first.
Passing one without the other is refused at construction: a log that cannot see what was opened
would accept every declaration, which is the vacuous-gate class.
**Every declaration names the approach it is for** (row 6). ``approach_ids`` is the set a
commissioned run can file under the mandate's ids plus ``own-proposal`` — and an id outside it
is refused with the valid ones named. ``None`` (the exploration, which has no ids until it mints
them) records any non-empty id verbatim.
"""
if (opened is None) != (requirements is None):
raise ExplorationError(
@ -1462,20 +1485,36 @@ def navigator_tools(
"'Krav 4.1.2-1'), read_file to read it, then declare it. The reply gives back the "
"document's own "
"title and number, so you can see whether you declared the requirement you meant: a "
"declaration of a requirement that is not about the measure is worth nothing."
"declaration of a requirement that is not about the measure is worth nothing. "
"approach_id names the approach the requirement binds: declare once for EACH approach "
"you propose for (the run's own proposal is 'own-proposal'). A proposal whose approach "
"declared no requirement is not validated, however good its numbers are."
),
)
def declare_requirement(bundle_id: str, path: str, ref: str) -> dict[str, Any]:
def declare_requirement(
bundle_id: str, path: str, ref: str, approach_id: str
) -> dict[str, Any]:
try:
return _declare_requirement(bundle_id, path, ref)
return _declare_requirement(bundle_id, path, ref, approach_id)
except _RETURNABLE_REFUSALS as exc:
return _refused_mapping(exc)
def _declare_requirement(bundle_id: str, path: str, ref: str) -> dict[str, Any]:
def _declare_requirement(
bundle_id: str, path: str, ref: str, approach_id: str
) -> dict[str, Any]:
assert opened is not None and requirements is not None # the constructor guard above
# The base is resolved by the SAME index every read rung uses, so an unknown base is
# refused here exactly as it is there rather than being accepted into the record.
resolved_dir = _resolve_bundle(index, bundle_id)
# Row 6: the address is checked before anything is read, so a declaration filed under no
# approach is refused whatever else is right about it.
if approach_ids is not None and approach_id not in approach_ids:
raise UnknownApproach(
f"{approach_id!r}; this run's approaches are {', '.join(map(repr, approach_ids))}. "
"Declare the requirement under the id of the approach it binds"
)
if not approach_id.strip():
raise UnknownApproach("an empty approach_id; name the approach this requirement binds")
read_paths = [call.path for call in opened if call.name == "read_file" and call.path]
if path not in read_paths:
raise RequirementNotRead(
@ -1518,7 +1557,9 @@ def navigator_tools(
"Use read_dir with a 'filter' word from the approach's own label to find the "
"candidates, then read_file the ones that could bind it"
)
requirements.append(DeclaredRequirement(bundle_id=bundle_id, path=path, ref=ref))
requirements.append(
DeclaredRequirement(bundle_id=bundle_id, path=path, ref=ref, approach_id=approach_id)
)
# P20/A1: give back the DOCUMENT's own title and number, read off the base rather than
# echoed from the arguments. MEASURED (P19 round 3, P17b): 13 declarations over 5 runs and
# NOT ONE named a fasit concept — the tool answered ``{"declared": true, ...}`` to every
@ -1532,6 +1573,7 @@ def navigator_tools(
"bundle_id": bundle_id,
"path": path,
"ref": ref,
"approach_id": approach_id,
"title": declared[0],
"req_number": declared[1],
"binds": (
@ -1577,7 +1619,10 @@ def requirement_payload(declared: Sequence[DeclaredRequirement]) -> list[dict[st
copies of "what a declaration looks like" would drift into two answers about one run, which is
the -(p) defect landing in exactly the files an operator reads after a paid run.
"""
return [{"bundle_id": d.bundle_id, "path": d.path, "ref": d.ref} for d in declared]
return [
{"bundle_id": d.bundle_id, "path": d.path, "ref": d.ref, "approach_id": d.approach_id}
for d in declared
]
def _refused(