feat(lab): stabilize autonomous TGS playback

This commit is contained in:
DCCONSTRUCTIONS
2026-08-27 20:34:20 +03:00
parent c8593207d3
commit 95a1ef5057
26 changed files with 3326 additions and 253 deletions
+199
View File
@@ -0,0 +1,199 @@
from __future__ import annotations
import hashlib
import json
import socket
from pathlib import Path
import numpy as np
from fastapi import FastAPI
from fastapi.testclient import TestClient
from k1link.laboratory.m49_physical_safety_playback import (
CHUNK_HEADER,
CHUNK_MAGIC,
read_m49_physical_safety_playback,
seal_m49_physical_safety_playback,
)
from k1link.web.m49_physical_safety_playback_api import (
build_m49_physical_safety_playback_router,
)
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
def _sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def _sealed_source(root: Path, *, frame_count: int = 65, cell_count: int = 8) -> Path:
root.mkdir()
centers = np.column_stack(
(
np.arange(cell_count, dtype=np.float32) * np.float32(0.15),
np.zeros(cell_count, dtype=np.float32),
)
).astype(np.float32)
states = np.ones((frame_count, cell_count), dtype=np.uint8)
z_bounds = np.zeros((frame_count, cell_count, 2), dtype=np.float32)
z_bounds[..., 1] = np.float32(0.12)
missing_sequence = 17
states[missing_sequence] = 0
z_bounds[missing_sequence] = np.nan
np.save(root / "costmap-cell-centers-xy-m.npy", centers, allow_pickle=False)
np.save(root / "costmap-states.npy", states, allow_pickle=False)
np.save(root / "costmap-z-bounds-m.npy", z_bounds, allow_pickle=False)
frames = [
{
"source_frame_index": sequence,
"session_seconds": sequence / 10,
"sample_available": sequence != missing_sequence,
"eligible_point_count": 0 if sequence == missing_sequence else cell_count,
"ground_point_count": 0 if sequence == missing_sequence else cell_count,
"nonground_point_count": 0,
"rejected_point_count": 0,
"occupied_cell_count": 0,
}
for sequence in range(frame_count)
]
(root / "frames.ndjson").write_text(
"".join(json.dumps(frame, sort_keys=True) + "\n" for frame in frames),
encoding="utf-8",
)
source_identity = {"schema_version": "missioncore.test-sealed-source/v1"}
source_identity_sha256 = hashlib.sha256(
json.dumps(source_identity, sort_keys=True, separators=(",", ":")).encode()
).hexdigest()
result_id = "m49-tgs-full-shadow-" + source_identity_sha256
report = {
"schema_version": "missioncore.m49-tgs-full-shadow-report/v1",
"result_id": result_id,
"configuration": {
"coordinate_frame": "map-gravity-local",
"cell_size_m": 0.15,
"radius_m": 12.0,
},
"visual_review": {
"state_codes": {
"UNOBSERVED": 0,
"GROUND_SUPPORT": 1,
"NONGROUND_OCCUPIED": 2,
"UNKNOWN_REJECTED": 3,
}
},
}
(root / "report.json").write_text(json.dumps(report), encoding="utf-8")
artifacts = []
for name in (
"costmap-cell-centers-xy-m.npy",
"costmap-states.npy",
"costmap-z-bounds-m.npy",
"frames.ndjson",
"report.json",
):
path = root / name
artifacts.append(
{
"path": name,
"byte_length": path.stat().st_size,
"sha256": _sha256(path),
}
)
(root / "manifest.json").write_text(
json.dumps(
{
"schema_version": "missioncore.m49-tgs-full-shadow-lab/v1",
"result_id": result_id,
"identity": source_identity,
"identity_sha256": source_identity_sha256,
"artifacts": artifacts,
}
),
encoding="utf-8",
)
return root
def test_local_builder_seals_bounded_chunks_without_worker_network(
monkeypatch,
tmp_path: Path,
) -> None:
source = _sealed_source(tmp_path / "source")
def forbid_network(*_args: object, **_kwargs: object) -> None:
raise AssertionError("local LAB builder attempted network access")
monkeypatch.setattr(socket, "create_connection", forbid_network)
sealed = seal_m49_physical_safety_playback(
source_root=source,
destination_root=tmp_path / "results",
profile_path=(
REPOSITORY_ROOT / "config" / "perception" / "m49-physical-safety-shadow-v0.json"
),
created_at_utc="2026-08-27T12:00:00Z",
)
assert sealed.manifest["execution"] == {
"execution_class": "local-sequential-offline",
"worker_role": "realtime-only",
"worker_runtime_dependency": False,
"worker_requests_required": 0,
}
playback = sealed.manifest["playback"]
assert playback["cell_size_m"] == 0.15
assert playback["chunk_frame_count"] == 32
assert playback["startup_prebuffer_chunk_count"] == 2
assert playback["resident_chunk_count_max"] == 3
assert [(chunk["start"], chunk["count"]) for chunk in playback["chunks"]] == [
(0, 32),
(32, 32),
(64, 1),
]
first = (sealed.root / playback["chunks"][0]["path"]).read_bytes()
magic, start, count, cells, state_bytes, z_bytes = CHUNK_HEADER.unpack_from(first)
assert (magic, start, count, cells) == (CHUNK_MAGIC, 0, 32, 8)
assert state_bytes == 32 * 8
assert z_bytes == 32 * 8 * 2 * 4
assert read_m49_physical_safety_playback(sealed.root).result_id == sealed.result_id
def test_local_api_serves_only_hash_verified_sealed_tracks(tmp_path: Path) -> None:
source = _sealed_source(tmp_path / "source")
destination = tmp_path / "results"
sealed = seal_m49_physical_safety_playback(
source_root=source,
destination_root=destination,
profile_path=(
REPOSITORY_ROOT / "config" / "perception" / "m49-physical-safety-shadow-v0.json"
),
created_at_utc="2026-08-27T12:00:00Z",
)
app = FastAPI()
app.include_router(
build_m49_physical_safety_playback_router(root_provider=lambda: destination)
)
client = TestClient(app)
manifest_response = client.get(
f"/api/v1/laboratory/m49/physical-safety-playback/{sealed.result_id}/manifest"
)
assert manifest_response.status_code == 200
manifest = manifest_response.json()
assert manifest["execution"]["worker_requests_required"] == 0
assert manifest["access"] == "read-only-sealed-local"
assert "worker" not in manifest["playback"]["centers"]["url"]
assert client.get(manifest["playback"]["centers"]["url"]).status_code == 200
assert client.get(manifest["playback"]["chunks"][0]["url"]).status_code == 200
source_result_id = sealed.manifest["identity"]["source_result_id"]
catalog = client.get(
"/api/v1/laboratory/m49/physical-safety-playback/results",
params={"source_result_id": source_result_id},
)
assert catalog.status_code == 200
assert [item["result_id"] for item in catalog.json()["items"]] == [sealed.result_id]
chunk_path = sealed.root / sealed.manifest["playback"]["chunks"][0]["path"]
damaged = bytearray(chunk_path.read_bytes())
damaged[-1] ^= 1
chunk_path.write_bytes(damaged)
assert client.get(manifest["playback"]["chunks"][0]["url"]).status_code == 503
+38 -1
View File
@@ -1,11 +1,18 @@
from __future__ import annotations
import hashlib
import json
from pathlib import Path
from fastapi.routing import APIRoute
from pytest import MonkeyPatch
from k1link.perception.threat_replay import read_threat_replay_result
from k1link.perception.geometry import RecordedGeometryStore
from k1link.perception.threat_replay import (
read_threat_replay_result,
read_threat_replay_result_metadata,
)
from k1link.perception.threat_timeline import RecordedThreatTimeline
from k1link.sessions import RecordedCameraFrame
from k1link.web.m4_threat_replay_api import build_m4_threat_replay_router
@@ -128,6 +135,9 @@ def test_m4_6_timeline_is_indexed_and_spatial_evidence_is_chunked() -> None:
get_timeline = _endpoint("/api/v1/laboratory/m4-threat/results/{result_id}/timeline")
get_chunk = _endpoint("/api/v1/laboratory/m4-threat/results/{result_id}/timeline/chunk")
get_playback = _endpoint("/api/v1/laboratory/m4-threat/results/{result_id}/timeline/playback")
get_playback_chunk = _endpoint(
"/api/v1/laboratory/m4-threat/results/{result_id}/timeline/playback/chunks/{chunk_index}"
)
timeline = get_timeline(RESULT_ID)
assert timeline["schema_version"] == "missioncore.recorded-spatial-evidence-timeline/v1"
@@ -178,6 +188,33 @@ def test_m4_6_timeline_is_indexed_and_spatial_evidence_is_chunked() -> None:
assert playback["track"]["shape"] == [9_207_270, 3]
assert playback["track"]["bytes"] == 110_487_240
assert len(playback["track"]["sha256"]) == 64
assert playback["chunk_frame_count"] == 24
assert playback["resident_chunk_count_max"] == 4
assert playback["forward_prefetch_chunk_count"] == 1
assert len(playback["chunks"]) == 188
first_chunk = playback["chunks"][0]
assert first_chunk["start"] == 0
assert first_chunk["count"] == 24
assert first_chunk["bytes"] < 3 * 1024 * 1024
chunk_response = get_playback_chunk(RESULT_ID, 0)
assert len(chunk_response.body) == first_chunk["bytes"]
assert hashlib.sha256(chunk_response.body).hexdigest() == first_chunk["sha256"]
def test_m4_6_timeline_metadata_defers_large_geometry_archives(
monkeypatch: MonkeyPatch,
) -> None:
result = read_threat_replay_result_metadata(RESULTS_ROOT / RESULT_ID)
def reject_eager_load(*_args: object, **_kwargs: object) -> RecordedGeometryStore:
raise AssertionError("timeline metadata eagerly loaded the geometry archives")
monkeypatch.setattr(RecordedGeometryStore, "from_repository", reject_eager_load)
timeline = RecordedThreatTimeline(repository_root=REPOSITORY_ROOT, result=result)
assert timeline.metadata()["frame_count"] == 4489
assert timeline.metadata()["maximum_source_points_per_frame"] == 3092
assert timeline._store is None
def test_m4_6_exact_camera_endpoint_is_bound_to_selected_visual_sequence() -> None: