feat(okf): derive a cost baseline from a priced schedule, or refuse

MAJOR-4 (misjonsreview v2 section 7), owner B = the consumer. Neither existing
projection into ir.CostBaseline can serve an ingested tender corpus:
cost-baseline.json is hand-written per project and baseline_from_project belongs
to the road domain, so a K2-shaped bundle could be navigated and never anchored.
okf.derive_cost_baseline reads the numbers already in the bundle.

The premise was MEASURED before anything was built on it, and the order's two
pointers named two different forms. examples/*/expected-bundle/ carry pipe
tables, but every one of them is csv- or sql-sourced via render.render_table --
the xlsx path never reaches render_table at all. Measured with pandoc 3.10.2
under the producer's own writer and arguments: extract._extract_office converts,
and inbox.py hands that text to render_inbox_concept untouched, so an
xlsx-sourced concept file carries a pandoc SIMPLE table whose dash rule defines
the column spans. A pipe-only reader would have been inert on exactly the corpus
this exists for. Both forms are read, by two scanners over one role mapping and
one number grammar.

No judgement anywhere: the header vocabulary and the number grammar are closed,
and every ambiguity refuses -- no candidate table, more than one, two columns
claiming one role, two rows sharing a cost code, a row that prices nothing. A
partly-priced schedule refuses in full, because a half-derived baseline anchors
some codes while cost_baseline_anchored reports True.

Wired behind --derive-cost-baseline and never silently: one resolution in
run.py's bundle arm serves both the full run and the dry run, and the refusal
propagates rather than degrading to the file loader.

The two fixtures are pandoc's output verbatim, not hand-typed. The unpriced one
is K2's actual pre-award shape, and the columns survive as blanks -- so the
table IS a candidate and the refusal is the sharp one.

Load-bearing MEASURED: 18 mutations all red against the WHOLE suite, green
control 1230 passed / 5 skipped (from 1208/5, superset, 0 removed), golden
demo-transcript.stdout byte-unchanged (ea8c534773acdbe41ae68f2c55724d69aaf8be4f).
M17 is the one that matters for arm (b): dropping the positivity guard makes the
mutant raise pydantic ValidationError, which IS a ValueError but is NOT the named
class -- so pytest.raises(ValueError) would have stayed green against exactly the
mutation the arm exists to catch. Verified directly, not argued.

Honesty limits stated in the invariant row: NS 3451 section rows are not
classified (K2 itself was not available to measure), the stamp records that a run
was anchored and never which projection anchored it, the hosted surface is
deliberately untouched, and no live K2 file was read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-03 03:15:39 +02:00
commit 0add73531b
11 changed files with 870 additions and 7 deletions

31
tests/fixtures/README.md vendored Normal file
View file

@ -0,0 +1,31 @@
# Test fixtures
## `k2-prisskjema-SYNTETISK/` and `k2-prisskjema-uprisert-SYNTETISK/`
**SYNTETISK — every number in these bundles is invented.** They copy the *structure* of a
public-tender `Prisskjema.xlsx` (NS 3451 posts: code, description, unit, quantity, unit price,
sum) and nothing else. **No private tender document is reproduced here**, and the unit prices in
the priced variant are round synthetic values chosen to be obviously not a real bid — they must
never be read as anyone's pricing. This is the first building block of the S7b corpus.
The two variants are the two halves of the same measurement:
| bundle | `Enhetspris` column | what it stands for |
| --- | --- | --- |
| `k2-prisskjema-SYNTETISK/` | filled | a priced schedule a baseline CAN be derived from |
| `k2-prisskjema-uprisert-SYNTETISK/` | empty | K2's actual pre-award state — codes and descriptions, no quantities or prices |
### The table form is MEASURED, not hand-typed
Both bodies are the **verbatim output** of the producer's own office conversion, so the fixture
states something about the format rather than recording our own idea of it:
pandoc -f xlsx -t markdown --eol=lf --wrap=none <sheet>.xlsx
with `pandoc 3.10.2` — the writer and the two arguments are `llm_ingestion_okf.extract`'s
`_PANDOC_WRITER` / `_PANDOC_ARGS`. That is a pandoc **simple table** (a dash rule whose groups
define the column spans), NOT the pipe table `render_table` emits: measured in session 78, the
xlsx path reaches a concept body through `inbox.py`'s `render_inbox_concept(sanitized_text, …)`
and never touches `render_table` at all. An empty spreadsheet cell therefore arrives as blank
space inside its column span — which is exactly what the unpriced variant has to carry for the
refusal it gates to be the real one.

View file

@ -0,0 +1,8 @@
---
type: index
title: K2 Prisskjema (SYNTETISK, priset)
---
# K2 Prisskjema (SYNTETISK)
Syntetisk priset prisskjema. Se [prisskjema-SYNTETISK.md](prisskjema-SYNTETISK.md).

View file

@ -0,0 +1,12 @@
---
type: dataset
title: Prisskjema (SYNTETISK, priset)
---
## Prisskjema {#sheet-1}
Postnr Beskrivelse Enhet Mengde Enhetspris Sum
-------- ---------------------------------------------------- ------- --------- ------------ -----------
21.1 Grunnarbeider, utgraving og bortkjoering av masser m3 1250.0 850.0 1062500.0
24.2 Baerekonstruksjon i staal, levert og montert kg 48000.0 42.5 2040000.0
36.1 Ventilasjonsaggregat med varmegjenvinning stk 4.0 187500.0 750000.0

View file

@ -0,0 +1,9 @@
---
type: index
title: K2 Prisskjema (SYNTETISK, upriset)
---
# K2 Prisskjema (SYNTETISK, upriset)
Syntetisk gjengivelse av K2s pre-award-form. Se
[prisskjema-uprisert-SYNTETISK.md](prisskjema-uprisert-SYNTETISK.md).

View file

@ -0,0 +1,12 @@
---
type: dataset
title: Prisskjema (SYNTETISK, upriset)
---
## Prisskjema {#sheet-1}
Postnr Beskrivelse Enhet Mengde Enhetspris Sum
-------- ---------------------------------------------------- ------- -------- ------------ -----
21.1 Grunnarbeider, utgraving og bortkjoering av masser m3
24.2 Baerekonstruksjon i staal, levert og montert kg
36.1 Ventilasjonsaggregat med varmegjenvinning stk

View file

@ -0,0 +1,386 @@
"""``okf.derive_cost_baseline`` — the THIRD projection into ``ir.CostBaseline``, and the one that
must refuse rather than invent (MAJOR-4, misjonsreview v2 §7; owner B = the consumer, PM decision
2026-09-02).
``ir.CostBaseline``'s docstring names two projections INTO it — a bundle's hand-written
``cost-baseline.json`` (``okf.load_cost_baseline``) and the road reference domain's ``cost_items``
(``validator.baseline_from_project``). Neither can be produced from an ingested tender corpus, so
K2 could be navigated but never RUN: ``run_project``'s bundle arm resolves an OPTIONAL baseline and
a bundle carrying only ingested documents is simply un-anchored. This adds the third: derive the
baseline from a priced schedule the producer already rendered into a concept file.
**The premise was measured before anything was built on it, and the order's two pointers named two
DIFFERENT forms.** The order says "markdown-tabell rendret av produsenten fra xlsx via pandoc" and
also "les examples/ i llm-ingestion-okf for formen":
* ``examples/*/expected-bundle/`` carry **pipe** tables but every one of them is csv- or
sql-sourced, rendered by ``render.render_table``.
* The **xlsx** path never reaches ``render_table``. ``extract._extract_office`` converts through
``pypandoc`` with ``_PANDOC_WRITER = "markdown"`` and ``_PANDOC_ARGS = ("--eol=lf",
"--wrap=none")``, and ``inbox.py`` hands that text straight to
``render_inbox_concept(decision.sanitized_text, )``. Measured with pandoc 3.10.2 against the
producer's own ``tests/fixtures/two-line-krav.xlsx`` and against a hand-laid Prisskjema: the
result is a pandoc **simple table**, whose dash rule defines the column spans.
So a pipe-only reader would have been INERT on exactly the corpus MAJOR-4 exists for, and a
simple-only reader would be inert on the examples the order pointed at. Both forms are read, by two
scanners over ONE role mapping and ONE number grammar the ``_split_frontmatter`` shape (one
scan, two readers), never two copies of the semantics.
**Arm (b) is the point of the whole thing.** K2 is pre-award: measured by the PM agent on
2026-09-02, ``Prisskjema.xlsx`` has 0/28 unit-price cells filled and the NS 3451 posts 0192 carry
codes and descriptions with no quantities or prices. The columns still exist measured here: an
empty spreadsheet cell arrives as blank space inside its span, so the header row still names
``Mengde`` and ``Enhetspris`` and the table IS a candidate. That is what makes the refusal sharp:
the deriver identifies the columns, reads empty cells, and must refuse BY NAME rather than emit a
``CostBaseline`` with an invented or zero ``unit_cost``.
``pytest.raises(ValueError)`` would NOT have gated that. ``pydantic.ValidationError`` subclasses
``ValueError`` and ``CostBaselineLine.unit_cost`` is ``Field(gt=0)``, so a deriver that fabricated
``0.0`` would raise a ``ValueError`` too and the arm would stay green against the single mutation
it exists to catch. Every refusal arm asserts the NAMED class and the token that distinguishes it.
The fixtures' numbers are chosen so a role-swap is visible: no row has ``quantity ==
unit_cost`` and the two magnitudes differ per row (-(y)'s ``observed != limit`` rule — a fixture
where they coincide cannot tell a correct mapping from a transposed one).
Gates ``okf.derive_cost_baseline`` + ``run.run_project(derive_cost_baseline=)`` + the CLI door.
"""
from __future__ import annotations
import json
import shutil
from pathlib import Path
import pytest
from portfolio_optimiser import okf, run
from portfolio_optimiser.ir import CostBaseline
_FIXTURES = Path(__file__).parent / "fixtures"
_PRICED = str(_FIXTURES / "k2-prisskjema-SYNTETISK")
_UNPRICED = str(_FIXTURES / "k2-prisskjema-uprisert-SYNTETISK")
#: What the priced fixture's table says, transcribed from the fixture rather than from the
#: spreadsheet it was rendered from: arm (a) asserts the derivation equals the TABLE.
_PRICED_ROWS = {
"21.1": (1250.0, 850.0),
"24.2": (48000.0, 42.5),
"36.1": (4.0, 187500.0),
}
def _bundle(path: str) -> okf.Bundle:
return okf.navigate_bundle(path)
def _write_bundle(root: Path, body: str, *, name: str = "table.md") -> str:
root.mkdir(parents=True, exist_ok=True)
(root / "index.md").write_text(
f"---\ntype: index\ntitle: T\n---\n\n[t]({name})\n", encoding="utf-8"
)
(root / name).write_text(f"---\ntype: dataset\ntitle: T\n---\n\n{body}", encoding="utf-8")
return str(root)
# --------------------------------------------------------------------------------------------
# ARM (b) FIRST — the K2 form must refuse, typed and by name.
# --------------------------------------------------------------------------------------------
def test_b_unpriced_k2_form_refuses_by_name() -> None:
"""Codes and descriptions, EMPTY quantity and price columns: the refusal, not a baseline."""
with pytest.raises(okf.CostBaselineDerivationError) as excinfo:
okf.derive_cost_baseline(_bundle(_UNPRICED), project_id="K2")
message = str(excinfo.value)
assert "empty" in message.lower()
# Names the row AND the column: "which post" and "which cell" are two operative questions.
assert "21.1" in message
assert "Enhetspris" in message or "Mengde" in message
def test_b_the_refusal_is_the_named_class_not_a_bare_value_error() -> None:
"""``pytest.raises(ValueError)`` cannot gate this arm: ``pydantic.ValidationError`` IS a
``ValueError``, so a deriver fabricating ``unit_cost=0.0`` would satisfy it. The named class is
what separates 'we refused' from 'pydantic caught our invention on the way out'."""
assert issubclass(okf.CostBaselineDerivationError, ValueError)
with pytest.raises(okf.CostBaselineDerivationError):
okf.derive_cost_baseline(_bundle(_UNPRICED), project_id="K2")
# And the refusal is OURS, raised before any CostBaseline is constructed.
try:
okf.derive_cost_baseline(_bundle(_UNPRICED), project_id="K2")
except okf.CostBaselineDerivationError as exc:
assert type(exc).__name__ == "CostBaselineDerivationError"
def test_b_a_zero_unit_cost_is_refused_by_name_never_carried(tmp_path: Path) -> None:
"""A cell that PARSES but prices nothing. ``CostBaselineLine`` would reject it anyway — the
point is that the refusal names the column instead of surfacing as a schema error about a
number we chose to pass on."""
body = "| Postnr | Mengde | Enhetspris |\n| --- | --- | --- |\n| 21.1 | 1250 | 0 |\n"
with pytest.raises(okf.CostBaselineDerivationError, match="Enhetspris"):
okf.derive_cost_baseline(_bundle(_write_bundle(tmp_path / "zero", body)), project_id="P")
def test_b_partial_pricing_refuses_the_WHOLE_derivation(tmp_path: Path) -> None:
"""One priced row and one unpriced row is not "a baseline with one line". A half-derived
baseline anchors some codes while ``ProvenanceStamp.cost_baseline_anchored`` reports ``True``
a false positive on the one bit made REQUIRED-without-default precisely because both of its
defaults would lie."""
body = (
"| Postnr | Mengde | Enhetspris |\n"
"| --- | --- | --- |\n"
"| 21.1 | 1250 | 850 |\n"
"| 24.2 | 48000 | |\n"
)
with pytest.raises(okf.CostBaselineDerivationError, match="24.2"):
okf.derive_cost_baseline(_bundle(_write_bundle(tmp_path / "half", body)), project_id="P")
# --------------------------------------------------------------------------------------------
# ARM (a) — a priced table derives, and derives what the table SAYS.
# --------------------------------------------------------------------------------------------
def test_a_priced_pandoc_table_derives_the_table_verbatim() -> None:
baseline = okf.derive_cost_baseline(_bundle(_PRICED), project_id="K2")
assert isinstance(baseline, CostBaseline)
assert baseline.project_id == "K2"
assert set(baseline.items) == set(_PRICED_ROWS)
for code, (quantity, unit_cost) in _PRICED_ROWS.items():
assert baseline.items[code].quantity == quantity, code
assert baseline.items[code].unit_cost == unit_cost, code
def test_a_no_row_has_quantity_equal_to_unit_cost() -> None:
"""The fixture's own discriminating property, asserted rather than assumed: if any row had
``quantity == unit_cost``, the arm above would pass against a deriver that transposed the two
columns, and the fixture would be gating nothing."""
for code, (quantity, unit_cost) in _PRICED_ROWS.items():
assert quantity != unit_cost, code
def test_a_the_pipe_table_form_derives_identically(tmp_path: Path) -> None:
"""The csv/sql-sourced form (``render.render_table``) reads through the SAME role mapping."""
body = (
"| Postnr | Beskrivelse | Mengde | Enhetspris |\n"
"| --- | --- | --- | --- |\n"
"| 21.1 | Grunnarbeider | 1250 | 850 |\n"
"| 24.2 | Staal | 48000 | 42.5 |\n"
)
baseline = okf.derive_cost_baseline(
_bundle(_write_bundle(tmp_path / "pipe", body)), project_id="P"
)
assert baseline.items["21.1"].quantity == 1250.0
assert baseline.items["21.1"].unit_cost == 850.0
assert baseline.items["24.2"].unit_cost == 42.5
# --------------------------------------------------------------------------------------------
# Determinism: no judgement. Where the columns cannot be identified UNAMBIGUOUSLY, refuse.
# --------------------------------------------------------------------------------------------
def test_no_identifiable_table_refuses(tmp_path: Path) -> None:
body = "| Fritekst | Notat |\n| --- | --- |\n| a | b |\n"
with pytest.raises(okf.CostBaselineDerivationError, match="no"):
okf.derive_cost_baseline(_bundle(_write_bundle(tmp_path / "none", body)), project_id="P")
def test_two_candidate_tables_refuse_rather_than_pick(tmp_path: Path) -> None:
"""Two priced schedules in one bundle is a DATA question, never one the reader answers by
order of appearance (``scripted_proposer``'s ambiguity rule)."""
root = tmp_path / "two"
body = "| Postnr | Mengde | Enhetspris |\n| --- | --- | --- |\n| 21.1 | 1250 | 850 |\n"
_write_bundle(root, body, name="a.md")
(root / "b.md").write_text(
f"---\ntype: dataset\ntitle: B\n---\n\n{body}",
encoding="utf-8",
)
(root / "index.md").write_text(
"---\ntype: index\ntitle: T\n---\n\n[a](a.md)\n[b](b.md)\n", encoding="utf-8"
)
with pytest.raises(okf.CostBaselineDerivationError) as excinfo:
okf.derive_cost_baseline(_bundle(str(root)), project_id="P")
assert "a.md" in str(excinfo.value) and "b.md" in str(excinfo.value)
def test_a_duplicated_role_in_one_header_refuses(tmp_path: Path) -> None:
"""Two columns claiming the same role: a reader that took the first would be choosing."""
body = (
"| Postnr | Mengde | Enhetspris | Enhetspris |\n"
"| --- | --- | --- | --- |\n"
"| 21.1 | 1250 | 850 | 900 |\n"
)
with pytest.raises(okf.CostBaselineDerivationError, match="Enhetspris"):
okf.derive_cost_baseline(_bundle(_write_bundle(tmp_path / "dup", body)), project_id="P")
def test_a_duplicated_cost_code_refuses(tmp_path: Path) -> None:
"""A dict would silently last-write-win, and the baseline would quietly describe one of two
rows an operator can see in the document."""
body = (
"| Postnr | Mengde | Enhetspris |\n"
"| --- | --- | --- |\n"
"| 21.1 | 1250 | 850 |\n"
"| 21.1 | 20 | 30 |\n"
)
with pytest.raises(okf.CostBaselineDerivationError, match="21.1"):
okf.derive_cost_baseline(_bundle(_write_bundle(tmp_path / "dupcode", body)), project_id="P")
def test_a_number_outside_the_grammar_refuses(tmp_path: Path) -> None:
"""The grammar is dot-decimal, and it is closed. The measured pandoc output emits ``1250.0`` /
``42.5``, so no comma handling is needed and admitting one would import ``1,250``'s
thousands-versus-decimal ambiguity for free, which is exactly the judgement this function is
forbidden to exercise. A hand-authored Norwegian ``1 250,50`` is a REFUSED case, stated."""
for cell in ("1 250", "850,50", "850 kr", "n/a"):
body = f"| Postnr | Mengde | Enhetspris |\n| --- | --- | --- |\n| 21.1 | 1250 | {cell} |\n"
root = _write_bundle(tmp_path / f"num-{abs(hash(cell))}", body)
with pytest.raises(okf.CostBaselineDerivationError, match="Enhetspris"):
okf.derive_cost_baseline(_bundle(root), project_id="P")
def test_the_verdict_layer_is_not_scanned(tmp_path: Path) -> None:
"""Built from ``context_files``, never ``files`` (MAJOR-3's rule): a ``type: verdict`` file is
not project cost data, and a reader over ``files`` would let a prior verdict's table decide a
project's ground truth."""
root = tmp_path / "verdict"
root.mkdir()
(root / "index.md").write_text(
"---\ntype: index\ntitle: T\n---\n\n[v](v.md)\n", encoding="utf-8"
)
(root / "v.md").write_text(
"---\ntype: verdict\ntitle: V\n---\n\n"
"| Postnr | Mengde | Enhetspris |\n| --- | --- | --- |\n| 21.1 | 1250 | 850 |\n",
encoding="utf-8",
)
with pytest.raises(okf.CostBaselineDerivationError):
okf.derive_cost_baseline(_bundle(str(root)), project_id="P")
# --------------------------------------------------------------------------------------------
# CONTROL — the two existing projections are untouched, and the file remains PREFERRED.
# --------------------------------------------------------------------------------------------
def test_control_a_bundle_shipping_a_handwritten_baseline_is_untouched() -> None:
"""``load_optional_cost_baseline`` still answers, unchanged, and remains what the run path
reaches for when the flag is absent."""
shipped = okf.load_optional_cost_baseline("shared/examples/tunnel-hauglia")
assert shipped is not None
assert shipped.project_id == "TUNNEL-HAUGLIA"
assert "ENERGI-TUNNEL-EL" in shipped.items
def test_control_the_fixture_bundles_ship_no_cost_baseline_json() -> None:
"""Otherwise arm (a) could be satisfied by the file loader and prove nothing about derivation."""
for path in (_PRICED, _UNPRICED):
assert not (Path(path) / "cost-baseline.json").exists()
assert okf.load_optional_cost_baseline(path) is None
def test_control_the_fixtures_are_marked_synthetic() -> None:
readme = (_FIXTURES / "README.md").read_text(encoding="utf-8")
assert "SYNTETISK" in readme
assert "SYNTETISK" in Path(_PRICED).name
assert "SYNTETISK" in Path(_UNPRICED).name
# --------------------------------------------------------------------------------------------
# WIRING — the third projection is reachable only behind the explicit flag, never silently.
# --------------------------------------------------------------------------------------------
#: The IR projection ``_project_from_bundle`` fail-fasts on. Written HERE, in the test, and never
#: into the checked-in fixture: ``measure`` and ``claimed_saving_nok`` are the human's or the
#: mandate's to state, so a SYNTETISK K2 bundle carrying one would look like a runnable tender
#: whose candidate measure somebody authored. The derivation reads the priced TABLE; this file only
#: satisfies the pre-existing bundle contract so the wiring can be exercised at all.
_IR_PROJECTION = {
"project_id": "K2",
"measure": "PLACEHOLDER — authored by this test purely to satisfy the bundle contract",
"affected_items": [{"code": "21.1", "quantity": 1250, "unit_cost": 850.0}],
"claimed_saving_nok": 1000.0,
}
def _runnable(source: str, tmp_path: Path) -> str:
"""A copy of a fixture bundle plus the hand-authored IR projection the bundle arm requires."""
root = tmp_path / "runnable"
shutil.copytree(source, root)
(root / "validator-input.json").write_text(json.dumps(_IR_PROJECTION), encoding="utf-8")
return str(root)
async def test_run_project_uses_the_derived_baseline_when_the_flag_is_given(tmp_path: Path) -> None:
bundle_dir = _runnable(_PRICED, tmp_path)
report = await run.run_project(
"K2",
"local",
docs_dir=bundle_dir,
bundle_dir=bundle_dir,
derive_cost_baseline=True,
live_dry_run=True,
)
assert isinstance(report, run.DryRunReport)
assert report.cost_baseline_anchored is True
async def test_run_project_without_the_flag_leaves_the_fixture_unanchored(tmp_path: Path) -> None:
"""The default is the unchanged path: no ``cost-baseline.json``, so no anchoring — and the
derivation is NOT reached. Silence never derives."""
bundle_dir = _runnable(_PRICED, tmp_path)
report = await run.run_project(
"K2", "local", docs_dir=bundle_dir, bundle_dir=bundle_dir, live_dry_run=True
)
assert isinstance(report, run.DryRunReport)
assert report.cost_baseline_anchored is False
async def test_run_project_propagates_the_refusal_rather_than_falling_back(tmp_path: Path) -> None:
"""The flag is an explicit commission. A derivation that fails must not quietly degrade to the
(absent) file and report an un-anchored run: a run that goes ahead on a silently degraded
order is the defect ``load_mandate`` fail-fasts against."""
bundle_dir = _runnable(_UNPRICED, tmp_path)
with pytest.raises(okf.CostBaselineDerivationError):
await run.run_project(
"K2",
"local",
docs_dir=bundle_dir,
bundle_dir=bundle_dir,
derive_cost_baseline=True,
live_dry_run=True,
)
def test_cli_derive_requires_bundle_dir(capsys: pytest.CaptureFixture[str]) -> None:
rc = run.main(["P1", "--docs-dir", "docs", "--derive-cost-baseline"])
assert rc == 1
err = capsys.readouterr().err
# Keyed on the DISTINGUISHING token: every refusal below also contains the flag name.
assert "--bundle-dir" in err
def test_cli_derive_is_refused_in_portfolio_mode(capsys: pytest.CaptureFixture[str]) -> None:
rc = run.main(["--portfolio", "--derive-cost-baseline"])
assert rc == 1
err = capsys.readouterr().err
assert "--portfolio" in err
assert "single-project mode" in err
def test_cli_derive_is_refused_in_report_mode(
tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
"""Report mode returns BEFORE every downstream refusal, so a flag missing from
``report_forbidden`` is silently DROPPED rather than refused (F4 found exactly that gap). The
argv is one report mode would otherwise ACCEPT, so rc 1 is the mutant's opposite outcome."""
ledger = tmp_path / "ledger.json"
ledger.write_text(json.dumps([]), encoding="utf-8")
assert run.main(["--report", "--ledger", str(ledger)]) == 0
capsys.readouterr()
rc = run.main(["--report", "--ledger", str(ledger), "--derive-cost-baseline"])
assert rc == 1
assert "mode-exclusive" in capsys.readouterr().err