portfolio-optimiser/tests/test_prepass_cli_door_loadbearing.py
Kjell Tore Guttormsen ca98888358 feat(prepass): --prepass-payload with six refusals by name, README and the hosted surface untouched
Steg 6 og 7 av planen. Flagget lastes fail-fast ved siden av `--mandate` (samme
try/except, saa manglende/ugyldig fil lander paa `run refused:` uten traceback) og
traades inn i BEGGE `run_project`-dispatchene -- dry-run og full kjoering. En egen arm
SPIONERER paa argumentet, ikke paa exit-koden: et flagg som parses, valideres og
droppes er F4-klassen, og de to utfallene er samme rc.

SEKS NEKTER, hver ved NAVN, hver med en rc-0-kontroll paa en argv som ellers ville
blitt AKSEPTERT:
- `report_forbidden` -- report-modus returnerer OVER hver dispatch, saa en utelatelse
  er et stille DROPP. Kontrollen bruker en JSON-ARRAY-ledger; et objekt ville gjort
  armen roed av feil grunn (maalt i oekt 89).
- `single_only` -- navngir `--portfolio`, ALDRI det delte `--prepass-payload`-tokenet:
  en droppet rad faller gjennom til `--bundle-dir`-kravet, hvis melding ogsaa navngir
  flagget, saa en arm paa det delte tokenet ville staatt groenn mot sin egen mutasjon.
- krever `--bundle-dir`; nektet med `--proposals-from-mandate` (returnerer over
  debatten, saa flagget ville vaert stille inert), med `--dimension-config` (pre-passet
  kuttet uten aa kjenne dimensjoner, saa aa aere skopet ville droppe utdrag
  deklarasjonen teller som LEVERT -- da er nevnerne feil for kjoeringen som publiserte
  dem) og med `--explore` (utforskningen leser HELE basen med de fire verktoeyene
  payloadet trekker, saa kjoeringen som helhet ville lest langt utenfor kuttet den
  erklaerer).

Blokka ligger paa FUNKSJONS-nivaa etter mode-dispatchen, aldri nestet under en annen
grens -- under en av dem ville en bar kombinasjon falt rett gjennom.

`hosting.py` er BEVISST URØRT (briefens non-goal, MAJOR-4/S7b-presedensen): feltet
kommer inn i ingen av de tre settene, saa den generiske `unknown field(s)`-400-en
svarer alt, og Fase 4es to halvdeler staar. Gatet av en testarm i stedet for en
redigering -- inkludert den negative halvdelen (hvert videresendt felt ER en
`run_project`-parameter, hvert konsumert er det ikke).

README-blokka navngir alle seks partnerne, uttrykker seg i kundevendt terminologi
(aldri "OKF bundle") og sier BEGGE aerlighets-grensene hoeyt: dette kjoeper et
DEKLARERT kutt, ikke en billigere kjoering; og en TOM leveranse er bevis for fravaer
mens en FULL ikke er bevis for tilstedevaerelse. Uttrekkeren tar BLOKKA (ikke en
delstreng over hele fila -- `--portfolio` og `--report` staar overalt), med
`--plan-review`-blokka som kjent-positiv kontroll.

1466 passed / 5 skipped (fra 1446/5, +20, 0 fjernet). ruff + mypy rene. Golden
`shasum -a 1` av INNHOLDET = ea8c534773acdbe41ae68f2c55724d69aaf8be4f, BYTE-UENDRET.

Co-Authored-By: Claude <claude-opus-5>
2026-09-07 11:27:35 +02:00

414 lines
15 KiB
Python

"""Load-bearing gate for the ``--prepass-payload`` operator door (order 20260907T080223Z).
Two halves, and each has its own failure mode.
- **The wiring.** A flag that is parsed, validated and never passed to ``run_project`` is the F4
silent-drop class: the operator sees rc 0 and a navigating run. So one arm asserts the value
ARRIVES, by spying on the dispatch rather than by reading an exit code.
- **The refusals.** Six surfaces would otherwise accept the flag and do nothing with it. Each is
refused BY NAME and each arm is paired with an **rc-0 control on an argv that would otherwise
be ACCEPTED** — without which a red arm can come from the fixture rather than from the row.
"""
from __future__ import annotations
import json
import shutil
from pathlib import Path
from typing import Any
import pytest
import portfolio_optimiser.run as run_module
from portfolio_optimiser.run import main
FIXTURE = Path(__file__).parent / "fixtures" / "prepass" / "bygg-energi-mikro-fixture.payload.json"
SHIPPED_BASE = Path(__file__).parent.parent / "shared" / "examples" / "bygg-energi-mikro"
PROJECT_ID = "BYGG-KONTOR-NORD"
_PROPOSAL = json.dumps(
{
"project_id": PROJECT_ID,
"measure": "energy_efficiency",
"claimed_saving_nok": 30000,
"affected_items": [{"code": "ENERGI-TOTAL-EL", "quantity": 120000.0, "unit_cost": 1.25}],
"assumptions": {},
}
)
def _base(tmp_path: Path) -> str:
root = tmp_path / "mounted-under-another-name"
shutil.copytree(SHIPPED_BASE, root)
index = root / "index.md"
lines = index.read_text(encoding="utf-8").split("\n")
lines.insert(1, "bundle_id: bygg-energi-mikro-fixture")
index.write_text("\n".join(lines), encoding="utf-8")
return str(root)
def _payload_file(tmp_path: Path) -> str:
path = tmp_path / "payload.json"
path.write_text(FIXTURE.read_text(encoding="utf-8"), encoding="utf-8")
return str(path)
def _docs(tmp_path: Path) -> str:
d = tmp_path / "docs"
d.mkdir(exist_ok=True)
(d / "cost.txt").write_text("Energitiltak i kontorbygg.", encoding="utf-8")
return str(d)
def _replies(tmp_path: Path) -> str:
path = tmp_path / "replies.json"
path.write_text(
json.dumps({"proposer": _PROPOSAL, "checker": "VERDICT: APPROVE"}), encoding="utf-8"
)
return str(path)
def _run_argv(tmp_path: Path, *extra: str) -> list[str]:
"""An argv the CLI ACCEPTS — the control every refusal arm below is measured against."""
return [
PROJECT_ID,
"--bundle-dir",
_base(tmp_path),
"--docs-dir",
_docs(tmp_path),
"--scripted-replies",
_replies(tmp_path),
*extra,
]
def _refuse_model(monkeypatch: pytest.MonkeyPatch) -> None:
"""Any model client construction becomes a failure, so a refusal that fired AFTER the spend is
distinguishable from one that fired before it. At the exit code the two look identical."""
def refuse(profile: Any) -> Any:
raise AssertionError("a model client was built despite a refusal") # pragma: no cover
monkeypatch.setattr(run_module, "_default_factory", refuse)
# --- the wiring ------------------------------------------------------------------------------
def test_the_flag_reaches_run_project(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""Parsed-and-dropped and parsed-and-passed are the same exit code. This arm reads the
argument, not the outcome."""
seen: list[Any] = []
original = run_module.run_project
async def spy(*args: Any, **kwargs: Any) -> Any:
seen.append(kwargs.get("prepass_payload"))
return await original(*args, **kwargs)
monkeypatch.setattr(run_module, "run_project", spy)
rc = main(_run_argv(tmp_path, "--prepass-payload", _payload_file(tmp_path)))
assert rc == 0
assert seen and seen[0] is not None
assert seen[0].bundle.bundle_id == "bygg-energi-mikro-fixture"
def test_the_notice_reaches_stdout(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
rc = main(_run_argv(tmp_path, "--prepass-payload", _payload_file(tmp_path)))
assert rc == 0
assert "DECLARED CUT" in capsys.readouterr().out
def test_the_dry_run_dispatch_is_threaded_too(
tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
"""``DryRunReport.prepass`` and the dry-run notice call are dead code without this."""
# ``--scripted-replies`` and ``--live-dry-run`` are a documented contradiction, so the dry-run
# argv is built without it.
rc = main(
[
PROJECT_ID,
"--bundle-dir",
_base(tmp_path),
"--docs-dir",
_docs(tmp_path),
"--live-dry-run",
"--prepass-payload",
_payload_file(tmp_path),
]
)
assert rc == 0
assert "DECLARED CUT" in capsys.readouterr().out
def test_a_missing_payload_file_refuses_without_a_traceback(
tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
) -> None:
_refuse_model(monkeypatch)
rc = main(_run_argv(tmp_path, "--prepass-payload", str(tmp_path / "nope.json")))
assert rc == 1
assert "run refused:" in capsys.readouterr().err
def test_a_malformed_payload_file_refuses_without_a_traceback(
tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
) -> None:
_refuse_model(monkeypatch)
bad = tmp_path / "bad.json"
bad.write_text("{not json", encoding="utf-8")
rc = main(_run_argv(tmp_path, "--prepass-payload", str(bad)))
assert rc == 1
assert "run refused:" in capsys.readouterr().err
def test_a_payload_for_another_base_refuses_before_any_model_call(
tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
) -> None:
"""``PrepassRefused`` is a ``ValueError``, so it lands on the refusal surface and not the
crash channel — and the refusal happens before a client is built."""
_refuse_model(monkeypatch)
raw = json.loads(FIXTURE.read_text(encoding="utf-8"))
raw["bundle"]["bundle_id"] = "a-different-corpus"
path = tmp_path / "other.json"
path.write_text(json.dumps(raw), encoding="utf-8")
rc = main(_run_argv(tmp_path, "--prepass-payload", str(path)))
assert rc == 1
assert "run refused:" in capsys.readouterr().err
# --- the six refusals, each with an rc-0 control ---------------------------------------------
def test_the_flag_requires_a_bundle_dir(
tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
) -> None:
_refuse_model(monkeypatch)
rc = main(
[
PROJECT_ID,
"--docs-dir",
_docs(tmp_path),
"--scripted-replies",
_replies(tmp_path),
"--prepass-payload",
_payload_file(tmp_path),
]
)
assert rc == 1
assert "--bundle-dir" in capsys.readouterr().err
def test_it_is_refused_in_portfolio_mode_by_name(
tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
) -> None:
"""NAMING ``--portfolio``, never the shared ``--prepass-payload`` token: a dropped row falls
through to the ``--bundle-dir`` requirement, whose message names the flag too — so an arm
asserting on the shared token would be green against the mutation it exists for."""
_refuse_model(monkeypatch)
rc = main(
[
"--portfolio",
"--scripted-replies",
_replies(tmp_path),
"--prepass-payload",
_payload_file(tmp_path),
]
)
assert rc == 1
assert "--portfolio" in capsys.readouterr().err
def test_portfolio_mode_without_the_flag_is_accepted(
tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
"""The rc-0 control for the arm above."""
rc = main(["--portfolio", "--scripted-replies", _replies(tmp_path)])
assert rc == 0
def _ledger(tmp_path: Path) -> str:
"""A JSON **ARRAY**. An object is refused by the ledger loader itself, which would make every
report arm below red for the wrong reason (measured in økt 89)."""
path = tmp_path / "ledger.json"
path.write_text(json.dumps([]), encoding="utf-8")
return str(path)
def test_it_is_refused_in_report_mode(
tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
) -> None:
"""Report mode returns ABOVE every dispatch, so an omission here is a silent DROP."""
_refuse_model(monkeypatch)
rc = main(
["--report", "--ledger", _ledger(tmp_path), "--prepass-payload", _payload_file(tmp_path)]
)
assert rc == 1
assert "--report" in capsys.readouterr().err
def test_report_mode_without_the_flag_is_accepted(tmp_path: Path) -> None:
"""The rc-0 control for the arm above."""
assert main(["--report", "--ledger", _ledger(tmp_path)]) == 0
def test_it_is_refused_with_proposals_from_mandate(
tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
) -> None:
"""That mode returns above the debate, so the flag would be silently inert."""
_refuse_model(monkeypatch)
mandate = tmp_path / "m.json"
mandate.write_text(
json.dumps(
{
"objective": "x",
"approaches": [
{
"id": "a1",
"label": "energy_efficiency",
"rationale": "r",
"affected_codes": ["ENERGI-TOTAL-EL"],
"claimed_saving_nok": 1000,
}
],
"allow_own_proposals": False,
}
),
encoding="utf-8",
)
rc = main(
_run_argv(
tmp_path,
"--proposals-from-mandate",
"--mandate",
str(mandate),
"--derive-cost-baseline",
"--prepass-payload",
_payload_file(tmp_path),
)
)
assert rc == 1
assert "--proposals-from-mandate" in capsys.readouterr().err
def test_it_is_refused_with_a_dimension_config(
tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
) -> None:
"""The pre-pass has no dimension concept, so its CUT is unscoped. Composing them would mean
po discarding excerpts the declaration counted as delivered — which makes the payload's own
denominators wrong for the run that published them."""
_refuse_model(monkeypatch)
dim = tmp_path / "dim.json"
dim.write_text(
json.dumps(
{
"id": "energi",
"label": "Energi",
"allowed_measure_types": ["energy_efficiency"],
}
),
encoding="utf-8",
)
rc = main(
_run_argv(
tmp_path, "--dimension-config", str(dim), "--prepass-payload", _payload_file(tmp_path)
)
)
assert rc == 1
assert "--dimension-config" in capsys.readouterr().err
def test_a_dimension_config_without_the_flag_is_accepted(tmp_path: Path) -> None:
"""The rc-0 control: the two flags are each fine alone."""
dim = tmp_path / "dim.json"
dim.write_text(
json.dumps(
{
"id": "energi",
"label": "Energi",
"allowed_measure_types": ["energy_efficiency"],
}
),
encoding="utf-8",
)
assert main(_run_argv(tmp_path, "--dimension-config", str(dim))) == 0
def test_it_is_refused_with_explore(
tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
) -> None:
"""The exploration reads the WHOLE base with all four navigator tools and then hands its
mandate to a debate told it is under a declared cut — this plan's own grounds for withdrawing
the tools, one caller over."""
_refuse_model(monkeypatch)
rc = main(
_run_argv(
tmp_path, "--explore", "finn tiltak", "--prepass-payload", _payload_file(tmp_path)
)
)
assert rc == 1
assert "--explore" in capsys.readouterr().err
def test_a_plain_run_without_the_flag_is_accepted(tmp_path: Path) -> None:
"""The rc-0 control shared by the arms that add exactly one flag to this argv."""
assert main(_run_argv(tmp_path)) == 0
# --- the hosted surface, deliberately untouched ------------------------------------------------
def test_the_hosted_surface_refuses_the_field_without_being_edited() -> None:
"""The brief's Non-Goal (MAJOR-4 / S7b precedent): the field enters NONE of the three sets, so
the generic ``unknown field(s)`` 400 already answers it and Fase 4e's two halves stand."""
from portfolio_optimiser import hosting
assert "prepass_payload" not in hosting._ALLOWED_FIELDS
with pytest.raises(ValueError, match="unknown field"):
hosting._run_kwargs({"project_id": PROJECT_ID, "prepass_payload": "/x.json"})
def test_the_phase_4e_partitions_still_hold() -> None:
"""The negative half: every forwarded field is a real ``run_project`` parameter and every
consumed one is not. Adding a parameter without touching hosting must not break it."""
import inspect
from portfolio_optimiser import hosting
from portfolio_optimiser.run import run_project
parameters = set(inspect.signature(run_project).parameters)
assert set(hosting._REQUIRED_FIELDS) <= parameters
assert set(hosting._OPTIONAL_FIELDS) <= parameters
assert set(hosting._CONSUMED_FIELDS).isdisjoint(parameters)
# --- the README block --------------------------------------------------------------------------
def _readme_block(flag: str) -> str:
"""The prose block for ONE flag, extracted rather than substring-matched: ``--portfolio`` and
``--report`` occur all over the README, so a file-wide search cannot tell a documented refusal
from an unrelated mention."""
readme = (Path(__file__).parent.parent / "README.md").read_text(encoding="utf-8")
start = readme.index(f"(`{flag}`)")
end = readme.find("\n **", start)
return readme[start : end if end != -1 else len(readme)]
def test_the_readme_documents_the_flag_and_every_partner_refusal() -> None:
block = _readme_block("--prepass-payload")
for partner in (
"--bundle-dir",
"--portfolio",
"--report",
"--proposals-from-mandate",
"--dimension-config",
"--explore",
):
assert partner in block, partner
# Customer-facing terminology: never "OKF bundle" on a published surface.
assert "OKF bundle" not in block
def test_the_extractor_finds_a_block_that_has_existed_since_f4() -> None:
"""The known-positive control: an extractor that silently finds nothing would make the arm
above green against a README with no block at all."""
assert "--explore" in _readme_block("--plan-review")