Files
NODEDC_MISSION_CORE/tests/test_session_store.py
T

1889 lines
76 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import hashlib
import json
import shutil
import sqlite3
from pathlib import Path
import pytest
from k1link.device_plugins.xgrids_k1 import xgrids_k1_archive_source
from k1link.device_plugins.xgrids_k1.mqtt.capture import (
FRAME_HEADER,
RAW_MAGIC,
iter_capture_frames,
)
from k1link.sessions import (
LabReplayCapability,
LayoutConflictError,
SessionIntegrityError,
SessionNotFoundError,
SessionStore,
resolve_missioncore_evidence_dir,
)
def lab_method() -> dict[str, object]:
return {
"schema_version": "missioncore.laboratory-method/v1",
"completeness": "complete",
"execution_class": "deterministic",
"pipeline_id": "test-pipeline/v1",
"components": [
{
"kind": "algorithm",
"name": "test algorithm",
"version": "v1",
"role": "contract fixture",
"identity_sha256": "9" * 64,
}
],
}
def make_legacy_session(
sessions_root: Path,
session_id: str,
*,
created_at: str = "2026-07-16T20:56:32.699Z",
) -> Path:
session = sessions_root / session_id
capture = session / "captures" / "mqtt_live"
capture.mkdir(parents=True)
frames = [
("lixel/application/report/lio_pcl", b"point-frame"),
("lixel/application/report/lio_pose", b"pose-frame"),
]
raw = bytearray(RAW_MAGIC)
metadata: list[dict[str, object]] = []
for sequence, (topic, payload) in enumerate(frames, start=1):
topic_bytes = topic.encode()
frame_offset = len(raw)
raw.extend(FRAME_HEADER.pack(len(topic_bytes), len(payload)))
raw.extend(topic_bytes)
raw.extend(payload)
metadata.append(
{
"schema_version": 1,
"record_type": "message",
"sequence": sequence,
"received_at_utc": f"2026-07-16T20:56:{31 + sequence:02d}.699Z",
"received_at_epoch_ns": 1_784_235_391_699_000_000 + sequence * 1_000_000_000,
"received_monotonic_ns": 9_000_000_000 + sequence * 1_000_000_000,
"topic": topic,
"payload_bytes": len(payload),
"raw_frame_offset": frame_offset,
"raw_payload_offset": frame_offset + FRAME_HEADER.size + len(topic_bytes),
"raw_frame_bytes": FRAME_HEADER.size + len(topic_bytes) + len(payload),
}
)
raw_path = capture / "mqtt.raw.k1mqtt"
raw_path.write_bytes(raw)
raw_hash = hashlib.sha256(raw).hexdigest()
metadata_path = capture / "mqtt.metadata.jsonl"
metadata_path.write_text(
"".join(json.dumps(record, separators=(",", ":")) + "\n" for record in metadata),
encoding="utf-8",
)
metadata_hash = hashlib.sha256(metadata_path.read_bytes()).hexdigest()
(capture / "mqtt.summary.json").write_text(
json.dumps(
{
"created_at_utc": created_at,
"completed_at_utc": "2026-07-16T21:20:43.018Z",
"capture_elapsed_seconds": 1440.1,
"stop_reason": "external_stop",
"error": None,
"message_count": 2,
"raw_bytes": len(raw),
"payload_bytes": sum(len(payload) for _, payload in frames),
"topic_counts": {
"lixel/application/report/lio_pcl": 1,
"lixel/application/report/lio_pose": 1,
},
"artifact_hashes": {
"raw_sha256": raw_hash,
"metadata_jsonl_sha256": metadata_hash,
},
# This is deliberately sensitive and must not enter API DTOs.
"target_ipv4": "192.168.99.77",
}
),
encoding="utf-8",
)
(session / "manifest.redacted.json").write_text(
json.dumps({"started_at_utc": created_at, "target": "redacted"}),
encoding="utf-8",
)
return session
def add_capture_clock(session: Path, *, scope: str = "session") -> Path:
capture = session / "captures" / "mqtt_live"
origin_document = {
"schema_version": 1,
"started_at_epoch_ns": 1_784_235_391_699_000_000,
"started_monotonic_ns": 9_000_000_000,
}
origin_path = capture / "mqtt.timeline.origin.json"
origin_path.write_text(
json.dumps(origin_document, separators=(",", ":")) + "\n",
encoding="utf-8",
)
clock_document = {
**origin_document,
"completed_at_epoch_ns": 1_784_235_394_699_000_000,
"completed_monotonic_ns": 12_000_000_000,
}
clock_payload = (json.dumps(clock_document, separators=(",", ":")) + "\n").encode()
clock_digest = hashlib.sha256(clock_payload).hexdigest()
provisional_path = capture / "mqtt.timeline.json"
provisional_path.write_bytes(clock_payload)
clock_path = (
capture / f"mqtt.timeline.session-{clock_digest}.json"
if scope == "session"
else provisional_path
)
if clock_path != provisional_path:
clock_path.write_bytes(clock_payload)
summary_path = capture / "mqtt.summary.json"
summary = json.loads(summary_path.read_text(encoding="utf-8"))
summary["schema_version"] = 2
summary["capture_clock_scope"] = scope
summary["session_elapsed_seconds"] = 3.0
summary["artifacts"] = {
"raw": "mqtt.raw.k1mqtt",
"metadata_jsonl": "mqtt.metadata.jsonl",
"capture_clock_origin": origin_path.name,
"capture_clock": clock_path.name,
"summary": "mqtt.summary.json",
}
summary["artifact_hashes"]["capture_clock_origin_sha256"] = hashlib.sha256(
origin_path.read_bytes()
).hexdigest()
summary["artifact_hashes"]["capture_clock_sha256"] = hashlib.sha256(
clock_path.read_bytes()
).hexdigest()
summary_path.write_text(json.dumps(summary), encoding="utf-8")
return clock_path
def test_evidence_root_is_private_and_configurable(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
repository = tmp_path / "repo"
monkeypatch.delenv("MISSIONCORE_EVIDENCE_DIR", raising=False)
monkeypatch.delenv("MISSIONCORE_DATA_DIR", raising=False)
assert (
resolve_missioncore_evidence_dir(repository)
== (repository / ".runtime" / "mission-core" / "evidence" / "sessions").resolve()
)
configured = tmp_path / "external-evidence"
monkeypatch.setenv("MISSIONCORE_EVIDENCE_DIR", str(configured))
assert resolve_missioncore_evidence_dir(repository) == configured.resolve()
def test_catalog_reconciles_sessions_removed_from_one_evidence_root(tmp_path: Path) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
assert store.reconcile_archive(xgrids_k1_archive_source(sessions)) == (session.name,)
shutil.rmtree(session)
assert store.reconcile_archive(xgrids_k1_archive_source(sessions)) == ()
assert store.list_recent().items == ()
def test_corrupt_candidate_does_not_starve_later_valid_session(tmp_path: Path) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
corrupt = sessions / "20260716T125502Z_viewer_live"
corrupt.mkdir(parents=True)
(corrupt / "manifest.redacted.json").write_text("{}", encoding="utf-8")
valid = make_legacy_session(sessions, "20260718T201659Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
assert store.reconcile_archive(xgrids_k1_archive_source(sessions)) == (valid.name,)
assert [item.session_id for item in store.list_recent().items] == [valid.name]
def test_present_corrupt_candidate_does_not_delete_its_last_valid_catalog_row(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
retained = make_legacy_session(sessions, "20260716T125502Z_viewer_live")
later = make_legacy_session(sessions, "20260718T201659Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
source = xgrids_k1_archive_source(sessions)
assert store.reconcile_archive(source) == (retained.name, later.name)
shutil.rmtree(retained / "captures")
assert store.reconcile_archive(source) == (later.name,)
assert {item.session_id for item in store.list_recent().items} == {
retained.name,
later.name,
}
def test_catalog_uses_valid_project_name_as_session_display_name(tmp_path: Path) -> None:
sessions = tmp_path / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
manifest_path = session / "manifest.redacted.json"
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
manifest["project_name"] = " K1 Route Alpha "
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
store = SessionStore(tmp_path / "repo", data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
assert store.list_recent().items[0].display_name == "K1 Route Alpha"
def test_catalog_rejects_non_utf8_project_display_name(tmp_path: Path) -> None:
sessions = tmp_path / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
manifest_path = session / "manifest.redacted.json"
manifest_path.write_text(
json.dumps({"project_name": "unsafe-\ud800"}),
encoding="utf-8",
)
store = SessionStore(tmp_path / "repo", data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
assert store.list_recent().items[0].display_name == session.name
def make_recorded_camera_source(
session: Path,
source_id: str = "sensor.camera.left",
*,
complete: bool = True,
) -> Path:
epoch = session / "media" / source_id / "epoch-1"
segments = epoch / "segments"
segments.mkdir(parents=True)
(epoch / "init.mp4").write_bytes(b"ftyp-mission-core")
(segments / "1.m4s").write_bytes(b"moof-camera-frame")
(epoch / "index.jsonl").write_text(
json.dumps({"sequence": 1, "started_at_seconds": 0.0}) + "\n",
encoding="utf-8",
)
if complete:
(epoch / "summary.json").write_text(
json.dumps(
{
"schema_version": "missioncore.camera-recording/v1",
"source_id": source_id,
"segment_count": 1,
}
),
encoding="utf-8",
)
return epoch
def replace_summary_with_recovery_metadata(
session: Path,
*,
corrupt_trailing_line: bool = False,
) -> None:
capture = session / "captures" / "mqtt_live"
raw_path = capture / "mqtt.raw.k1mqtt"
timestamps = (
("2026-07-16T20:56:32.699Z", 1_784_235_392_699_000_000, 10_000_000_000),
("2026-07-16T20:56:35.199Z", 1_784_235_395_199_000_000, 12_500_000_000),
)
records = []
for frame, (timestamp, epoch_ns, monotonic_ns) in zip(
iter_capture_frames(raw_path),
timestamps,
strict=True,
):
records.append(
{
"schema_version": 1,
"record_type": "message",
"sequence": frame.sequence,
"received_at_utc": timestamp,
"received_at_epoch_ns": epoch_ns,
"received_monotonic_ns": monotonic_ns,
"topic": frame.topic,
"payload_bytes": frame.raw_frame_bytes
- FRAME_HEADER.size
- len(frame.topic.encode("utf-8")),
"raw_frame_offset": frame.raw_frame_offset,
"raw_payload_offset": frame.raw_payload_offset,
"raw_frame_bytes": frame.raw_frame_bytes,
}
)
metadata = b"".join(
(json.dumps(record, separators=(",", ":")) + "\n").encode("utf-8") for record in records
)
if corrupt_trailing_line:
metadata += b'{"record_type":"message"'
(capture / "mqtt.metadata.jsonl").write_bytes(metadata)
(capture / "mqtt.summary.json").write_text("{corrupt", encoding="utf-8")
def serialized(value: object) -> str:
return json.dumps(value, ensure_ascii=False, default=str)
def test_store_uses_private_wal_database_and_idempotently_imports_legacy_session(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
first = store.reconcile_archive(xgrids_k1_archive_source(sessions))
second = store.reconcile_archive(xgrids_k1_archive_source(sessions))
assert first == second == ("20260716T205632Z_viewer_live",)
page = store.list_recent()
assert len(page.items) == 1
summary = page.items[0]
assert summary.status == "ready"
assert summary.modalities == ("point-cloud", "trajectory")
assert summary.replayable is True
assert summary.source_count == 2
assert "192.168.99.77" not in serialized(page.as_dict())
assert str(repository) not in serialized(page.as_dict())
detail = store.get_session(summary.session_id)
assert [source.source_id for source in detail.sources] == [
"sensor.lidar.primary",
"spatial.trajectory",
]
assert all(source.seekable for source in detail.sources)
assert str(repository) not in serialized(detail.as_dict())
command = store.prepare_replay(summary.session_id, speed=2.0, loop=True)
assert command.primary_artifact.path.name == "mqtt.raw.k1mqtt"
assert command.speed == 2.0
assert command.loop is True
with sqlite3.connect(store.database_path) as connection:
assert connection.execute("PRAGMA journal_mode").fetchone()[0] == "wal"
assert store.database_path.stat().st_mode & 0o777 == 0o600
def test_completed_capture_clock_is_a_digest_bound_replay_artifact(tmp_path: Path) -> None:
sessions = tmp_path / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
clock_path = add_capture_clock(session)
store = SessionStore(tmp_path / "repo", data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
assert store.get_session(session.name).summary.duration_seconds == 3.0
command = store.prepare_replay(session.name)
clock_artifact = next(
artifact for artifact in command.artifacts if artifact.artifact_id == "raw-transport-clock"
)
assert command.timeline_origin_monotonic_ns == 9_000_000_000
assert command.timeline_origin_epoch_ns == 1_784_235_391_699_000_000
assert clock_artifact.path == clock_path
assert clock_artifact.replay_byte_length == clock_path.stat().st_size
assert clock_artifact.expected_sha256 == hashlib.sha256(clock_path.read_bytes()).hexdigest()
def test_completed_capture_rejects_corrupt_declared_clock(tmp_path: Path) -> None:
sessions = tmp_path / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
clock_path = add_capture_clock(session)
clock_path.write_text('{"schema_version":1,"tampered":true}\n', encoding="utf-8")
store = SessionStore(tmp_path / "repo", data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
detail = store.get_session(session.name)
assert detail.summary.status == "failed"
assert detail.summary.replayable is False
def test_completed_capture_rejects_clock_filename_with_wrong_digest_suffix(
tmp_path: Path,
) -> None:
sessions = tmp_path / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
clock_path = add_capture_clock(session)
wrong_path = clock_path.with_name(f"mqtt.timeline.session-{'0' * 64}.json")
wrong_path.write_bytes(clock_path.read_bytes())
summary_path = session / "captures" / "mqtt_live" / "mqtt.summary.json"
summary = json.loads(summary_path.read_text(encoding="utf-8"))
summary["artifacts"]["capture_clock"] = wrong_path.name
summary_path.write_text(json.dumps(summary), encoding="utf-8")
store = SessionStore(tmp_path / "repo", data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
detail = store.get_session(session.name)
assert detail.summary.status == "failed"
assert detail.summary.replayable is False
def test_camera_session_does_not_advertise_provisional_transport_clock(
tmp_path: Path,
) -> None:
sessions = tmp_path / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
add_capture_clock(session, scope="transport")
make_recorded_camera_source(session)
store = SessionStore(tmp_path / "repo", data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
detail = store.get_session(session.name)
assert detail.summary.replayable is False
def test_interrupted_camera_session_uses_durable_origin_and_fails_closed(
tmp_path: Path,
) -> None:
sessions = tmp_path / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
add_capture_clock(session, scope="transport")
replace_summary_with_recovery_metadata(session)
make_recorded_camera_source(session)
store = SessionStore(tmp_path / "repo", data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
detail = store.get_session(session.name)
assert detail.summary.status == "interrupted"
assert detail.summary.replayable is False
def test_interrupted_raw_replay_preserves_durable_pre_message_origin(tmp_path: Path) -> None:
sessions = tmp_path / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
add_capture_clock(session, scope="transport")
replace_summary_with_recovery_metadata(session)
store = SessionStore(tmp_path / "repo", data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
command = store.prepare_replay(session.name)
assert command.timeline_origin_monotonic_ns == 9_000_000_000
assert any(
artifact.artifact_id == "raw-transport-clock-origin" for artifact in command.artifacts
)
def test_reconcile_claims_pre_plugin_catalog_row_without_changing_session_identity(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
source = xgrids_k1_archive_source(sessions)
store.reconcile_archive(source)
with sqlite3.connect(store.database_path) as connection:
connection.execute(
"UPDATE observation_sessions SET plugin_id = '', archive_id = '', "
"primary_replay_artifact_id = NULL, timeline_origin_epoch_ns = NULL, "
"timeline_origin_monotonic_ns = NULL WHERE session_id = ?",
(session.name,),
)
connection.execute(
"UPDATE observation_session_artifacts SET replay_byte_length = 0 WHERE session_id = ?",
(session.name,),
)
connection.commit()
assert store.reconcile_archive(source) == (session.name,)
command = store.prepare_replay(session.name)
assert command.plugin_id == source.plugin_id
assert command.primary_artifact.replay_byte_length > 0
def test_recent_sessions_are_sorted_and_cursor_paginated(tmp_path: Path) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
make_legacy_session(
sessions,
"20260716T191025Z_viewer_live",
created_at="2026-07-16T19:10:25.352Z",
)
make_legacy_session(
sessions,
"20260716T205632Z_viewer_live",
created_at="2026-07-16T20:56:32.699Z",
)
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
first = store.list_recent(limit=1)
assert first.items[0].session_id == "20260716T205632Z_viewer_live"
assert first.next_cursor == "20260716T205632Z_viewer_live"
second = store.list_recent(limit=1, cursor=first.next_cursor)
assert second.items[0].session_id == "20260716T191025Z_viewer_live"
assert second.next_cursor is None
def test_legacy_import_adds_video_only_for_validated_recording_tree(tmp_path: Path) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
complete = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
incomplete = make_legacy_session(sessions, "20260716T205632Z_viewer_live_2")
make_recorded_camera_source(complete)
make_recorded_camera_source(incomplete, complete=False)
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
complete_detail = store.get_session(complete.name)
assert complete_detail.summary.modalities == ("point-cloud", "trajectory", "video")
assert complete_detail.summary.source_count == 3
camera = next(
source for source in complete_detail.sources if source.source_id == "sensor.camera.left"
)
assert camera.modality == "video"
assert camera.semantic_channel_id == "camera.video.recorded"
assert camera.seekable is True
video_artifact = next(
artifact
for artifact in complete_detail.artifacts
if artifact.artifact_id == camera.artifact_id
)
assert video_artifact.kind == "recorded-video"
assert video_artifact.integrity_status == "validated-structure"
assert str(repository) not in serialized(complete_detail.as_dict())
incomplete_detail = store.get_session(incomplete.name)
assert incomplete_detail.summary.modalities == ("point-cloud", "trajectory")
assert all(source.modality != "video" for source in incomplete_detail.sources)
def test_interrupted_capture_is_recovered_from_aligned_raw_and_metadata_prefix(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
replace_summary_with_recovery_metadata(session, corrupt_trailing_line=True)
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
detail = store.get_session(session.name)
assert detail.summary.status == "interrupted"
assert detail.summary.replayable is True
assert detail.summary.modalities == ("point-cloud", "trajectory")
assert detail.summary.started_at_utc == "2026-07-16T20:56:32.699Z"
assert detail.summary.completed_at_utc == "2026-07-16T20:56:35.199Z"
assert detail.summary.duration_seconds == 2.5
assert detail.artifacts[0].integrity_status == "validated-prefix"
assert store.prepare_replay(session.name).primary_artifact.path.name == "mqtt.raw.k1mqtt"
def test_interrupted_capture_streams_metadata_beyond_legacy_total_limit(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
capture = session / "captures" / "mqtt_live"
raw = bytearray(RAW_MAGIC)
records: list[bytes] = []
topic = "lixel/application/report/lio_pose"
topic_bytes = topic.encode()
padding = "x" * (60 * 1024)
for sequence in range(1, 1_101):
payload = b"p"
frame_offset = len(raw)
raw.extend(FRAME_HEADER.pack(len(topic_bytes), len(payload)))
raw.extend(topic_bytes)
raw.extend(payload)
record = {
"schema_version": 1,
"record_type": "message",
"sequence": sequence,
"received_at_utc": "2026-07-16T20:56:32.699Z",
"received_at_epoch_ns": 1_784_235_391_699_000_000 + sequence,
"received_monotonic_ns": 9_000_000_000 + sequence,
"topic": topic,
"payload_bytes": len(payload),
"raw_frame_offset": frame_offset,
"raw_payload_offset": frame_offset + FRAME_HEADER.size + len(topic_bytes),
"raw_frame_bytes": FRAME_HEADER.size + len(topic_bytes) + len(payload),
"padding": padding,
}
records.append((json.dumps(record, separators=(",", ":")) + "\n").encode())
(capture / "mqtt.raw.k1mqtt").write_bytes(raw)
metadata_path = capture / "mqtt.metadata.jsonl"
metadata_path.write_bytes(b"".join(records))
assert metadata_path.stat().st_size > 64 * 1024 * 1024
(capture / "mqtt.summary.json").write_text("{corrupt", encoding="utf-8")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
detail = store.get_session(session.name)
assert detail.summary.status == "interrupted"
assert detail.summary.replayable is True
command = store.prepare_replay(session.name)
assert command.primary_artifact.replay_byte_length == len(raw)
assert next(
artifact.replay_byte_length
for artifact in command.artifacts
if artifact.artifact_id == "raw-transport-index"
) == metadata_path.stat().st_size
def test_catalog_upsert_promotes_recovered_session_after_summary_is_completed(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
summary_path = session / "captures" / "mqtt_live" / "mqtt.summary.json"
metadata_path = session / "captures" / "mqtt_live" / "mqtt.metadata.jsonl"
completed_summary = summary_path.read_text(encoding="utf-8")
completed_metadata = metadata_path.read_text(encoding="utf-8")
replace_summary_with_recovery_metadata(session)
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
assert store.get_session(session.name).summary.status == "interrupted"
summary_path.write_text(completed_summary, encoding="utf-8")
metadata_path.write_text(completed_metadata, encoding="utf-8")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
promoted = store.get_session(session.name).summary
assert promoted.status == "ready"
assert promoted.duration_seconds == 1440.1
assert promoted.replayable is True
def test_interrupted_capture_does_not_trust_metadata_outside_session(tmp_path: Path) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
replace_summary_with_recovery_metadata(session)
metadata = session / "captures" / "mqtt_live" / "mqtt.metadata.jsonl"
outside = tmp_path / "outside.metadata.jsonl"
outside.write_bytes(metadata.read_bytes())
metadata.unlink()
metadata.symlink_to(outside)
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
detail = store.get_session(session.name)
assert detail.summary.status == "failed"
assert detail.summary.replayable is False
assert detail.summary.modalities == ()
def test_interrupted_capture_rejects_newline_terminated_metadata_corruption(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
replace_summary_with_recovery_metadata(session)
metadata = session / "captures" / "mqtt_live" / "mqtt.metadata.jsonl"
with metadata.open("ab") as stream:
stream.write(b"{corrupt\n")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
detail = store.get_session(session.name)
assert detail.summary.status == "failed"
assert detail.summary.replayable is False
def test_replay_resolution_rejects_artifact_symlink_escape(tmp_path: Path) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
raw = session / "captures" / "mqtt_live" / "mqtt.raw.k1mqtt"
outside = tmp_path / "outside.k1mqtt"
outside.write_bytes(raw.read_bytes())
raw.unlink()
raw.symlink_to(outside)
with pytest.raises(SessionIntegrityError, match="escapes"):
store.prepare_replay(session.name)
def test_completed_capture_is_not_replayable_when_declared_integrity_fails(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
raw = session / "captures" / "mqtt_live" / "mqtt.raw.k1mqtt"
corrupted = bytearray(raw.read_bytes())
corrupted[-1] ^= 0x01
raw.write_bytes(corrupted)
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
detail = store.get_session(session.name)
assert detail.summary.status == "failed"
assert detail.summary.replayable is False
assert detail.summary.modalities == ()
def test_completed_capture_is_not_replayable_when_declared_count_is_wrong(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
summary_path = session / "captures" / "mqtt_live" / "mqtt.summary.json"
summary = json.loads(summary_path.read_text(encoding="utf-8"))
summary["message_count"] = 3
summary_path.write_text(json.dumps(summary), encoding="utf-8")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
assert store.get_session(session.name).summary.replayable is False
def test_current_session_marker_keeps_active_capture_out_of_replay(tmp_path: Path) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
(sessions / ".current_session").write_text(
f"sessions/{session.name}\n",
encoding="utf-8",
)
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
with pytest.raises(SessionNotFoundError):
store.get_session(session.name)
(sessions / ".current_session").unlink()
store.reconcile_archive(xgrids_k1_archive_source(sessions))
assert store.get_session(session.name).summary.replayable is True
def test_delete_session_removes_only_the_exact_evidence_tree_and_catalog_row(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
deleted = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
retained = make_legacy_session(sessions, "20260716T205633Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
store.delete_session(deleted.name)
assert not deleted.exists()
assert retained.is_dir()
with pytest.raises(SessionNotFoundError):
store.get_session(deleted.name)
assert store.get_session(retained.name).summary.session_id == retained.name
def test_lab_instance_is_independent_and_never_deletes_source_evidence(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
archive = xgrids_k1_archive_source(sessions)
store.reconcile_archive(archive)
source_command = store.prepare_replay(source.name)
binding = store.publish_lab_instance(
session_id="lab-e21-d0201712",
source_session_id=source.name,
display_name="LAB E21 · RT 1× · d0201712",
lab_id="LAB E21",
result_kind="e10-integrated-perception",
result_id="e10-integrated-perception-" + "a" * 64,
source_result_id="e21-realtime-envelope-" + "b" * 64,
config_sha256="c" * 64,
run_created_at_utc="2026-07-23T15:51:25.000Z",
provenance={
"storage_mode": "hard-linked-immutable-payloads",
"method": lab_method(),
},
)
assert binding.replay_capability is None
detail = store.get_session(binding.session_id)
lab_command = store.prepare_replay(binding.session_id)
assert detail.summary.lab == binding
assert detail.summary.display_name == "LAB E21 · RT 1× · d0201712"
assert lab_command.primary_artifact.path == source_command.primary_artifact.path
assert lab_command.session_id == binding.session_id
assert source.is_dir()
assert [item.session_id for item in store.list_recent(scope="source").items] == [
source.name
]
assert [
item.session_id for item in store.list_recent(scope="laboratory").items
] == [binding.session_id]
with pytest.raises(SessionIntegrityError, match="has LAB instances"):
store.delete_session(source.name)
assert source.is_dir()
store.delete_session(binding.session_id)
assert source.is_dir()
assert store.get_session(source.name).summary.lab is None
with pytest.raises(SessionNotFoundError):
store.get_session(binding.session_id)
def test_lab_instance_publication_is_idempotent_but_provenance_is_immutable(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
parameters = {
"session_id": "lab-e19-36964643",
"source_session_id": source.name,
"display_name": "LAB E19 · Ground-aware 3D",
"lab_id": "LAB E19",
"result_kind": "e10-integrated-perception",
"result_id": "e10-integrated-perception-" + "d" * 64,
"source_result_id": "e10-integrated-perception-" + "e" * 64,
"config_sha256": "f" * 64,
"run_created_at_utc": "2026-07-23T05:19:43.138Z",
"provenance": {"source": "accepted", "method": lab_method()},
}
first = store.publish_lab_instance(**parameters)
second = store.publish_lab_instance(**parameters)
assert second == first
with pytest.raises(SessionIntegrityError, match="different provenance"):
store.publish_lab_instance(**{**parameters, "config_sha256": "0" * 64})
def test_lab_replay_capability_column_migrates_existing_catalog(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
data_dir = tmp_path / "data"
initial = SessionStore(repository, data_dir=data_dir)
initial.reconcile_archive(xgrids_k1_archive_source(sessions))
legacy = initial.publish_lab_instance(
session_id="lab-e19-pre-capability",
source_session_id=source.name,
display_name="LAB E19 · pre-capability",
lab_id="LAB E19",
result_kind="e19-legacy",
result_id="e19-legacy-result",
run_created_at_utc="2026-07-23T05:19:43.138Z",
provenance={"method": lab_method()},
)
with sqlite3.connect(initial.database_path) as connection:
connection.execute(
"ALTER TABLE observation_lab_instances DROP COLUMN replay_capability_json"
)
connection.execute(
"ALTER TABLE observation_lab_instances DROP COLUMN include_recorded_media"
)
connection.execute(
"ALTER TABLE observation_lab_instances DROP COLUMN operator_display_name"
)
connection.commit()
migrated = SessionStore(repository, data_dir=data_dir)
with sqlite3.connect(migrated.database_path) as connection:
columns = {
row[1]
for row in connection.execute(
"PRAGMA table_info(observation_lab_instances)"
)
}
assert "replay_capability_json" in columns
assert "include_recorded_media" in columns
assert "operator_display_name" in columns
assert migrated.get_lab_instance(legacy.session_id).replay_capability is None
def test_migration_types_only_the_exact_rolling_canonical_projection(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260828T130511Z_viewer_live")
make_recorded_camera_source(source)
data_dir = tmp_path / "data"
initial = SessionStore(repository, data_dir=data_dir)
initial.reconcile_archive(xgrids_k1_archive_source(sessions))
with sqlite3.connect(initial.database_path) as connection:
connection.execute(
"UPDATE observation_sessions SET archive_id = ? WHERE session_id = ?",
("xgrids-k1.viewer-live.evidence", source.name),
)
connection.commit()
evidence_identity = "a" * 64
capability = LabReplayCapability(
schema_version="missioncore.observation-lab-replay-capability/v1",
kind="canonical-recorded-rerun",
viewer_profile="recorded-session",
timeline="session_time",
activation="explicit",
commands_enabled=False,
)
provenance = {
"schema_version": "missioncore.canonical-recorded-lab-projection/v1",
"evidence_identity_sha256": evidence_identity,
"result_document_sha256": "b" * 64,
"replay_capability": capability.as_dict(),
"authority": {
"commands_enabled": False,
"navigation_or_safety_accepted": False,
"actuation_accepted": False,
},
"method": {
"schema_version": "missioncore.laboratory-method/v1",
"completeness": "legacy-partial",
"execution_class": "ai-inference",
"pipeline_id": "ravnoves004tree-full-eomt-ddrnet-recorded-review/v1",
"components": [
{
"kind": "source",
"name": "sealed full-route LAB result",
"version": "missioncore.lab-v1-vegetation-shadow/v1",
"role": "immutable Session catalog projection",
"identity_sha256": evidence_identity,
}
],
},
}
parameters = {
"session_id": f"lab-v1-vegetation-shadow-{evidence_identity}",
"source_session_id": source.name,
"display_name": "RAVNOVES004TREE · полный маршрут восприятия",
"lab_id": "LAB V1",
"result_kind": "recorded-perception-qualification",
"result_id": f"lab-v1-vegetation-shadow-{evidence_identity}",
"source_result_id": "lab-v1-vegetation-shadow-" + "c" * 64,
"config_sha256": None,
"run_created_at_utc": "2026-08-29T18:05:11.329061+00:00",
"duration_seconds": 718.0,
"replay_capability": capability,
"provenance": provenance,
"include_recorded_media": False,
}
binding = initial.publish_lab_instance(**parameters)
with sqlite3.connect(initial.database_path) as connection:
connection.execute(
"UPDATE observation_sessions SET modalities_json = ?, source_count = ?, "
"total_bytes = ? WHERE session_id = ?",
('["point-cloud","trajectory","video"]', 3, 999_999_999, binding.session_id),
)
connection.execute(
"ALTER TABLE observation_lab_instances DROP COLUMN replay_capability_json"
)
connection.execute(
"ALTER TABLE observation_lab_instances DROP COLUMN include_recorded_media"
)
connection.commit()
migrated = SessionStore(repository, data_dir=data_dir)
assert migrated.get_lab_instance(binding.session_id).replay_capability == capability
migrated_detail = migrated.get_session(binding.session_id)
assert migrated_detail.summary.modalities == ("point-cloud", "trajectory")
assert migrated_detail.summary.source_count == 2
assert all(source.modality != "video" for source in migrated_detail.sources)
assert migrated.list_recent(
scope="laboratory",
include_capability_projections=False,
).items == ()
assert [
item.session_id
for item in migrated.list_recent(
scope="laboratory",
include_capability_projections=True,
).items
] == [binding.session_id]
assert migrated.publish_lab_instance(**parameters).session_id == binding.session_id
def test_migration_rejects_a_non_replayable_canonical_projection(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260828T130511Z_viewer_live")
make_recorded_camera_source(source)
data_dir = tmp_path / "data"
initial = SessionStore(repository, data_dir=data_dir)
initial.reconcile_archive(xgrids_k1_archive_source(sessions))
with sqlite3.connect(initial.database_path) as connection:
connection.execute(
"UPDATE observation_sessions SET archive_id = ? WHERE session_id = ?",
("xgrids-k1.viewer-live.evidence", source.name),
)
connection.commit()
capability = LabReplayCapability(
schema_version="missioncore.observation-lab-replay-capability/v1",
kind="canonical-recorded-rerun",
viewer_profile="recorded-session",
timeline="session_time",
activation="explicit",
commands_enabled=False,
)
evidence_identity = "a" * 64
result_id = f"lab-v1-vegetation-shadow-{evidence_identity}"
initial.publish_lab_instance(
session_id=result_id,
source_session_id=source.name,
display_name="RAVNOVES004TREE · полный маршрут восприятия",
lab_id="LAB V1",
result_kind="recorded-perception-qualification",
result_id=result_id,
source_result_id="lab-v1-vegetation-shadow-" + "c" * 64,
run_created_at_utc="2026-08-29T18:05:11.329061+00:00",
duration_seconds=718.0,
include_recorded_media=False,
replay_capability=capability,
provenance={
"schema_version": "missioncore.canonical-recorded-lab-projection/v1",
"evidence_identity_sha256": evidence_identity,
"result_document_sha256": "b" * 64,
"replay_capability": capability.as_dict(),
"authority": {
"commands_enabled": False,
"navigation_or_safety_accepted": False,
"actuation_accepted": False,
},
"method": {
"schema_version": "missioncore.laboratory-method/v1",
"completeness": "legacy-partial",
"execution_class": "ai-inference",
"pipeline_id": "ravnoves004tree-full-eomt-ddrnet-recorded-review/v1",
"components": [
{
"kind": "source",
"name": "sealed full-route LAB result",
"version": "missioncore.lab-v1-vegetation-shadow/v1",
"role": "immutable Session catalog projection",
"identity_sha256": evidence_identity,
}
],
},
},
)
with sqlite3.connect(initial.database_path) as connection:
connection.execute(
"UPDATE observation_sessions SET replayable = 0, "
"primary_replay_artifact_id = NULL, timeline_origin_epoch_ns = NULL, "
"timeline_origin_monotonic_ns = NULL WHERE session_id = ?",
(result_id,),
)
connection.execute(
"ALTER TABLE observation_lab_instances DROP COLUMN replay_capability_json"
)
connection.execute(
"ALTER TABLE observation_lab_instances DROP COLUMN include_recorded_media"
)
connection.commit()
with pytest.raises(SessionIntegrityError, match="LAB"):
SessionStore(repository, data_dir=data_dir)
def test_migration_rejects_boolean_aliases_in_rolling_authority(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
data_dir = tmp_path / "data"
store = SessionStore(repository, data_dir=data_dir)
with sqlite3.connect(store.database_path) as connection:
capability = {
"schema_version": "missioncore.observation-lab-replay-capability/v1",
"kind": "canonical-recorded-rerun",
"viewer_profile": "recorded-session",
"timeline": "session_time",
"activation": "explicit",
"commands_enabled": False,
}
evidence_identity = "a" * 64
provenance = {
"schema_version": "missioncore.canonical-recorded-lab-projection/v1",
"evidence_identity_sha256": evidence_identity,
"result_document_sha256": "b" * 64,
"replay_capability": capability,
"authority": {
"commands_enabled": 0,
"navigation_or_safety_accepted": 0,
"actuation_accepted": 0,
},
"method": {
"schema_version": "missioncore.laboratory-method/v1",
"completeness": "legacy-partial",
"execution_class": "ai-inference",
"pipeline_id": "ravnoves004tree-full-eomt-ddrnet-recorded-review/v1",
"components": [
{
"kind": "source",
"name": "sealed full-route LAB result",
"version": "missioncore.lab-v1-vegetation-shadow/v1",
"role": "immutable Session catalog projection",
"identity_sha256": evidence_identity,
}
],
},
}
result_id = f"lab-v1-vegetation-shadow-{evidence_identity}"
connection.execute("PRAGMA foreign_keys = OFF")
connection.execute(
"INSERT INTO observation_sessions "
"(session_id, plugin_id, archive_id, display_name, status, modalities_json, "
"replayable, origin, source_count, total_bytes, allowed_root, session_root, "
"created_at_utc, updated_at_utc) "
"VALUES (?, ?, ?, ?, 'ready', '[]', 0, ?, 0, 0, ?, ?, ?, ?)",
(
result_id,
"fixture.plugin",
"missioncore.lab-instances",
"invalid rolling authority",
"missioncore.lab-instance/v1",
str(repository),
str(repository),
"2026-08-30T00:00:00Z",
"2026-08-30T00:00:00Z",
),
)
connection.execute(
"INSERT INTO observation_lab_instances "
"(session_id, source_session_id, lab_id, result_kind, result_id, "
"source_result_id, config_sha256, run_created_at_utc, published_at_utc, "
"include_recorded_media, replay_capability_json, provenance_json) "
"VALUES (?, ?, 'LAB V1', 'recorded-perception-qualification', ?, ?, "
"NULL, ?, ?, NULL, NULL, ?)",
(
result_id,
"20260828T130511Z_viewer_live",
result_id,
"lab-v1-vegetation-shadow-" + "c" * 64,
"2026-08-29T18:05:11.329061+00:00",
"2026-08-30T00:00:00Z",
json.dumps(provenance, sort_keys=True),
),
)
connection.commit()
migrated = SessionStore(repository, data_dir=data_dir)
with sqlite3.connect(migrated.database_path) as connection:
row = connection.execute(
"SELECT replay_capability_json, include_recorded_media "
"FROM observation_lab_instances WHERE session_id = ?",
("lab-v1-vegetation-shadow-" + "a" * 64,),
).fetchone()
assert row == (None, None)
def test_lab_instance_persists_typed_explicit_recorded_replay_capability(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
capability = LabReplayCapability(
schema_version="missioncore.observation-lab-replay-capability/v1",
kind="canonical-recorded-rerun",
viewer_profile="recorded-session",
timeline="session_time",
activation="explicit",
commands_enabled=False,
)
binding = store.publish_lab_instance(
session_id="lab-recorded-replay",
source_session_id=source.name,
display_name="LAB V1 · recorded replay",
lab_id="LAB V1",
result_kind="recorded-perception-qualification",
result_id="lab-v1-vegetation-shadow-" + "a" * 64,
source_result_id="lab-v1-vegetation-shadow-" + "b" * 64,
run_created_at_utc="2026-08-29T18:05:11.329061+00:00",
replay_capability=capability,
provenance={
"replay_capability": capability.as_dict(),
"method": lab_method(),
},
)
assert binding.replay_capability == capability
assert "replay_capability" not in binding.as_dict()
assert binding.as_dict()["provenance"]["replay_capability"] == capability.as_dict()
assert store.get_lab_instance(binding.session_id) == binding
with sqlite3.connect(store.database_path) as connection:
connection.execute(
"UPDATE observation_lab_instances SET replay_capability_json = ? "
"WHERE session_id = ?",
('{"kind":"unknown"}', binding.session_id),
)
connection.commit()
with pytest.raises(SessionIntegrityError, match="replay capability"):
store.get_lab_instance(binding.session_id)
def test_capability_projection_rename_is_only_an_operator_alias(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
source_payload = source / "captures" / "mqtt_live" / "mqtt.raw.k1mqtt"
source_payload_sha256 = hashlib.sha256(source_payload.read_bytes()).hexdigest()
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
_source_detail, source_snapshot = store.get_session_with_catalog_snapshot(source.name)
capability = LabReplayCapability(
schema_version="missioncore.observation-lab-replay-capability/v1",
kind="canonical-recorded-rerun",
viewer_profile="recorded-session",
timeline="session_time",
activation="explicit",
commands_enabled=False,
)
parameters = {
"session_id": "lab-recorded-operator-alias",
"source_session_id": source.name,
"display_name": "LAB V1 · canonical recorded replay",
"lab_id": "LAB V1",
"result_kind": "recorded-perception-qualification",
"result_id": "lab-v1-vegetation-shadow-" + "a" * 64,
"source_result_id": "lab-v1-vegetation-shadow-" + "b" * 64,
"run_created_at_utc": "2026-08-29T18:05:11.329061+00:00",
"include_recorded_media": False,
"replay_capability": capability,
"provenance": {
"replay_capability": capability.as_dict(),
"method": lab_method(),
},
}
binding = store.publish_lab_instance(**parameters)
immutable_binding = store.get_lab_instance(binding.session_id)
_projection_detail, projection_snapshot = store.get_session_with_catalog_snapshot(
binding.session_id
)
assert store.rename_capability_lab_projection(
binding.session_id,
" Маршрут у школы ",
) == "Маршрут у школы"
assert store.get_session(binding.session_id).summary.display_name == "Маршрут у школы"
assert (
store.list_recent(scope="laboratory").items[0].display_name
== "Маршрут у школы"
)
assert store.get_lab_instance(binding.session_id) == immutable_binding
assert store.get_session_with_catalog_snapshot(source.name)[1] == source_snapshot
assert (
store.get_session_with_catalog_snapshot(binding.session_id)[1]
== projection_snapshot
)
assert hashlib.sha256(source_payload.read_bytes()).hexdigest() == source_payload_sha256
with sqlite3.connect(store.database_path) as connection:
row = connection.execute(
"SELECT sessions.display_name, lab.operator_display_name "
"FROM observation_sessions AS sessions "
"JOIN observation_lab_instances AS lab USING (session_id) "
"WHERE sessions.session_id = ?",
(binding.session_id,),
).fetchone()
assert row == ("LAB V1 · canonical recorded replay", "Маршрут у школы")
# A strict publisher retry still sees the untouched canonical name and
# immutable provenance, even while the catalog presents the alias.
assert store.publish_lab_instance(**parameters) == binding
assert store.get_session(binding.session_id).summary.display_name == "Маршрут у школы"
assert store.rename_capability_lab_projection(
binding.session_id,
parameters["display_name"],
) == parameters["display_name"]
with sqlite3.connect(store.database_path) as connection:
override = connection.execute(
"SELECT operator_display_name FROM observation_lab_instances "
"WHERE session_id = ?",
(binding.session_id,),
).fetchone()[0]
assert override is None
def test_capability_projection_delete_removes_only_catalog_projection(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
source_payload = source / "captures" / "mqtt_live" / "mqtt.raw.k1mqtt"
source_payload_sha256 = hashlib.sha256(source_payload.read_bytes()).hexdigest()
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
source_detail, source_snapshot = store.get_session_with_catalog_snapshot(source.name)
source_replay = store.prepare_replay(source.name)
capability = LabReplayCapability(
schema_version="missioncore.observation-lab-replay-capability/v1",
kind="canonical-recorded-rerun",
viewer_profile="recorded-session",
timeline="session_time",
activation="explicit",
commands_enabled=False,
)
binding = store.publish_lab_instance(
session_id="lab-recorded-delete-only-projection",
source_session_id=source.name,
display_name="LAB V1 · disposable catalog projection",
lab_id="LAB V1",
result_kind="recorded-perception-qualification",
result_id="lab-v1-vegetation-shadow-" + "c" * 64,
source_result_id="lab-v1-vegetation-shadow-" + "d" * 64,
run_created_at_utc="2026-08-29T18:05:11.329061+00:00",
include_recorded_media=False,
replay_capability=capability,
provenance={
"replay_capability": capability.as_dict(),
"method": lab_method(),
},
)
projection_replay = store.prepare_replay(binding.session_id)
cache_sentinel = store.data_dir / "recordings" / binding.session_id / "sentinel.rrd"
cache_sentinel.parent.mkdir(parents=True)
cache_sentinel.write_bytes(b"cache-owned-by-eviction-policy")
assert projection_replay.primary_artifact.path == source_replay.primary_artifact.path
store.delete_capability_lab_projection(binding.session_id)
with pytest.raises(SessionNotFoundError):
store.get_session(binding.session_id)
retained, retained_snapshot = store.get_session_with_catalog_snapshot(source.name)
assert retained == source_detail
assert retained_snapshot == source_snapshot
assert source.is_dir()
assert source_payload.is_file()
assert hashlib.sha256(source_payload.read_bytes()).hexdigest() == source_payload_sha256
assert cache_sentinel.read_bytes() == b"cache-owned-by-eviction-policy"
assert (
store.prepare_replay(source.name).primary_artifact.path
== source_replay.primary_artifact.path
)
with sqlite3.connect(store.database_path) as connection:
assert connection.execute(
"SELECT COUNT(*) FROM observation_lab_instances WHERE session_id = ?",
(binding.session_id,),
).fetchone()[0] == 0
assert connection.execute(
"SELECT COUNT(*) FROM observation_session_artifacts WHERE session_id = ?",
(binding.session_id,),
).fetchone()[0] == 0
def test_capability_projection_mutations_reject_source_legacy_and_corrupt_rows(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
legacy = store.publish_lab_instance(
session_id="lab-legacy-not-observatory-owned",
source_session_id=source.name,
display_name="LAB E21 · legacy",
lab_id="LAB E21",
result_kind="e21-realtime-envelope",
result_id="e21-realtime-envelope-" + "e" * 64,
run_created_at_utc="2026-07-23T15:55:15.548Z",
provenance={"method": lab_method()},
)
capability = LabReplayCapability(
schema_version="missioncore.observation-lab-replay-capability/v1",
kind="canonical-recorded-rerun",
viewer_profile="recorded-session",
timeline="session_time",
activation="explicit",
commands_enabled=False,
)
corrupt = store.publish_lab_instance(
session_id="lab-recorded-corrupt-owner",
source_session_id=source.name,
display_name="LAB V1 · corrupt owner",
lab_id="LAB V1",
result_kind="recorded-perception-qualification",
result_id="lab-v1-vegetation-shadow-" + "f" * 64,
run_created_at_utc="2026-08-29T18:05:11.329061+00:00",
include_recorded_media=False,
replay_capability=capability,
provenance={
"replay_capability": capability.as_dict(),
"method": lab_method(),
},
)
mismatch = store.publish_lab_instance(
session_id="lab-recorded-provenance-mismatch",
source_session_id=source.name,
display_name="LAB V1 · provenance mismatch",
lab_id="LAB V1",
result_kind="recorded-perception-qualification",
result_id="lab-v1-vegetation-shadow-" + "1" * 64,
run_created_at_utc="2026-08-29T18:05:11.329061+00:00",
include_recorded_media=False,
replay_capability=capability,
provenance={
"replay_capability": capability.as_dict(),
"method": lab_method(),
},
)
with sqlite3.connect(store.database_path) as connection:
connection.execute(
"UPDATE observation_sessions SET archive_id = ? WHERE session_id = ?",
("not-observatory-owned", corrupt.session_id),
)
mismatched_provenance = {
"replay_capability": {
**capability.as_dict(),
"commands_enabled": True,
},
"method": lab_method(),
}
connection.execute(
"UPDATE observation_lab_instances SET provenance_json = ? "
"WHERE session_id = ?",
(
json.dumps(mismatched_provenance, separators=(",", ":")),
mismatch.session_id,
),
)
connection.commit()
for session_id in (source.name, legacy.session_id, corrupt.session_id):
with pytest.raises(SessionIntegrityError, match="capability-owned"):
store.rename_capability_lab_projection(session_id, "Нельзя изменить")
with pytest.raises(SessionIntegrityError, match="capability-owned"):
store.delete_capability_lab_projection(session_id)
with pytest.raises(SessionIntegrityError, match="does not match provenance"):
store.rename_capability_lab_projection(mismatch.session_id, "Нельзя изменить")
with pytest.raises(SessionIntegrityError, match="does not match provenance"):
store.delete_capability_lab_projection(mismatch.session_id)
assert store.get_session(source.name).summary.session_id == source.name
assert store.get_session(legacy.session_id).summary.display_name == "LAB E21 · legacy"
with sqlite3.connect(store.database_path) as connection:
retained_ids = {
row[0]
for row in connection.execute(
"SELECT session_id FROM observation_sessions"
).fetchall()
}
assert {
source.name,
legacy.session_id,
corrupt.session_id,
mismatch.session_id,
} <= retained_ids
@pytest.mark.parametrize("value", [0, 1, None, True])
def test_lab_replay_capability_rejects_non_literal_false(value: object) -> None:
with pytest.raises(ValueError, match="replay capability"):
LabReplayCapability(
schema_version="missioncore.observation-lab-replay-capability/v1",
kind="canonical-recorded-rerun",
viewer_profile="recorded-session",
timeline="session_time",
activation="explicit",
commands_enabled=value, # type: ignore[arg-type]
)
def test_lab_instance_rejects_boolean_alias_in_capability_provenance(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
capability = LabReplayCapability(
schema_version="missioncore.observation-lab-replay-capability/v1",
kind="canonical-recorded-rerun",
viewer_profile="recorded-session",
timeline="session_time",
activation="explicit",
commands_enabled=False,
)
aliased = {**capability.as_dict(), "commands_enabled": 0}
with pytest.raises(ValueError, match="exactly match provenance"):
store.publish_lab_instance(
session_id="lab-recorded-replay-alias",
source_session_id=source.name,
display_name="LAB V1 · recorded replay alias",
lab_id="LAB V1",
result_kind="recorded-perception-qualification",
result_id="lab-v1-vegetation-shadow-" + "a" * 64,
run_created_at_utc="2026-08-29T18:05:11.329061+00:00",
replay_capability=capability,
provenance={
"replay_capability": aliased,
"method": lab_method(),
},
)
def test_lab_instance_rejects_publication_without_a_method_manifest(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
with pytest.raises(ValueError, match="method manifest"):
store.publish_lab_instance(
session_id="lab-without-method",
source_session_id=source.name,
display_name="LAB E30 · incomplete method",
lab_id="LAB E30",
result_kind="e30-test",
result_id="e30-test",
run_created_at_utc="2026-07-26T15:00:00Z",
provenance={"schema_version": "legacy"},
)
def test_bounded_lab_instance_excludes_unbounded_recorded_media(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
make_recorded_camera_source(source)
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
assert store.list_recorded_media(source.name)
binding = store.publish_lab_instance(
session_id="lab-e21-window-d0201712",
source_session_id=source.name,
display_name="LAB E21.2 · bounded 60s",
lab_id="LAB E21.2",
result_kind="e21-realtime-envelope",
result_id="e10-integrated-perception-" + "1" * 64,
source_result_id="e21-realtime-envelope-" + "2" * 64,
config_sha256="3" * 64,
run_created_at_utc="2026-07-23T15:55:15.548Z",
duration_seconds=59.962,
include_recorded_media=False,
provenance={"timeline_scope": "bounded", "method": lab_method()},
)
detail = store.get_session(binding.session_id)
assert detail.summary.duration_seconds == pytest.approx(59.962)
assert store.list_recorded_media(binding.session_id) == ()
assert store.prepare_replay(binding.session_id).primary_artifact.path.is_file()
def test_capability_projection_summary_is_exact_and_idempotently_repairable(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
make_recorded_camera_source(source)
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
_source_detail, source_snapshot_sha256 = (
store.get_session_with_catalog_snapshot(source.name)
)
capability = LabReplayCapability(
schema_version="missioncore.observation-lab-replay-capability/v1",
kind="canonical-recorded-rerun",
viewer_profile="recorded-session",
timeline="session_time",
activation="explicit",
commands_enabled=False,
)
parameters = {
"session_id": "lab-recorded-bounded",
"source_session_id": source.name,
"display_name": "LAB V1 · bounded recorded review",
"lab_id": "LAB V1",
"result_kind": "recorded-perception-qualification",
"result_id": "lab-v1-vegetation-shadow-" + "1" * 64,
"source_result_id": "lab-v1-vegetation-shadow-" + "2" * 64,
"run_created_at_utc": "2026-08-29T18:05:11.329061+00:00",
"duration_seconds": 59.962,
"include_recorded_media": False,
"expected_source_catalog_sha256": source_snapshot_sha256,
"replay_capability": capability,
"provenance": {
"replay_capability": capability.as_dict(),
"method": lab_method(),
},
}
binding = store.publish_lab_instance(**parameters)
detail = store.get_session(binding.session_id)
assert detail.summary.modalities == ("point-cloud", "trajectory")
assert detail.summary.source_count == len(detail.sources) == 2
referenced_artifacts = {item.artifact_id for item in detail.sources}
assert detail.summary.total_bytes == sum(
artifact.byte_length
for artifact in detail.artifacts
if artifact.artifact_id in referenced_artifacts
)
with sqlite3.connect(store.database_path) as connection:
connection.execute(
"UPDATE observation_sessions SET modalities_json = ?, source_count = ?, "
"total_bytes = ? WHERE session_id = ?",
('["point-cloud","trajectory","video"]', 3, 999_999_999, binding.session_id),
)
connection.commit()
assert store.publish_lab_instance(**parameters) == binding
repaired = store.get_session(binding.session_id)
assert repaired.summary.modalities == ("point-cloud", "trajectory")
assert repaired.summary.source_count == 2
assert repaired.summary.total_bytes == detail.summary.total_bytes
with pytest.raises(SessionIntegrityError, match="recorded-media policy"):
store.publish_lab_instance(**{**parameters, "include_recorded_media": True})
with sqlite3.connect(store.database_path) as connection:
connection.execute(
"UPDATE observation_session_artifacts SET sha256 = ? "
"WHERE session_id = ? AND artifact_id = 'raw-transport-primary'",
("0" * 64, binding.session_id),
)
connection.commit()
with pytest.raises(SessionIntegrityError, match="source snapshot"):
store.publish_lab_instance(**parameters)
def test_lab_publication_rejects_a_source_changed_after_snapshot(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
_detail, snapshot_sha256 = store.get_session_with_catalog_snapshot(source.name)
with sqlite3.connect(store.database_path) as connection:
connection.execute(
"UPDATE observation_session_artifacts SET byte_length = byte_length + 1 "
"WHERE session_id = ? AND artifact_id = 'raw-transport-primary'",
(source.name,),
)
connection.commit()
with pytest.raises(SessionIntegrityError, match="changed after admission"):
store.publish_lab_instance(
session_id="lab-source-snapshot-race",
source_session_id=source.name,
display_name="LAB E21 · source snapshot race",
lab_id="LAB E21",
result_kind="source-snapshot-race",
result_id="source-snapshot-race-result",
run_created_at_utc="2026-08-29T18:05:11.329061+00:00",
provenance={"method": lab_method()},
expected_source_catalog_sha256=snapshot_sha256,
)
assert store.get_lab_instance("lab-source-snapshot-race") is None
def test_delete_session_rejects_a_catalog_target_outside_its_allowed_root(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
with sqlite3.connect(store.database_path) as connection:
connection.execute(
"UPDATE observation_sessions SET session_root = ? WHERE session_id = ?",
(str(repository), session.name),
)
with pytest.raises(SessionIntegrityError, match="escapes"):
store.delete_session(session.name)
assert session.is_dir()
def test_interrupted_capture_replays_only_committed_prefix_before_partial_raw_tail(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
replace_summary_with_recovery_metadata(session)
raw = session / "captures" / "mqtt_live" / "mqtt.raw.k1mqtt"
committed_bytes = raw.stat().st_size
with raw.open("ab") as stream:
stream.write(FRAME_HEADER.pack(12, 100)[:7])
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
detail = store.get_session(session.name)
command = store.prepare_replay(session.name)
assert detail.summary.status == "interrupted"
assert detail.summary.replayable is True
assert command.primary_artifact.replay_byte_length == committed_bytes
assert (
command.primary_artifact.replay_byte_length < command.primary_artifact.path.stat().st_size
)
def test_interrupted_capture_rejects_non_frame_raw_tail(tmp_path: Path) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
replace_summary_with_recovery_metadata(session)
raw = session / "captures" / "mqtt_live" / "mqtt.raw.k1mqtt"
with raw.open("ab") as stream:
stream.write(FRAME_HEADER.pack(0, 0))
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
assert store.get_session(session.name).summary.replayable is False
def test_interrupted_capture_tolerates_bounded_group_commit_raw_tail(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
replace_summary_with_recovery_metadata(session)
raw = session / "captures" / "mqtt_live" / "mqtt.raw.k1mqtt"
committed_bytes = raw.stat().st_size
with raw.open("ab") as stream:
for payload in (b"pending-one", b"pending-two"):
topic = b"RealtimePath"
stream.write(FRAME_HEADER.pack(len(topic), len(payload)))
stream.write(topic)
stream.write(payload)
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
command = store.prepare_replay(session.name)
assert store.get_session(session.name).summary.status == "interrupted"
assert command.primary_artifact.replay_byte_length == committed_bytes
def test_failed_final_summary_falls_back_to_last_committed_prefix(tmp_path: Path) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
capture = session / "captures" / "mqtt_live"
metadata = capture / "mqtt.metadata.jsonl"
first_record = metadata.read_text(encoding="utf-8").splitlines(keepends=True)[0]
metadata.write_text(first_record, encoding="utf-8")
summary_path = capture / "mqtt.summary.json"
summary = json.loads(summary_path.read_text(encoding="utf-8"))
summary["error"] = "synthetic metadata fsync failure"
summary_path.write_text(json.dumps(summary), encoding="utf-8")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
detail = store.get_session(session.name)
assert detail.summary.status == "interrupted"
assert detail.summary.replayable is True
assert detail.summary.modalities == ("point-cloud",)
def test_layout_save_is_atomic_and_revision_checked(tmp_path: Path) -> None:
store = SessionStore(tmp_path / "repo", data_dir=tmp_path / "data")
first = store.save_layout(
"observation.spatial",
schema_version=1,
expected_revision=0,
name="Операторская сцена",
layout={"visible_source_ids": ["sensor.lidar.primary"], "windows": []},
)
assert first.revision == 1
assert store.get_layout("observation.spatial") == first
with pytest.raises(LayoutConflictError, match="revision changed"):
store.save_layout(
"observation.spatial",
schema_version=1,
expected_revision=0,
name="Устаревшая запись",
layout={},
)
second = store.save_layout(
"observation.spatial",
schema_version=1,
expected_revision=1,
name="Операторская сцена",
layout={"visible_source_ids": [], "windows": []},
)
assert second.revision == 2
assert store.get_layout("observation.spatial").layout["visible_source_ids"] == []
def test_layout_startup_migration_removes_transient_tool_window_state(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
data_dir = tmp_path / "data"
initial = SessionStore(repository, data_dir=data_dir)
initial.save_layout(
"observation.spatial",
schema_version=1,
expected_revision=0,
name="Пространственная сцена",
layout={
"tool_windows": {
"sources_open": True,
"display_open": True,
"layers_open": True,
"order": ["sources", "display", "layers"],
},
"visible_source_ids": [],
},
)
migrated = SessionStore(repository, data_dir=data_dir).get_layout("observation.spatial")
assert migrated.schema_version == 2
assert "tool_windows" not in migrated.layout