feat(observatory): attest recording equipment and capture profiles
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from k1link.observatory.portable_run_definitions import PortableRunDefinitionRegistry
|
||||
from k1link.observatory.portable_setup_projection import PortableSetupProjector
|
||||
from k1link.sessions.equipment import (
|
||||
EquipmentCaptureRegistry,
|
||||
RecordedCaptureAttestation,
|
||||
)
|
||||
from k1link.sessions.models import (
|
||||
ObservationArtifactCandidate,
|
||||
ObservationSessionCandidate,
|
||||
SessionSource,
|
||||
SessionSummary,
|
||||
)
|
||||
from k1link.sessions.plugin_contract import ObservationArchiveSource
|
||||
from k1link.sessions.store import SessionStore
|
||||
|
||||
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def _equipment_registry() -> EquipmentCaptureRegistry:
|
||||
registry = EquipmentCaptureRegistry.from_repository(REPOSITORY_ROOT)
|
||||
assert registry is not None
|
||||
return registry
|
||||
|
||||
|
||||
def _definition_registry() -> PortableRunDefinitionRegistry:
|
||||
return PortableRunDefinitionRegistry.from_file(
|
||||
REPOSITORY_ROOT / "config" / "observatory-portable-run-definitions.json"
|
||||
)
|
||||
|
||||
|
||||
def test_k1_equipment_and_capture_profiles_have_exact_content_identities() -> None:
|
||||
registry = _equipment_registry()
|
||||
assert len(registry.equipment_models) == 1
|
||||
assert len(registry.capture_profiles) == 1
|
||||
equipment = registry.equipment_models[0]
|
||||
capture = registry.capture_profiles[0]
|
||||
|
||||
assert equipment.equipment_model_id == "xgrids.lixelkity-k1"
|
||||
assert equipment.equipment_model_sha256 == (
|
||||
"c95d7183ffd2e425892fd9ddfc4cd8dcbccc0231289127ddc14db6b52bbb92aa"
|
||||
)
|
||||
assert capture.equipment == equipment
|
||||
for definition in _definition_registry().definitions:
|
||||
assert (
|
||||
registry.compatible_profile_for_requirements(
|
||||
definition.source_requirements.as_dict()
|
||||
)
|
||||
== capture
|
||||
)
|
||||
|
||||
|
||||
def test_session_store_persists_immutable_k1_capture_attestation(tmp_path: Path) -> None:
|
||||
archive_root = tmp_path / "evidence"
|
||||
session_root = archive_root / "recording-001"
|
||||
session_root.mkdir(parents=True)
|
||||
raw = session_root / "capture.bin"
|
||||
video = session_root / "camera.mp4"
|
||||
raw.write_bytes(b"sealed spatial capture")
|
||||
video.write_bytes(b"sealed camera capture")
|
||||
candidate = _k1_candidate(archive_root, session_root, raw, video)
|
||||
source = ObservationArchiveSource(
|
||||
plugin_id="nodedc.device.xgrids-lixelkity-k1",
|
||||
archive_id="xgrids-k1.viewer-live.evidence",
|
||||
root=archive_root,
|
||||
discover=lambda _root: (candidate,),
|
||||
)
|
||||
data_dir = tmp_path / "data"
|
||||
store = SessionStore(REPOSITORY_ROOT, data_dir=data_dir)
|
||||
|
||||
assert store.reconcile_archive(source) == ("recording-001",)
|
||||
attestation = store.get_session("recording-001").summary.capture_attestation
|
||||
assert attestation is not None
|
||||
assert attestation.equipment_model_id == "xgrids.lixelkity-k1"
|
||||
assert attestation.capture_profile_id == "xgrids-k1.viewer-live.fw-3.0.2.v1"
|
||||
assert attestation.state == "attested-with-legacy-evidence"
|
||||
|
||||
reopened = SessionStore(REPOSITORY_ROOT, data_dir=data_dir)
|
||||
assert reopened.get_session("recording-001").summary.capture_attestation == attestation
|
||||
|
||||
|
||||
def test_other_equipment_is_blocked_before_any_profile_probe() -> None:
|
||||
registry = _equipment_registry()
|
||||
calls: list[str] = []
|
||||
|
||||
class Probe:
|
||||
def probe(self, **kwargs: str) -> object:
|
||||
calls.append(kwargs["source_session_id"])
|
||||
raise AssertionError("incompatible equipment must not reach source probing")
|
||||
|
||||
projector = PortableSetupProjector(
|
||||
registry=_definition_registry(),
|
||||
capability_probe=Probe(),
|
||||
dispatch_available=True,
|
||||
equipment_capture_registry=registry,
|
||||
)
|
||||
source = _summary(
|
||||
RecordedCaptureAttestation(
|
||||
equipment_model_id="example.other-scanner",
|
||||
equipment_model_sha256="1" * 64,
|
||||
equipment_display_name="Other scanner",
|
||||
capture_profile_id="example.other.capture.v1",
|
||||
capture_profile_sha256="2" * 64,
|
||||
capture_attestation_sha256="3" * 64,
|
||||
state="attested",
|
||||
)
|
||||
)
|
||||
|
||||
catalog = projector.catalog(source)
|
||||
assert calls == []
|
||||
assert len(catalog["setups"]) == 2
|
||||
for setup in catalog["setups"]:
|
||||
assert setup["source_compatibility"]["compatible"] is False
|
||||
assert setup["source_compatibility"]["reason_code"] == (
|
||||
"equipment-model-mismatch"
|
||||
)
|
||||
assert setup["preflight"]["submission_allowed"] is False
|
||||
|
||||
|
||||
def _summary(attestation: RecordedCaptureAttestation) -> SessionSummary:
|
||||
return SessionSummary(
|
||||
session_id="other-equipment-recording-001",
|
||||
display_name="Other equipment",
|
||||
status="ready",
|
||||
started_at_utc="2026-09-01T10:00:00Z",
|
||||
completed_at_utc="2026-09-01T10:01:00Z",
|
||||
duration_seconds=60.0,
|
||||
modalities=("point-cloud", "trajectory", "video"),
|
||||
source_count=3,
|
||||
total_bytes=1,
|
||||
replayable=True,
|
||||
origin="example.other.archive",
|
||||
capture_attestation=attestation,
|
||||
)
|
||||
|
||||
|
||||
def _k1_candidate(
|
||||
archive_root: Path,
|
||||
session_root: Path,
|
||||
raw: Path,
|
||||
video: Path,
|
||||
) -> ObservationSessionCandidate:
|
||||
return ObservationSessionCandidate(
|
||||
session_id="recording-001",
|
||||
display_name="K1 recording",
|
||||
status="ready",
|
||||
started_at_utc="2026-09-01T10:00:00Z",
|
||||
completed_at_utc="2026-09-01T10:01:00Z",
|
||||
duration_seconds=60.0,
|
||||
modalities=("point-cloud", "trajectory", "video"),
|
||||
replayable=True,
|
||||
total_bytes=raw.stat().st_size + video.stat().st_size,
|
||||
allowed_root=archive_root,
|
||||
session_root=session_root,
|
||||
primary_replay_artifact_id="raw-transport-primary",
|
||||
timeline_origin_epoch_ns=1,
|
||||
timeline_origin_monotonic_ns=1,
|
||||
sources=(
|
||||
SessionSource(
|
||||
source_id="sensor.lidar.primary",
|
||||
semantic_channel_id="spatial.point-cloud.recorded",
|
||||
modality="point-cloud",
|
||||
status="recorded",
|
||||
seekable=True,
|
||||
artifact_id="raw-transport-primary",
|
||||
),
|
||||
SessionSource(
|
||||
source_id="spatial.trajectory",
|
||||
semantic_channel_id="spatial.pose.recorded",
|
||||
modality="trajectory",
|
||||
status="recorded",
|
||||
seekable=True,
|
||||
artifact_id="raw-transport-primary",
|
||||
),
|
||||
SessionSource(
|
||||
source_id="sensor.camera.right",
|
||||
semantic_channel_id="camera.video.recorded",
|
||||
modality="video",
|
||||
status="recorded",
|
||||
seekable=True,
|
||||
artifact_id="camera-right",
|
||||
),
|
||||
),
|
||||
artifacts=(
|
||||
ObservationArtifactCandidate(
|
||||
artifact_id="raw-transport-primary",
|
||||
kind="native-recording",
|
||||
media_type="application/x-k1-recording",
|
||||
locator=raw,
|
||||
byte_length=raw.stat().st_size,
|
||||
replay_byte_length=raw.stat().st_size,
|
||||
sha256=None,
|
||||
integrity_status="validated-structure",
|
||||
),
|
||||
ObservationArtifactCandidate(
|
||||
artifact_id="camera-right",
|
||||
kind="recorded-video",
|
||||
media_type="video/mp4",
|
||||
locator=video,
|
||||
byte_length=video.stat().st_size,
|
||||
replay_byte_length=0,
|
||||
sha256=None,
|
||||
integrity_status="validated-structure",
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -174,22 +174,23 @@ def test_calculation_profile_policies_cover_both_exact_portable_definitions() ->
|
||||
assert resolved["m49-tgs-portable-v2"].display_name == PORTABLE_M49_DISPLAY_NAME
|
||||
|
||||
|
||||
def test_new_compatible_source_passes_capability_but_uninstalled_executor_blocks() -> None:
|
||||
def test_new_compatible_source_passes_capability_but_disabled_dispatch_blocks() -> None:
|
||||
source = _source(NEW_SESSION_ID)
|
||||
setup = _projector(lambda session_id: _capability(session_id)).project(source)
|
||||
|
||||
assert setup["source_compatibility"] == {
|
||||
"outcome": "pass",
|
||||
"compatible": True,
|
||||
"reason_code": "source-compatible",
|
||||
"reason": "Запись соответствует требованиям EoMT + DDRNet.",
|
||||
}
|
||||
assert setup["executor"]["state"] == "not-installed"
|
||||
assert setup["executor"]["ready"] is False
|
||||
assert setup["executor"]["state"] == "ready"
|
||||
assert setup["executor"]["ready"] is True
|
||||
assert setup["existing_results"] == []
|
||||
assert setup["preflight"] == {
|
||||
"outcome": "blocked",
|
||||
"action": "blocked",
|
||||
"reason": "Вычислительный контур LAB V1 пока недоступен.",
|
||||
"reason": "Server-side проверка и постановка portable-сетапа в очередь недоступны.",
|
||||
"submission_allowed": False,
|
||||
"existing_result_ids": [],
|
||||
}
|
||||
@@ -207,7 +208,7 @@ def test_legacy_vegetation_result_is_never_existing_for_portable_v2() -> None:
|
||||
assert setup["preflight"] == {
|
||||
"outcome": "blocked",
|
||||
"action": "blocked",
|
||||
"reason": "Вычислительный контур LAB V1 пока недоступен.",
|
||||
"reason": "Server-side проверка и постановка portable-сетапа в очередь недоступны.",
|
||||
"submission_allowed": False,
|
||||
"existing_result_ids": [],
|
||||
}
|
||||
@@ -225,6 +226,7 @@ def test_real_capability_probe_rejection_wins_over_summary() -> None:
|
||||
assert setup["source_compatibility"] == {
|
||||
"outcome": "blocked",
|
||||
"compatible": False,
|
||||
"reason_code": "source-contract-mismatch",
|
||||
"reason": "Запись не соответствует требованиям EoMT + DDRNet.",
|
||||
}
|
||||
assert setup["existing_results"] == []
|
||||
|
||||
@@ -292,6 +292,7 @@ def test_session_router_lists_details_and_dispatches_opaque_replay(tmp_path: Pat
|
||||
"modalities": ["point-cloud", "trajectory"],
|
||||
"duration_seconds": 1440.1,
|
||||
"replayable": True,
|
||||
"capture_attestation": None,
|
||||
}
|
||||
assert detail["timeline"]["seekable"] is True
|
||||
assert detail["modalities"] == ["point-cloud", "trajectory"]
|
||||
|
||||
Reference in New Issue
Block a user