Measured 2026-09-17 17:43: vegnormal-okf rebuilt build/ferdig/r761-2025 while this repository's v1 gate, the stress judge and four corpus tests pointed straight at it. Rows 6-7 went IKKE MAALT and five tests fell, for a change no one here made. The failure mode was never falsehood - the gate says IKKE MAALT and exits non-zero, never green - it was instability: two projects shared a directory neither owns, so what this repository MEASURES could move without a commit here. A copy alone would push that directory one move away, so the copy comes with a pin. frozen_bundles.json (tracked) carries path + sha256 + file count per base; the bundles themselves are NEVER committed here. Three states, separated by construction: match -> resolves; gone -> FrozenBundleMissing (an OSError, so the gate's existing except OSError gives IKKE MAALT + exit 1 unchanged and the corpus tests SKIP, MAJOR-3's ceiling); drift -> FrozenBundleDrift (a ValueError), loud, named, and never a skip. The two classes are deliberately unrelated: a caller that catches "missing" to skip must not swallow "drift". The NAME is hashed alongside the bytes, and the directory name carries the first 12 chars of the digest so a stale copy is visible in ls. Renewal is a decision: new copy + new pin in the SAME commit (README). --bundle-root / PORTFOLIO_VEGNORMAL_ROOT stays as the operator's explicit, UNPINNED live mount. Iron Law: the tests were written and run RED first (collection error, then two arms of my own making). Load-bearing MEASURED, eight mutations all red against the WHOLE suite with a green control of 1984 passed / 5 skipped / 5 xfailed and a strict node-id superset (1977 -> 1994, 0 removed): M1 the pin is never verified (7) - M2 drift collapsed into missing (5) - M3 the name is not hashed (40) - M4 the gate seam reverted to root/name (1) - M5 the corpus helpers skip on drift too (4, one per file) - M6a the slash spelling back in src (1) - M6b the quoted path segment back in a test (1) - M7 the directory name drops the short digest (1, and 45 skipped, which proves absence is a SKIP and not a false green) - M8 the explicit override ignored (3, two of them in test_stress_judge_loadbearing.py, independent witnesses older than this work). M2 FALSIFIED THE TEST FIRST: the four parametrised arms did not go red, they went to SKIP (5 -> 9 skipped) and stayed green - pytest.skip inside a pytest.raises is not a failure. The arm now catches pytest.skip.Exception explicitly and turns it into an AssertionError. grep -rnE 'vegnormal-okf/build|["'"'"']vegnormal-okf["'"'"']' src tests contexts -> 0 (3 + 4 hits before; the three remaining prose mentions document history and are allowed). Gate re-run against the frozen copy: identical to the live mount (rows 0/3 - 0/3 - 3/8 - no report - 3/8 - IKKE MAALT - 1/20, exit 1). Order 20260917T223645Z-1296211942-from-.claude. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
667 lines
33 KiB
Python
667 lines
33 KiB
Python
"""P16 DEL A - the STRESS JUDGE: session 102's hand-read criterion made deterministic.
|
|
|
|
**The measured silence.** Session 102 adjudicated "did the run work against the base it said it
|
|
would" BY HAND: (a) the run builds on the right fasit concept, or refuses anchored * (b') it NAMES
|
|
that concept * (c) zero hallucinations. Measured 14.09: nothing in the tree read
|
|
``contexts/<set>/fasit.json`` against an outbox, so the criterion had no repeatable form. Four runs
|
|
now and N runs later cannot rest on a reading somebody did once.
|
|
|
|
This module reads ONLY artefacts that already exist - it adds no new field to any run:
|
|
|
|
* ``{run_id}[-{approach_id}]-proposal.json`` - the candidate IR + ``provenance.citations``
|
|
* ``{run_id}[-{approach_id}]-outcome.json`` - ``validated`` / ``rejected``
|
|
* ``{run_id}-debate.json`` - ``tool_calls[]`` (``name``/``bundle_id``/``path``, S2c)
|
|
|
|
and the set's own ``mandate.json`` / ``fasit.json`` / ``bundle.txt``.
|
|
|
|
**THE ORDER'S (a) WAS VACUOUS AS WRITTEN, AND THE DEVIATION IS MEASURED, NOT CHOSEN.** The order
|
|
defines grounded as "a ``must_cite`` path was OPENED *or* CITED". But on the S2c navigation path
|
|
``run_project`` stamps ``citations = bundle_citations(bundle)``, which is one citation PER CONTEXT
|
|
FILE - the whole corpus. Measured on n100-2023: 446 context files, 446 citations, and **6 of 6
|
|
fasit paths already "cited" before a single model call**. A judge honouring that literally would be
|
|
a gate that can only be green - the repo's own vacuous-gate class, inside the gate built to catch
|
|
it. So a CITATION grounds an approach only when the citation list is NARROWER than the base (a
|
|
declared pre-pass cut, where the stamp really does name what was read). Both halves are reported
|
|
either way (``opened`` / ``cited`` / ``citation_scope``), so which one fired stays readable.
|
|
|
|
**(b') was checked for the same vacuity and is CLEAN for the N corpora, so the order stands.**
|
|
``bundle_citations`` snippets are concept BODIES while ``ref``/``title`` live in FRONTMATTER:
|
|
measured 0 of 446 n100 bodies contain ``Krav 4.1.2-1``. ``named_in_measure`` / ``named_in_snippet``
|
|
are still reported apart, because the measure is the model's own prose and a snippet is the base's.
|
|
|
|
**P18/C2 (PM decision, P16 § 6.2): the snippet arm counts only under a NARROWED scope, as (a)
|
|
does.** The paragraph above holds for a reference like ``Krav 4.1.2-1``, which no body repeats — it
|
|
does NOT hold for R761, where a process number such as ``12.1`` stands in the bodies themselves.
|
|
Under a whole-base citation list that mark is "cited" before any model call, so the row was
|
|
``named`` for a run in which the model had said nothing of the kind. The scope gate is the same
|
|
correction (a) already carries, applied to the half that was still exposed.
|
|
|
|
**A DENOMINATOR, ALWAYS** (Verifiseringsloven ansikt 4). Every verdict names how many tool calls,
|
|
citations, approach rows and base concepts it saw, and an outbox with no proposal artefact - or a
|
|
base that scans to no concepts - RAISES ``EmptyMeasurement`` instead of reporting "0
|
|
hallucinations". An empty measurement is not a clean bill of health.
|
|
|
|
**``not_evaluated`` is ABSENCE, measured.** ``run_project`` writes one artefact pair per EVALUATED
|
|
approach; its coverage rows (including ``not_evaluated``) are printed by ``settle`` and reach no
|
|
file. So a commissioned approach with no artefact is reported as ``not_evaluated`` here rather than
|
|
omitted - an omitted row is indistinguishable from an approach nobody ordered
|
|
(``ApproachOutcome``'s own rule).
|
|
|
|
**Hallucinated READ paths are run-level, and they poison every row.** ``{run_id}-debate.json`` is
|
|
written once per run, so a guessed path cannot be attributed to one approach. Folding it into each
|
|
row's ``ferdig`` is the conservative reading of "(c) must be 0"; the per-row ``hallucinations``
|
|
tuple carries only what IS attributable (that row's citations and codes).
|
|
|
|
**a4 / ``must_refuse`` is the falsification half in D-1 form.** ``po`` is not a lookup tool, so an
|
|
"unanswerable question" has no runnable form - but a commissioned approach whose GROUND the base
|
|
does not carry does. It passes iff no ``validated`` row is that approach AND no validated proposal
|
|
anywhere carries one of its codes. Whether the model SAID the base does not carry it is read by
|
|
hand and reported separately, marked manual.
|
|
|
|
**Framework-free** (stdlib + the MAF-free ``okf`` / ``mandate`` leaves), and it is its OWN module
|
|
rather than a branch of ``run.py``: no partition row in ``main()`` is touched, and a judge that
|
|
cannot start a run cannot accidentally cost anything.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from collections.abc import Mapping, Sequence
|
|
from dataclasses import asdict, dataclass
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from portfolio_optimiser import frozen_bundles, okf
|
|
from portfolio_optimiser.mandate import Mandate, load_mandate
|
|
from portfolio_optimiser.validator import classify_codes, rejection_stage
|
|
|
|
#: Where a base is read from: the FROZEN store this repository pins, resolved at CALL time (the
|
|
#: ``shared_root()`` idiom). ``--bundle-root`` stays as the operator's explicit, unpinned live
|
|
#: mount. See ``frozen_bundles`` for why a shared build directory is not read directly any more.
|
|
|
|
|
|
class EmptyMeasurement(RuntimeError):
|
|
"""A measurement with no denominator - never reported as a clean result.
|
|
|
|
Raised when the outbox holds no proposal artefact at all, or when the base scans to zero
|
|
concepts. Both are the ansikt-4 failure: "found nothing" is a measurement result, and reporting
|
|
it as "0 hallucinations, everything clean" would turn an instrument failure into a fact about
|
|
the world."""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ApproachVerdict:
|
|
"""One commissioned approach, judged against the set's fasit."""
|
|
|
|
approach_id: str
|
|
label: str
|
|
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
|
|
opened: tuple[str, ...]
|
|
cited: tuple[str, ...]
|
|
citation_scope: str # "whole-base" | "narrowed" | "absent"
|
|
#: (b') - the fasit's ``ref`` or ``title`` occurs in the model's own ``measure`` (strong) or in
|
|
#: one of this approach's citation snippets (weaker, but measured non-vacuous).
|
|
named: bool
|
|
named_in_measure: bool
|
|
named_in_snippet: bool
|
|
#: (c) - attributable hallucinations, ``citation:<file>`` / ``code:<code>``.
|
|
hallucinations: tuple[str, ...]
|
|
#: P19 A4 - the binding requirement this row can be attributed, and whether it is one of the
|
|
#: fasit's own concepts for this approach. ``requirement_source`` says WHICH of the two places
|
|
#: it came from, because they are different claims: ``approach`` is per-approach by
|
|
#: construction (the mandate carries it), while ``run`` is the DEBATE's declaration, which is
|
|
#: written once per run and therefore cannot be attributed to one approach on its own.
|
|
requirement_declared: tuple[str, ...]
|
|
requirement_source: str # "approach" | "run" | "absent"
|
|
requirement_hit: bool
|
|
#: P19/B2 - this row's ``affected_item`` codes that are not shaped like an identifier of the
|
|
#: delivered corpora at all. Read off the artefact's own ``provenance.code_forms`` when the run
|
|
#: wrote one, and RE-DERIVED with the same classifier when it did not, so rounds 1 and 2 -
|
|
#: written before the field existed - can be re-judged with the same instrument.
|
|
prose_codes: tuple[str, ...]
|
|
#: P21 B3 - this row's ``affected_item`` codes ARE lines of the project's own price schedule.
|
|
#: Measured against ``contexts/<set>/cost-baseline.json`` (the schedule the run is given with
|
|
#: ``--cost-baseline``) and reported whether or not the run was anchored, so rounds written
|
|
#: before the schedule existed can be re-judged with the same instrument. ``False`` for a row
|
|
#: with no proposal, and for one whose codes the project does not buy.
|
|
priced: bool
|
|
#: P19 D2 - WHY this row was not evaluated: ``rounds`` / ``tokens`` when a cap cut the run
|
|
#: short, ``absent`` when the artefact is simply missing and no coverage file says otherwise,
|
|
#: and ``""`` for a row that WAS evaluated. Before this, "no artefact" could not be told from
|
|
#: "an approach nobody ordered" -- the silence ``ApproachOutcome`` exists to remove, one layer
|
|
#: out, and it reached no file until ``{run_id}-coverage.json``.
|
|
not_evaluated_reason: str
|
|
ferdig: bool
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RefusalVerdict:
|
|
"""One ``must_refuse`` approach (a4): the falsification arm."""
|
|
|
|
approach_id: str
|
|
passed: bool
|
|
detail: str
|
|
#: P21 B3 - WHICH falsifier refused it, from ``validator.rejection_stage`` over the artefact's
|
|
#: own reason. ``stage0-baseline`` is the answer this whole order exists to make reachable: it
|
|
#: is the only stage that knows what the PROJECT buys, and before a project price schedule it
|
|
#: was skipped in every paid run. ``""`` when the arm produced no rejection to classify (it was
|
|
#: validated, or never evaluated) - an honest absence rather than a stage nobody reached.
|
|
#: REQUIRED without a default (``cost_baseline_anchored``'s rule): every construction site has
|
|
#: to say which falsifier spoke, and a default would let one of the three forget.
|
|
stage: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ContextSetVerdict:
|
|
"""The whole set's verdict, denominators included."""
|
|
|
|
context_set: str
|
|
run_id: str
|
|
bundle_id: str
|
|
approaches: tuple[ApproachVerdict, ...]
|
|
must_refuse: tuple[RefusalVerdict, ...]
|
|
#: Run-level: read paths the base does not carry (guessed by the navigator).
|
|
hallucinated_reads: tuple[str, ...]
|
|
#: P19 A4: every requirement the RUN declared as binding, in declaration order, with the
|
|
#: denominator every other field here carries. Reported even when empty - "the run declared
|
|
#: none" is the measurement, and a missing field would be indistinguishable from a judge that
|
|
#: did not look.
|
|
requirements_declared: tuple[str, ...]
|
|
#: P19 DEL C - how the run asked for its listings. ``filter_calls`` is how many calls narrowed
|
|
#: a level by word, ``paged_calls`` how many asked for a window other than the default. P18 § 1
|
|
#: could only infer that the window HAD been widened (five of 31 documents read lay outside the
|
|
#: default) and never with which knob; these two make it readable directly.
|
|
filter_calls: int
|
|
paged_calls: int
|
|
#: P19 D1 - what the run SPENT, read off ``provenance.token_usage``, which has been stamped on
|
|
#: every proposal artefact since S3.4 and which P18's report wrongly said could not be given.
|
|
#: ``0`` when no artefact carried one.
|
|
token_usage: int
|
|
#: P19 D2 - ``BudgetExceeded.kind`` when a cap cut the run short, ``""`` when nothing did, and
|
|
#: ``"absent"`` when the run wrote no coverage file at all (every run before today).
|
|
stop_reason: str
|
|
#: P21 B3 - whether the run's OWN stamp says stage 0 had a baseline to reconcile against, read
|
|
#: off ``provenance.cost_baseline_anchored`` rather than re-derived from a file: the judge
|
|
#: reports what the run DID, and a second resolution here would be free to disagree with it.
|
|
#: ``False`` when no artefact carried one (every round before P21). REQUIRED without a
|
|
#: default, for the reason ``ProvenanceStamp.cost_baseline_anchored`` is.
|
|
anchored: bool
|
|
tool_calls_seen: int
|
|
citations_seen: int
|
|
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."""
|
|
return asdict(self)
|
|
|
|
|
|
def read_bundle_declarations(path: str | Path) -> tuple[dict[str, str], ...]:
|
|
"""Parse a context set's ``bundle.txt`` into ONE declaration per knowledge base.
|
|
|
|
``key: value`` lines, nothing else; each ``name:`` OPENS a block and each block must close
|
|
with its own ``bundle_id:``. A set naming ONE base is one block, so every file written before
|
|
P17b parses byte-identically — the multi-base form (P17b DEL 2) is an extension, not a new
|
|
format.
|
|
|
|
**The ONE reader.** Before P17b the rule had two private copies — one in ``stress.main``'s
|
|
argument parsing, one in the P14 gate's own test file — and the multi-base form is exactly the
|
|
kind of change that would have let them drift into two answers about one set (kø-(p)). Both
|
|
now call this.
|
|
|
|
:raises ValueError: a malformed line, or a block that declares no ``bundle_id``.
|
|
"""
|
|
blocks: list[dict[str, str]] = []
|
|
for line in Path(path).read_text(encoding="utf-8").splitlines():
|
|
line = line.strip()
|
|
if not line or line.startswith("#"):
|
|
continue
|
|
if ": " not in line:
|
|
raise ValueError(f"malformed bundle.txt line in {path}: {line!r}")
|
|
key, value = (part.strip() for part in line.split(": ", 1))
|
|
if key == "name" or not blocks:
|
|
blocks.append({})
|
|
blocks[-1][key] = value
|
|
if not blocks:
|
|
raise ValueError(f"{path} declares no knowledge base at all")
|
|
for block in blocks:
|
|
for required in ("name", "bundle_id"):
|
|
if required not in block:
|
|
raise ValueError(f"{path} declares no {required!r}")
|
|
return tuple(blocks)
|
|
|
|
|
|
def _read_json(path: Path) -> dict[str, Any]:
|
|
return json.loads(path.read_text(encoding="utf-8")) # type: ignore[no-any-return]
|
|
|
|
|
|
def _artefacts(outbox: Path, run_id: str, approach_id: str) -> tuple[Path, Path]:
|
|
"""The proposal/outcome pair for one approach - the per-approach key when a mandate was run,
|
|
the bare ``run_id`` key when it was not (``outbox.write_outbox``'s own two forms)."""
|
|
stem = f"{run_id}-{approach_id}"
|
|
proposal = outbox / f"{stem}-proposal.json"
|
|
if not proposal.is_file():
|
|
proposal = outbox / f"{run_id}-proposal.json"
|
|
outcome = outbox / f"{stem}-outcome.json"
|
|
if not outcome.is_file():
|
|
outcome = outbox / f"{run_id}-outcome.json"
|
|
return proposal, outcome
|
|
|
|
|
|
def _inside(base: Path, raw: str) -> Path | None:
|
|
"""Resolve a model-supplied path inside the base, or ``None`` when it escapes."""
|
|
try:
|
|
resolved = (base / raw).resolve()
|
|
except (OSError, ValueError):
|
|
return None
|
|
root = base.resolve()
|
|
return resolved if resolved == root or root in resolved.parents else None
|
|
|
|
|
|
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. 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"
|
|
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"
|
|
|
|
|
|
def score_context_set(
|
|
context_dir: str | Path,
|
|
outbox_dir: str | Path,
|
|
run_id: str,
|
|
bundle_dir: str | Path,
|
|
bundle_id: str | None = None,
|
|
) -> ContextSetVerdict:
|
|
"""Judge ONE context set against ONE outbox. ``bundle_dir`` is the MOUNTED base itself (the
|
|
CLI resolves it from ``--bundle-root`` plus the set's own ``bundle.txt`` name).
|
|
|
|
``bundle_id`` RESTRICTS the judgement to the approaches a multi-base set routed at THIS base
|
|
(P17b DEL 2). Without it, judging a two-base set's n200 outbox would report the r761 approach
|
|
as ``not_evaluated`` with reason ``absent`` — a false finding, because that approach WAS
|
|
evaluated, against the other base, under the other ``run_id``. ``None`` keeps every single-base
|
|
set judged exactly as before, which is why this is a restriction rather than a new mode: the
|
|
order offered a ``--multibase`` summary reader, and MEASURED against the shape the artefacts
|
|
actually take, the per-base run already has its own full artefact set and its own run_id — so
|
|
what the judge was missing was not a new file to read but the one thing the mandate already
|
|
knows, namely which approaches belong here."""
|
|
context = Path(context_dir)
|
|
outbox = Path(outbox_dir)
|
|
base = Path(bundle_dir)
|
|
|
|
fasit = _read_json(context / "fasit.json")
|
|
mandate: Mandate = load_mandate(context / "mandate.json")
|
|
|
|
bundle = okf.navigate_bundle(str(base))
|
|
concept_names = {f.name for f in bundle.context_files}
|
|
if not concept_names:
|
|
raise EmptyMeasurement(
|
|
f"{base} scanned to 0 concepts - nothing was measured, so no verdict is honest"
|
|
)
|
|
|
|
baseline = okf.load_optional_cost_baseline(str(base))
|
|
baseline_codes = set(baseline.items) if baseline is not None else set()
|
|
|
|
# P21 B3: the PROJECT's own price schedule, which is where the prices live — a road normal
|
|
# carries requirements and no amounts, so the ``load_optional_cost_baseline`` above finds
|
|
# nothing on every one of the four bases (measured). Read from the SET, which is the same file
|
|
# the run is given with ``--cost-baseline``, and read whether or not the run was anchored: that
|
|
# is what lets rounds written before the schedule existed be re-judged with this instrument.
|
|
# It never overwrites ``baseline_codes`` above — the hallucination arm's allowance is about
|
|
# what the BASE could ground, and merging the two would let a priced code launder a
|
|
# hallucinated one.
|
|
priced_codes: set[str] = set()
|
|
project_schedule = okf.load_optional_cost_baseline(str(context))
|
|
if project_schedule is not None:
|
|
priced_codes = set(project_schedule.items)
|
|
|
|
must_cite = {row["approach_id"]: row.get("concepts", []) for row in fasit.get("must_cite", [])}
|
|
refuse_ids = {row["approach_id"] for row in fasit.get("must_refuse", [])}
|
|
|
|
# P17b: the approaches THIS base was asked about. Read off the mandate, never off a second
|
|
# per-row key in the fasit — the routing already has exactly one home (kø-(p)).
|
|
judged_approaches = mandate.approaches
|
|
if bundle_id is not None:
|
|
judged_approaches = tuple(
|
|
a for a in judged_approaches if (a.bundle_id or bundle_id) == bundle_id
|
|
)
|
|
if not judged_approaches:
|
|
raise EmptyMeasurement(
|
|
f"no approach in {context} is routed at {bundle_id!r} - a judgement over zero "
|
|
"rows has no denominator"
|
|
)
|
|
|
|
# ---- run-level trace ---------------------------------------------------------------------
|
|
debate = outbox / f"{run_id}-debate.json"
|
|
tool_calls: list[dict[str, Any]] = (
|
|
list(_read_json(debate).get("tool_calls", [])) if debate.is_file() else []
|
|
)
|
|
opened_paths = {c.get("path", "") for c in tool_calls if c.get("name") == "read_file"}
|
|
opened_paths.discard("")
|
|
# P19 A4: WHERE the declarations live was measured, not assumed. A ``--mandate`` run (which is
|
|
# 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_records: list[dict[str, Any]] = []
|
|
for artefact in (debate, outbox / f"{run_id}-exploration.json"):
|
|
if artefact.is_file():
|
|
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", ""))
|
|
if not raw:
|
|
continue
|
|
target = _inside(base, raw)
|
|
name = call.get("name")
|
|
if name == "read_file" and (target is None or not target.is_file()):
|
|
hallucinated_reads.append(raw)
|
|
elif name == "read_dir" and (target is None or not target.is_dir()):
|
|
hallucinated_reads.append(raw)
|
|
reads_clean = not hallucinated_reads
|
|
|
|
# ---- P19 D1/D2: what the run spent, and why it stopped -----------------------------------
|
|
coverage_path = outbox / f"{run_id}-coverage.json"
|
|
stop_reason = (
|
|
str(_read_json(coverage_path).get("stop_reason", "")) if coverage_path.is_file() else ""
|
|
)
|
|
coverage_seen = coverage_path.is_file()
|
|
# P21 B3: read off the run's OWN stamp, accumulated over the artefacts below. A run is anchored
|
|
# or it is not, so ANY artefact saying so is the run saying so.
|
|
anchored = False
|
|
|
|
# ---- per approach ------------------------------------------------------------------------
|
|
rows: list[ApproachVerdict] = []
|
|
citations_seen = 0
|
|
rows_seen = 0
|
|
token_usage = 0
|
|
validated_codes: set[str] = set()
|
|
validated_ids: set[str] = set()
|
|
|
|
for approach in judged_approaches:
|
|
proposal_path, outcome_path = _artefacts(outbox, run_id, approach.id)
|
|
concepts = must_cite.get(approach.id, [])
|
|
wanted = {c["path"] for c in concepts}
|
|
|
|
if not proposal_path.is_file() or not outcome_path.is_file():
|
|
rows.append(
|
|
ApproachVerdict(
|
|
approach_id=approach.id,
|
|
label=approach.label,
|
|
status="not_evaluated",
|
|
grounded=False,
|
|
opened=tuple(sorted(wanted & opened_paths)),
|
|
cited=(),
|
|
citation_scope="absent",
|
|
named=False,
|
|
named_in_measure=False,
|
|
named_in_snippet=False,
|
|
hallucinations=(),
|
|
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",
|
|
ferdig=False,
|
|
)
|
|
)
|
|
continue
|
|
|
|
rows_seen += 1
|
|
payload = _read_json(proposal_path)
|
|
token_usage = max(token_usage, int(payload.get("provenance", {}).get("token_usage", 0)))
|
|
anchored = anchored or bool(payload.get("provenance", {}).get("cost_baseline_anchored"))
|
|
proposal = payload.get("proposal", {})
|
|
citations = payload.get("provenance", {}).get("citations", [])
|
|
citations_seen += len(citations)
|
|
status = str(_read_json(outcome_path).get("outcome_type", "rejected"))
|
|
if status == "validated":
|
|
validated_ids.add(approach.id)
|
|
validated_codes.update(
|
|
str(item.get("code", "")) for item in proposal.get("affected_items", [])
|
|
)
|
|
|
|
cited_files = {str(c.get("file", "")) for c in citations}
|
|
scope = (
|
|
"absent"
|
|
if not cited_files
|
|
else "whole-base"
|
|
if concept_names <= cited_files
|
|
else "narrowed"
|
|
)
|
|
|
|
opened = tuple(sorted(wanted & opened_paths))
|
|
cited = tuple(sorted(wanted & cited_files))
|
|
# A whole-base list is stamped before any model work - see the module docstring.
|
|
grounded = bool(opened) or (bool(cited) and scope == "narrowed")
|
|
|
|
measure = str(proposal.get("measure", ""))
|
|
snippets = " ".join(str(c.get("snippet", "")) for c in citations)
|
|
marks = [m for c in concepts for m in (c.get("ref", ""), c.get("title", "")) if m]
|
|
named_in_measure = any(m in measure for m in marks)
|
|
# P18/C2 (PM decision, P16 § 6.2): the snippet arm counts ONLY under a narrowed citation
|
|
# scope, exactly as (a) does. A whole-base citation list is stamped by ``bundle_citations``
|
|
# before a single model call — measured on n100, 446 context files, 446 citations, 6 of 6
|
|
# fasit paths "cited" for free — so a mark found in THOSE snippets is evidence about the
|
|
# base's contents, not about this run. Measured on r761: ``12.1`` appears in whole-base
|
|
# snippets and gave this row ``named`` without the model having said anything.
|
|
named_in_snippet = scope == "narrowed" and any(m in snippets for m in marks)
|
|
|
|
codes = [str(i.get("code", "")) for i in proposal.get("affected_items", [])]
|
|
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_records)
|
|
requirement_hit = bool(set(attributable) & wanted)
|
|
|
|
halluc = [f"citation:{f}" for f in sorted(cited_files - concept_names)]
|
|
allowed = set(approach.affected_codes) | baseline_codes
|
|
halluc += [
|
|
f"code:{code}"
|
|
for code in sorted(
|
|
{str(i.get("code", "")) for i in proposal.get("affected_items", [])} - allowed
|
|
)
|
|
]
|
|
|
|
rows.append(
|
|
ApproachVerdict(
|
|
approach_id=approach.id,
|
|
label=approach.label,
|
|
status=status,
|
|
grounded=grounded,
|
|
opened=opened,
|
|
cited=cited,
|
|
citation_scope=scope,
|
|
named=named_in_measure or named_in_snippet,
|
|
named_in_measure=named_in_measure,
|
|
named_in_snippet=named_in_snippet,
|
|
hallucinations=tuple(halluc),
|
|
requirement_declared=attributable,
|
|
requirement_source=requirement_source,
|
|
requirement_hit=requirement_hit,
|
|
prose_codes=prose_codes,
|
|
priced=bool(codes) and all(c in priced_codes for c in codes),
|
|
not_evaluated_reason="",
|
|
ferdig=(
|
|
grounded
|
|
and (named_in_measure or named_in_snippet)
|
|
and not halluc
|
|
and reads_clean
|
|
),
|
|
)
|
|
)
|
|
|
|
if rows_seen == 0:
|
|
raise EmptyMeasurement(
|
|
f"{outbox} holds no proposal artefact for run {run_id!r} - "
|
|
"an empty outbox is not a clean result"
|
|
)
|
|
|
|
# ---- the falsification arm ---------------------------------------------------------------
|
|
refusals: list[RefusalVerdict] = []
|
|
judged_ids = {a.id for a in judged_approaches}
|
|
for row in fasit.get("must_refuse", []):
|
|
rid = row["approach_id"]
|
|
# P17b: a falsification arm routed at ANOTHER base was neither asked nor answered here,
|
|
# and reporting it would put a pass/fail on a run that did not happen in this outbox.
|
|
if rid not in judged_ids:
|
|
continue
|
|
commissioned = next((a for a in judged_approaches if a.id == rid), None)
|
|
refuse_codes = set(commissioned.affected_codes) if commissioned is not None else set()
|
|
leaked = sorted(refuse_codes & validated_codes)
|
|
# P21 B3: WHICH falsifier spoke, from the arm's own artefact. ``validator.rejection_stage``
|
|
# owns the classification because it owns the sentences (kø-(p)); this only reads the
|
|
# reason the run wrote down.
|
|
stage = ""
|
|
_, arm_outcome = _artefacts(outbox, run_id, rid)
|
|
if arm_outcome.is_file():
|
|
reason = str(_read_json(arm_outcome).get("reason", ""))
|
|
if reason:
|
|
stage = rejection_stage(reason)
|
|
if rid in validated_ids:
|
|
refusals.append(
|
|
RefusalVerdict(
|
|
rid, False, f"{rid} was VALIDATED - the base carries no ground for it", stage
|
|
)
|
|
)
|
|
elif leaked:
|
|
refusals.append(
|
|
RefusalVerdict(
|
|
rid, False, f"{rid}'s codes {leaked} ride inside a validated proposal", stage
|
|
)
|
|
)
|
|
else:
|
|
refusals.append(RefusalVerdict(rid, True, "no validated outcome rests on it", stage))
|
|
|
|
judged = [r for r in rows if r.approach_id not in refuse_ids]
|
|
return ContextSetVerdict(
|
|
context_set=context.name,
|
|
run_id=run_id,
|
|
bundle_id=bundle_id if bundle_id is not None else str(fasit.get("bundle_id", "")),
|
|
approaches=tuple(rows),
|
|
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,
|
|
filter_calls=sum(1 for c in tool_calls if str(c.get("filter", ""))),
|
|
paged_calls=sum(
|
|
1 for c in tool_calls if int(c.get("offset", 0) or 0) or int(c.get("limit", 0) or 0)
|
|
),
|
|
tool_calls_seen=len(tool_calls),
|
|
citations_seen=citations_seen,
|
|
approach_rows_seen=rows_seen,
|
|
concepts_in_base=len(concept_names),
|
|
ferdig=bool(judged) and all(r.ferdig for r in judged) and all(r.passed for r in refusals),
|
|
)
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
"""``python -m portfolio_optimiser.stress <context_dir> --outbox-dir D --run-id R``.
|
|
|
|
Writes the verdict to ``<outbox>/<run_id>-verdict.json`` AND prints it, so a CI reader and a
|
|
human reader get the same bytes. Its own module, never a ``run.py`` mode: no partition row is
|
|
touched and this entry point cannot start a paid run."""
|
|
parser = argparse.ArgumentParser(prog="portfolio-optimiser-stress", description=__doc__)
|
|
parser.add_argument("context_dir", help="contexts/<set>")
|
|
parser.add_argument("--outbox-dir", required=True)
|
|
parser.add_argument("--run-id", required=True)
|
|
parser.add_argument(
|
|
"--bundle-root",
|
|
default=None,
|
|
help="an explicit, UNPINNED live mount to read the set's bundle.txt name under; without "
|
|
"it the frozen store answers and its sha256 pin is verified",
|
|
)
|
|
parser.add_argument(
|
|
"--bundle",
|
|
default=None,
|
|
help="which of the set's declared bases this outbox is for (name or bundle_id). Required "
|
|
"when the set declares more than one (P17b): each approach is judged against ITS OWN "
|
|
"base, so a judge that guessed would score one base's run against another's fasit rows",
|
|
)
|
|
args = parser.parse_args(argv)
|
|
|
|
context = Path(args.context_dir)
|
|
declared = read_bundle_declarations(context / "bundle.txt")
|
|
if args.bundle is None and len(declared) > 1:
|
|
print(
|
|
f"stress refused: {context} declares {len(declared)} knowledge bases "
|
|
f"({', '.join(d['name'] for d in declared)}); name the one this outbox is for with "
|
|
"--bundle, because a judge that picked would be scoring one base's run against "
|
|
"another base's fasit rows",
|
|
file=sys.stderr,
|
|
)
|
|
return 1
|
|
chosen = declared[0] if args.bundle is None else None
|
|
for block in declared:
|
|
if args.bundle in (block["name"], block["bundle_id"]):
|
|
chosen = block
|
|
if chosen is None:
|
|
print(
|
|
f"stress refused: {args.bundle!r} is not one of the knowledge bases {context} "
|
|
f"declares ({', '.join(d['name'] for d in declared)})",
|
|
file=sys.stderr,
|
|
)
|
|
return 1
|
|
try:
|
|
base = frozen_bundles.bundle_dir(chosen["name"], override=args.bundle_root)
|
|
except (frozen_bundles.FrozenBundleMissing, frozen_bundles.FrozenBundleDrift) as exc:
|
|
print(f"stress refused: {exc}", file=sys.stderr)
|
|
return 1
|
|
|
|
try:
|
|
verdict = score_context_set(
|
|
context,
|
|
args.outbox_dir,
|
|
args.run_id,
|
|
base,
|
|
bundle_id=chosen["bundle_id"] if len(declared) > 1 else None,
|
|
)
|
|
except EmptyMeasurement as exc:
|
|
print(f"stress refused: {exc}", file=sys.stderr)
|
|
return 1
|
|
|
|
payload = json.dumps(verdict.to_payload(), sort_keys=True, indent=2, ensure_ascii=False) + "\n"
|
|
out = Path(args.outbox_dir) / f"{args.run_id}-verdict.json"
|
|
out.write_text(payload, encoding="utf-8")
|
|
print(payload, end="")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover - exercised by a subprocess test
|
|
raise SystemExit(main())
|