fix(replay): stream sealed perception overlays
This commit is contained in:
@@ -14,6 +14,8 @@ import k1link.compute.results as result_module
|
||||
from k1link.compute import (
|
||||
RecordedCalibratedFusionStore,
|
||||
RecordedPerceptionEpochStore,
|
||||
RecordedPerceptionOverlayArtifact,
|
||||
RecordedPerceptionOverlayMux,
|
||||
RecordedPerceptionOverlayStore,
|
||||
prepare_camera_compute_job,
|
||||
validate_recorded_calibrated_fusion,
|
||||
@@ -500,6 +502,56 @@ def test_calibrated_fusion_is_bound_to_full_epoch_and_reuses_render_cache(
|
||||
assert calls == 1
|
||||
|
||||
|
||||
def test_perception_mux_preserves_file_backed_artifact(tmp_path: Path) -> None:
|
||||
payload = b"RRF2sealed"
|
||||
overlay = tmp_path / "sealed.rrd"
|
||||
overlay.write_bytes(payload)
|
||||
artifact = RecordedPerceptionOverlayArtifact(
|
||||
path=overlay,
|
||||
byte_length=len(payload),
|
||||
sha256=hashlib.sha256(payload).hexdigest(),
|
||||
)
|
||||
|
||||
class Primary:
|
||||
def materialize(
|
||||
self,
|
||||
_session_id: str,
|
||||
*,
|
||||
application_id: str,
|
||||
recording_id: str,
|
||||
) -> RecordedPerceptionOverlayArtifact:
|
||||
assert application_id == "nodedc_mission_core_recorded"
|
||||
assert recording_id == "recording-1"
|
||||
return artifact
|
||||
|
||||
def render(
|
||||
self,
|
||||
_session_id: str,
|
||||
*,
|
||||
application_id: str,
|
||||
recording_id: str,
|
||||
) -> bytes:
|
||||
raise AssertionError(f"byte rendering must not run: {application_id}/{recording_id}")
|
||||
|
||||
mux = RecordedPerceptionOverlayMux(Primary(), None)
|
||||
assert (
|
||||
mux.materialize(
|
||||
"session-1",
|
||||
application_id="nodedc_mission_core_recorded",
|
||||
recording_id="recording-1",
|
||||
)
|
||||
== artifact
|
||||
)
|
||||
assert (
|
||||
mux.render(
|
||||
"session-1",
|
||||
application_id="nodedc_mission_core_recorded",
|
||||
recording_id="recording-1",
|
||||
)
|
||||
== payload
|
||||
)
|
||||
|
||||
|
||||
def test_calibrated_fusion_rejects_changed_array_artifact(tmp_path: Path) -> None:
|
||||
job = _job(tmp_path)
|
||||
perception_root = _epoch_result(tmp_path, job)
|
||||
|
||||
@@ -134,14 +134,15 @@ def test_integrated_overlay_recovers_admission_from_sealed_cache_without_revalid
|
||||
ffmpeg_path=tmp_path / "ffmpeg",
|
||||
)
|
||||
|
||||
assert (
|
||||
store.render(
|
||||
"session-1",
|
||||
application_id="nodedc_mission_core_recorded",
|
||||
recording_id=recording_id,
|
||||
)
|
||||
== payload
|
||||
artifact = store.materialize(
|
||||
"session-1",
|
||||
application_id="nodedc_mission_core_recorded",
|
||||
recording_id=recording_id,
|
||||
)
|
||||
assert artifact is not None
|
||||
assert artifact.path == output.resolve()
|
||||
assert artifact.byte_length == len(payload)
|
||||
assert artifact.sha256 == hashlib.sha256(payload).hexdigest()
|
||||
admission = json.loads((cache_root / "session-1" / "admission.json").read_text())
|
||||
assert admission["result_id"] == result.name
|
||||
assert store.status("session-1", recording_id=recording_id) == {
|
||||
@@ -150,6 +151,27 @@ def test_integrated_overlay_recovers_admission_from_sealed_cache_without_revalid
|
||||
"elapsed_seconds": pytest.approx(0.0, abs=0.1),
|
||||
"byte_length": len(payload),
|
||||
}
|
||||
restarted = IntegratedPerceptionOverlayStore(
|
||||
jobs_root=jobs_root,
|
||||
results_root=results_root,
|
||||
lidar_packs_root=lidar_packs_root,
|
||||
cache_root=cache_root,
|
||||
ffmpeg_path=tmp_path / "ffmpeg",
|
||||
)
|
||||
restarted_artifact = restarted.materialize(
|
||||
"session-1",
|
||||
application_id="nodedc_mission_core_recorded",
|
||||
recording_id=recording_id,
|
||||
)
|
||||
assert restarted_artifact == artifact
|
||||
assert (
|
||||
restarted.render(
|
||||
"session-1",
|
||||
application_id="nodedc_mission_core_recorded",
|
||||
recording_id=recording_id,
|
||||
)
|
||||
== payload
|
||||
)
|
||||
|
||||
|
||||
def test_integrated_overlay_serializes_heavy_materialization_across_sessions(
|
||||
|
||||
@@ -13,10 +13,11 @@ from typing import Any
|
||||
|
||||
import pytest
|
||||
from fastapi import APIRouter, HTTPException, Request, Response
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
import k1link.sessions.media as recorded_media_module
|
||||
from k1link.compute import RecordedPerceptionVideo
|
||||
from k1link.compute import RecordedPerceptionOverlayArtifact, RecordedPerceptionVideo
|
||||
from k1link.device_plugins.xgrids_k1 import xgrids_k1_archive_source
|
||||
from k1link.device_plugins.xgrids_k1.mqtt.capture import FRAME_HEADER, RAW_MAGIC
|
||||
from k1link.sessions import (
|
||||
@@ -1383,6 +1384,70 @@ def test_recorded_perception_endpoint_returns_one_complete_optional_overlay(
|
||||
assert empty.status_code == 204
|
||||
|
||||
|
||||
def test_recorded_perception_endpoint_streams_validated_file_artifact(
|
||||
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))
|
||||
payload = b"RRF2file-backed-perception"
|
||||
overlay = tmp_path / "sealed-perception.rrd"
|
||||
overlay.write_bytes(payload)
|
||||
digest = hashlib.sha256(payload).hexdigest()
|
||||
calls: list[tuple[str, str, str]] = []
|
||||
|
||||
class Provider:
|
||||
def materialize(
|
||||
self,
|
||||
session_id: str,
|
||||
*,
|
||||
application_id: str,
|
||||
recording_id: str,
|
||||
) -> RecordedPerceptionOverlayArtifact:
|
||||
calls.append((session_id, application_id, recording_id))
|
||||
return RecordedPerceptionOverlayArtifact(
|
||||
path=overlay,
|
||||
byte_length=len(payload),
|
||||
sha256=digest,
|
||||
)
|
||||
|
||||
def render(
|
||||
self,
|
||||
_session_id: str,
|
||||
*,
|
||||
application_id: str,
|
||||
recording_id: str,
|
||||
) -> bytes:
|
||||
raise AssertionError(f"heap rendering must not run: {application_id}/{recording_id}")
|
||||
|
||||
router = build_session_router(store, perception_overlay_provider=Provider())
|
||||
perception_route = endpoint(
|
||||
router,
|
||||
"/api/v1/observation-sessions/{session_id}/perception.rrd",
|
||||
"POST",
|
||||
)
|
||||
response = asyncio.run(
|
||||
perception_route(
|
||||
session_id=session.name,
|
||||
request=RecordedPerceptionRequest(
|
||||
application_id="nodedc_mission_core_recorded",
|
||||
recording_id="recording-001",
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
assert isinstance(response, FileResponse)
|
||||
assert Path(response.path) == overlay
|
||||
assert response.media_type == "application/vnd.rerun.rrd"
|
||||
assert response.headers["content-length"] == str(len(payload))
|
||||
assert response.headers["etag"] == f'"sha256:{digest}"'
|
||||
assert response.headers["content-disposition"] == 'inline; filename="perception.rrd"'
|
||||
assert str(overlay) not in str(response.headers)
|
||||
assert calls == [(session.name, "nodedc_mission_core_recorded", "recording-001")]
|
||||
|
||||
|
||||
def test_recorded_point_color_endpoint_is_strict_and_forwards_confined_command(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user