portfolio-optimiser-claude/tests/test_provenance_sdk_version_loadbearing.py
Kjell Tore Guttormsen bf87776bb3 feat(portfolio): stamp the producing SDK build in provenance (wiki-advisory F1) [skip-docs]
The advisory finding: provenance.py/artifacts.py stamped no SDK version, while
the SDK's total_cost_usd is a client-side ESTIMATE computed against a price
table frozen when the SDK was built. An untraceable estimate is a figure nobody
can check later, so the run now records which build produced it.

Provenance gains sdk_version: str | None. The value comes from the PRODUCING
CLIENT — getattr(client, "sdk_version", None) — exactly as model and cost_usd
already do, never from importlib.metadata at stamp time. That distinction is
the seam: a run driven by the scripted stand-in used no SDK at all, and
stamping the installed version there would attribute a build to a run that
never touched it (§1). SdkModelClient reads the installed build once from
package metadata (offline: no key, no network); every other client reports
null. A blank string is refused by the schema — null is the one way to say
"not produced by the SDK".

Scope note: this traceability covers OUR run cost only. The savings the
framework recommends are settled by the deterministic validator against the
golden suite, and no SDK estimate touches them.

Two seams, both detach-proven RED:
- make the stamp read importlib.metadata instead of the client → a scripted run
  claims a build it never used → red
- back-fill runs/s10/provenance.json → red

That second guard is the point of the change as much as the first. runs/s10/ is
the byte-frozen record of the ONE live run (2026-07-03), executed before this
field existed; the suite reads it nowhere else, so nothing would have caught a
retro-stamp. Adding a build id to it now would be a guess presented as
provenance. It stays without one, and the README says why.

run_s10.py is deliberately untouched (byte-frozen fasit script), and the field
defaults to None, so every existing caller and artifact shape is unchanged.

603 passed · ruff clean · mypy strict clean · runs/s10/ byte-identical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQu2xxwedckjU56byu1aUG
2026-07-25 06:57:30 +02:00

140 lines
6.1 KiB
Python

"""SDK build in the provenance stamp — LOAD-BEARING (§1, §9, §11).
The seam this file keeps alive: a run's provenance records **which SDK build
produced it**, and it takes that from the PRODUCING CLIENT, never from the
environment. That distinction is the whole point. The SDK's reported
``total_cost_usd`` is a client-side estimate computed against a price table
frozen when the SDK was built, so a cost figure is only traceable if the run
says which build produced it. But a run driven by the scripted stand-in used no
SDK at all — stamping the installed version there would attribute a build to a
run that never touched it, which is exactly the fabrication §1 forbids.
Detach proof: make the stamp read ``importlib.metadata`` instead of the client
→ a scripted run claims an SDK build it never used → red.
Second guard: the S10 fasit predates this field and is a historical record of a
run executed 2026-07-03. Retro-stamping it would invent provenance for a run
whose SDK build we would be guessing. The fasit stays byte-frozen — red the
moment someone back-fills it.
"""
from __future__ import annotations
import importlib.metadata
import json
from pathlib import Path
from typing import Callable
import pytest
from pydantic import ValidationError
from _scripted import ScriptedClient, reply
from portfolio_optimiser_claude.contracts import Contracts, ModelMapContract
from portfolio_optimiser_claude.ir import load_validator_input
from portfolio_optimiser_claude.loop import ModelClient
from portfolio_optimiser_claude.provenance import Citation, Provenance
from portfolio_optimiser_claude.run import main
from portfolio_optimiser_claude.sdk_client import SdkModelClient
REPO_ROOT = Path(__file__).resolve().parents[1]
BUNDLE = REPO_ROOT / "shared" / "examples" / "bygg-energi-mikro"
S10_PROVENANCE = REPO_ROOT / "runs" / "s10" / "provenance.json"
ClientFactory = Callable[[Contracts, float], ModelClient]
# A build id no installed package could ever report, so a test that sees it
# knows the value came from the CLIENT and from nowhere else.
FIXTURE_BUILD = "0.2.120-fixture-not-installed"
def _scripted_factory(*, sdk_version: str | None) -> tuple[ClientFactory, list[ScriptedClient]]:
created: list[ScriptedClient] = []
def factory(contracts: Contracts, max_budget_usd_per_call: float) -> ModelClient:
client = ScriptedClient(
replies=[
reply("debate reasoning"),
reply("VERDICT: APPROVE"),
reply(json.dumps(load_validator_input(BUNDLE).model_dump())),
]
)
if sdk_version is not None:
# Only a client that genuinely carries the attribute reports one.
client.sdk_version = sdk_version # type: ignore[attr-defined]
created.append(client)
return client
return factory, created
def _run_and_read_provenance(tmp_path: Path, *, sdk_version: str | None) -> dict[str, object]:
out = tmp_path / "out"
factory, _ = _scripted_factory(sdk_version=sdk_version)
code = main(["--bundle", str(BUNDLE), "--out", str(out)], client_factory=factory)
assert code == 0
payload: dict[str, object] = json.loads((out / "provenance.json").read_text(encoding="utf-8"))
return payload
class TestTheStampComesFromTheClient:
"""LOAD-BEARING (§11): the producing client reports the build, or nobody does."""
def test_a_run_not_produced_by_the_sdk_stamps_no_build(self, tmp_path: Path) -> None:
# Detach point: read importlib.metadata instead of the client → this
# scripted run claims the installed build it never used → RED.
payload = _run_and_read_provenance(tmp_path, sdk_version=None)
assert payload["sdk_version"] is None
assert payload["sdk_version"] != importlib.metadata.version("claude-agent-sdk")
def test_a_producing_client_has_its_build_stamped_verbatim(self, tmp_path: Path) -> None:
payload = _run_and_read_provenance(tmp_path, sdk_version=FIXTURE_BUILD)
assert payload["sdk_version"] == FIXTURE_BUILD
def test_the_sdk_client_reports_the_installed_build_offline(self) -> None:
# Constructing the real client needs no key and touches no network
# (the same premise test_sdk_isolation.py relies on), so the build id
# it exposes is readable in the offline suite.
client = SdkModelClient(
ModelMapContract(profiles={"anthropic": {"default": "claude-haiku-4-5-20251001"}})
)
assert client.sdk_version == importlib.metadata.version("claude-agent-sdk")
class TestTheStampStaysOptional:
"""The field is additive: every existing caller keeps validating unchanged."""
def test_provenance_validates_without_a_build(self) -> None:
stamp = Provenance(
citations=[Citation(file="index.md", span="chars 0-1", snippet="x")],
model="claude-haiku-4-5-20251001",
role="proposer",
validator_decision="validated",
tokens_used=10,
)
assert stamp.sdk_version is None
def test_a_blank_build_is_refused_rather_than_stored(self) -> None:
# An empty string would read as "no SDK" while occupying the field —
# null is the one way to say "not produced by the SDK" (§1).
with pytest.raises(ValidationError):
Provenance(
citations=[Citation(file="index.md", span="chars 0-1", snippet="x")],
model="m",
role="proposer",
validator_decision="validated",
tokens_used=10,
sdk_version="",
)
class TestTheFasitIsNotRetroStamped:
"""LOAD-BEARING (§1): the historical record is never back-filled."""
def test_the_s10_provenance_carries_no_sdk_build(self) -> None:
# runs/s10/ is the byte-frozen record of the ONE live run (2026-07-03),
# executed before this field existed. Adding a build id to it now would
# be a guess presented as provenance. RED if someone back-fills it.
payload = json.loads(S10_PROVENANCE.read_text(encoding="utf-8"))
assert "sdk_version" not in payload
assert payload["model"] == "claude-haiku-4-5-20251001"