fix(lab): enforce canonical replay runtime
This commit is contained in:
@@ -37,6 +37,7 @@ from k1link.sessions import (
|
||||
SessionStore,
|
||||
validate_recorded_media_timeline,
|
||||
)
|
||||
from k1link.sessions.canonical_lab_spatial import canonical_lab_spatial_frame
|
||||
from k1link.sessions.plugin_contract import RecordedPointColorRenderer
|
||||
from k1link.viewer.recorded import (
|
||||
APPLICATION_ID as RECORDED_APPLICATION_ID,
|
||||
@@ -824,6 +825,76 @@ def build_session_router(
|
||||
**response_kwargs,
|
||||
)
|
||||
|
||||
@router.get(
|
||||
"/api/v1/observation-sessions/{session_id}/canonical-lab/spatial-frame"
|
||||
)
|
||||
async def get_observation_session_canonical_lab_spatial_frame(
|
||||
session_id: str,
|
||||
generation: Annotated[str, Query(min_length=64, max_length=64)],
|
||||
time_ns: Annotated[int, Query(ge=0, le=MAX_SAFE_INTEGER)],
|
||||
) -> JSONResponse:
|
||||
"""Serve one body-frame sample for the canonical recorded-LAB clock.
|
||||
|
||||
The camera timeline owns playback. Spatial evidence is sampled from
|
||||
the same immutable recording instead of starting a second Rerun clock.
|
||||
"""
|
||||
|
||||
if SAFE_SHA256.fullmatch(generation) is None:
|
||||
raise HTTPException(
|
||||
status_code=412,
|
||||
detail="Поколение spatial-записи не совпадает.",
|
||||
)
|
||||
if recording_preparation_manager is None:
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail="Сервис canonical LAB spatial playback не настроен.",
|
||||
)
|
||||
snapshot = recording_preparation_manager.status(session_id)
|
||||
if snapshot is None or snapshot.state != "ready" or snapshot.recording is None:
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="Запись canonical LAB ещё не подготовлена.",
|
||||
)
|
||||
_require_matching_recording_generation(snapshot.recording.sha256, generation)
|
||||
pinned = recording_preparation_manager.pin_ready(
|
||||
session_id,
|
||||
preparation_id=snapshot.preparation_id,
|
||||
)
|
||||
if pinned is None:
|
||||
raise HTTPException(
|
||||
status_code=412,
|
||||
detail="Подготовленная spatial-запись была заменена.",
|
||||
)
|
||||
pinned_snapshot, release_recording = pinned
|
||||
try:
|
||||
recording = pinned_snapshot.recording
|
||||
if recording is None:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Подготовленная spatial-запись недоступна.",
|
||||
)
|
||||
payload = await run_in_threadpool(
|
||||
canonical_lab_spatial_frame,
|
||||
recording.path,
|
||||
generation,
|
||||
time_ns,
|
||||
)
|
||||
except (OSError, ValueError) as exc:
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail="Canonical LAB spatial frame не прошёл проверку.",
|
||||
) from exc
|
||||
finally:
|
||||
release_recording()
|
||||
return JSONResponse(
|
||||
payload,
|
||||
headers={
|
||||
"Cache-Control": "private, max-age=31536000, immutable",
|
||||
"ETag": f'"{generation}:{payload["source_time_ns"]}"',
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
},
|
||||
)
|
||||
|
||||
@router.post("/api/v1/observation-sessions/{session_id}/blueprint.rrd")
|
||||
async def get_observation_session_blueprint(
|
||||
session_id: str,
|
||||
|
||||
Reference in New Issue
Block a user