feat(observatory): ship modular AI inference labs

This commit is contained in:
DCCONSTRUCTIONS
2026-09-04 17:59:05 +03:00
parent eff60e490a
commit cada687173
145 changed files with 17651 additions and 1667 deletions
+227 -42
View File
@@ -9,6 +9,7 @@ import time
from collections.abc import Callable
from dataclasses import replace
from pathlib import Path
from types import SimpleNamespace
from typing import Any
import pytest
@@ -41,6 +42,7 @@ from k1link.web.camera_archive import CameraArchiveWriter
from k1link.web.observatory_api import build_observatory_router
from k1link.web.session_api import (
LayoutPutRequest,
RecordedBlueprintLifecycleRequest,
RecordedBlueprintRequest,
RecordedPerceptionRequest,
RecordedPointColorsRequest,
@@ -441,20 +443,15 @@ def test_session_router_versions_capability_and_calculation_profile_projections(
)["items"]
v3_by_id = {item["id"]: item for item in v3_labs}
assert v3_by_id[legacy.session_id]["lab"]["calculation_profile"] is None
assert (
v3_by_id[canonical.session_id]["lab"]["calculation_profile"]
== calculation_profile
)
assert (
v3_by_id[canonical.session_id]["lab"]["replay_capability"]
== capability.as_dict()
)
assert v3_by_id[canonical.session_id]["lab"]["calculation_profile"] == calculation_profile
assert v3_by_id[canonical.session_id]["lab"]["replay_capability"] == capability.as_dict()
application = FastAPI()
application.include_router(router)
assert TestClient(application).get(
"/api/v1/observation-sessions?lab_contract=v4"
).status_code == 422
assert (
TestClient(application).get("/api/v1/observation-sessions?lab_contract=v4").status_code
== 422
)
def test_observatory_projection_api_renames_alias_and_deletes_only_projection(
@@ -508,28 +505,37 @@ def test_observatory_projection_api_renames_alias_and_deletes_only_projection(
client = TestClient(application)
url = f"/api/v1/observatory/lab-projections/{projection.session_id}"
assert client.patch(
url,
json={
"schema_version": "missioncore.observatory-lab-projection-rename/v0",
"display_name": "Неверная версия",
},
).status_code == 422
assert client.patch(
url,
json={
"schema_version": "missioncore.observatory-lab-projection-rename/v1",
"display_name": "Новый разбор",
"unexpected": True,
},
).status_code == 422
assert client.patch(
url,
json={
"schema_version": "missioncore.observatory-lab-projection-rename/v1",
"display_name": " ",
},
).status_code == 422
assert (
client.patch(
url,
json={
"schema_version": "missioncore.observatory-lab-projection-rename/v0",
"display_name": "Неверная версия",
},
).status_code
== 422
)
assert (
client.patch(
url,
json={
"schema_version": "missioncore.observatory-lab-projection-rename/v1",
"display_name": "Новый разбор",
"unexpected": True,
},
).status_code
== 422
)
assert (
client.patch(
url,
json={
"schema_version": "missioncore.observatory-lab-projection-rename/v1",
"display_name": " ",
},
).status_code
== 422
)
renamed = client.patch(
url,
@@ -785,16 +791,16 @@ def test_canonical_lab_spatial_frame_uses_ready_immutable_recording(
"GET",
)
try:
response = asyncio.run(spatial_route(
session_id=session.name,
generation=generation,
time_ns=500_000_000,
profile="source-paced-ground-v3",
))
assert json.loads(response.body) == expected
assert response.headers["etag"] == (
f'"{generation}:source-paced-ground-v3:499000000"'
response = asyncio.run(
spatial_route(
session_id=session.name,
generation=generation,
time_ns=500_000_000,
profile="source-paced-ground-v3",
)
)
assert json.loads(response.body) == expected
assert response.headers["etag"] == (f'"{generation}:source-paced-ground-v3:499000000"')
assert response.headers["cache-control"].endswith("immutable")
finally:
manager.close()
@@ -1561,6 +1567,89 @@ def test_production_router_never_materializes_recording_inline(tmp_path: Path) -
assert calls == 0
def test_blueprint_lifecycle_releases_without_repreparing_source(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
calls: list[tuple[str, tuple[str, str, str]]] = []
class Sessions:
def release(self, key: tuple[str, str, str]) -> None:
calls.append(("release", key))
def renew(self, key: tuple[str, str, str]) -> bool:
calls.append(("renew", key))
return True
def forbidden_prepare(*args: object, **kwargs: object) -> None:
pytest.fail("lifecycle must not reprepare a recording")
monkeypatch.setattr("k1link.web.session_api.recorded_blueprint_sessions", Sessions())
monkeypatch.setattr("k1link.web.session_api._prepare_replay", forbidden_prepare)
store = SessionStore(tmp_path / "repo", data_dir=tmp_path / "data")
route = endpoint(
build_session_router(store),
"/api/v1/observation-sessions/{session_id}/blueprint-lifecycle",
"POST",
)
for action in ("renew", "release"):
request = RecordedBlueprintLifecycleRequest.model_validate(
{
"action": action,
"application_id": "nodedc_mission_core_recorded",
"recording_id": "recording",
"blueprint_session_id": "f" * 32,
}
)
response = asyncio.run(route(session_id="removed-source", request=request))
assert response.status_code == 204
assert response.headers["cache-control"] == "no-store"
assert calls == [
(action, ("nodedc_mission_core_recorded", "recording", "f" * 32))
for action in ("renew", "release")
]
with pytest.raises(HTTPException) as error:
asyncio.run(route(session_id="../unsafe", request=request))
assert error.value.status_code == 422
with pytest.raises(ValueError):
RecordedBlueprintLifecycleRequest.model_validate({**request.model_dump(), "path": "/tmp"})
def test_released_blueprint_update_returns_gone(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
from k1link.viewer.recorded_blueprint_lifecycle import BlueprintSessionReleased
def released(*args: object, **kwargs: object) -> bytes:
raise BlueprintSessionReleased("closed")
monkeypatch.setattr("k1link.web.session_api._prepare_replay", lambda *args: None)
monkeypatch.setattr("k1link.web.session_api.recorded_blueprint_rrd", released)
store = SessionStore(tmp_path / "repo", data_dir=tmp_path / "data")
route = endpoint(
build_session_router(store),
"/api/v1/observation-sessions/{session_id}/blueprint.rrd",
"POST",
)
with pytest.raises(HTTPException) as error:
asyncio.run(
route(
session_id="source",
request=RecordedBlueprintRequest(
application_id="nodedc_mission_core_recorded",
recording_id="recording",
blueprint_session_id="f" * 32,
accumulation_seconds=0.0,
show_points=True,
show_trajectory=False,
show_grid=True,
),
)
)
assert error.value.status_code == 410
def test_recorded_blueprint_endpoint_is_small_strict_and_session_scoped(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
@@ -1607,10 +1696,14 @@ def test_recorded_blueprint_endpoint_is_small_strict_and_session_scoped(
active_view="perception",
view_reset_generation=1,
unified_perception=True,
unified_camera_share=0.73,
show_detections_2d=True,
show_segmentation=True,
show_cuboids_3d=True,
follow_trajectory=True,
eye_position=(3.0, 4.0, 5.0),
eye_look_target=(1.0, 2.0, 0.0),
eye_up=(0.0, 0.0, 1.0),
),
)
)
@@ -1629,10 +1722,14 @@ def test_recorded_blueprint_endpoint_is_small_strict_and_session_scoped(
assert observed_kwargs[0]["active_view"] == "perception"
assert observed_kwargs[0]["view_reset_generation"] == 1
assert observed_kwargs[0]["unified_perception"] is True
assert observed_kwargs[0]["unified_camera_share"] == pytest.approx(0.73)
assert observed_kwargs[0]["show_detections_2d"] is True
assert observed_kwargs[0]["show_segmentation"] is True
assert observed_kwargs[0]["show_cuboids_3d"] is True
assert observed_kwargs[0]["follow_trajectory"] is True
assert observed_kwargs[0]["eye_position"] == (3.0, 4.0, 5.0)
assert observed_kwargs[0]["eye_look_target"] == (1.0, 2.0, 0.0)
assert observed_kwargs[0]["eye_up"] == (0.0, 0.0, 1.0)
with pytest.raises(ValueError):
RecordedBlueprintRequest.model_validate(
@@ -1644,6 +1741,32 @@ def test_recorded_blueprint_endpoint_is_small_strict_and_session_scoped(
"source_url": "https://outside.invalid/recording.rrd",
}
)
with pytest.raises(ValueError):
RecordedBlueprintRequest.model_validate(
{
"application_id": "nodedc_mission_core_recorded",
"recording_id": "recording-001",
"blueprint_session_id": "a" * 32,
"accumulation_seconds": 12.0,
"show_points": True,
"show_trajectory": True,
"show_grid": True,
"unified_camera_share": 1.2,
}
)
with pytest.raises(ValueError):
RecordedBlueprintRequest.model_validate(
{
"application_id": "nodedc_mission_core_recorded",
"recording_id": "recording-001",
"blueprint_session_id": "a" * 32,
"accumulation_seconds": 12.0,
"show_points": True,
"show_trajectory": True,
"show_grid": True,
"eye_position": [3.0, 4.0, 5.0],
}
)
with pytest.raises(HTTPException) as missing:
asyncio.run(
blueprint_route(
@@ -1662,6 +1785,68 @@ def test_recorded_blueprint_endpoint_is_small_strict_and_session_scoped(
assert missing.value.status_code == 404
def test_recorded_blueprint_restores_camera_bounds_after_base_launch_lease_expires(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> 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))
recording_path = tmp_path / "published.rrd"
calls: list[tuple[Path, dict[str, object]]] = []
class PublishedMaterializer:
def restore_published(self, command: ReplayCommand) -> object:
assert command.session_id == session.name
return SimpleNamespace(path=recording_path)
def camera_bounds(path: Path, **kwargs: object) -> float:
calls.append((path, kwargs))
return 123.5
monkeypatch.setattr(
"k1link.web.session_api.recorded_blueprint_rrd",
lambda *_args, **_kwargs: b"RRF2",
)
monkeypatch.setattr("k1link.web.session_api.recorded_orbital_radius_limit", camera_bounds)
route = endpoint(
build_session_router(store, recording_materializer=PublishedMaterializer()), # type: ignore[arg-type]
"/api/v1/observation-sessions/{session_id}/blueprint.rrd",
"POST",
)
response = asyncio.run(
route(
session_id=session.name,
request=RecordedBlueprintRequest(
application_id="nodedc_mission_core_recorded",
recording_id="recording-camera-bounds",
blueprint_session_id="b" * 32,
accumulation_seconds=305.0,
show_points=True,
show_trajectory=False,
show_grid=True,
current_time_ns=39_215_000_000,
),
)
)
assert response.headers["x-missioncore-camera-max-orbital-radius"] == "123.5"
assert calls == [
(
recording_path,
{
"current_time_ns": 39_215_000_000,
"accumulation_seconds": 305.0,
"show_points": True,
"show_trajectory": False,
},
)
]
def test_recorded_perception_endpoint_returns_one_complete_optional_overlay(
tmp_path: Path,
) -> None: