feat(s36): MAF-free costsim skeleton + model-map reader + registration

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 09:54:25 +02:00
commit 47147e5f7c
3 changed files with 92 additions and 1 deletions

View file

@ -0,0 +1,45 @@
"""S3.6 costsim (kostnadssimulering) — offline, MAF-free, deterministic load-bearing seams.
``costsim`` estimates token/cost for a portfolio run BEFORE running (what-if over the model-map ×
effort levels), with pricing as schema-validated config. It is MAF-free (reads ``model_map.json`` as
plain data via ``__file__``-relative path, never imports ``backends``/``budget``/``contracts``)
proven load-bearing by BOTH the direct-import AST guard (``test_okf_is_maf_free`` covering
``costsim.py``) AND the transitive import-graph seam below.
"""
from __future__ import annotations
import subprocess
import sys
from pathlib import Path
_COSTSIM = Path(__file__).resolve().parents[1] / "src" / "portfolio_optimiser" / "costsim.py"
def test_costsim_registered_maf_free() -> None:
"""Meta: costsim.py is registered in the MAF-free guard list, so ``test_okf_is_maf_free``
actually scans it otherwise the MAF-free claim would be green-but-dead (never checked)."""
from tests.test_okf import _MAF_FREE_MODULES
assert "costsim.py" in _MAF_FREE_MODULES
def test_costsim_import_is_maf_free() -> None:
"""The genuine transitive-import seam: loading costsim.py's module body in ISOLATION must NOT
pull ``agent_framework``/``mcp`` into ``sys.modules``. Loaded standalone via
``spec_from_file_location`` NOT as ``portfolio_optimiser.costsim``, since the package
``__init__`` imports MAF-bound ``run``/``contracts`` and would mask costsim's own cleanliness.
The AST guard (``test_okf_is_maf_free``) only catches DIRECT imports; a transitive
``from portfolio_optimiser.contracts import ...`` in costsim would slip past it but trip THIS
check ``exec_module`` runs costsim's import statements, so any MAF-bound import lands
``agent_framework`` in ``sys.modules``. Detach point: add such an import RED."""
check = (
"import importlib.util, sys\n"
f"spec = importlib.util.spec_from_file_location('costsim_standalone', {str(_COSTSIM)!r})\n"
"mod = importlib.util.module_from_spec(spec)\n"
"spec.loader.exec_module(mod)\n"
"assert 'agent_framework' not in sys.modules, 'agent_framework leaked into costsim import graph'\n"
"assert 'mcp' not in sys.modules, 'mcp leaked into costsim import graph'\n"
)
result = subprocess.run([sys.executable, "-c", check], capture_output=True, text=True)
assert result.returncode == 0, result.stderr

View file

@ -18,7 +18,7 @@ from portfolio_optimiser import okf
# Framework-neutral, D7-portable modules that must never import MAF/mcp (C2:
# the guard previously scanned only okf.py; dimension.py is now covered too).
_MAF_FREE_MODULES = ["okf.py", "dimension.py", "outbox.py"]
_MAF_FREE_MODULES = ["okf.py", "dimension.py", "outbox.py", "costsim.py"]
BUNDLE_DIR = Path(__file__).resolve().parents[1] / "shared" / "examples" / "bygg-energi-mikro"