announce() read _DEFAULT_MAX_ROUNDS/_DEFAULT_MAX_TOKENS directly, which was correct only while main() could not do otherwise. MEASURED on the first free drill after --max-rounds landed: the same stdout said "Stops at: 3 rounds / 100000 tokens" two lines above "max_rounds=8, max_tokens=120000". The announcement is the ONE thing printed before the first paid call and its whole job is to say what the run will do -- the Fase-3 class, introduced by the very flag being announced. Load-bearing MEASURED: arm (f) red before the fix; M16 (read the constants again) -> 1 red, that arm ALONE. Green control 1670/5, golden BYTE-UNCHANGED (ea8c534...). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
185 lines
7.1 KiB
Python
185 lines
7.1 KiB
Python
"""P16 B2 - the CLI had NO door onto a paid run's cap, and the documented command proved it.
|
|
|
|
**The measured silence.** ``STATE.md``, ``docs/2026-09-12-p14-kontekstsett.md § 4.1`` and order
|
|
``20260914T091846Z`` all publish the same stress command, ending ``--max-rounds 8 --max-tokens
|
|
120000``. Measured 14.09: ``run.py`` accepts neither flag, all four free ``--live-dry-run`` drills
|
|
refused with ``unrecognized arguments``, and ``main()`` never passed ``max_rounds``/``max_tokens``
|
|
to ``run_project`` at all - so **every CLI run ever made was silently bound to the defaults**
|
|
(``max_rounds=3``, ``max_tokens=100_000``) with no way for an operator to raise or lower the cap on
|
|
a run they were paying for. Three surfaces described a door that did not exist: the Fase-3 class
|
|
(a claim the surface makes about itself), spread across the operator's own instructions.
|
|
|
|
**Widening, never breaking.** Both flags DEFAULT to the values ``run_project`` already used, so
|
|
every invocation that exists is byte-identical; what changes is that the cap can now be stated.
|
|
|
|
**Wired to BOTH dispatches, and refused in report mode.** ``run_portfolio`` takes the same two
|
|
parameters and ``main()`` dropped them there too, so a portfolio pass could not be capped either.
|
|
Report mode returns ABOVE every dispatch, so a flag left out of ``report_forbidden`` is a SILENT
|
|
DROP rather than a refusal - the gap F4 measured on ``--plan-review``.
|
|
|
|
Arms: (a) the flags parse and reach ``run_project`` * (b) they reach ``run_portfolio`` *
|
|
(c) the defaults are unchanged when the flags are absent * (d) report mode refuses each by name *
|
|
(e) the free dry-run drill accepts the documented command form.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from portfolio_optimiser import run as run_module
|
|
|
|
_REPO = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def _base(root: Path) -> Path:
|
|
base = root / "b"
|
|
(base / "krav").mkdir(parents=True)
|
|
(base / "index.md").write_text("---\nbundle_id: b\n---\n\n- [c](krav/c.md)\n", encoding="utf-8")
|
|
(base / "krav" / "c.md").write_text(
|
|
'---\ntype: concept\ntitle: "C"\n---\n\nbody\n', encoding="utf-8"
|
|
)
|
|
return base
|
|
|
|
|
|
def _capture(monkeypatch: pytest.MonkeyPatch, target: str) -> dict[str, Any]:
|
|
"""Record the kwargs main() hands the named coroutine, then stop the run."""
|
|
seen: dict[str, Any] = {}
|
|
|
|
async def _fake(*args: Any, **kwargs: Any) -> Any:
|
|
seen.update(kwargs)
|
|
raise SystemExit(0)
|
|
|
|
monkeypatch.setattr(run_module, target, _fake)
|
|
return seen
|
|
|
|
|
|
def test_a_the_two_flags_reach_run_project(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
|
seen = _capture(monkeypatch, "run_project")
|
|
with pytest.raises(SystemExit):
|
|
b = str(_base(tmp_path))
|
|
run_module.main(
|
|
[
|
|
"P1",
|
|
"--profile",
|
|
"local",
|
|
"--docs-dir",
|
|
b,
|
|
"--bundle-dir",
|
|
b,
|
|
"--max-rounds",
|
|
"8",
|
|
"--max-tokens",
|
|
"120000",
|
|
]
|
|
)
|
|
assert seen["max_rounds"] == 8
|
|
assert seen["max_tokens"] == 120000
|
|
|
|
|
|
def test_b_the_two_flags_reach_run_portfolio(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
seen = _capture(monkeypatch, "run_portfolio")
|
|
with pytest.raises(SystemExit):
|
|
run_module.main(
|
|
["--portfolio", "--profile", "local", "--max-rounds", "9", "--max-tokens", "77000"]
|
|
)
|
|
assert seen["max_rounds"] == 9
|
|
assert seen["max_tokens"] == 77000
|
|
|
|
|
|
def test_c_absent_flags_keep_the_values_every_run_so_far_used(
|
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
) -> None:
|
|
"""Widening, never breaking: the defaults ARE what main() bound implicitly before."""
|
|
seen = _capture(monkeypatch, "run_project")
|
|
with pytest.raises(SystemExit):
|
|
b = str(_base(tmp_path))
|
|
run_module.main(["P1", "--profile", "local", "--docs-dir", b, "--bundle-dir", b])
|
|
assert seen["max_rounds"] == run_module._DEFAULT_MAX_ROUNDS == 3
|
|
assert seen["max_tokens"] == run_module._DEFAULT_MAX_TOKENS == 100_000
|
|
|
|
|
|
@pytest.mark.parametrize("flag,value", [("--max-rounds", "8"), ("--max-tokens", "120000")])
|
|
def test_d_report_mode_refuses_each_flag_by_name(
|
|
flag: str, value: str, tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
ledger = tmp_path / "l.json"
|
|
ledger.write_text("[]", encoding="utf-8")
|
|
# The control: the SAME argv without the flag is ACCEPTED, so rc 1 below is the mutant's
|
|
# opposite outcome and not the fixture refusing for a reason of its own.
|
|
assert run_module.main(["--report", "--ledger", str(ledger)]) == 0
|
|
assert run_module.main(["--report", "--ledger", str(ledger), flag, value]) == 1
|
|
assert "mode-exclusive" in capsys.readouterr().err
|
|
|
|
|
|
def test_e_the_documented_dry_run_command_form_is_accepted(tmp_path: Path) -> None:
|
|
"""The B2 drill itself: the command STATE.md and the order publish must at least parse."""
|
|
base = _base(tmp_path)
|
|
proc = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
"-m",
|
|
"portfolio_optimiser.run",
|
|
"P1",
|
|
"--profile",
|
|
"local",
|
|
"--docs-dir",
|
|
str(base),
|
|
"--bundle-dir",
|
|
str(base),
|
|
"--max-rounds",
|
|
"8",
|
|
"--max-tokens",
|
|
"120000",
|
|
"--live-dry-run",
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
cwd=_REPO,
|
|
)
|
|
assert "unrecognized arguments" not in proc.stderr, proc.stderr
|
|
assert proc.returncode == 0, proc.stderr
|
|
|
|
|
|
def test_f_the_announcement_states_the_cap_the_run_will_actually_use(
|
|
tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
"""``announce`` is the ONE thing printed BEFORE the first paid call, and its whole job is to
|
|
say what the run will do. It read ``_DEFAULT_MAX_ROUNDS``/``_DEFAULT_MAX_TOKENS`` directly,
|
|
which was correct only by accident while ``main()`` could not do otherwise. MEASURED on the
|
|
first free drill after the flags landed: the same stdout said ``Stops at: 3 rounds /
|
|
100000 tokens`` two lines above ``max_rounds=8, max_tokens=120000`` — the Fase-3 class (a
|
|
claim the surface makes about itself), introduced by the flag it announces."""
|
|
base = _base(tmp_path)
|
|
mandate = tmp_path / "m.json"
|
|
mandate.write_text(
|
|
'{"objective":"o","success_criteria":"s","approaches":'
|
|
'[{"id":"a1","label":"L","affected_codes":["C"],"claimed_saving_nok":1.0}]}',
|
|
encoding="utf-8",
|
|
)
|
|
rc = run_module.main(
|
|
[
|
|
"P1",
|
|
"--profile",
|
|
"local",
|
|
"--docs-dir",
|
|
str(base),
|
|
"--bundle-dir",
|
|
str(base),
|
|
"--mandate",
|
|
str(mandate),
|
|
"--max-rounds",
|
|
"8",
|
|
"--max-tokens",
|
|
"120000",
|
|
"--live-dry-run",
|
|
]
|
|
)
|
|
assert rc == 0
|
|
out = capsys.readouterr().out
|
|
assert "Stops at: 8 rounds / 120000 tokens" in out, out
|
|
assert "3 rounds / 100000 tokens" not in out
|