DEL A + DEL C av ordre 20260903T204605Z-215167684-from-.claude.
Soem 1: okf.load_optional_ir_projection ved siden av den fail-faste, moensteret
fra load_cost_baseline/load_optional_cost_baseline. Et PAR, ikke et required=-flagg:
PM-tillegg 5 maalte hva en uoevet parameter koster (elleve evidence_for-kallsteder
brukte defaulten til den andre grenen raatnet), og et flagg ville dessuten gjort
usanne de fem docstringene som siterer load_ir_projection som DEN fail-faste
presedensen. Toleransen stopper ved fravaer: en malformed projeksjon reiser fortsatt.
De tre kallstedene fikk HVER SIN stilling:
* _project_from_bundle - fravaer hopper over en fail-fast som ikke har noe aa
sjekke mot; en projeksjon som FINNES og navngir et annet prosjekt nekter
fortsatt. Divergens-vakten er kontrakten multi-base-dispatchen hviler paa.
* run_mandate_across_bundles - FILA FOERST, basens ERKLAERTE bundle_id som
fallback (S7a-3). Presedensen baerer i begge retninger: erklaering-foerst ville
re-adressert hver eksisterende base der project_id != bundle_id.
* bundle_candidate_features - optional_bundle_candidate_features, og de TO
konsumentene svarer ULIKT paa fravaeret. Steg-1-folden HOPPER OVER og sier
hvor mange tidligere dommer som dermed aldri naadde hypotese-prompten;
seed_store_from_bundle NEKTER ved navn (VerdictKeyUnavailable), fordi aa mynte
en noekkel for en dom som erklaerer ingen er nettopp defekten S3.2 lukker.
Synligheten: RunResult.unkeyed_verdicts (ANTALL, ikke flagg - koe-(y)-regelen) +
run.unkeyed_verdicts_notice som ENESTE renderer, None ved null (omisjon, aldri tom
rad). Baereren er MAALT: dry-run-kuttet returnerer OVER folden, saa et felt paa
DryRunReport kunne bare rapportert null - ulikt cost_baseline_anchored og
skipped_links, begge opploest over kuttet. Ikke paa ProvenanceStamp: stempelet
beskriver gaten som doemte EN kandidat.
Prosjektnavnet var et ikke-spoersmaal, og det er maalt: SavingsProposal har intet
navnefelt, saa projeksjonen har aldri vaert en navnekilde. Project.name kommer
fortsatt fra type: project-konseptets title med id-en som siste utvei.
DEL C: --mandate lagt i report_forbidden. Den var ELDRE enn partisjonen og hadde
aldri faatt en rad, saa --report --ledger X --mandate Y droppet kommisjonen i
STILLHET - F4-klassen. Testarmen kjoerer mot en argv report-modus ellers ville
AKSEPTERT, med en kontroll som beviser rc 0 uten flagget.
Kontroll: 1290 passed / 5 skipped (fra 1275/5 - supersett, 0 fjernet).
Golden demo-transcript.stdout BYTE-UENDRET, shasum -a 1 (INNHOLD, ikke git-blob)
= ea8c534773acdbe41ae68f2c55724d69aaf8be4f. ruff + mypy rene.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
439 lines
20 KiB
Python
439 lines
20 KiB
Python
"""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
|