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:
parent
9847e014e7
commit
938a1ca30e
23 changed files with 718 additions and 115 deletions
|
|
@ -69,7 +69,7 @@ import argparse
|
|||
import json
|
||||
import os
|
||||
import sys
|
||||
from collections.abc import Sequence
|
||||
from collections.abc import Mapping, Sequence
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
|
@ -98,7 +98,7 @@ class ApproachVerdict:
|
|||
|
||||
approach_id: str
|
||||
label: str
|
||||
status: str # "validated" | "rejected" | "not_evaluated"
|
||||
status: str # "validated" | "rejected" | "unsupported" | "not_evaluated"
|
||||
#: (a) - grounded by an OPENED path, or by a citation under a NARROWED list. See the module
|
||||
#: docstring: a whole-base citation list is stamped before any model call and grounds nothing.
|
||||
grounded: bool
|
||||
|
|
@ -197,6 +197,10 @@ class ContextSetVerdict:
|
|||
approach_rows_seen: int
|
||||
concepts_in_base: int
|
||||
ferdig: bool
|
||||
#: Row 6 - declarations with no ``approach_id``: written before declarations carried one. A run
|
||||
#: with any cannot be measured against the rule that a validated proposal needs its own
|
||||
#: approach's declaration, and the v1 gate says so instead of counting.
|
||||
unaddressed_declarations: int = 0
|
||||
|
||||
def to_payload(self) -> dict[str, Any]:
|
||||
"""Byte-stable plain data: the ONE rendering, shared by the CLI's stdout and its file."""
|
||||
|
|
@ -265,21 +269,29 @@ def _inside(base: Path, raw: str) -> Path | None:
|
|||
return resolved if resolved == root or root in resolved.parents else None
|
||||
|
||||
|
||||
def _attributable(approach: Any, declared: Sequence[str]) -> tuple[tuple[str, ...], str]:
|
||||
def _attributable(
|
||||
approach: Any, declared: Sequence[Mapping[str, Any]]
|
||||
) -> tuple[tuple[str, ...], str]:
|
||||
"""Which declared requirement paths this approach may be judged on, and where they came from.
|
||||
|
||||
The approach's OWN requirement wins when it has one: the mandate carries it per approach, so
|
||||
it is unambiguous by construction. Otherwise the RUN's declarations are attributable - the
|
||||
debate declares once for the whole run, so the row says ``run`` rather than pretending the
|
||||
declaration was made about it. ``absent`` is the third value and is not the same as "declared
|
||||
nothing that matched": a run that declared nothing is a different finding from one that
|
||||
declared the wrong document."""
|
||||
it is unambiguous by construction. Next, a declaration filed under THIS approach's id (row 6)
|
||||
is the approach's own and says ``approach`` too. A declaration with no ``approach_id`` at all
|
||||
was written before declarations carried one; it can only be attributed to the whole run, and
|
||||
the row says ``run`` rather than pretending it was made about this approach. A declaration
|
||||
filed under ANOTHER approach's id is not this one's, so a run whose declarations all name other
|
||||
approaches reads ``absent`` here — the same value as a run that declared nothing.
|
||||
"""
|
||||
own = getattr(approach, "requirement", None)
|
||||
path = "" if own is None else str(getattr(own, "path", "") or "")
|
||||
if path:
|
||||
return (path,), "approach"
|
||||
if declared:
|
||||
return tuple(declared), "run"
|
||||
addressed = tuple(str(r["path"]) for r in declared if r.get("approach_id") == approach.id)
|
||||
if addressed:
|
||||
return addressed, "approach"
|
||||
legacy = tuple(str(r["path"]) for r in declared if "approach_id" not in r)
|
||||
if legacy:
|
||||
return legacy, "run"
|
||||
return (), "absent"
|
||||
|
||||
|
||||
|
|
@ -359,14 +371,13 @@ def score_context_set(
|
|||
# what every stress round has been) writes no ``{run_id}-exploration.json`` at all - the
|
||||
# hypothesiser never runs - so the debate artefact is the only one that can carry them there.
|
||||
# Both are read, because an ``--explore`` run carries them in the other.
|
||||
declared_paths: list[str] = []
|
||||
declared_records: list[dict[str, Any]] = []
|
||||
for artefact in (debate, outbox / f"{run_id}-exploration.json"):
|
||||
if artefact.is_file():
|
||||
declared_paths += [
|
||||
str(r.get("path", ""))
|
||||
for r in _read_json(artefact).get("requirements", [])
|
||||
if r.get("path")
|
||||
declared_records += [
|
||||
r for r in _read_json(artefact).get("requirements", []) if r.get("path")
|
||||
]
|
||||
declared_paths = [str(r["path"]) for r in declared_records]
|
||||
hallucinated_reads: list[str] = []
|
||||
for call in tool_calls:
|
||||
raw = str(call.get("path", ""))
|
||||
|
|
@ -417,9 +428,11 @@ def score_context_set(
|
|||
named_in_measure=False,
|
||||
named_in_snippet=False,
|
||||
hallucinations=(),
|
||||
requirement_declared=_attributable(approach, declared_paths)[0],
|
||||
requirement_source=_attributable(approach, declared_paths)[1],
|
||||
requirement_hit=bool(set(_attributable(approach, declared_paths)[0]) & wanted),
|
||||
requirement_declared=_attributable(approach, declared_records)[0],
|
||||
requirement_source=_attributable(approach, declared_records)[1],
|
||||
requirement_hit=bool(
|
||||
set(_attributable(approach, declared_records)[0]) & wanted
|
||||
),
|
||||
prose_codes=(),
|
||||
priced=False,
|
||||
not_evaluated_reason=stop_reason or "absent",
|
||||
|
|
@ -472,7 +485,7 @@ def score_context_set(
|
|||
forms = payload.get("provenance", {}).get("code_forms") or classify_codes(codes)
|
||||
prose_codes = tuple(sorted(c for c in codes if forms.get(c) == "prose"))
|
||||
|
||||
attributable, requirement_source = _attributable(approach, declared_paths)
|
||||
attributable, requirement_source = _attributable(approach, declared_records)
|
||||
requirement_hit = bool(set(attributable) & wanted)
|
||||
|
||||
halluc = [f"citation:{f}" for f in sorted(cited_files - concept_names)]
|
||||
|
|
@ -563,6 +576,7 @@ def score_context_set(
|
|||
must_refuse=tuple(refusals),
|
||||
hallucinated_reads=tuple(hallucinated_reads),
|
||||
requirements_declared=tuple(declared_paths),
|
||||
unaddressed_declarations=sum(1 for r in declared_records if "approach_id" not in r),
|
||||
token_usage=token_usage,
|
||||
stop_reason=stop_reason if coverage_seen else "absent",
|
||||
anchored=anchored,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue