fix(observatory): refine catalog and replay UX

This commit is contained in:
DCCONSTRUCTIONS
2026-08-30 20:36:12 +03:00
parent 023151c186
commit be83283ee0
18 changed files with 2137 additions and 201 deletions
+136
View File
@@ -38,6 +38,7 @@ from k1link.sessions.media import _manifest_generation_sha256
from k1link.viewer.recorded import recorded_blueprint_rrd
from k1link.viewer.rerun_bridge import RerunSceneSettings
from k1link.web.camera_archive import CameraArchiveWriter
from k1link.web.observatory_api import build_observatory_router
from k1link.web.session_api import (
LayoutPutRequest,
RecordedBlueprintRequest,
@@ -422,6 +423,141 @@ def test_session_router_rolls_capability_projections_out_only_in_v2(
).status_code == 422
def test_observatory_projection_api_renames_alias_and_deletes_only_projection(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> 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_snapshot = store.get_session_with_catalog_snapshot(source.name)[1]
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,
)
projection = store.publish_lab_instance(
session_id="lab-observatory-api-projection",
source_session_id=source.name,
display_name="LAB V1 · canonical API projection",
lab_id="LAB V1",
result_kind="recorded-perception-qualification",
result_id="lab-v1-vegetation-shadow-" + "8" * 64,
source_result_id="lab-v1-vegetation-shadow-" + "9" * 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(),
},
)
cache_sentinel = store.data_dir / "recordings" / projection.session_id / "cached.rrd"
cache_sentinel.parent.mkdir(parents=True)
cache_sentinel.write_bytes(b"must-not-be-touched")
monkeypatch.setattr(
store,
"delete_session",
lambda _session_id: pytest.fail(
"Observatory router must not enter generic evidence/cache deletion"
),
)
application = FastAPI()
application.include_router(build_observatory_router(store))
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
renamed = client.patch(
url,
json={
"schema_version": "missioncore.observatory-lab-projection-rename/v1",
"display_name": " Маршрут у школы ",
},
)
assert renamed.status_code == 200
assert renamed.json() == {
"schema_version": "missioncore.observatory-lab-projection/v1",
"session_id": projection.session_id,
"display_name": "Маршрут у школы",
}
assert store.get_session(projection.session_id).summary.display_name == "Маршрут у школы"
deleted = client.delete(url)
assert deleted.status_code == 204
assert deleted.content == b""
assert client.delete(url).status_code == 404
assert store.get_session_with_catalog_snapshot(source.name)[1] == source_snapshot
assert hashlib.sha256(source_payload.read_bytes()).hexdigest() == source_payload_sha256
assert source.is_dir()
assert cache_sentinel.read_bytes() == b"must-not-be-touched"
def test_observatory_projection_api_rejects_source_and_legacy_lab(
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-observatory-api-legacy",
source_session_id=source.name,
display_name="LAB E21 · legacy API row",
lab_id="LAB E21",
result_kind="e21-realtime-envelope",
result_id="e21-realtime-envelope-" + "a" * 64,
run_created_at_utc="2026-07-23T15:55:15.548Z",
provenance={"method": lab_method()},
)
application = FastAPI()
application.include_router(build_observatory_router(store))
client = TestClient(application)
rename_document = {
"schema_version": "missioncore.observatory-lab-projection-rename/v1",
"display_name": "Нельзя изменить",
}
for session_id in (source.name, legacy.session_id):
url = f"/api/v1/observatory/lab-projections/{session_id}"
assert client.patch(url, json=rename_document).status_code == 409
assert client.delete(url).status_code == 409
assert source.is_dir()
assert store.get_session(source.name).summary.session_id == source.name
assert store.get_session(legacy.session_id).summary.display_name == "LAB E21 · legacy API row"
def test_delete_session_removes_evidence_and_cache_but_refuses_an_open_recording(
tmp_path: Path,
) -> None: