Source: tests/test_support_redesign_contracts.py · Plain text

tests/test_support_redesign_contracts.py

"""Support redesign boundaries without any real holdout loading."""

import hashlib
import json
from dataclasses import FrozenInstanceError, asdict, replace

import pytest

from mcss.mechanism_pilot.support_redesign_contracts import (
    EXPOSED_SCENES,
    FinalHoldoutGate,
    MatchedTrainingConfig,
    SceneRoleLock,
    SupportProtocolConfig,
    assert_matched_training,
    old_exposed_manifest,
    seal_method_lock,
    validate_dev_scene_ids,
)


def roles():
    train = ("train0", "train1", "train2")
    dev = tuple(f"dev{i}" for i in range(8))
    held = tuple(f"held{i}" for i in range(8))
    identities = tuple((sid, f"physical_{sid}") for sid in train + dev + held + EXPOSED_SCENES)
    return SceneRoleLock(train, dev, held, physical_identities=identities)


def fixture_lock(tmp_path):
    config, scene_roles = SupportProtocolConfig("R0"), roles()
    paths = {}
    for name in ["carrier_checkpoint", "support_config", "scene_role_lock", "inference_code"]:
        path = tmp_path / name
        path.write_text(name)
        paths[name] = path
    gate = tmp_path / "gate.json"
    config_digest = hashlib.sha256(
        json.dumps(asdict(config), sort_keys=True, separators=(",", ":"), allow_nan=False).encode()
    ).hexdigest()
    gate.write_text(
        json.dumps(
            {
                "status": "PASS",
                "method": config.method,
                "config_digest": config_digest,
                "dev_scene_ids": list(scene_roles.redesign_dev_scenes),
                "artifact_hashes": {
                    name: hashlib.sha256(path.read_bytes()).hexdigest()
                    for name, path in paths.items()
                },
            }
        )
    )
    return config, scene_roles, paths, gate, seal_method_lock(config, scene_roles, paths, gate)


@pytest.mark.parametrize("name", ["ORACLE_VOLUME", "ORACLE_SUPPORT", "ORACLE_VOLUME_SUPPORT"])
def test_oracle_cannot_enter_deployment_or_holdout(name):
    with pytest.raises(PermissionError):
        SupportProtocolConfig(name)
    config = SupportProtocolConfig(
        name, scope="DIAGNOSTIC_ORACLE", input_channels=("context_gt_depth", "query_gt_geometry")
    )
    config.assert_allowed_scene_role("REDESIGN_DEV")
    with pytest.raises(PermissionError):
        config.assert_allowed_scene_role("FINAL_QUALIFICATION_HOLDOUT")


@pytest.mark.parametrize("channel", ["context_gt_depth", "query_gt", "future_rgb", "model_score"])
def test_gt_free_config_rejects_forbidden_input(channel):
    with pytest.raises(PermissionError):
        SupportProtocolConfig("R1", input_channels=(channel,))


def test_configuration_is_deeply_immutable():
    config = SupportProtocolConfig("R2", hyperparameters=(("threshold", 0.5),))
    with pytest.raises(FrozenInstanceError):
        config.method = "R1"
    with pytest.raises(TypeError):
        SupportProtocolConfig("R1", input_channels=["arrived_rgb"])
    with pytest.raises(TypeError):
        SupportProtocolConfig("R1", hyperparameters=(("nested", []),))


def test_holdout_denied_before_method_lock(tmp_path):
    gate = FinalHoldoutGate(tmp_path / "claim.json")
    with pytest.raises(PermissionError, match="before sealed"):
        gate.claim(SupportProtocolConfig("R0"), roles().final_holdout_scenes)
    assert not gate.claim_path.exists()


def test_failed_dev_gate_denied(tmp_path):
    config, scene_roles, paths, gate, _ = fixture_lock(tmp_path)
    gate.write_text(json.dumps({"status": "FAIL"}))
    with pytest.raises(PermissionError):
        seal_method_lock(config, scene_roles, paths, gate)


def test_atomic_once_claim_and_model_scene_allowlist(tmp_path):
    config, scene_roles, _, _, lock = fixture_lock(tmp_path)
    gate = FinalHoldoutGate(tmp_path / "claim.json", lock)
    session = gate.claim(config, scene_roles.final_holdout_scenes)
    with pytest.raises(PermissionError, match="already been claimed"):
        FinalHoldoutGate(gate.claim_path, lock).claim(config, scene_roles.final_holdout_scenes)
    calls = []
    assert session.load_model_scene("held0", lambda: calls.append("loaded")) is None
    with pytest.raises(PermissionError):
        session.load_model_scene("held0", lambda: calls.append("bad"))
    with pytest.raises(PermissionError):
        session.load_model_scene("train0", lambda: calls.append("bad"))
    with pytest.raises(PermissionError, match="sealed states"):
        session.read_query_ground_truth("held0", "q", [], lambda: calls.append("bad"))
    assert calls == ["loaded"]


def test_locked_config_and_artifacts_cannot_mutate(tmp_path):
    config, scene_roles, paths, _, lock = fixture_lock(tmp_path)
    gate = FinalHoldoutGate(tmp_path / "claim.json", lock)
    with pytest.raises(PermissionError, match="configuration"):
        gate.claim(replace(config, method="R1"), scene_roles.final_holdout_scenes)
    paths["carrier_checkpoint"].write_text("mutated")
    with pytest.raises(PermissionError, match="mutated|artifacts differ"):
        gate.claim(config, scene_roles.final_holdout_scenes)
    assert not gate.claim_path.exists()


def test_mid_inference_artifact_mutation_is_detected(tmp_path):
    config, scene_roles, paths, _, lock = fixture_lock(tmp_path)
    session = FinalHoldoutGate(tmp_path / "claim", lock).claim(
        config, scene_roles.final_holdout_scenes
    )
    with pytest.raises(PermissionError, match="mutated|artifacts differ"):
        session.load_model_scene("held0", lambda: paths["support_config"].write_text("mutated"))


def test_roles_disallow_identity_and_physical_environment_overlap():
    r = roles()
    with pytest.raises(PermissionError):
        replace(r, final_holdout_scenes=("train0",) + r.final_holdout_scenes[1:])
    pairs = tuple(
        (sid, "physical_train0" if sid == "held0" else physical)
        for sid, physical in r.physical_identities
    )
    with pytest.raises(PermissionError):
        replace(r, physical_identities=pairs)
    with pytest.raises(PermissionError):
        replace(r, exposed_scenes=EXPOSED_SCENES[:-1])
    assert old_exposed_manifest()["role"] == "REDESIGN_DEV_EXPOSED"
    assert old_exposed_manifest()["scene_ids"] == list(EXPOSED_SCENES)


def test_dev_guard_runs_before_loader_and_rejects_holdout():
    r = roles()
    d = {
        "TRAIN_SCENES": r.train_scenes,
        "REDESIGN_DEV_SCENES": r.redesign_dev_scenes,
        "FINAL_HOLDOUT_SCENES": r.final_holdout_scenes,
    }
    assert validate_dev_scene_ids(d, ("dev0", EXPOSED_SCENES[0]))
    for ids in [("held0",), ("train0",), ("unknown",), ("dev0", "dev0")]:
        with pytest.raises(PermissionError):
            validate_dev_scene_ids(d, ids)


@pytest.mark.parametrize(
    "field,value",
    [
        ("seeds", (2,)),
        ("total_steps", 200),
        ("loss", "different"),
        ("parameter_count", 900),
        ("scene_ids", ("other",)),
        ("data_sha256", "other"),
        ("optimizer_hyperparameters", (("lr", 0.02),)),
    ],
)
def test_matched_training_rejects_every_nonsupport_difference(field, value):
    config = MatchedTrainingConfig(
        "R0",
        ("train0",),
        (1,),
        "Adam",
        100,
        "RGB+AbsRel",
        "RGB+depth",
        308,
        "minval",
        "hash",
        (("lr", 0.001),),
    )
    redesigned = replace(config, support_method="R12")
    assert assert_matched_training(config, redesigned)
    with pytest.raises(PermissionError):
        assert_matched_training(config, replace(redesigned, **{field: value}))
    assert asdict(config)["support_method"] == "R0"