feat(p21): the PROJECT carries the price, so a run against a road normal can be anchored
Four paid stress rounds ran entirely UN-ANCHORED, all of them, because the one file loader reads cost-baseline.json out of the BUNDLE and no vegnormal ships one: N100, N200, N500 and R761 are knowledge, and knowledge carries requirements, never amounts. The validator's stage 0 -- the one stage that tells an invented cost line from a line this project actually buys -- was skipped in every single run, so "validated" could not mean what it says. P20 G1/G2 measured real R761 process numbers (12.11 three times on Soraasen, 1.1.1 on Lindaas) validating with amounts nobody had anywhere. --cost-baseline FILE is PM decision (e), taken over the three alternatives P20 wrote down. A LOADED object, never a path (prepass_payload's rule): the CLI owns the file and loads it ONCE, so the notice, the stamp and every base of an --across-bundle pass all descend from one read. ONE parse, two doors -- load_cost_baseline delegates to load_cost_baseline_file -- while safe_resolve stays on the bundle door alone, because a project's own schedule is legitimately outside every base. No tolerant twin: this path exists only because an operator NAMED a file. DEL B: five anchored context sets, a1-a3 with their line and a4 with none, so stage 0 is what catches the falsification arm. THE ORDER'S OWN ARM (h) WAS FELLED BY MEASUREMENT: "no baseline code is a requirement number the base declares" is measured 0 of 4 on the project-coded sets and 5 of 5 on kontrakt-sorasen -- which is what R761 Prosesskoden IS, a bill of quantities priced BY process code. The complement keeps both, and the order's own mutation still bites. DEL B3: the judge reports anchored (off the run's own stamp), priced per row, and WHICH falsifier caught the falsification arm. Load-bearing MEASURED, five mutations all red against the WHOLE suite, green control 1850/5 (from 1809/5, superset, 0 removed), golden byte-unchanged: A3(i) the flag is read but the baseline is unused (3 red) . A3(ii) only the first base gets it (1) . A3(iii) report_forbidden drops it (1) . B2(i) a4 gets a line (1, arm (g) alone) . B2(ii) a code swapped to 12.11 (2, arms (f) and (h)). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
587480f050
commit
7b4f85d77c
20 changed files with 1259 additions and 15 deletions
|
|
@ -46,6 +46,7 @@ import pytest
|
|||
from pydantic import ValidationError
|
||||
|
||||
from portfolio_optimiser import okf
|
||||
from portfolio_optimiser.ir import CostBaseline, CostBaselineLine
|
||||
from portfolio_optimiser.mandate import load_mandate
|
||||
from portfolio_optimiser.stress import read_bundle_declarations
|
||||
|
||||
|
|
@ -492,3 +493,169 @@ def test_the_fasit_titles_are_distinct_not_the_collapsed_sources_title(set_dir:
|
|||
"okf.parse_frontmatter collapsed these titles onto the sources block again — the P15 fix "
|
||||
"in okf._frontmatter_from_text has regressed"
|
||||
)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------------
|
||||
# (f) + (g) + (h): the set is ANCHORED (P21 B2).
|
||||
#
|
||||
# The measured reason these exist. Four paid rounds ran entirely un-anchored, because the only
|
||||
# file loader reads ``cost-baseline.json`` out of the BUNDLE and no road normal carries prices — a
|
||||
# vegnormal is knowledge, the price belongs to the PROJECT. With ``--cost-baseline`` the project
|
||||
# supplies its own schedule, so the validator's stage 0 judges again: (f) every answerable approach
|
||||
# has a line to reconcile against, and (g) the falsification arm has NONE, so the code it proposes
|
||||
# is refused as "not in the project's cost baseline" — by stage 0, the one stage that can tell an
|
||||
# invented line from a real one, instead of by the weaker downstream gates.
|
||||
#
|
||||
# (f) and (g) are SEPARATE arms rather than one loop over all approaches, because they are opposite
|
||||
# claims about opposite rows: a single arm asserting "exactly the non-refuse codes are present"
|
||||
# would go red for either defect and name neither.
|
||||
# --------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _set_baseline(set_dir: Path) -> CostBaseline:
|
||||
return okf.load_cost_baseline_file(str(set_dir / "cost-baseline.json"))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("set_dir", _SETS, ids=_SET_IDS)
|
||||
def test_f_every_answerable_approach_has_a_cost_line(set_dir: Path) -> None:
|
||||
"""Unconditional — the schedule is the PROJECT's and needs no knowledge base to read.
|
||||
|
||||
The total is asserted against the approach's own estimate as well as the code's presence:
|
||||
``SavingsProposal`` refuses ``claimed_saving_nok > sum(affected_items.total)``, so a line that
|
||||
exists but is smaller than the saving commissioned against it would make the approach
|
||||
unbuildable — a set that looks anchored and cannot be run.
|
||||
"""
|
||||
baseline = _set_baseline(set_dir)
|
||||
assert 4 <= len(baseline.items) <= 8, (
|
||||
f"{set_dir.name}: {len(baseline.items)} cost lines — the order asks for 4-8"
|
||||
)
|
||||
assert baseline.project_id == set_dir.name, (
|
||||
f"{set_dir.name}: the schedule names project {baseline.project_id!r}"
|
||||
)
|
||||
fasit = json.loads((set_dir / "fasit.json").read_text(encoding="utf-8"))
|
||||
refused = {row["approach_id"] for row in fasit["must_refuse"]}
|
||||
for approach in load_mandate(set_dir / "mandate.json").approaches:
|
||||
if approach.id in refused:
|
||||
continue
|
||||
missing = [c for c in approach.affected_codes if c not in baseline.items]
|
||||
assert not missing, (
|
||||
f"{set_dir.name}: {approach.id} is answerable but {missing} carry no line in the "
|
||||
f"project's schedule ({sorted(baseline.items)})"
|
||||
)
|
||||
total = sum(
|
||||
baseline.items[c].quantity * baseline.items[c].unit_cost
|
||||
for c in approach.affected_codes
|
||||
)
|
||||
assert approach.claimed_saving_nok is not None
|
||||
assert total >= approach.claimed_saving_nok, (
|
||||
f"{set_dir.name}: {approach.id} claims {approach.claimed_saving_nok:g} against lines "
|
||||
f"totalling {total:g} — no proposal on it can satisfy claimed <= total"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("set_dir", _SETS, ids=_SET_IDS)
|
||||
def test_g_the_falsification_arm_has_no_cost_line(set_dir: Path) -> None:
|
||||
"""The ``must_refuse`` approach's code is ABSENT, so stage 0 is what catches it.
|
||||
|
||||
This is the half that makes the anchoring worth measuring rather than just present: rule U
|
||||
already says the base carries no GROUND for that line, and P7's stage 0b says the identifier is
|
||||
ungrounded in the delivered input — but neither of those is the stage that knows what this
|
||||
project actually buys. Stage 0 is, and it can only speak when the schedule exists.
|
||||
"""
|
||||
baseline = _set_baseline(set_dir)
|
||||
fasit = json.loads((set_dir / "fasit.json").read_text(encoding="utf-8"))
|
||||
by_id = {a.id: a for a in load_mandate(set_dir / "mandate.json").approaches}
|
||||
assert fasit["must_refuse"], f"{set_dir.name} declares no falsification arm"
|
||||
for row in fasit["must_refuse"]:
|
||||
approach = by_id[row["approach_id"]]
|
||||
carried = [c for c in approach.affected_codes if c in baseline.items]
|
||||
assert not carried, (
|
||||
f"{set_dir.name}: the falsification arm {approach.id} carries {carried} in the "
|
||||
"project's schedule, so stage 0 would ACCEPT the line it exists to refuse"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("set_dir", _SETS, ids=_SET_IDS)
|
||||
def test_h_no_cost_line_smuggles_in_an_uncommissioned_requirement_number(set_dir: Path) -> None:
|
||||
"""No line of the schedule is a reference number the base declares AND nobody commissions.
|
||||
|
||||
**The order words this arm as "no baseline code is a requirement number the base declares", and
|
||||
that rule was FELLED BY MEASUREMENT before anything was built on it.** Measured 15.09 against
|
||||
``okf.declared_reference_numbers`` over the four mounted bases: the four project-coded sets
|
||||
carry 0 such codes, and ``kontrakt-sorasen-2027`` carries FIVE of five — ``12.1``, ``12.12``,
|
||||
``22.1``, ``52.11``, ``51.1`` are real R761 ``prosessnr``. That is not an accident in the set;
|
||||
it is what R761 Prosesskoden IS. A Norwegian road contract's bill of quantities is priced BY
|
||||
process code, so the project's schedule and the corpus's vocabulary share an identifier
|
||||
namespace by design — and the order's rule would have forced a rewrite of the ONE set P20's
|
||||
decision (e) was chosen to preserve.
|
||||
|
||||
The COMPLEMENT keeps both: a schedule may price what the commission names, and may not
|
||||
INTRODUCE a corpus identifier as a cost line nobody ordered. The order's own mutation still
|
||||
bites — swapping a code for ``12.11`` (a declared ``prosessnr`` no approach commissions) goes
|
||||
red here — while the five real process codes pass because an approach names each of them.
|
||||
|
||||
Needs the base (the vocabulary is the base's), so it SKIPS with the root named.
|
||||
"""
|
||||
baseline = _set_baseline(set_dir)
|
||||
commissioned = {
|
||||
code
|
||||
for approach in load_mandate(set_dir / "mandate.json").approaches
|
||||
for code in approach.affected_codes
|
||||
}
|
||||
declared: set[str] = set()
|
||||
concepts = 0
|
||||
for block in read_bundle_txt(set_dir / "bundle.txt"):
|
||||
bundle = okf.navigate_bundle(str(_require_base(block)))
|
||||
for f in bundle.context_files:
|
||||
concepts += 1
|
||||
declared |= set(okf.declared_reference_numbers(f))
|
||||
assert concepts >= 100, (
|
||||
f"{set_dir.name}: scanned {concepts} concepts — too few to be the base(s)"
|
||||
)
|
||||
assert declared, f"{set_dir.name}: the base(s) declare NO reference numbers — nothing to test"
|
||||
smuggled = sorted(c for c in baseline.items if c in declared and c not in commissioned)
|
||||
assert not smuggled, (
|
||||
f"{set_dir.name}: cost line(s) {smuggled} are reference numbers the knowledge base "
|
||||
"declares and no approach commissions — the schedule would be introducing the corpus's "
|
||||
"own identifiers as prices nobody ordered"
|
||||
)
|
||||
|
||||
|
||||
def test_known_positive_f_a_missing_cost_line_is_caught(tmp_path: Path) -> None:
|
||||
baseline = _set_baseline(_CONTEXT_ROOT / "gate-nordvik-2027")
|
||||
assert "GATE-KRYSS-01" in baseline.items
|
||||
stripped = CostBaseline(
|
||||
project_id=baseline.project_id,
|
||||
items={k: v for k, v in baseline.items.items() if k != "GATE-KRYSS-01"},
|
||||
)
|
||||
assert "GATE-KRYSS-01" not in stripped.items
|
||||
|
||||
|
||||
def test_known_positive_g_a_line_for_the_falsification_arm_is_caught() -> None:
|
||||
"""The order's mutation (i): give a4 a line, and (g)'s assertion must fail on this set."""
|
||||
baseline = _set_baseline(_CONTEXT_ROOT / "gate-nordvik-2027")
|
||||
priced = dict(baseline.items)
|
||||
priced["GATE-GANG-ENHET"] = CostBaselineLine(quantity=6, unit_cost=50_000.0)
|
||||
fasit = json.loads((_CONTEXT_ROOT / "gate-nordvik-2027" / "fasit.json").read_text("utf-8"))
|
||||
by_id = {
|
||||
a.id: a
|
||||
for a in load_mandate(_CONTEXT_ROOT / "gate-nordvik-2027" / "mandate.json").approaches
|
||||
}
|
||||
for row in fasit["must_refuse"]:
|
||||
carried = [c for c in by_id[row["approach_id"]].affected_codes if c in priced]
|
||||
assert carried == ["GATE-GANG-ENHET"]
|
||||
|
||||
|
||||
def test_known_positive_h_an_uncommissioned_requirement_number_is_caught() -> None:
|
||||
"""The order's mutation (ii): swap a code for ``12.11``.
|
||||
|
||||
Driven against a KNOWN vocabulary rather than the mounted base, so this known-positive runs
|
||||
unconditionally — a control that skipped with the base would leave the arm's discriminator
|
||||
unproven on exactly the machines that cannot run the arm.
|
||||
"""
|
||||
declared = {"12.1", "12.11", "12.12"}
|
||||
commissioned = {"12.1", "12.12"}
|
||||
assert sorted(c for c in {"12.1", "12.12"} if c in declared and c not in commissioned) == []
|
||||
assert sorted(c for c in {"12.1", "12.11"} if c in declared and c not in commissioned) == [
|
||||
"12.11"
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue