feat(perception): qualify conservative static occupancy
This commit is contained in:
@@ -152,6 +152,15 @@ def test_profile_is_strict_digest_bound_and_store_accepts_exact_evidence() -> No
|
||||
assert store.profile.local_surface_sha256 == (
|
||||
"f57eb2485b6cef47f2a97a2d9ff1aa9fd9265fe1eb69cd5852d12f39e13b8bc6"
|
||||
)
|
||||
step_candidates = store.point_step_candidates_for_frame(0)
|
||||
assert step_candidates is not None
|
||||
assert step_candidates.shape == (2389,)
|
||||
assert step_candidates.dtype == np.uint8
|
||||
assert step_candidates.flags.writeable is False
|
||||
with pytest.raises(ValueError):
|
||||
step_candidates[0] = 0
|
||||
with pytest.raises(GeometryProviderError, match="frame index"):
|
||||
store.point_step_candidates_for_frame(True)
|
||||
|
||||
|
||||
def test_provider_arbitrates_points_and_publishes_classless_geometry_only() -> None:
|
||||
|
||||
@@ -127,7 +127,7 @@ def test_product_registry_declares_every_advanced_evidence_source() -> None:
|
||||
repository_root / "config" / "laboratories"
|
||||
)
|
||||
|
||||
assert len(registry.definitions) == 38
|
||||
assert len(registry.definitions) == 39
|
||||
assert {item.work_id for item in registry.definitions} >= {
|
||||
"e31-source-binding",
|
||||
"e46j-raw-fisheye-realtime",
|
||||
@@ -141,6 +141,7 @@ def test_product_registry_declares_every_advanced_evidence_source() -> None:
|
||||
"m47-reference-graph-shadow",
|
||||
"m48-object-centric-quality",
|
||||
"m48-small-static-passage-regression",
|
||||
"m48-static-occupancy-qualification",
|
||||
"m48s-fixed-class-detector",
|
||||
"m48t-risk-quality-temporal",
|
||||
}
|
||||
|
||||
@@ -92,6 +92,7 @@ def test_repository_registry_classifies_every_evidence_definition() -> None:
|
||||
|
||||
assert {row.work_id for row in execution.definitions} == {
|
||||
"m48-small-static-passage-regression",
|
||||
"m48-static-occupancy-qualification",
|
||||
"m48-object-centric-quality",
|
||||
"m4-replay-threat",
|
||||
"e33-worker-shadow",
|
||||
@@ -108,6 +109,9 @@ def test_repository_registry_classifies_every_evidence_definition() -> None:
|
||||
assert by_work_id["m48-object-centric-quality"].evidence_contract == (
|
||||
"missioncore.m48-object-centric-quality-result/v1"
|
||||
)
|
||||
assert by_work_id["m48-static-occupancy-qualification"].evidence_contract == (
|
||||
"missioncore.m48-static-occupancy-qualification-result/v1"
|
||||
)
|
||||
assert by_work_id["e47-semantic-slam-shadow"].lifecycle == "experimental"
|
||||
assert by_work_id["e47-semantic-slam-shadow"].isolation == "bounded-adapter"
|
||||
assert by_work_id["m48s-fixed-class-detector"].lifecycle == "experimental"
|
||||
|
||||
@@ -217,6 +217,82 @@ def _fixture(
|
||||
lambda _: regression_result,
|
||||
)
|
||||
|
||||
static_occupancy_result_id = f"m48-static-occupancy-qualification-{'e' * 64}"
|
||||
static_occupancy_root = tmp_path / "static-occupancy" / static_occupancy_result_id
|
||||
static_occupancy_root.mkdir(parents=True)
|
||||
static_occupancy_result = SimpleNamespace(
|
||||
result_id=static_occupancy_result_id,
|
||||
result_root=static_occupancy_root,
|
||||
manifest={
|
||||
"created_at_utc": "2026-08-26T12:00:00Z",
|
||||
"accepted": False,
|
||||
},
|
||||
report={
|
||||
"source": {
|
||||
"m47_lab_result_id": f"m47-reference-graph-lab-{'c' * 64}",
|
||||
"small_static_result_id": regression_result_id,
|
||||
},
|
||||
"configuration": {
|
||||
"run_label": "M4.8R2",
|
||||
"pipeline_id": "m4-current-rolling-plus-step-static-occupancy/v1",
|
||||
"experiment_id": "m48-static-occupancy-qualification/v1",
|
||||
},
|
||||
"metrics": {
|
||||
"operator_static_anchor_count": 1,
|
||||
"baseline_qualified_count": 0,
|
||||
"candidate_qualified_count": 1,
|
||||
"unresolved_unknown_count": 0,
|
||||
"critical_near_anchor_count": 1,
|
||||
"critical_near_baseline_recall": 0.0,
|
||||
"critical_near_candidate_recall": 1.0,
|
||||
"approach_anchor_count": 0,
|
||||
"approach_baseline_recall": 0.0,
|
||||
"approach_candidate_recall": 0.0,
|
||||
"canonical_engineering_anchor_count": 4,
|
||||
"canonical_engineering_recall": 1.0,
|
||||
"false_free_count": 0,
|
||||
},
|
||||
"gates": {
|
||||
"critical_near_candidate_recall": True,
|
||||
"approach_candidate_recall": False,
|
||||
"canonical_engineering_recall": True,
|
||||
"zero_false_free": True,
|
||||
"independent_truth_available": False,
|
||||
},
|
||||
"decision": {
|
||||
"state": "partial-static-occupancy-qualification",
|
||||
"critical_near_candidate_ready_for_shadow": True,
|
||||
"production_accepted": False,
|
||||
"summary": "fixture",
|
||||
"next_action": "worker shadow",
|
||||
},
|
||||
},
|
||||
cases=(
|
||||
{
|
||||
"anchor_id": regression_anchor_id,
|
||||
"clip_id": "neutral-clip-01",
|
||||
"sequence": 1,
|
||||
"extent_xyxy": [0.2, 0.2, 0.3, 0.4],
|
||||
"distance_m": 3.0,
|
||||
"distance_band": "critical-near",
|
||||
"accepted_graph": {
|
||||
"matched": False,
|
||||
"component_count": 0,
|
||||
"components": [],
|
||||
"free_space_claimed": False,
|
||||
},
|
||||
"baseline_qualified": False,
|
||||
"candidate_qualified": True,
|
||||
"outcome": "candidate-qualified",
|
||||
},
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
api,
|
||||
"read_m48_static_occupancy_qualification",
|
||||
lambda _: static_occupancy_result,
|
||||
)
|
||||
|
||||
observations: dict[str, list[dict[str, Any]]] = {
|
||||
"reviews": [],
|
||||
"adjudications": [],
|
||||
@@ -444,6 +520,7 @@ def _fixture(
|
||||
truth_root_provider=lambda: tmp_path / "truth",
|
||||
result_root_provider=lambda: tmp_path / "results",
|
||||
small_static_result_root_provider=lambda: tmp_path / "small-static",
|
||||
static_occupancy_result_root_provider=lambda: tmp_path / "static-occupancy",
|
||||
camera_frame_provider=camera,
|
||||
camera_playback_provider=camera_playback, # type: ignore[arg-type]
|
||||
spatial_evidence_provider=spatial_frame if spatial else None,
|
||||
@@ -1128,3 +1205,31 @@ def test_m48_small_static_regression_is_separate_read_only_assisted_evidence(
|
||||
assert body["ground_truth"] is False
|
||||
assert body["camera_url"].endswith("/frames/1/camera")
|
||||
assert body["spatial_url"].endswith("/frames/1/spatial")
|
||||
|
||||
|
||||
def test_m48_static_occupancy_qualification_is_read_only_bounded_evidence(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
client, _, _ = _fixture(tmp_path, monkeypatch, spatial=True)
|
||||
result_id = f"m48-static-occupancy-qualification-{'e' * 64}"
|
||||
|
||||
summary = client.get(
|
||||
f"/api/v1/laboratory/m48/regressions/static-occupancy/{result_id}"
|
||||
)
|
||||
assert summary.status_code == 200
|
||||
assert summary.headers["cache-control"] == "no-store"
|
||||
assert summary.json()["run_label"] == "M4.8R2"
|
||||
assert summary.json()["accepted"] is False
|
||||
assert summary.json()["ground_truth"] is False
|
||||
assert summary.json()["independent_truth"] is False
|
||||
assert summary.json()["metrics"]["critical_near_candidate_recall"] == 1.0
|
||||
assert summary.json()["decision"]["production_accepted"] is False
|
||||
|
||||
catalog = client.get(
|
||||
f"/api/v1/laboratory/m48/regressions/static-occupancy/{result_id}/cases"
|
||||
)
|
||||
assert catalog.status_code == 200
|
||||
assert catalog.json()["case_count"] == 1
|
||||
assert catalog.json()["cases"][0]["outcome"] == "candidate-qualified"
|
||||
assert catalog.json()["cases"][0]["accepted_graph"]["free_space_claimed"] is False
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
import k1link.laboratory.m48_static_occupancy_qualification as qualification
|
||||
from k1link.laboratory.evidence_registry import LaboratoryEvidenceRegistry
|
||||
from k1link.laboratory.evidence_report import verify_laboratory_evidence_result
|
||||
|
||||
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
|
||||
AUTHORITY = {
|
||||
"mode": "replay-simulated",
|
||||
"physical_live": False,
|
||||
"commands_enabled": False,
|
||||
"actuation_allowed": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
|
||||
def _sealed_result(tmp_path: Path) -> qualification.M48StaticOccupancyQualificationResult:
|
||||
identity = {
|
||||
"schema_version": qualification.M48_STATIC_OCCUPANCY_RESULT_SCHEMA,
|
||||
"human_lab_id": "M4.8R2",
|
||||
"run_label": "fixture",
|
||||
"run_created_at_utc": "2026-08-26T12:00:00Z",
|
||||
"authority": AUTHORITY,
|
||||
}
|
||||
result_id = qualification.M48_STATIC_OCCUPANCY_PREFIX + qualification._canonical_sha256(
|
||||
identity
|
||||
)
|
||||
report = {
|
||||
"schema_version": qualification.M48_STATIC_OCCUPANCY_REPORT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"metrics": {
|
||||
"operator_static_anchor_count": 1,
|
||||
"candidate_qualified_count": 1,
|
||||
},
|
||||
"decision": {"production_accepted": False},
|
||||
"authority": AUTHORITY,
|
||||
}
|
||||
cases = (
|
||||
{
|
||||
"schema_version": qualification.M48_STATIC_OCCUPANCY_CASE_SCHEMA,
|
||||
"anchor_id": "anchor-" + "a" * 24,
|
||||
"sequence": 10,
|
||||
"baseline_qualified": False,
|
||||
"candidate_qualified": True,
|
||||
"outcome": "candidate-qualified",
|
||||
},
|
||||
)
|
||||
canonical = (
|
||||
{
|
||||
"schema_version": qualification.M48_STATIC_OCCUPANCY_CANONICAL_SCHEMA,
|
||||
"anchor_id": "hemisphere-01",
|
||||
"sequence": 1880,
|
||||
"matched": True,
|
||||
},
|
||||
)
|
||||
destination = tmp_path / "results" / result_id
|
||||
qualification._publish_result(
|
||||
destination,
|
||||
identity,
|
||||
"2026-08-26T12:00:00Z",
|
||||
False,
|
||||
report,
|
||||
cases,
|
||||
canonical,
|
||||
)
|
||||
return qualification.read_m48_static_occupancy_qualification(destination)
|
||||
|
||||
|
||||
def test_seals_bounded_static_occupancy_evidence_without_production_authority(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
result = _sealed_result(tmp_path)
|
||||
|
||||
assert result.manifest["accepted"] is False
|
||||
assert result.manifest["ground_truth"] is False
|
||||
assert result.manifest["authority"] == AUTHORITY
|
||||
assert result.report["decision"]["production_accepted"] is False
|
||||
assert result.cases[0]["outcome"] == "candidate-qualified"
|
||||
|
||||
registry = LaboratoryEvidenceRegistry.from_directory(
|
||||
REPOSITORY_ROOT / "config/laboratories"
|
||||
)
|
||||
definition = next(
|
||||
row
|
||||
for row in registry.definitions
|
||||
if row.work_id == "m48-static-occupancy-qualification"
|
||||
)
|
||||
proof = verify_laboratory_evidence_result(definition, result.result_root)
|
||||
assert proof["result_id"] == result.result_id
|
||||
assert proof["artifact_count"] == 3
|
||||
|
||||
|
||||
def test_reader_rejects_changed_static_occupancy_case_ledger(tmp_path: Path) -> None:
|
||||
result = _sealed_result(tmp_path)
|
||||
cases_path = result.result_root / "cases.jsonl"
|
||||
cases_path.write_bytes(cases_path.read_bytes() + b"{}\n")
|
||||
|
||||
with pytest.raises(
|
||||
qualification.M48StaticOccupancyQualificationError,
|
||||
match="artifact proof",
|
||||
):
|
||||
qualification.read_m48_static_occupancy_qualification(result.result_root)
|
||||
Reference in New Issue
Block a user