Source: tests/test_v2_access.py · Plain text

tests/test_v2_access.py

import copy
import hashlib
import json

import numpy as np
import pytest
from PIL import Image

from mcss.vision_probe.v2_access import (
    AccessDenied,
    IndependenceError,
    PredictionAccessGuard,
    validate_independent_manifest,
)


def sha(path):
    return hashlib.sha256(path.read_bytes()).hexdigest()


def manifest(tmp_path):
    frames = []
    for i in range(3):
        image = tmp_path / f"image{i}.png"
        mask = tmp_path / f"mask{i}.png"
        Image.fromarray(np.full((2, 2, 3), i, dtype=np.uint8)).save(image)
        Image.fromarray(np.full((2, 2), i + 1, dtype=np.uint8)).save(mask)
        frames.append(
            {
                "frame_id": str(i),
                "image_path": str(image),
                "mask_path": str(mask),
                "image_sha256": sha(image),
                "target_slot": i,
            }
        )
    return {
        "independence_status": "VERIFIED",
        "sequences": [
            {
                "sequence": "fresh",
                "source_dataset": "synthetic",
                "source_video_id": "video-fresh",
                "source_video_identity_status": "VERIFIED",
                "near_duplicate_status": "VERIFIED",
                "authorized": True,
                "license": "test fixture",
                "independence_status": "VERIFIED",
                "source": frames[0],
                "targets": frames[1:],
            }
        ],
    }


def prediction_lock(tmp_path):
    artifact = tmp_path / "predictions.json"
    artifact.write_text(json.dumps({"actions": ["OFF", "OFF"]}))
    path = tmp_path / "prediction_manifest.json"
    path.write_text(
        json.dumps(
            {
                "complete": True,
                "predictions": [
                    {
                        "sequence": "fresh",
                        "target_slot": i,
                        "source_id": "0",
                        "target_id": str(i),
                        "artifact_path": str(artifact),
                        "artifact_sha256": sha(artifact),
                    }
                    for i in (1, 2)
                ],
            }
        )
    )
    return path, sha(path)


def test_prediction_phase_denies_target_reads_and_direct_python_bypass(tmp_path):
    plan = manifest(tmp_path)
    guard = PredictionAccessGuard(plan)
    with guard:
        with pytest.raises(AccessDenied):
            guard.read_target_mask("fresh", "0", "1")
        with pytest.raises(AccessDenied):
            Image.open(plan["sequences"][0]["targets"][0]["mask_path"])
        with pytest.raises(AccessDenied):
            open(plan["sequences"][0]["source"]["mask_path"], "rb")
        source = guard.read_source_mask("fresh", "0", "1")
        assert (source == 1).all()
        assert guard.target_log == []
        assert len(guard.source_log) == 1
        path, digest = prediction_lock(tmp_path)
        guard.lock_predictions(path, digest)
        target = guard.read_target_mask("fresh", "0", "1")
        assert (target == 2).all()
        assert len(guard.target_log) == 1


def test_partial_or_tampered_prediction_manifest_cannot_unlock(tmp_path):
    guard = PredictionAccessGuard(manifest(tmp_path))
    path, digest = prediction_lock(tmp_path)
    document = json.loads(path.read_text())
    document["predictions"].pop()
    path.write_text(json.dumps(document))
    with pytest.raises(AccessDenied, match="hash mismatch"):
        guard.lock_predictions(path, digest)
    with pytest.raises(AccessDenied, match="All planned"):
        guard.lock_predictions(path, sha(path))
    assert guard.phase == "prediction"
    assert guard.target_log == []


def test_post_lock_artifact_mutation_denies_gt(tmp_path):
    guard = PredictionAccessGuard(manifest(tmp_path))
    path, digest = prediction_lock(tmp_path)
    guard.lock_predictions(path, digest)
    (tmp_path / "predictions.json").write_text("changed")
    with pytest.raises(AccessDenied, match="artifact changed"):
        guard.read_target_mask("fresh", "0", "1")
    assert guard.target_log == []


def test_historical_overlap_hash_overlap_uncertain_and_protected_reject(tmp_path):
    plan = manifest(tmp_path)
    with pytest.raises(IndependenceError, match="historical sequence"):
        validate_independent_manifest(plan, historical_ids={"video-fresh"})
    with pytest.raises(IndependenceError, match="exact image hash"):
        validate_independent_manifest(
            plan, historical_hashes={plan["sequences"][0]["source"]["image_sha256"]}
        )
    uncertain = copy.deepcopy(plan)
    uncertain["sequences"][0]["independence_status"] = "UNVERIFIED"
    with pytest.raises(IndependenceError, match="UNVERIFIED"):
        validate_independent_manifest(uncertain)
    with pytest.raises(IndependenceError, match="protected"):
        PredictionAccessGuard(plan, protected_ids={"fresh"})


def test_discovery_only_exact_ids_source_access_and_protected_paths(tmp_path):
    plan = manifest(tmp_path)
    with pytest.raises(AccessDenied, match="exactly match"):
        PredictionAccessGuard(plan, mode="discovery", allowed_ids={"other"})
    guard = PredictionAccessGuard(
        plan, mode="discovery", allowed_ids={"fresh"}, protected_ids={"reserve", "official-val"}
    )
    with guard:
        guard.read_source_mask("fresh", "0", "2")
        with pytest.raises(AccessDenied):
            guard.read_target_mask("fresh", "0", "2")
        with pytest.raises(AccessDenied):
            (tmp_path / "reserve" / "frame.png").read_bytes()
        with pytest.raises(AccessDenied):
            (tmp_path / "official-val" / "frame.png").read_bytes()
        with pytest.raises(AccessDenied, match="independent confirmation"):
            guard.lock_predictions(tmp_path / "anything", "x")
    assert guard.target_log == []


def test_same_frame_and_mask_aliasing_rejected(tmp_path):
    plan = manifest(tmp_path)
    plan["sequences"][0]["targets"][0]["frame_id"] = "0"
    with pytest.raises(AccessDenied, match="different frames"):
        PredictionAccessGuard(plan, mode="discovery", allowed_ids={"fresh"})
    plan["sequences"][0]["targets"][0]["frame_id"] = "1"
    plan["sequences"][0]["targets"][0]["mask_path"] = plan["sequences"][0]["source"]["mask_path"]
    with pytest.raises(AccessDenied, match="aliasing"):
        PredictionAccessGuard(plan, mode="discovery", allowed_ids={"fresh"})


def test_independence_binds_actual_image_bytes(tmp_path):
    plan = manifest(tmp_path)
    plan["sequences"][0]["source"]["image_sha256"] = "a" * 64
    with pytest.raises(IndependenceError, match="Exact image bytes"):
        PredictionAccessGuard(plan)


def test_unknown_video_or_missing_near_duplicate_evidence_fails_closed(tmp_path):
    for field in ("source_video_id", "source_video_identity_status", "near_duplicate_status"):
        plan = manifest(tmp_path)
        plan["sequences"][0].pop(field)
        with pytest.raises(IndependenceError, match="UNVERIFIED"):
            validate_independent_manifest(plan)