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>
357 lines
16 KiB
Python
357 lines
16 KiB
Python
"""P20 DEL B — a clause number is not a price, and the base's own vocabulary is what says so.
|
|
|
|
**What was measured.** Three paid rounds and one multi-base pass carried FOUR ``validated``
|
|
proposals whose cost code was a chapter number of a standard. Two survive in the recorded outboxes
|
|
and are this arm's known positives:
|
|
|
|
* ``10.4`` — tunnel-hauglia round 3, base ``vegnormal-n500-2024``, ``validated``;
|
|
* ``1.10.4`` — lindaas P17b, base ``vegnormal-r761-2025``, ``validated``.
|
|
|
|
Both are GROUNDED in P7's sense (they occur verbatim in the input) and neither is INERT in
|
|
P18/B1's sense (``10.4`` in 12 of 274 documents, ``1.10.4`` in 1 of 2 756). Stage 0 never ran:
|
|
no vegnormal base ships a cost baseline. Nothing in the gate could say what they are.
|
|
|
|
**THE ORDER'S OWN RULE WAS FELLED BY MEASUREMENT BEFORE ANYTHING WAS BUILT ON IT.** B1 reads: a
|
|
code is a requirement when it has form 2 or 3 AND "står som ``req_number``/``prosessnr`` i
|
|
toppnivå-frontmatter i minst ett av grunnlagets dokumenter" — refuse that. Measured 15.09:
|
|
|
|
* n500 declares ``seksjon: 10.4.1`` … ``10.4.4`` and ``req_number: Krav 10.4.3—2``. The bare
|
|
``10.4`` is declared NOWHERE — it is a section PREFIX;
|
|
* r761 declares 2 727 ``prosessnr`` and 2 753 ``seksjon``. ``1.10.4`` is NONE of them: it occurs
|
|
once, as prose, in "Krav til materialer skal være iht. vegnormal N200 Vegbygging kap. 1.10.4".
|
|
|
|
The ordered rule therefore fires on NEITHER of its own known positives. The COMPLEMENT fires on
|
|
BOTH, and it closes a hole ``_ground_against_input`` already admits in writing — "it fails OPEN …
|
|
on a coincidental match". For one shape, a clause number, the base hands us the vocabulary needed
|
|
to tell a real reference from a coincidence, and that is the rule built here.
|
|
|
|
The complement is also what SPARES the one context set built on real process codes: all five of
|
|
``contexts/kontrakt-sorasen-2027``'s codes are declared ``prosessnr`` and pass. Under the ordered
|
|
rule every one of them would have been refused on an unanchored r761 run, and the set's positive
|
|
arms would have become unmeasurable — the R761 risk the order names, arriving through the door it
|
|
was pointed away from.
|
|
|
|
Measured over EVERY code of round 3 and P17b (24 codes, 10 runs): exactly two are
|
|
requirement-shaped, they are the two known positives, and the replay flips exactly those two.
|
|
|
|
What each arm pins:
|
|
|
|
(a) known positive — the ``10.4`` proposal, replayed against the base it actually ran on, is
|
|
``rejected``, and the reason names the denominator;
|
|
(b) known positive — ditto ``1.10.4`` on r761;
|
|
(c) known negative — a code the base DOES declare (sorasen's real ``12.1``) still validates;
|
|
(d) known negative — the gate is OFF when the run is anchored, even for a clause-shaped code;
|
|
(e) the generality guard — an input that declares no reference numbers at all cannot trip the rule,
|
|
which is what leaves every pre-P20 fixture untouched rather than exempted;
|
|
(f) K2's identifier forms are untouched: ``SHA-01`` is not requirement-shaped;
|
|
(g) ``classify_codes``' third value, and its denominator-free reading (``grounding=None``) that
|
|
``stress.py`` re-derives with;
|
|
(h) the vocabulary travels WITH the text through ``_grounding_text``, so the gate the generation
|
|
loop runs sees what the run composed;
|
|
(i) a run composes the vocabulary from the base it opened — asserted end-to-end through
|
|
``run_project``, not on the composer.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from portfolio_optimiser import frozen_bundles
|
|
|
|
from portfolio_optimiser import okf
|
|
from portfolio_optimiser.generate import _grounding_text
|
|
from portfolio_optimiser.ir import CostBaseline, SavingsProposal
|
|
from portfolio_optimiser.reference_domain import Project
|
|
from portfolio_optimiser.validator import (
|
|
Grounding,
|
|
Rejection,
|
|
ValidatedProposal,
|
|
classify_codes,
|
|
has_requirement_form,
|
|
validate_proposal,
|
|
)
|
|
|
|
_ROUND3 = Path("scratchpad/p19-stress/tunnel-hauglia-2027")
|
|
_P17B = Path("scratchpad/p17b-multibase/lindaas")
|
|
|
|
|
|
def _base(name: str) -> Path:
|
|
"""The FROZEN copy this repository pins, resolved at call time.
|
|
|
|
Absence SKIPS (MAJOR-3's ceiling: no corpus is mounted in the handover archive), drift is
|
|
allowed to propagate and FAIL — a measurement of the wrong corpus is not a missing one.
|
|
"""
|
|
try:
|
|
return frozen_bundles.bundle_dir(name)
|
|
except frozen_bundles.FrozenBundleMissing as exc:
|
|
pytest.skip(str(exc))
|
|
|
|
|
|
def _grounding_over(name: str) -> Grounding:
|
|
"""The delivered base exactly as ``run_project`` composes it — documents AND vocabulary."""
|
|
bundle = okf.navigate_bundle(str(_base(name)))
|
|
return Grounding(
|
|
documents=tuple(
|
|
"\n".join([f.name, *f.frontmatter.values(), f.body]) for f in bundle.context_files
|
|
),
|
|
declared_references=tuple(
|
|
ref for f in bundle.context_files for ref in okf.declared_reference_numbers(f)
|
|
),
|
|
)
|
|
|
|
|
|
def _recorded(path: Path) -> SavingsProposal:
|
|
if not path.is_file():
|
|
pytest.skip(f"the recorded artefact {path} is not present in this checkout")
|
|
return SavingsProposal.model_validate(json.loads(path.read_text(encoding="utf-8"))["proposal"])
|
|
|
|
|
|
def _proposal(code: str, *, saving: float = 1000.0) -> SavingsProposal:
|
|
return SavingsProposal(
|
|
project_id="p",
|
|
measure="m",
|
|
affected_items=[{"code": code, "quantity": 10.0, "unit_cost": 1000.0}],
|
|
claimed_saving_nok=saving,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------------- known positives
|
|
|
|
|
|
def test_the_section_number_that_reached_validated_on_n500_is_refused() -> None:
|
|
"""(a) tunnel-04's ``10.4``, replayed against the base that run actually opened."""
|
|
proposal = _recorded(_ROUND3 / "tunnel-hauglia-2027-04-a4-enhetspris-ventilator-proposal.json")
|
|
assert [i.code for i in proposal.affected_items] == ["10.4"]
|
|
outcome = validate_proposal(proposal, baseline=None, grounding=_grounding_over("n500-2024"))
|
|
assert isinstance(outcome, Rejection)
|
|
assert "'10.4'" in outcome.reason
|
|
assert "not one of the 365 this knowledge base declares" in outcome.reason
|
|
|
|
|
|
def test_the_process_number_that_reached_validated_on_r761_is_refused() -> None:
|
|
"""(b) lindaas a4's ``1.10.4`` — a chapter of ANOTHER standard, quoted in one r761 document."""
|
|
proposal = _recorded(_P17B / "lindaas-01-vegnormal-r761-2025-a4-indeksregulering-proposal.json")
|
|
assert [i.code for i in proposal.affected_items] == ["1.10.4"]
|
|
outcome = validate_proposal(proposal, baseline=None, grounding=_grounding_over("r761-2025"))
|
|
assert isinstance(outcome, Rejection)
|
|
assert "not one of the 2765 this knowledge base declares" in outcome.reason
|
|
|
|
|
|
# ---------------------------------------------------------------------------------- known negatives
|
|
|
|
|
|
def test_a_process_code_the_base_declares_still_validates() -> None:
|
|
"""(c) The arm that keeps this a rule about the corpus and not about shapes.
|
|
|
|
``12.1`` is ``contexts/kontrakt-sorasen-2027``'s own first code and a REAL declared
|
|
``prosessnr`` of R761. Under the ordered rule it would have been refused; it must not be.
|
|
"""
|
|
grounding = _grounding_over("r761-2025")
|
|
assert "12.1" in grounding.reference_vocabulary
|
|
outcome = validate_proposal(_proposal("12.1"), baseline=None, grounding=grounding)
|
|
assert isinstance(outcome, ValidatedProposal), getattr(outcome, "reason", "")
|
|
|
|
|
|
def test_every_sorasen_code_is_in_the_bases_vocabulary() -> None:
|
|
"""(c) The whole context set, not one sample: five real codes, five declared numbers."""
|
|
codes = [
|
|
code
|
|
for approach in json.loads(
|
|
Path("contexts/kontrakt-sorasen-2027/mandate.json").read_text(encoding="utf-8")
|
|
)["approaches"]
|
|
for code in approach.get("affected_codes", [])
|
|
]
|
|
vocabulary = _grounding_over("r761-2025").reference_vocabulary
|
|
shaped = [c for c in codes if has_requirement_form(c)]
|
|
assert len(shaped) == 5, shaped
|
|
assert [c for c in shaped if c not in vocabulary] == []
|
|
|
|
|
|
def test_the_fasit_references_are_classified_requirement() -> None:
|
|
"""(g) known negative (c) of the order: a fasit reference IS a requirement, and says so."""
|
|
fasit = json.loads(
|
|
Path("contexts/kontrakt-sorasen-2027/fasit.json").read_text(encoding="utf-8")
|
|
)
|
|
refs = sorted({c["ref"] for entry in fasit["must_cite"] for c in entry["concepts"]})
|
|
forms = classify_codes(refs, _grounding_over("r761-2025"))
|
|
assert set(forms.values()) == {"requirement"}, forms
|
|
|
|
|
|
def test_an_anchored_run_is_untouched_by_the_rule() -> None:
|
|
"""(d) Stage 0 has already ruled; the weaker stage must not overrule the stronger."""
|
|
grounding = Grounding(documents=("12.9 is a clause",), declared_references=("12.1", "12.2"))
|
|
baseline = CostBaseline(project_id="p", items={"12.9": {"quantity": 10.0, "unit_cost": 1000.0}})
|
|
outcome = validate_proposal(_proposal("12.9"), baseline=baseline, grounding=grounding)
|
|
assert isinstance(outcome, ValidatedProposal), getattr(outcome, "reason", "")
|
|
# The control: the SAME code and the SAME text, unanchored, is refused.
|
|
unanchored = validate_proposal(_proposal("12.9"), baseline=None, grounding=grounding)
|
|
assert isinstance(unanchored, Rejection)
|
|
|
|
|
|
def test_an_input_that_declares_no_reference_numbers_cannot_trip_the_rule() -> None:
|
|
"""(e) The generality guard — and the reason every pre-P20 fixture is untouched."""
|
|
grounding = Grounding(documents=("a document mentioning 12.9 once",))
|
|
assert grounding.reference_vocabulary == frozenset()
|
|
outcome = validate_proposal(_proposal("12.9"), baseline=None, grounding=grounding)
|
|
assert isinstance(outcome, ValidatedProposal), getattr(outcome, "reason", "")
|
|
|
|
|
|
def test_a_cost_line_identifier_is_not_requirement_shaped() -> None:
|
|
"""(f) K2's 50 identifiers and this repo's own code: shape, measured."""
|
|
assert not has_requirement_form("SHA-01")
|
|
assert not has_requirement_form("ENERGI-TOTAL-EL")
|
|
assert not has_requirement_form("65 ASFALTDEKKER")
|
|
assert has_requirement_form("10.4") and has_requirement_form("Krav 4.1.2—1")
|
|
|
|
|
|
def test_classify_codes_without_a_grounding_is_the_pre_p20_answer() -> None:
|
|
"""(g) ``stress.py`` re-derives for runs written before the field existed."""
|
|
assert classify_codes(["12.1", "SHA-01", "impulsventilator"]) == {
|
|
"12.1": "identifier",
|
|
"SHA-01": "identifier",
|
|
"impulsventilator": "prose",
|
|
}
|
|
grounding = Grounding(documents=("x",), declared_references=("12.1",))
|
|
assert classify_codes(["12.1"], grounding) == {"12.1": "requirement"}
|
|
|
|
|
|
# ---------------------------------------------------------------------------------- the wiring
|
|
|
|
|
|
def test_the_vocabulary_travels_with_the_text_into_the_generation_gate() -> None:
|
|
"""(h) ``_grounding_text`` composes the run's three sources; the vocabulary must survive it."""
|
|
delivered = Grounding(documents=("d",), declared_references=("12.1",))
|
|
project = Project(
|
|
id="p", name="n", description="d", currency="NOK", cost_items=(), docs_dir="."
|
|
)
|
|
composed = _grounding_text(project, None, delivered)
|
|
assert composed.reference_vocabulary == frozenset({"12.1"})
|
|
|
|
|
|
def test_a_run_composes_the_vocabulary_from_the_base_it_opened(tmp_path: Path) -> None:
|
|
"""(i) End-to-end through ``run_project``: the stamp says ``requirement`` for a declared code.
|
|
|
|
Asserted on the ARTEFACT a run leaves, never on the composer — a vocabulary wired nowhere would
|
|
satisfy every arm above and none of this one.
|
|
"""
|
|
import asyncio
|
|
|
|
from agent_framework import BaseChatClient
|
|
|
|
from portfolio_optimiser.run import run_project
|
|
from portfolio_optimiser.simulation import ScriptedChatClient
|
|
|
|
base = tmp_path / "mini"
|
|
base.mkdir()
|
|
(base / "index.md").write_text(
|
|
"---\nbundle_id: mini\n---\n\n- [Krav](krav.md) — one clause.\n", encoding="utf-8"
|
|
)
|
|
(base / "krav.md").write_text(
|
|
"---\ntype: Krav\ntitle: Krav 4.1.2-1\nprosessnr: '12.1'\n---\n\nEn kostlinje 12.1.\n",
|
|
encoding="utf-8",
|
|
)
|
|
(base / "validator-input.json").write_text(
|
|
json.dumps({"project_id": "mini-p", "measure": "m", "affected_codes": ["12.1"]}),
|
|
encoding="utf-8",
|
|
)
|
|
reply = (
|
|
'{"measure":"m","affected_items":[{"code":"12.1","quantity":10,"unit_cost":1000}],'
|
|
'"claimed_saving_nok":1000}'
|
|
)
|
|
|
|
def factory(role: str) -> BaseChatClient:
|
|
return ScriptedChatClient(
|
|
"Reasoning holds.\nVERDICT: APPROVE" if role == "checker" else reply, role=role
|
|
)
|
|
|
|
result = asyncio.run(
|
|
run_project(
|
|
"mini-p",
|
|
"local",
|
|
docs_dir=str(base),
|
|
bundle_dir=str(base),
|
|
client_factory=factory,
|
|
)
|
|
)
|
|
assert result.provenance.code_forms == {"12.1": "requirement"}
|
|
assert result.provenance.validator_decision == "validated"
|
|
|
|
|
|
def test_each_approachs_artefact_carries_its_own_code_forms(tmp_path: Path) -> None:
|
|
"""(j) MEASURED on round 3 AND round 4: every per-approach artefact carried the SELECTED
|
|
proposal's codes.
|
|
|
|
``code_forms``' own comment says it is derived "off the proposal being stamped", and the
|
|
per-approach branch copied the run's stamp while overriding only ``validator_decision``. So an
|
|
artefact about approach 2 reported approach 1's codes — and ``stress.py``, which reads this
|
|
field before re-deriving, then produced an EMPTY ``prose_codes`` for every approach but the
|
|
first, because none of that approach's codes was a key in the map it was handed.
|
|
|
|
Run-level was the drift, not the intent: model, citations and token usage ARE the run's, and
|
|
they stay so.
|
|
"""
|
|
import asyncio
|
|
|
|
from agent_framework import BaseChatClient
|
|
|
|
from portfolio_optimiser.mandate import Approach, Mandate
|
|
from portfolio_optimiser.run import run_project
|
|
from portfolio_optimiser.simulation import ScriptedChatClient
|
|
|
|
base = tmp_path / "mini"
|
|
base.mkdir()
|
|
(base / "index.md").write_text(
|
|
"---\nbundle_id: mini\n---\n\n- [Krav](krav.md) — one clause.\n", encoding="utf-8"
|
|
)
|
|
(base / "krav.md").write_text(
|
|
"---\ntype: Krav\ntitle: Krav 4.1.2-1\nprosessnr: '12.1'\n---\n\nLines 12.1 and PRIS-EN.\n",
|
|
encoding="utf-8",
|
|
)
|
|
(base / "validator-input.json").write_text(
|
|
json.dumps({"project_id": "mini-p", "measure": "m", "affected_codes": ["12.1"]}),
|
|
encoding="utf-8",
|
|
)
|
|
outbox = tmp_path / "out"
|
|
|
|
# Each approach's prompt names its own label, so the selector answers with ITS OWN code.
|
|
def select(prompt: str, role: str) -> str:
|
|
if role == "checker":
|
|
return "Reasoning holds.\nVERDICT: APPROVE"
|
|
code = "PRIS-EN" if "SECOND" in prompt else "12.1"
|
|
return (
|
|
'{"measure":"m","affected_items":[{"code":"%s","quantity":10,"unit_cost":1000}],'
|
|
'"claimed_saving_nok":1000}' % code
|
|
)
|
|
|
|
def factory(role: str) -> BaseChatClient:
|
|
return ScriptedChatClient(reply_selector=select, role=role)
|
|
|
|
asyncio.run(
|
|
run_project(
|
|
"mini-p",
|
|
"local",
|
|
docs_dir=str(base),
|
|
bundle_dir=str(base),
|
|
client_factory=factory,
|
|
mandate=Mandate(
|
|
objective="o",
|
|
approaches=(
|
|
Approach(id="a1", label="FIRST", description="d"),
|
|
Approach(id="a2", label="SECOND", description="d"),
|
|
),
|
|
allow_own_proposals=False,
|
|
),
|
|
outbox_dir=str(outbox),
|
|
run_id="r1",
|
|
)
|
|
)
|
|
forms = {}
|
|
for name in ("a1", "a2"):
|
|
payload = json.loads((outbox / f"r1-{name}-proposal.json").read_text(encoding="utf-8"))
|
|
codes = [i["code"] for i in payload["proposal"]["affected_items"]]
|
|
forms[name] = (codes, payload["provenance"]["code_forms"])
|
|
assert forms["a1"] == (["12.1"], {"12.1": "requirement"}), forms["a1"]
|
|
assert forms["a2"] == (["PRIS-EN"], {"PRIS-EN": "identifier"}), forms["a2"]
|