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

@ -14,10 +14,9 @@ action is exactly partial. Such a probe goes green only when it is rewritten to
and observe what it does.
**Row 6 (a validated proposal whose approach declared no requirement).** Two probes against the
real ``run_project``: no declaration anywhere, and a declaration made by the RUN (the debate) but
not by the approach. The second is the reading the gate measures the stress outboxes with: a
run-level declaration cannot be attributed to one approach (the judge labels it ``run``), so it
does not count as the approach having declared anything.
real ``run_project`` on the shipped micro base, where the debate holds ``declare_requirement``:
no declaration anywhere, and a declaration filed for ANOTHER approach (the run's own proposal).
Neither may leave ``a1`` validated a declaration counts only under its own approach's id.
"""
from __future__ import annotations
@ -110,42 +109,34 @@ def _mandate() -> Mandate:
)
async def _statuses(script: dict[str, Any], tmp_path: Path) -> dict[str, str]:
async def _statuses(script: dict[str, Any], tmp_path: Path, base: Path) -> dict[str, str]:
result = await run_project(
_PID,
"local",
docs_dir=str(_BUNDLE),
bundle_dir=str(_BUNDLE),
docs_dir=str(base),
bundle_dir=str(base),
store=VerdictStore(verdicts=[]),
client_factory=scripted_factory(script, []),
mandate=_mandate(),
outbox_dir=str(tmp_path),
outbox_dir=str(tmp_path / "out"),
run_id="v1-row6",
)
return {row.id: row.status for row in result.coverage}
@pytest.mark.xfail(strict=True, reason="row 6: no stage refuses a validation with no declaration")
@pytest.mark.asyncio
async def test_row6_an_approach_that_declared_nothing_cannot_be_validated(tmp_path: Path) -> None:
statuses = await _statuses({"proposer": _VALID_REPLY, "checker": _CHECKER_REPLY}, tmp_path)
debate = json.loads((tmp_path / "v1-row6-debate.json").read_text(encoding="utf-8"))
assert debate["requirements"] == [] # precondition: nothing was declared anywhere
assert statuses["a1"] != "validated", "validated without any declared requirement"
@pytest.mark.xfail(strict=True, reason="row 6: a run-level declaration still stands in")
@pytest.mark.asyncio
async def test_row6_a_run_level_declaration_does_not_stand_in_for_the_approach(
tmp_path: Path,
) -> None:
concepts = [f.name for f in okf.navigate_bundle(str(_BUNDLE)).context_files][:3]
script = {
def _declaring_script(base: Path, approach_id: str) -> dict[str, Any]:
concepts = [f.name for f in okf.navigate_bundle(str(base)).context_files][:3]
return {
"proposer": [
*({"call": "read_file", "args": {"bundle_id": _BASE_ID, "path": n}} for n in concepts),
{
"call": "declare_requirement",
"args": {"bundle_id": _BASE_ID, "path": concepts[0], "ref": "probe"},
"args": {
"bundle_id": _BASE_ID,
"path": concepts[0],
"ref": "Krav 1.1-1",
"approach_id": approach_id,
},
},
_VALID_REPLY,
_VALID_REPLY,
@ -154,7 +145,30 @@ async def test_row6_a_run_level_declaration_does_not_stand_in_for_the_approach(
],
"checker": _CHECKER_REPLY,
}
statuses = await _statuses(script, tmp_path)
debate = json.loads((tmp_path / "v1-row6-debate.json").read_text(encoding="utf-8"))
assert [r["path"] for r in debate["requirements"]] == [concepts[0]] # precondition
def _declared(tmp_path: Path) -> list[dict[str, Any]]:
debate = json.loads((tmp_path / "out" / "v1-row6-debate.json").read_text(encoding="utf-8"))
return list(debate["requirements"])
@pytest.mark.asyncio
async def test_row6_an_approach_that_declared_nothing_cannot_be_validated(tmp_path: Path) -> None:
base = _BUNDLE
statuses = await _statuses(
{"proposer": _VALID_REPLY, "checker": _CHECKER_REPLY}, tmp_path, base
)
assert _declared(tmp_path) == [] # precondition: nothing was declared anywhere
assert statuses["a1"] != "validated", "validated without any declared requirement"
@pytest.mark.asyncio
async def test_row6_a_run_level_declaration_does_not_stand_in_for_the_approach(
tmp_path: Path,
) -> None:
"""The debate declares a requirement — but for the run's OWN proposal, not for ``a1``."""
base = _BUNDLE
statuses = await _statuses(_declaring_script(base, "own-proposal"), tmp_path, base)
declared = _declared(tmp_path)
assert [d.get("approach_id") for d in declared] == ["own-proposal"] # precondition
assert statuses["a1"] != "validated", "validated on a declaration the approach never made"