feat(lab): seal M4.8R3 occupancy shadows

This commit is contained in:
DCCONSTRUCTIONS
2026-08-26 13:39:54 +03:00
parent 90bdc27785
commit b82a97fe8b
8 changed files with 1498 additions and 0 deletions
+20
View File
@@ -126,6 +126,9 @@ from k1link.web.lidar_api import build_lidar_router
from k1link.web.lidar_local_surface_service import K1LocalSurfaceReadService
from k1link.web.m4_threat_replay_api import build_m4_threat_replay_router
from k1link.web.m48_object_quality_api import build_m48_object_quality_router
from k1link.web.m48r3_static_occupancy_api import (
build_m48r3_static_occupancy_router,
)
from k1link.web.m48s_fixed_class_detector_lab_api import (
build_m48s_fixed_class_detector_lab_router,
)
@@ -961,6 +964,23 @@ app.include_router(
),
)
)
app.include_router(
build_m48r3_static_occupancy_router(
root_provider=lambda: (
REPOSITORY_ROOT
/ ".runtime"
/ "compute-experiments"
/ "m48"
/ "static-occupancy-shadow-results"
),
repository_root_provider=lambda: REPOSITORY_ROOT,
camera_frame_provider=(
session_recorded_camera_frame_service.extract
if session_recorded_camera_frame_service is not None
else None
),
)
)
app.include_router(
build_m48s_fixed_class_detector_lab_router(
root_provider=lambda: (
@@ -0,0 +1,290 @@
"""Read-only API for sealed M4.8R3 static-occupancy Worker shadows."""
from __future__ import annotations
import copy
import re
from collections.abc import Callable
from functools import lru_cache
from pathlib import Path
from typing import Final
from fastapi import APIRouter, HTTPException, Query, Response
from k1link.laboratory.m48r3_static_occupancy_shadow import (
M48R3_SHADOW_PREFIX,
M48R3StaticOccupancyShadowError,
M48R3StaticOccupancyShadowResult,
read_m48r3_static_occupancy_shadow,
)
from k1link.perception.m48s_replay_timeline import (
M48R3_FRAME_EVIDENCE_SCHEMA,
M48sReplayTimeline,
M48sReplayTimelineError,
)
from k1link.perception.threat_timeline import RECORDED_SPATIAL_MAX_CHUNK_FRAMES
from k1link.sessions import RecordedCameraFrame, SessionIntegrityError
RootProvider = Callable[[], Path | None]
CameraFrameProvider = Callable[[str, int], RecordedCameraFrame]
RESULT_ID: Final = re.compile(rf"^{re.escape(M48R3_SHADOW_PREFIX)}[a-f0-9]{{64}}$")
RESULT_VIEW_SCHEMA: Final = "missioncore.m48r3-static-occupancy-shadow-view/v1"
RESULT_CATALOG_SCHEMA: Final = "missioncore.m48r3-static-occupancy-shadow-catalog/v1"
CASE_CATALOG_SCHEMA: Final = "missioncore.m48r3-static-occupancy-shadow-cases/v1"
ENDPOINT_ROOT: Final = "/api/v1/laboratory/m48r3/static-occupancy"
def build_m48r3_static_occupancy_router(
*,
root_provider: RootProvider = lambda: None,
repository_root_provider: RootProvider = lambda: None,
camera_frame_provider: CameraFrameProvider | None = None,
) -> APIRouter:
router = APIRouter(prefix=ENDPOINT_ROOT, tags=["laboratory"])
def result(result_id: str) -> M48R3StaticOccupancyShadowResult:
candidate = _resolve_candidate(root_provider, result_id)
try:
return _read_result_cached(str(candidate), _result_signature(candidate))
except (M48R3StaticOccupancyShadowError, OSError, ValueError):
raise HTTPException(status_code=404, detail="M4.8R3 result not found") from None
def timeline(result_id: str) -> M48sReplayTimeline:
candidate = _resolve_candidate(root_provider, result_id)
repository = _configured_root(repository_root_provider)
if repository is None:
raise HTTPException(status_code=503, detail="M4.8R3 timeline source unavailable")
result(result_id)
try:
return _read_timeline_cached(
str(repository),
str(candidate),
result_id,
_timeline_signature(candidate),
)
except (M48sReplayTimelineError, OSError, ValueError):
raise HTTPException(
status_code=503,
detail="M4.8R3 bounded timeline failed verification",
) from None
@router.get("/results")
def list_results(limit: int = Query(default=1, ge=1, le=10)) -> dict[str, object]:
root = _configured_root(root_provider)
if root is None:
return _empty_catalog(configured=False)
items: list[dict[str, object]] = []
invalid_total = 0
for candidate in sorted(root.iterdir()):
if not candidate.is_dir() or RESULT_ID.fullmatch(candidate.name) is None:
continue
try:
sealed = _read_result_cached(str(candidate), _result_signature(candidate))
items.append(_project_result(sealed))
except (M48R3StaticOccupancyShadowError, OSError, ValueError):
invalid_total += 1
items.sort(
key=lambda item: (str(item["created_at_utc"]), str(item["result_id"])),
reverse=True,
)
return {
"schema_version": RESULT_CATALOG_SCHEMA,
"configured": True,
"items": items[:limit],
"candidate_total": len(items) + invalid_total,
"invalid_total": invalid_total,
"access": "read-only",
}
@router.get("/{result_id}")
def get_result(result_id: str) -> dict[str, object]:
return _project_result(result(result_id))
@router.get("/{result_id}/cases")
def get_cases(result_id: str) -> dict[str, object]:
sealed = result(result_id)
return {
"schema_version": CASE_CATALOG_SCHEMA,
"result_id": result_id,
"cases": copy.deepcopy(sealed.cases),
"case_count": len(sealed.cases),
"ground_truth": False,
"authority": "replay-simulated",
"access": "read-only",
}
@router.get("/{result_id}/timeline")
def get_timeline(result_id: str) -> dict[str, object]:
return copy.deepcopy(timeline(result_id).metadata())
@router.get("/{result_id}/timeline/chunk")
def get_timeline_chunk(
result_id: str,
start: int = Query(default=0, ge=0),
count: int = Query(default=12, ge=1, le=RECORDED_SPATIAL_MAX_CHUNK_FRAMES),
) -> Response:
try:
content = timeline(result_id).chunk_json(
start_sequence=start,
frame_count=count,
)
except M48sReplayTimelineError:
raise HTTPException(status_code=404, detail="M4.8R3 chunk not found") from None
return _immutable_json(content)
@router.get("/{result_id}/timeline/frames/{sequence}/camera-points")
def get_camera_points(result_id: str, sequence: int) -> Response:
try:
content = timeline(result_id).camera_point_overlay_json(sequence=sequence)
except M48sReplayTimelineError:
raise HTTPException(
status_code=404,
detail="M4.8R3 camera points not found",
) from None
return _immutable_json(content)
@router.get("/{result_id}/timeline/frames/{sequence}/camera")
def get_camera(result_id: str, sequence: int) -> Response:
if camera_frame_provider is None:
raise HTTPException(status_code=503, detail="M4.8R3 camera decoder unavailable")
projected = timeline(result_id)
if not 0 <= sequence < len(projected.source_times_ns):
raise HTTPException(status_code=404, detail="M4.8R3 frame not found")
try:
camera = camera_frame_provider(projected.profile.session_id, sequence)
except (OSError, SessionIntegrityError, ValueError):
raise HTTPException(status_code=503, detail="M4.8R3 camera unavailable") from None
if camera.width != 800 or camera.height != 600:
raise HTTPException(status_code=503, detail="M4.8R3 camera size changed")
return Response(
content=camera.payload,
media_type=camera.media_type,
headers={
"Cache-Control": "private, max-age=31536000, immutable",
"ETag": f'"{camera.sha256}"',
"X-Content-Type-Options": "nosniff",
},
)
return router
@lru_cache(maxsize=2)
def _read_result_cached(
result_root: str,
signature: tuple[int, ...],
) -> M48R3StaticOccupancyShadowResult:
del signature
return read_m48r3_static_occupancy_shadow(Path(result_root))
@lru_cache(maxsize=2)
def _read_timeline_cached(
repository_root: str,
result_root: str,
result_id: str,
signature: tuple[int, ...],
) -> M48sReplayTimeline:
del signature
return M48sReplayTimeline(
repository_root=Path(repository_root),
result_root=Path(result_root),
result_id=result_id,
frames_name="frames.jsonl",
worker_result_name="worker-result.json",
frame_evidence_schema=M48R3_FRAME_EVIDENCE_SCHEMA,
frame_diff_name="frame-diff.jsonl",
camera_endpoint_root=ENDPOINT_ROOT,
)
def _project_result(result: M48R3StaticOccupancyShadowResult) -> dict[str, object]:
return {
"schema_version": RESULT_VIEW_SCHEMA,
"result_id": result.result_id,
"created_at_utc": result.manifest["created_at_utc"],
"accepted": result.manifest["accepted"],
"profile": copy.deepcopy(result.report["profile"]),
"metrics": copy.deepcopy(result.report["metrics"]),
"gates": copy.deepcopy(result.report["gates"]),
"decision": copy.deepcopy(result.report["decision"]),
"limitations": copy.deepcopy(result.report["limitations"]),
"ground_truth": False,
"authority": copy.deepcopy(result.report["authority"]),
"access": "read-only",
}
def _resolve_candidate(provider: RootProvider, result_id: str) -> Path:
if RESULT_ID.fullmatch(result_id) is None:
raise HTTPException(status_code=404, detail="M4.8R3 result not found")
root = _configured_root(provider)
if root is None:
raise HTTPException(status_code=404, detail="M4.8R3 result not found")
candidate = root / result_id
if candidate.is_symlink() or not candidate.is_dir():
raise HTTPException(status_code=404, detail="M4.8R3 result not found")
resolved = candidate.resolve(strict=True)
if resolved.parent != root:
raise HTTPException(status_code=404, detail="M4.8R3 result not found")
return resolved
def _configured_root(provider: RootProvider) -> Path | None:
value = provider()
if value is None:
return None
if value.is_symlink() or not value.is_dir():
return None
return value.resolve(strict=True)
def _result_signature(candidate: Path) -> tuple[int, ...]:
return _signature(
candidate,
("manifest.json", "report.json", "cases.jsonl", "worker-result.json", "frames.jsonl"),
)
def _timeline_signature(candidate: Path) -> tuple[int, ...]:
return _signature(
candidate,
("worker-result.json", "frames.jsonl", "frame-diff.jsonl"),
)
def _signature(candidate: Path, names: tuple[str, ...]) -> tuple[int, ...]:
result: list[int] = []
for name in names:
path = candidate / name
if path.is_symlink() or not path.is_file():
raise ValueError("M4.8R3 artifact unavailable")
stat = path.stat()
result.extend((stat.st_size, stat.st_mtime_ns))
return tuple(result)
def _immutable_json(content: bytes) -> Response:
return Response(
content=content,
media_type="application/json",
headers={
"Cache-Control": "private, max-age=31536000, immutable",
"X-Content-Type-Options": "nosniff",
},
)
def _empty_catalog(*, configured: bool) -> dict[str, object]:
return {
"schema_version": RESULT_CATALOG_SCHEMA,
"configured": configured,
"items": [],
"candidate_total": 0,
"invalid_total": 0,
"access": "read-only",
}
__all__ = ["build_m48r3_static_occupancy_router"]