feat(s36): costsim CLI + no-network + no-hardcoded-price guards

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0145ZKPLMVeqM47z2jxxokym
This commit is contained in:
Kjell Tore Guttormsen 2026-07-15 10:05:29 +02:00
commit 76d9f793c6
3 changed files with 127 additions and 0 deletions

View file

@ -90,3 +90,18 @@ def test_estimate_table_structure() -> None:
assert isinstance(row["estimat_kr"], str) # edge-formatted string, never a float
assert table["kost_mot_verdi"]["kjoring_kostet_ore"] > 0
assert table["pricing_source"] == "unit-test prices"
def test_cli_prints_estimate_table(capsys: pytest.CaptureFixture[str]) -> None:
"""CLI smoke: ``main`` prints the estimate table + kost-mot-verdi + adoption footer, rc 0."""
rc = costsim.main(["--projects", "4", "--profile", "local"])
out = capsys.readouterr().out
assert rc == 0
assert "Kostnadsestimat" in out
assert "kost-mot-verdi" in out
assert "Adopsjonssti" in out
def test_cli_bad_config_returns_nonzero(capsys: pytest.CaptureFixture[str]) -> None:
rc = costsim.main(["--pricing", "/nonexistent/pricing.json"])
assert rc == 1

View file

@ -9,6 +9,7 @@ proven load-bearing by BOTH the direct-import AST guard (``test_okf_is_maf_free`
from __future__ import annotations
import ast
import subprocess
import sys
from pathlib import Path
@ -168,3 +169,50 @@ def test_table_is_byte_deterministic() -> None:
t1 = costsim.build_estimate_table("p", ["low", "high"], pricing, n_projects=3, model_map=mm)
t2 = costsim.build_estimate_table("p", ["low", "high"], pricing, n_projects=3, model_map=mm)
assert costsim.dump_estimate_table(t1).encode() == costsim.dump_estimate_table(t2).encode()
def _costsim_ast() -> ast.Module:
return ast.parse(_COSTSIM.read_text(encoding="utf-8"))
def test_no_network_path_in_module() -> None:
"""SC4: costsim imports NO network library — offline by construction. Detach point: add a
``urllib``/``http``/``socket`` import RED."""
net = {"socket", "urllib", "http", "requests", "httpx", "ftplib", "smtplib"}
imported: list[str] = []
for node in ast.walk(_costsim_ast()):
if isinstance(node, ast.Import):
imported += [a.name.split(".")[0] for a in node.names]
elif isinstance(node, ast.ImportFrom):
imported.append((node.module or "").split(".")[0])
offenders = sorted(set(imported) & net)
assert offenders == [], f"costsim must have no network import, found: {offenders}"
def test_costsim_source_has_no_float_literal() -> None:
"""SC3 supporting guard (well-defined): costsim is pure integer-øre, so ANY ``float`` literal in
its source is either a stray hardcoded price or a no-float-invariant violation. Detach point:
hardcode a float price (e.g. ``0.03``) RED. The behavioral sentinel test below is the PRIMARY
price-from-config seam; this literal check backs it up without the brittle allowlist a
'price-shaped number' scan would need."""
floats = [
node.value
for node in ast.walk(_costsim_ast())
if isinstance(node, ast.Constant) and isinstance(node.value, float)
]
assert floats == [], (
f"costsim must contain no float literal (integer øre only), found: {floats}"
)
def test_price_comes_from_config() -> None:
"""SC3 PRIMARY seam (behavioral): a sentinel price in injected config flows into the estimate. A
hardcoded price shadowing config would NOT yield the sentinel-derived value RED."""
sentinel = {
"source": "sentinel",
"date": "2026-07-15",
"models": {"m-x": {"ore_per_1k_tokens": 999_999}},
}
pricing = costsim.PricingContract(**sentinel)
expected = 100_000 * 999_999 // 1000 # standard effort (100%), default max_tokens
assert costsim.estimate_run_ore("m-x", "standard", pricing) == expected