Source: tests/dynamic/test_policy_checkpoint.py · Plain text

tests/dynamic/test_policy_checkpoint.py

import hashlib

import pytest
import torch

from mcss.dynamic.policy import FEATURE_SCHEMA_VERSION, LearnedActionPolicy
from mcss.dynamic.policy_checkpoint import load_policy_checkpoint, save_policy_checkpoint


def _binding():
    return {
        "model_content_hash": "carrier-write-content",
        "feature_schema_version": FEATURE_SCHEMA_VERSION,
        "render_protocol": {"renderer": "fixed", "n_samples": 8},
        "budget_protocol": {"max_units": 1000.0},
        "utility_protocol": {"horizon": 2, "metric": "depth_abs_rel"},
        "teacher_dataset_hash": "teacher-dataset",
        "teacher_source_hashes": {"rows.jsonl": "source-hash"},
    }


def test_policy_checkpoint_round_trip_and_expected_binding(tmp_path):
    torch.manual_seed(2)
    policy = LearnedActionPolicy(hidden_dim=7, seed=4, target_mean=0.2, target_scale=0.5)
    path = tmp_path / "policy.pt"
    saved_hash = save_policy_checkpoint(
        path,
        policy,
        binding=_binding(),
        provenance={"seed": 4},
    )

    restored, metadata = load_policy_checkpoint(
        path,
        expected_binding={
            "model_content_hash": "carrier-write-content",
            "feature_schema_version": FEATURE_SCHEMA_VERSION,
            "budget_protocol": {"max_units": 1000.0},
        },
    )
    assert restored.hidden_dim == 7
    assert restored.target_mean == 0.2
    assert restored.target_scale == 0.5
    assert metadata["binding"] == _binding()
    assert metadata["provenance"] == {"seed": 4}
    assert saved_hash == hashlib.sha256(path.read_bytes()).hexdigest()
    for name, value in policy.state_dict().items():
        torch.testing.assert_close(value.cpu(), restored.state_dict()[name].cpu())


def test_policy_checkpoint_rejects_binding_mismatch(tmp_path):
    path = tmp_path / "policy.pt"
    save_policy_checkpoint(path, LearnedActionPolicy(hidden_dim=4), binding=_binding())

    with pytest.raises(ValueError, match="binding mismatch"):
        load_policy_checkpoint(
            path,
            expected_binding={"model_content_hash": "different"},
        )


def test_policy_checkpoint_rejects_missing_teacher_hash(tmp_path):
    path = tmp_path / "policy.pt"
    binding = _binding()
    binding["teacher_dataset_hash"] = ""

    with pytest.raises(ValueError, match="teacher_dataset_hash"):
        save_policy_checkpoint(path, LearnedActionPolicy(hidden_dim=4), binding=binding)


def test_policy_checkpoint_does_not_accept_dynamic_v1_payload(tmp_path):
    path = tmp_path / "policy.pt"
    torch.save({"schema_version": "mcss.dynamic.v1"}, path)

    with pytest.raises(ValueError, match="policy checkpoint schema"):
        load_policy_checkpoint(path)