feat(polygon): qualify GOOSE ground providers

This commit is contained in:
DCCONSTRUCTIONS
2026-07-25 16:02:20 +03:00
parent 60ba64004b
commit d6decbe05c
14 changed files with 2718 additions and 22 deletions
+128
View File
@@ -0,0 +1,128 @@
from __future__ import annotations
import json
import struct
import zipfile
from collections.abc import Mapping
from pathlib import Path
import numpy as np
import numpy.typing as npt
import pytest
from k1link.datasets import goose_qualification
from k1link.datasets.goose_qualification import (
DegradationProfile,
qualify_goose_ground,
)
from k1link.ground_segmentation import GroundSegmentation
from k1link.simulation import QualificationRunStore, RunState
class _NeverGround:
@property
def identity(self) -> Mapping[str, object]:
return {"provider_id": "test-current", "revision": "1"}
def segment(self, xyzi: npt.NDArray[np.float32]) -> GroundSegmentation:
count = xyzi.shape[0]
return GroundSegmentation(
ground_mask=np.zeros(count, dtype=np.bool_),
assigned_mask=np.ones(count, dtype=np.bool_),
latency_ms=2.0,
)
class _NegativeZGround:
@property
def identity(self) -> Mapping[str, object]:
return {"provider_id": "test-patchwork", "revision": "1"}
def segment(self, xyzi: npt.NDArray[np.float32]) -> GroundSegmentation:
count = xyzi.shape[0]
return GroundSegmentation(
ground_mask=np.asarray(xyzi[:, 2] < 0, dtype=np.bool_),
assigned_mask=np.ones(count, dtype=np.bool_),
latency_ms=3.0,
)
def _frame_bytes(offset: float) -> tuple[bytes, bytes]:
points = (
(-1.0 + offset, 0.0, -2.0, 0.1),
(0.0 + offset, 0.0, -2.0, 0.2),
(1.0 + offset, 0.0, 2.0, 0.3),
(2.0 + offset, 0.0, 2.0, 0.4),
)
labels = (1, 3, 2, 2)
return (
b"".join(struct.pack("<ffff", *point) for point in points),
b"".join(struct.pack("<I", label) for label in labels),
)
def _dataset(tmp_path: Path) -> Path:
root = tmp_path / "datasets"
archive_sha = "a" * 64
archive = root / "goose-3d/v2025-08-22/archives/goose_3d_val.zip"
archive.parent.mkdir(parents=True)
with zipfile.ZipFile(archive, "w") as target:
for index in range(2):
frame_id = f"2022-01-01_flight__0001_{index:019d}"
points, labels = _frame_bytes(index * 0.1)
target.writestr(f"goose/lidar/val/{frame_id}_vls128.bin", points)
target.writestr(f"goose/labels/val/{frame_id}_goose.label", labels)
state = root / "state/goose-3d-v2025-08-22.json"
state.parent.mkdir(parents=True)
state.write_text(json.dumps({"archive": {"sha256": archive_sha}}), encoding="utf-8")
install = root / "goose-3d/v2025-08-22/installs" / archive_sha
install.mkdir(parents=True)
(install / "goose_label_mapping.csv").write_text(
"class_name,label_key,hex\nasphalt,1,#111111\nrock,2,#222222\nsoil,3,#333333\n",
encoding="utf-8",
)
return root
def test_full_split_qualification_is_sealed_and_resumable(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
root = _dataset(tmp_path)
runs_root = tmp_path / "runs"
monkeypatch.setattr(goose_qualification, "_is_worker_dataset_root", lambda _: True)
report = qualify_goose_ground(
root,
runs_root,
mission_core_commit="1" * 40,
parallel_workers=1,
expected_frame_count=2,
current_segmenter=_NeverGround(),
patchwork_segmenter=_NegativeZGround(),
degradations=(DegradationProfile("range-20m", "maximum-range-m", 20.0),),
)
assert report["frame_count"] == 2
assert report["decision"]["status"] == "shadow-candidate"
assert report["aggregates"]["current"]["micro"]["ground_iou"] == 0
assert report["aggregates"]["patchworkpp"]["micro"]["ground_iou"] == 1
assert len(report["frames"]) == 2
run = QualificationRunStore(runs_root, read_only=True).load(report["run_id"])
assert run.state is RunState.COMPLETED
assert {artifact.kind for artifact in run.artifacts} == {
"goose-ground-qualification-report",
"goose-ground-qualification-failure-preview",
}
resumed = qualify_goose_ground(
root,
runs_root,
mission_core_commit="1" * 40,
parallel_workers=1,
expected_frame_count=2,
current_segmenter=_NeverGround(),
patchwork_segmenter=_NegativeZGround(),
degradations=(DegradationProfile("range-20m", "maximum-range-m", 20.0),),
)
assert resumed["identity_sha256"] == report["identity_sha256"]
+143 -3
View File
@@ -1,5 +1,7 @@
from __future__ import annotations
import hashlib
import json
from collections.abc import Callable
from pathlib import Path
from typing import Any
@@ -191,6 +193,146 @@ def test_polygon_api_rejects_corrupt_evidence_without_partial_response(
assert "целостности" in failure.value.detail
def _qualification_run(root: Path) -> tuple[QualificationRun, str]:
store = QualificationRunStore(root)
admitted = store.create(_run("goose-ground-test"))
starting = store.transition(
admitted.run_id,
RunState.STARTING,
expected_revision=0,
observed_at_utc="2026-07-24T15:35:01Z",
host_monotonic_ns=1,
)
store.transition(
admitted.run_id,
RunState.RUNNING,
expected_revision=starting.revision,
observed_at_utc="2026-07-24T15:35:02Z",
host_monotonic_ns=2,
)
evidence = root / admitted.run_id / "evidence"
failures = evidence / "failures"
failures.mkdir(parents=True)
frame_id = "2022-07-22_flight__0071_0001"
report = {
"schema_version": "missioncore.goose-ground-qualification-report/v1",
"identity_sha256": SHA_A,
"source_id": "goose-3d/v2025-08-22",
"split": "validation",
"frame_count": 961,
"aggregates": {"current": {}, "patchworkpp": {}},
"degradations": {},
"checks": [],
"worst_frames": [{"frame_id": frame_id}],
"decision": {"status": "shadow-candidate", "passed": True},
"safety": {"navigation_or_safety_accepted": False},
"frames": [{"private": "not-published"}],
}
preview = {
"schema_version": "missioncore.goose-ground-qualification-failure-preview/v1",
"source_id": "goose-3d/v2025-08-22",
"frame_id": frame_id,
"source_point_count": 2,
"point_count": 2,
"sampling": "deterministic-even-index",
"points_xyz_m": [[0, 0, 0], [1, 1, 1]],
"ground_truth_ground": [1, 0],
"evaluated": [1, 1],
"current_ground": [0, 0],
"patchwork_ground": [1, 0],
"current_disagreement": [1, 0],
"patchwork_disagreement": [0, 0],
"safety": {"navigation_or_safety_accepted": False},
}
for artifact_id, kind, path, value in (
(
"qualification-report",
"goose-ground-qualification-report",
evidence / "qualification.json",
report,
),
(
"failure-preview-01",
"goose-ground-qualification-failure-preview",
failures / f"{frame_id}.json",
preview,
),
):
path.write_text(json.dumps(value), encoding="utf-8")
store.register_artifact(
admitted.run_id,
QualificationArtifact(
artifact_id=artifact_id,
kind=kind,
relative_path=path.relative_to(root / admitted.run_id).as_posix(),
sha256=hashlib.sha256(path.read_bytes()).hexdigest(),
byte_length=path.stat().st_size,
source_of_record=True,
),
)
current = store.load(admitted.run_id)
stopping = store.transition(
admitted.run_id,
RunState.STOPPING,
expected_revision=current.revision,
observed_at_utc="2026-07-24T15:35:04Z",
host_monotonic_ns=4,
)
completed = store.transition(
admitted.run_id,
RunState.COMPLETED,
expected_revision=stopping.revision,
observed_at_utc="2026-07-24T15:35:05Z",
host_monotonic_ns=5,
reason="qualification-evidence-sealed",
)
return completed, frame_id
def test_polygon_api_publishes_verified_ground_qualification(
tmp_path: Path,
) -> None:
root = tmp_path / "runs"
run, frame_id = _qualification_run(root)
router = build_polygon_router(root_provider=lambda: root)
qualification = _endpoint(
router,
"/api/v1/polygon/runs/{run_id}/qualification",
"GET",
)(run_id=run.run_id)
preview = _endpoint(
router,
"/api/v1/polygon/runs/{run_id}/qualification/failures/{frame_id}",
"GET",
)(run_id=run.run_id, frame_id=frame_id)
assert qualification["schema_version"] == "missioncore.polygon-ground-qualification/v1"
assert qualification["frame_count"] == 961
assert "frames" not in qualification
assert preview["schema_version"] == "missioncore.polygon-ground-failure-preview/v1"
assert preview["frame_id"] == frame_id
assert str(tmp_path) not in repr({"qualification": qualification, "preview": preview})
def test_polygon_api_rejects_tampered_ground_qualification(
tmp_path: Path,
) -> None:
root = tmp_path / "runs"
run, _ = _qualification_run(root)
(root / run.run_id / "evidence/qualification.json").write_text("{}", encoding="utf-8")
router = build_polygon_router(root_provider=lambda: root)
with pytest.raises(HTTPException) as failure:
_endpoint(
router,
"/api/v1/polygon/runs/{run_id}/qualification",
"GET",
)(run_id=run.run_id)
assert failure.value.status_code == 500
assert "SHA-256" in failure.value.detail or "файла" in failure.value.detail
class _FakeWorkerGateway:
def __init__(self) -> None:
self.calls: list[tuple[str, str]] = []
@@ -286,9 +428,7 @@ class _FakeWorkerGateway:
"control_available": True,
"active_run_id": self.active_run_id,
"run_state": "running" if self.active_run_id else None,
"active_provider_ids": (
["px4-gazebo-stock-rover"] if self.active_run_id else []
),
"active_provider_ids": (["px4-gazebo-stock-rover"] if self.active_run_id else []),
"provider_profile": STOCK_ROVER_PROVIDER_PROFILE.to_dict(),
"isolation": {
"network": "loopback-only-netns",