feat(perception): seal M4.8S reference graph replay
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from k1link.perception.detector import RF_DETR_SHADOW_PROVIDER_ID
|
||||
from k1link.perception.m48s_advisory import (
|
||||
AdvisoryFamily,
|
||||
advisory_policy_matrix,
|
||||
)
|
||||
from k1link.perception.object_understanding import AdvisoryResponse
|
||||
from k1link.perception.providers import ProviderRole, ReferencePerceptionGraphConfigV2
|
||||
|
||||
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
|
||||
GRAPH_CONFIG = (
|
||||
REPOSITORY_ROOT
|
||||
/ "config/perception/m48s-rf-detr-reference-graph-shadow-v0.json"
|
||||
)
|
||||
|
||||
|
||||
def test_m48s_reference_graph_replaces_only_the_detector_pin() -> None:
|
||||
shadow_document = json.loads(GRAPH_CONFIG.read_text("utf-8"))
|
||||
canonical_document = json.loads(
|
||||
(REPOSITORY_ROOT / "config/perception/m4-reference-graph-v2.json").read_text(
|
||||
"utf-8"
|
||||
)
|
||||
)
|
||||
shadow = ReferencePerceptionGraphConfigV2.from_dict(shadow_document)
|
||||
canonical = ReferencePerceptionGraphConfigV2.from_dict(canonical_document)
|
||||
shadow_pins = {item.role: item for item in shadow.providers}
|
||||
canonical_pins = {item.role: item for item in canonical.providers}
|
||||
|
||||
assert shadow.graph_id == canonical.graph_id == "reference-perception-graph/v2"
|
||||
assert shadow.source_profile_id == canonical.source_profile_id
|
||||
assert shadow.queues == canonical.queues
|
||||
assert shadow.authority == canonical.authority
|
||||
assert shadow_pins[ProviderRole.DETECTOR].provider_id == RF_DETR_SHADOW_PROVIDER_ID
|
||||
assert shadow_pins[ProviderRole.DETECTOR] != canonical_pins[ProviderRole.DETECTOR]
|
||||
assert all(
|
||||
shadow_pins[role] == canonical_pins[role]
|
||||
for role in ProviderRole
|
||||
if role is not ProviderRole.DETECTOR
|
||||
)
|
||||
|
||||
|
||||
def test_m48s_reference_graph_pins_every_profile_digest() -> None:
|
||||
config = ReferencePerceptionGraphConfigV2.from_dict(
|
||||
json.loads(GRAPH_CONFIG.read_text("utf-8"))
|
||||
)
|
||||
paths = {
|
||||
ProviderRole.SOURCE: "m4-recorded-realtime-baseline-v1.json",
|
||||
ProviderRole.DETECTOR: "rf-detr-large-risk-shadow-v0.json",
|
||||
ProviderRole.GEOMETRY: "m4-geometry-association-v1.json",
|
||||
ProviderRole.TEMPORAL: "m4-temporal-motion-v1.json",
|
||||
ProviderRole.MOTION: "m4-temporal-motion-v1.json",
|
||||
ProviderRole.ROLLING: "m4-rolling-local-map-v1.json",
|
||||
ProviderRole.THREAT: "m4-replay-threat-v3.json",
|
||||
}
|
||||
pins = {item.role: item for item in config.providers}
|
||||
|
||||
for role, name in paths.items():
|
||||
payload = (REPOSITORY_ROOT / "config/perception" / name).read_bytes()
|
||||
assert pins[role].sha256 == hashlib.sha256(payload).hexdigest()
|
||||
|
||||
|
||||
def test_m48s_advisory_policy_is_bounded_distinct_and_commandless() -> None:
|
||||
matrix = advisory_policy_matrix()
|
||||
|
||||
assert set(matrix) == set(AdvisoryFamily)
|
||||
assert matrix[AdvisoryFamily.GENERIC_OBSTACLE] == (
|
||||
AdvisoryResponse.ROUTE_AROUND,
|
||||
)
|
||||
assert AdvisoryResponse.YIELD in matrix[AdvisoryFamily.PERSON]
|
||||
assert AdvisoryResponse.STOP in matrix[AdvisoryFamily.ANIMAL]
|
||||
assert AdvisoryResponse.REDUCE_SPEED in matrix[AdvisoryFamily.LIGHT_ROAD_USER]
|
||||
assert AdvisoryResponse.MONITOR in matrix[AdvisoryFamily.VEHICLE]
|
||||
assert len(set(matrix.values())) == len(matrix)
|
||||
@@ -0,0 +1,49 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
|
||||
RESULT_ID = (
|
||||
"m48s-reference-graph-shadow-gate-"
|
||||
"e8da7a521768daba0ead1a6e4803871ce3a85f91a7d8ee36c5719ac10433e791"
|
||||
)
|
||||
RESULT_ROOT = (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime/compute-experiments/m48s-semantic-shadow/"
|
||||
"reference-graph-shadow-results"
|
||||
/ RESULT_ID
|
||||
)
|
||||
|
||||
|
||||
def test_m48s_complete_reference_graph_shadow_is_accepted_without_production_authority() -> None:
|
||||
manifest = json.loads((RESULT_ROOT / "manifest.json").read_text("utf-8"))
|
||||
identity = manifest["identity"]
|
||||
evidence = identity["evidence"]
|
||||
execution = evidence["execution"]
|
||||
completion = evidence["world_state_completion_age_ms"]
|
||||
|
||||
assert manifest["result_id"] == RESULT_ID
|
||||
assert identity["completed"] is True
|
||||
assert identity["accepted"] is True
|
||||
assert identity["production_accepted"] is False
|
||||
assert identity["decision"]["source_paced_runtime_gate_accepted"] is True
|
||||
assert identity["decision"]["detector_replacement_authorized"] is False
|
||||
assert execution["admitted_frames"] == 4489
|
||||
assert execution["delivered_world_states"] == 4481
|
||||
assert execution["terminal_outcomes"] == {"delivered": 4481, "superseded": 8}
|
||||
assert execution["effective_world_state_fps"] >= 9.5
|
||||
assert completion["p95"] <= 175.0
|
||||
assert all(evidence["checks"].values())
|
||||
assert evidence["semantic_advisory"]["additional_inference_passes"] == 0
|
||||
assert evidence["identity_continuity"]["duplicate_component_ids_within_frame"] == 0
|
||||
assert identity["authority"] == {
|
||||
"actuation_allowed": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"ground_truth": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
for name, expected_sha256 in manifest["artifacts"].items():
|
||||
assert hashlib.sha256((RESULT_ROOT / name).read_bytes()).hexdigest() == expected_sha256
|
||||
@@ -4,7 +4,7 @@ import json
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
from collections.abc import Iterator
|
||||
from collections.abc import Callable, Iterator
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
from queue import Queue
|
||||
@@ -39,6 +39,7 @@ from k1link.perception.contracts import (
|
||||
TimestampBundle,
|
||||
)
|
||||
from k1link.perception.graph import (
|
||||
DeliveredFrame,
|
||||
GraphExecutionError,
|
||||
GraphRunMode,
|
||||
GraphRunResult,
|
||||
@@ -460,6 +461,8 @@ def _graph_v2(
|
||||
capacity: int = 8,
|
||||
run_mode: GraphRunMode = GraphRunMode.LOSSLESS_REPLAY,
|
||||
terminal_timeout_ns: int = 500_000_000,
|
||||
delivery_observer: Callable[[DeliveredFrame, int], None] | None = None,
|
||||
delivery_evidence_observer: Callable[..., None] | None = None,
|
||||
) -> ReferencePerceptionGraphV2:
|
||||
return ReferencePerceptionGraphV2(
|
||||
config=_config_v2(capacity, terminal_timeout_ns),
|
||||
@@ -471,6 +474,8 @@ def _graph_v2(
|
||||
rolling=_Rolling(),
|
||||
threat=_Threat(),
|
||||
run_mode=run_mode,
|
||||
delivery_observer=delivery_observer,
|
||||
delivery_evidence_observer=delivery_evidence_observer,
|
||||
clock_ns=lambda: 10_000,
|
||||
)
|
||||
|
||||
@@ -527,6 +532,50 @@ def test_reference_graph_v2_publishes_current_and_retained_occupancy() -> None:
|
||||
}
|
||||
|
||||
|
||||
def test_reference_graph_v2_observes_final_delivery_completion_age() -> None:
|
||||
observed: list[tuple[int, int, int]] = []
|
||||
|
||||
def observe(delivery: DeliveredFrame, completion_age_ns: int) -> None:
|
||||
observed.append(
|
||||
(
|
||||
delivery.sequence,
|
||||
delivery.obstacle_map.output_age_ns,
|
||||
completion_age_ns,
|
||||
)
|
||||
)
|
||||
|
||||
result = _graph_v2(
|
||||
_Source((_packet(0, source_age_ns=123),)),
|
||||
delivery_observer=observe,
|
||||
).run()
|
||||
|
||||
assert result.state is GraphState.STOPPED
|
||||
assert observed == [(0, 123, 123)]
|
||||
|
||||
|
||||
def test_reference_graph_v2_observes_exact_delivery_evidence_inputs() -> None:
|
||||
observed: list[tuple[int, int, tuple[str, ...], frozenset[str], int]] = []
|
||||
|
||||
def observe(delivery, packet, proposals, associated_ids, completion_age_ns) -> None:
|
||||
observed.append(
|
||||
(
|
||||
delivery.sequence,
|
||||
packet.envelope.sequence,
|
||||
tuple(proposal.proposal_id for proposal in proposals),
|
||||
associated_ids,
|
||||
completion_age_ns,
|
||||
)
|
||||
)
|
||||
|
||||
result = _graph_v2(
|
||||
_Source((_packet(0, source_age_ns=123),)),
|
||||
delivery_evidence_observer=observe,
|
||||
).run()
|
||||
|
||||
assert result.state is GraphState.STOPPED
|
||||
assert observed == [(0, 0, ("proposal-0",), frozenset({"proposal-0"}), 123)]
|
||||
|
||||
|
||||
def test_reference_graph_v2_lossless_mode_applies_bounded_backpressure() -> None:
|
||||
release = Event()
|
||||
|
||||
@@ -784,6 +833,32 @@ def test_decoded_recorded_source_attaches_images_without_detector_logic(tmp_path
|
||||
assert int(packets[1].image_payload[0, 0, 0]) == 7
|
||||
|
||||
|
||||
def test_decoded_recorded_source_primes_decoder_before_source_clock() -> None:
|
||||
events: list[str] = []
|
||||
|
||||
class Source(RecordedRavnoves00Source):
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
def packets(self, stop_event: Event) -> Iterator[SourcePacket]:
|
||||
events.append("source-clock-started")
|
||||
if not stop_event.is_set():
|
||||
yield _packet(0)
|
||||
|
||||
class Decoder:
|
||||
def frames(self, stop_event: Event) -> Iterator[np.ndarray]:
|
||||
events.append("decoder-primed")
|
||||
if not stop_event.is_set():
|
||||
yield np.zeros((600, 800, 3), dtype=np.uint8)
|
||||
|
||||
packets = list(
|
||||
DecodedRecordedSource(source=Source(), decoder=Decoder()).packets(Event())
|
||||
)
|
||||
|
||||
assert len(packets) == 1
|
||||
assert events == ["decoder-primed", "source-clock-started"]
|
||||
|
||||
|
||||
def test_camera_only_path_never_invents_metric_occupancy_or_free_space() -> None:
|
||||
result = _graph(_Source((_packet(0, lidar=False),))).run()
|
||||
delivery = result.deliveries[0]
|
||||
|
||||
Reference in New Issue
Block a user