feat(polygon): qualify GOOSE ground providers
This commit is contained in:
+143
-3
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user