diff --git a/assumptions_version.py b/assumptions_version.py new file mode 100644 index 00000000..ade5070f --- /dev/null +++ b/assumptions_version.py @@ -0,0 +1,80 @@ +"""Governed version receipt for observable simulation-result semantics.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Literal + + +AssumptionsVersionState = Literal["declared", "legacy_unverified"] + + +# Observable semantics owned by these modules are governed by this manifest. +# Keep this list here (rather than only in a test) so the scope of the contract +# is reviewable beside each release entry. +GOVERNED_SEMANTIC_MODULES: tuple[str, ...] = ( + "api/services.py", + "config/config_interfaces.py", + "country_registry.py", + "exchange_rate_service.py", + "simulation_core.py", + "simulation_engine/core.py", + "simulation_engine/detail.py", + "simulation_engine/multi_point.py", + "simulation_engine/result_artifact_materializer.py", + "simulation_engine/tasks.py", + "simulation_engine/validation.py", + "simulation_replay.py", + "simulation_tasks.py", + "storage/result_artifacts.py", + "tax.py", + "tax_engine/col_index.py", + "tax_engine/cross_border_assumptions.py", + "tax_engine/policies.py", + "tax_engine/regimes.py", + "tax_engine/treaties.py", + "tax_engine/vectorized.py", + "tax_engine/wrappers.py", +) + + +@dataclass(frozen=True) +class AssumptionsManifest: + version: str + effective_at: str + changes: tuple[str, ...] + governed_fingerprint: str + + +# Append a manifest entry whenever a governed semantic change is released. The +# fingerprint is checked by tests; see docs/assumptions-version-contract.md. +MANIFEST_HISTORY: tuple[AssumptionsManifest, ...] = ( + AssumptionsManifest( + version="2026-08-11.v1", + effective_at="2026-08-11T23:04:33Z", + changes=( + "expanded_country_registry", + "country_comparison_assumption_receipts_and_col_semantics", + "compact_sweep_point_materialization", + ), + governed_fingerprint="bbcd18460354ef2878d64d71ee19f189bf0aa039b57878570e4637faf6aa2ef1", + ), +) + +CURRENT_MANIFEST = MANIFEST_HISTORY[-1] +AMBIGUOUS_LEGACY_VERSIONS = frozenset({"2026-06-23.v1"}) + + +def state_for(version: str | None) -> AssumptionsVersionState: + """Classify a persisted receipt without inferring or backfilling it.""" + if version is None or version in AMBIGUOUS_LEGACY_VERSIONS: + return "legacy_unverified" + declared = {entry.version for entry in MANIFEST_HISTORY} + return "declared" if version in declared else "legacy_unverified" + + +def current_receipt() -> dict[str, str]: + return { + "assumptions_version": CURRENT_MANIFEST.version, + "assumptions_version_state": "declared", + } diff --git a/tests/test_assumptions_version.py b/tests/test_assumptions_version.py new file mode 100644 index 00000000..93fb3d7b --- /dev/null +++ b/tests/test_assumptions_version.py @@ -0,0 +1,91 @@ +from __future__ import annotations + +import hashlib +from pathlib import Path + +import assumptions_version +from storage import repository + + +ROOT = Path(__file__).resolve().parents[1] + + +def _governed_fingerprint() -> str: + lines = [] + for relative_path in assumptions_version.GOVERNED_SEMANTIC_MODULES: + digest = hashlib.sha256((ROOT / relative_path).read_bytes()).hexdigest() + lines.append(f"{digest} {relative_path}\n") + return hashlib.sha256("".join(lines).encode()).hexdigest() + + +def test_governed_semantics_include_server_input_transforms() -> None: + assert "api/services.py" in assumptions_version.GOVERNED_SEMANTIC_MODULES + + +def test_current_manifest_is_well_formed_and_matches_governed_semantics() -> None: + manifest = assumptions_version.CURRENT_MANIFEST + assert manifest.version == "2026-08-11.v1" + assert manifest.effective_at.endswith("Z") + assert manifest.changes + assert len(set(manifest.changes)) == len(manifest.changes) + assert manifest.governed_fingerprint == _governed_fingerprint() + + +def test_legacy_and_ambiguous_receipts_are_never_declared() -> None: + assert assumptions_version.state_for(None) == "legacy_unverified" + assert assumptions_version.state_for("2026-06-23.v1") == "legacy_unverified" + assert assumptions_version.state_for("unknown") == "legacy_unverified" + assert assumptions_version.state_for(assumptions_version.CURRENT_MANIFEST.version) == "declared" + + +def test_production_has_no_duplicate_assumptions_version_literal() -> None: + literal = assumptions_version.CURRENT_MANIFEST.version + owners = [] + for path in ROOT.rglob("*.py"): + if "tests" in path.parts or path.name == "assumptions_version.py": + continue + if literal in path.read_text(): + owners.append(str(path.relative_to(ROOT))) + assert owners == [] + + +def test_enqueue_persists_manifest_receipt(clean_database) -> None: + from config.default_config import sc + + with repository.get_connection(clean_database) as conn: + repository.insert_config(conn, name="semantic-receipt", simulation_config=sc) + run = repository.enqueue_run(conn, config_name="semantic-receipt") + assert run.assumptions_version == assumptions_version.CURRENT_MANIFEST.version + assert run.assumptions_version_state == "declared" + + stored = repository.get_run(conn, run.id) + assert stored.assumptions_version == run.assumptions_version + assert stored.assumptions_version_state == "declared" + + +def test_deterministic_replay_has_new_receipt_and_preserves_legacy_source( + clean_database, +) -> None: + from config.default_config import sc + + with repository.get_connection(clean_database) as conn: + repository.insert_config(conn, name="replay-source", simulation_config=sc) + source = repository.create_succeeded_run( + conn, + config_name="replay-source", + stats={"probability_of_failure": 0.1}, + runtime_ms=1, + random_seed=8675309, + assumptions_version="2026-06-23.v1", + ) + + replay = repository.enqueue_deterministic_replay(conn, source_run=source) + + assert replay.random_seed == source.random_seed + assert replay.assumptions_version == assumptions_version.CURRENT_MANIFEST.version + assert replay.assumptions_version_state == "declared" + assert replay.source_assumptions_version == "2026-06-23.v1" + assert replay.source_assumptions_version_state == "legacy_unverified" + stored_source = repository.get_run(conn, source.id) + assert stored_source.assumptions_version == "2026-06-23.v1" + assert stored_source.assumptions_version_state == "legacy_unverified"