portfolio-optimiser/src/portfolio_optimiser/stress.py
Kjell Tore Guttormsen 50c9763706
feat(frozen-bundles): the measurements read a pinned copy, not another repo's build dir
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>
2026-09-18 03:22:22 +02:00

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())