"""The hand-written IR projection is OPTIONAL, and its absence is said rather than raised (S7b søm 1, ordre ``20260903T204605Z-215167684-from-.claude`` DEL A). **The seam this closes was measured before it was built** (``docs/2026-09-03-forslag-fra-mandat.md`` § 1.1): ``okf.load_ir_projection`` is fail-fast required at THREE call sites, and an ingested tender corpus carries no hand-written ``validator-input.json``, so such a base could be navigated (``read_bundle``), catalogued (``list_bundles``) and judged through the deterministic mandate door (``evaluate_mandate_candidates``, which never reads the file) — and still could not run the eight-step loop at all. The refusal fired before the first model call, which is the one mercy it had. **The pattern is the repo's own, not a new one.** ``load_cost_baseline`` / ``load_optional_cost_baseline`` already say exactly this: ABSENCE is tolerated and the caller takes a stance; a file that EXISTS but is malformed, or that disagrees with the run, still refuses. The S4.0 row states the rule in one line — *"toleransen stopper ved fravær"* — and reading a corrupt projection as "no projection" would hand back an unkeyed run wearing a keyed one's clothes. **A pair, never a ``required=`` flag.** PM-tillegg 5 measured what an unexercised parameter costs: all eleven ``evidence_for`` call sites used the default, so the other branch rotted until it raised. A pair also keeps true the five docstrings across ``hitl``/``ledger``/``verdicts``/``contracts`` that cite ``load_ir_projection`` as *the* fail-fast precedent. **The project NAME was a non-question, and that is measured rather than assumed.** The archived self-order flagged the fallback as a real decision. Measured: ``SavingsProposal`` has five fields (``project_id``, ``measure``, ``affected_items``, ``claimed_saving_nok``, ``assumptions``) and none of them is a name — the projection has never been a name source. ``Project.name`` comes from the ``type: project`` concept's ``title`` with the id as last resort, before and after, so there is no new implicit ``project_id`` here; there is an old one, unchanged. **Each of the three sites needed its OWN stance, which is why this is not one loader swap.** * ``run._project_from_bundle`` — absence skips a fail-fast that has nothing to check against; a projection that is PRESENT and names another project still refuses. That divergence guard is what multi-base dispatch rests on ("den eksisterende fail-fasten blir rutingsnøkkelen"), so loosening absence must not loosen disagreement. * ``run.run_mandate_across_bundles`` — the routed run's ``project_id``. FILE FIRST, the base's DECLARED ``bundle_id`` as the fallback (S7a-3). The precedence is load-bearing in the other direction too: every existing base whose ``project_id`` differs from its ``bundle_id`` must keep routing on the file. * ``verdicts.bundle_candidate_features`` — the ExpeL query key, and the ONE place where absence is not merely tolerable. Two consumers, two different answers: the run's Step-1 fold SKIPS and SAYS how many prior verdicts did not reach the prompt, while ``seed_store_from_bundle``'s S3.2 fallback REFUSES BY NAME, because minting a key for a verdict that declares none is precisely the defect S3.2 closes. **The visibility carrier is ``RunResult`` alone, and that is a measurement.** ``cost_baseline_anchored`` and ``skipped_links`` are both resolved above the ``live_dry_run`` cut (``run.py``: the cut returns before generation), so both can be carried on ``DryRunReport``. The ExpeL fold happens BELOW that cut — a dry run stops before it — so a ``DryRunReport`` field could only ever report zero. It is not on ``ProvenanceStamp`` either, for ``skipped_links``' own reason: this is a RUN-level fact settled once, before any candidate exists, whereas the stamp describes the gate that judged ONE candidate. **Predictable vacuity, hunted for deliberately.** The shipped fixtures declare no ``bundle_id`` (S7a-3 measured zero ``^bundle_id`` matches under ``tests/``), so on them the declared id and the mount basename COINCIDE and a routing arm cannot tell the two apart — every routing arm here therefore runs against a base crafted to declare an id differing from its directory name (M14's lesson). And an arm that asserts only ``pytest.raises`` cannot discriminate when both implementations raise (M5's lesson), so the refusal arms assert the NAMED class plus a distinguishing token. """ from __future__ import annotations import json import shutil from pathlib import Path from typing import Any import pytest from portfolio_optimiser import okf, run, verdicts from portfolio_optimiser.mandate import Approach, Mandate from portfolio_optimiser.run import RunResult from portfolio_optimiser.simulation import scripted_factory from portfolio_optimiser.verdicts import ProposalFeatures, VerdictStore, capture_verdict _FIXTURES = Path(__file__).parent / "fixtures" #: MAJOR-4's priced fixture. It ships WITHOUT a ``validator-input.json`` — that absence is the whole #: subject here, so it is used exactly as it sits on disk unless an arm says otherwise. _PRICED = str(_FIXTURES / "k2-prisskjema-SYNTETISK") _PROJECT = "K2" #: A proposal the deterministic gate validates against the fixture's derived schedule: 21.1 is #: 1250 x 850 = 1 062 500, and ``energy_efficiency`` caps the claim at 15 % of that (F8's registry). _PROPOSAL_JSON = json.dumps( { "project_id": _PROJECT, "measure": "energy_efficiency", "affected_items": [{"code": "21.1", "quantity": 1250.0, "unit_cost": 850.0}], "claimed_saving_nok": 100_000.0, "assumptions": {"21.1": [800.0, 900.0]}, } ) def _copy(tmp_path: Path, name: str, *, declared: str | None = None) -> Path: """A working copy of the priced fixture, optionally DECLARING a bundle id that differs from its mount name — the only shape under which a routing arm can distinguish declaration from mount.""" root = tmp_path / name shutil.copytree(_PRICED, root) if declared is not None: for path in root.glob("*.md"): text = path.read_text(encoding="utf-8") path.write_text( text.replace("---\ntype:", f"---\nbundle_id: {declared}\ntype:", 1), "utf-8" ) return root def _write_projection(root: Path, project_id: str) -> None: (root / "validator-input.json").write_text( json.dumps( { "project_id": project_id, "measure": "energy_efficiency", "affected_items": [{"code": "21.1", "quantity": 1250.0, "unit_cost": 850.0}], "claimed_saving_nok": 100_000.0, } ), encoding="utf-8", ) def _factory(sink: list[str] | None = None) -> Any: return scripted_factory( {"proposer": _PROPOSAL_JSON, "checker": "VERDICT: APPROVE"}, [] if sink is None else sink, ) async def _run(bundle_dir: str, *, sink: list[str] | None = None, **kwargs: Any) -> Any: return await run.run_project( _PROJECT, "local", docs_dir=bundle_dir, bundle_dir=bundle_dir, derive_cost_baseline=True, client_factory=_factory(sink), max_rounds=2, **kwargs, ) # -------------------------------------------------------------------------------------------- # (a) the loader pair itself # -------------------------------------------------------------------------------------------- def test_the_optional_loader_returns_none_for_a_base_that_has_no_projection() -> None: assert okf.load_optional_ir_projection(_PRICED) is None with pytest.raises(FileNotFoundError): okf.load_ir_projection(_PRICED) def test_the_optional_loader_returns_the_projection_when_the_base_has_one(tmp_path: Path) -> None: root = _copy(tmp_path, "with-projection") _write_projection(root, _PROJECT) loaded = okf.load_optional_ir_projection(str(root)) assert loaded is not None assert loaded["project_id"] == _PROJECT def test_the_tolerance_stops_at_absence_a_malformed_projection_still_raises( tmp_path: Path, ) -> None: """S4.0's rule, transplanted verbatim: reading a corrupt projection as "no projection" would hand back an unkeyed run under the appearance of a keyed one.""" root = _copy(tmp_path, "malformed") (root / "validator-input.json").write_text("{not json", encoding="utf-8") with pytest.raises(json.JSONDecodeError): okf.load_optional_ir_projection(str(root)) # -------------------------------------------------------------------------------------------- # (b) the run path: a base with no projection runs the WHOLE loop # -------------------------------------------------------------------------------------------- async def test_a_base_without_a_projection_runs_the_whole_pipeline() -> None: """THE HEADLINE. Before this seam the same call raised ``FileNotFoundError`` from ``_project_from_bundle`` — before the first model call, which is why an ingested corpus could be navigated and never run.""" result = await _run(_PRICED) assert isinstance(result, RunResult) assert result.provenance.validator_decision in {"validated", "rejected"} assert result.provenance.cost_baseline_anchored is True async def test_a_projection_that_names_another_project_still_refuses(tmp_path: Path) -> None: """The divergence guard, which multi-base dispatch rests on. Absence became tolerable; DISAGREEMENT did not, and a loader swap that loosened both would be green on every other arm here.""" root = _copy(tmp_path, "wrong-project") _write_projection(root, "SOMEONE-ELSE") with pytest.raises(ValueError) as excinfo: await _run(str(root)) message = str(excinfo.value) assert "SOMEONE-ELSE" in message assert _PROJECT in message async def test_a_base_that_carries_the_projection_runs_exactly_as_before(tmp_path: Path) -> None: """The control the order asks for: with the file present nothing about the run changes.""" root = _copy(tmp_path, "runnable") _write_projection(root, _PROJECT) result = await _run(str(root)) assert isinstance(result, RunResult) assert result.provenance.validator_decision in {"validated", "rejected"} # -------------------------------------------------------------------------------------------- # (c) the dispatcher's routing key: file first, declared bundle id as the fallback # -------------------------------------------------------------------------------------------- async def test_the_dispatcher_routes_a_projectionless_base_on_its_declared_id( tmp_path: Path, ) -> None: """``run_mandate_across_bundles`` takes no ``project_id`` parameter BY DESIGN — it reads each base's own. Without a projection the base's DECLARED id is what remains, and S7a-3 already made that the identity every other door uses.""" declared = "k2-trinn1-20260903" root = _copy(tmp_path, "mounted-elsewhere", declared=declared) assert Path(root).name != declared # the two are genuinely distinct here result = await run.run_mandate_across_bundles( Mandate( objective="Finn besparelser", approaches=(Approach(id="a1", label="Tiltak", bundle_id=declared),), allow_own_proposals=True, ), (str(root),), "local", client_factory=_factory(), max_rounds=2, ) assert [r.bundle_id for r in result.runs] == [declared] assert [r.project_id for r in result.runs] == [declared] async def test_the_dispatcher_still_prefers_the_projection_when_the_base_has_one( tmp_path: Path, ) -> None: """PRECEDENCE, and it is load-bearing in the direction the headline arm cannot see: every base whose ``project_id`` differs from its ``bundle_id`` must keep routing on the FILE. The base here declares both, and they disagree — an implementation that read the declared id first would send the run under the wrong project name while every other arm stayed green.""" declared = "k2-trinn1-20260903" root = _copy(tmp_path, "both-known", declared=declared) _write_projection(root, _PROJECT) assert _PROJECT != declared result = await run.run_mandate_across_bundles( Mandate( objective="Finn besparelser", approaches=(Approach(id="a1", label="Tiltak", bundle_id=declared),), allow_own_proposals=True, ), (str(root),), "local", client_factory=_factory(), max_rounds=2, ) assert [r.project_id for r in result.runs] == [_PROJECT] # -------------------------------------------------------------------------------------------- # (d) the ExpeL fold: skipped for want of a key, and SAID # -------------------------------------------------------------------------------------------- #: A rationale that appears nowhere in the fixture, so its presence in a prompt can only come from #: the ExpeL fold (``simulate_learning_loop``'s marker rule). _MARKER = "TIDLIGERE-DOM-MARKOER" def _store() -> VerdictStore: return VerdictStore( verdicts=[ capture_verdict( ProposalFeatures( affected_codes=frozenset({"21.1"}), measure_type="energy_efficiency", claimed_saving_nok=90_000.0, ), "approved", _MARKER, ), capture_verdict( ProposalFeatures( affected_codes=frozenset({"36.1"}), measure_type="ventilation", claimed_saving_nok=50_000.0, ), "rejected", "tidligere dom nummer to", ), ] ) async def test_prior_verdicts_that_could_not_be_keyed_are_counted_not_silently_dropped() -> None: """Without a projection there is no pre-hypothesis candidate to key the fold against, so the fold cannot run. The run says how MANY prior verdicts therefore never reached the hypothesis prompt — the ``BudgetExceeded`` kø-(y) rule applied to a fold: "it did not happen" and "you lost two judgements" are different operative facts.""" store = _store() sink: list[str] = [] result = await _run(_PRICED, store=store, sink=sink) assert isinstance(result, RunResult) assert result.unkeyed_verdicts == 2 # THE DISCRIMINATOR: a counter can be wired to anything, so the arm reads the PROMPT the fold # would have written into. The marker is a rationale that exists only in this store. assert not any(_MARKER in prompt for prompt in sink) async def test_a_run_whose_fold_was_keyed_counts_none(tmp_path: Path) -> None: """The control. A constant-count implementation and a constant-zero one are both green without this pair — and it is what makes the renderer's OMISSION itself gated.""" root = _copy(tmp_path, "keyed") _write_projection(root, _PROJECT) sink: list[str] = [] result = await _run(str(root), store=_store(), sink=sink) assert isinstance(result, RunResult) assert result.unkeyed_verdicts == 0 # And the fold genuinely RAN — without this the arm above would be green against an # implementation that never folds at all and merely counts zero here. assert any(_MARKER in prompt for prompt in sink) def test_the_renderer_is_silent_when_every_prior_verdict_was_keyed() -> None: """Omission, never an empty row (``mandate.announce``'s rule, the one every ``*_notice`` renderer here follows).""" assert run.unkeyed_verdicts_notice(0) is None line = run.unkeyed_verdicts_notice(2) assert line is not None assert "2" in line def test_the_cli_prints_the_notice_for_a_projectionless_base_with_prior_verdicts( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] ) -> None: """The renderer reaching stdout is its own seam (the ``cost_baseline_notice`` precedent: a renderer nothing calls is a line nobody sees). Driven through ``main()`` with the verdict inbox, which is the only route by which the CLI can hand ``run_project`` a non-empty store.""" inbox = tmp_path / "inbox" inbox.mkdir() verdicts.write_verdict(str(inbox), _store().verdicts[0]) monkeypatch.setattr(run, "_default_factory", lambda _profile: _factory()) rc = run.main( [ _PROJECT, "--docs-dir", _PRICED, "--bundle-dir", _PRICED, "--derive-cost-baseline", "--verdict-dir", str(inbox), ] ) assert rc == 0 assert "1 prior expert verdict" in capsys.readouterr().out # -------------------------------------------------------------------------------------------- # (e) the seed fallback: REFUSED by name, never keyed on a guess # -------------------------------------------------------------------------------------------- def _verdict_concept(root: Path, name: str, *, structural: bool) -> None: key = ( 'affected_codes: "21.1"\nmeasure_type: "energy_efficiency"\nclaimed_saving_nok: "90000"\n' if structural else "" ) (root / name).write_text( f'---\ntype: verdict\ntitle: "Dom"\n{key}decision: approved\n---\n\nEn tidligere dom.\n', encoding="utf-8", ) index = root / "index.md" index.write_text( index.read_text(encoding="utf-8") + f"\nOgsaa [dom]({name}).\n", encoding="utf-8" ) def test_a_verdict_without_its_own_key_in_a_projectionless_base_is_refused_by_name( tmp_path: Path, ) -> None: """S3.2's rule at the one point where absence is NOT tolerable. The pre-S3.2 fallback keys such a verdict on the bundle's projection candidate; with no projection there is no candidate, and minting one would attach the verdict to a candidate it is not about — the exact defect S3.2 closes. Validation, never repair (``write_concept_file``'s rule).""" root = _copy(tmp_path, "unkeyable") _verdict_concept(root, "dom.md", structural=False) with pytest.raises(verdicts.VerdictKeyUnavailable) as excinfo: verdicts.seed_store_from_bundle(str(root)) message = str(excinfo.value) assert "dom.md" in message assert "affected_codes" in message def test_a_verdict_that_declares_its_own_key_seeds_without_any_projection(tmp_path: Path) -> None: """The control, and the reason the refusal above is NARROW: S3.2's fields are what ``promote_verdict`` writes, so a base grown by the loop itself seeds unchanged.""" root = _copy(tmp_path, "self-keyed") _verdict_concept(root, "dom.md", structural=True) store = verdicts.seed_store_from_bundle(str(root)) assert len(store.verdicts) == 1 assert store.verdicts[0].proposal_features.affected_codes == frozenset({"21.1"}) # -------------------------------------------------------------------------------------------- # (f) DEL C — the report-mode partition (the gap økt 82 measured and reported) # -------------------------------------------------------------------------------------------- def test_report_mode_refuses_a_mandate_instead_of_dropping_it( tmp_path: Path, capsys: pytest.CaptureFixture[str] ) -> None: """``--mandate`` was the one flag older than ``report_forbidden`` that never got a row, so ``--report --ledger X --mandate Y`` announced nothing and settled nothing — a SILENT DROP, the F4 class. The argv is one report mode would otherwise ACCEPT (a valid ``--ledger``, and the control proves rc 0 without the flag), so rc 1 is the mutant's opposite outcome rather than the same refusal arriving by another route.""" ledger = tmp_path / "ledger.json" ledger.write_text(json.dumps([]), encoding="utf-8") mandate = tmp_path / "mandate.json" mandate.write_text( Mandate(objective="x", approaches=(Approach(id="a1", label="t"),)).model_dump_json(), encoding="utf-8", ) assert run.main(["--report", "--ledger", str(ledger)]) == 0 capsys.readouterr() rc = run.main(["--report", "--ledger", str(ledger), "--mandate", str(mandate)]) assert rc == 1 assert "mode-exclusive" in capsys.readouterr().err