from __future__ import annotations import json from pathlib import Path import pytest from k1link.compute.multirate_perception_qualification import ( MERGED_FRAME_SCHEMA, _validate_merged_rows, ) from k1link.sessions import SessionIntegrityError def _merged(index: int, semantic: dict[str, object]) -> dict[str, object]: return { "schema_version": MERGED_FRAME_SCHEMA, "frame_index": index, "source_frame_index": 1000 + index, "session_seconds": 20.0 + index * 0.1, "detector_result_age_ms": 80.0, "tracks": [], "semantic": semantic, } def test_merged_rows_allow_explicit_unavailable_then_fresh(tmp_path: Path) -> None: path = tmp_path / "merged-frames.jsonl" unavailable = { "status": "unavailable", "source_frame_index": None, "source_age_ms": None, "completion_age_ms": None, "mask_sha256": None, } fresh = { "status": "fresh", "source_frame_index": 1000, "source_age_ms": 200.0, "completion_age_ms": 210.0, "mask_sha256": "a" * 64, } path.write_text( json.dumps(_merged(0, unavailable)) + "\n" + json.dumps(_merged(2, fresh)) + "\n", encoding="utf-8", ) _validate_merged_rows( path, tmp_path, expected_indices=(0, 2), source_start=1000, semantic_indices={0}, ) def test_merged_rows_reject_future_semantic_binding(tmp_path: Path) -> None: path = tmp_path / "merged-frames.jsonl" future = { "status": "fresh", "source_frame_index": 1005, "source_age_ms": 0.0, "completion_age_ms": 200.0, "mask_sha256": "a" * 64, } path.write_text(json.dumps(_merged(2, future)) + "\n", encoding="utf-8") with pytest.raises(SessionIntegrityError, match="binding"): _validate_merged_rows( path, tmp_path, expected_indices=(2,), source_start=1000, semantic_indices={5}, )