feat(perception): seal detector replay evidence

This commit is contained in:
DCCONSTRUCTIONS
2026-08-05 14:36:55 +03:00
parent a680debfaf
commit 95ac235122
12 changed files with 1754 additions and 31 deletions
+12 -2
View File
@@ -143,11 +143,21 @@ class TritonHttpInferenceBackend:
def __init__(self, endpoint: str, *, timeout_seconds: float = 60.0) -> None:
parsed = urllib.parse.urlsplit(endpoint)
if parsed.scheme != "http" or not parsed.hostname or parsed.query or parsed.fragment:
if (
parsed.scheme != "http"
or not parsed.hostname
or parsed.username is not None
or parsed.password is not None
or parsed.query
or parsed.fragment
):
raise YoloxDetectorError("Triton endpoint must be an explicit HTTP origin")
if not math.isfinite(timeout_seconds) or timeout_seconds <= 0:
raise YoloxDetectorError("Triton timeout must be positive")
self.path = f"{parsed.path.rstrip('/')}/v2/models/{YOLOX_MODEL_ID}/infer"
self.path = (
f"{parsed.path.rstrip('/')}/v2/models/{YOLOX_MODEL_ID}"
f"/versions/{YOLOX_MODEL_VERSION}/infer"
)
self.connection = http.client.HTTPConnection(
parsed.hostname,
parsed.port or 80,
+28
View File
@@ -58,6 +58,21 @@ from .detector import (
FrozenYoloxDetectorProvider,
proposals_from_detections,
)
from .detector_replay import run_detector_replay
from .detector_replay_result import (
DETECTOR_REPLAY_FRAME_SCHEMA,
DETECTOR_REPLAY_RESULT_SCHEMA,
DETECTOR_RUNTIME_IDENTITY_SCHEMA,
M4_DETECTOR_REPLAY_GATE,
DetectorReplayFrame,
DetectorReplayGate,
DetectorReplayMetrics,
DetectorReplayResult,
DetectorReplayResultError,
DetectorRuntimeIdentity,
read_detector_replay_result,
require_m4_detector_replay_acceptance,
)
from .graph import (
GRAPH_RESULT_SCHEMA,
REFERENCE_GRAPH_ID,
@@ -148,6 +163,19 @@ __all__ = [
"DetectorProviderSnapshot",
"FrozenYoloxDetectorProvider",
"proposals_from_detections",
"DETECTOR_REPLAY_FRAME_SCHEMA",
"DETECTOR_REPLAY_RESULT_SCHEMA",
"DETECTOR_RUNTIME_IDENTITY_SCHEMA",
"M4_DETECTOR_REPLAY_GATE",
"DetectorReplayFrame",
"DetectorReplayGate",
"DetectorReplayMetrics",
"DetectorReplayResult",
"DetectorReplayResultError",
"DetectorRuntimeIdentity",
"read_detector_replay_result",
"require_m4_detector_replay_acceptance",
"run_detector_replay",
"REFERENCE_GRAPH_CONFIG_SCHEMA",
"DetectorProvider",
"GeometryAssociationProvider",
+87 -25
View File
@@ -30,6 +30,9 @@ BASELINE_SOURCE_PACK_ID: Final = (
BASELINE_SOURCE_PACK_SHA256: Final = (
"0685d24219d8236caf8b7f1685e93f6d6b59e7fd015a768d88a92bbe8b154944"
)
BASELINE_PREPROCESS_PROFILE_SHA256: Final = (
"19c17dbc23f2b1c539eb6b8214e69fa487ea3fec8db9d88768dffc714895fb88"
)
_SHA256 = re.compile(r"^[a-f0-9]{64}$")
_EXPECTED_EVIDENCE_ROLES: Final = {
@@ -72,6 +75,60 @@ _SOURCE_KEYS: Final = {
"source_pack_id",
"source_pack_artifact_sha256",
}
_EXPECTED_CALIBRATION: Final[dict[str, object]] = {
"slot": "camera_1",
"model": "KB4",
"sha256": "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9",
"valid_fov_result_id": (
"valid-fov-mask-b4dd8ddf2b87c1d520ee8a0868c4fea062d7c14d1bae73ccabd3abe1f3acbac2"
),
"valid_fov_mask_sha256": (
"a40cee06b7c6f69b6a09a11563dcfd237f3de833b1ccd31459e66692e528ba63"
),
}
_EXPECTED_DETECTOR: Final[dict[str, object]] = {
"provider_id": "triton-yolox-s-raw-kb4/v1",
"model_id": "yolox_s",
"model_version": 1,
"model_sha256": "c5c2d13e59ae883e6af3b45daea64af4833a4951c92d116ec270d9ddbe998063",
"config_sha256": "5795c737a7935a655961b069e8404d336d891f9762fb6dffb93956a076479604",
"preprocess_profile_sha256": BASELINE_PREPROCESS_PROFILE_SHA256,
"minimum_score": 0.5,
"nms_iou_threshold": 0.45,
"runtime": "NVIDIA Triton 2.70.0 ONNX Runtime GPU backend",
}
_EXPECTED_NON_GOALS: Final = (
"semantic-class-quality",
"persistent-reidentification",
"physical-live-k1",
"physical-threat-authority",
"navigation-or-command-authority",
"second-source-transfer",
"second-worker-bootstrap",
"ros2-nav2-px4-gazebo-integration",
"deepstream-migration",
)
_EXPECTED_ROLLBACK: Final[dict[str, object]] = {
"worker_id": "worker-006",
"worker_node": "DESKTOP-OPJ8J04",
"container_name": "ndc-mission-core-perception-worker",
"container_image": (
"nvcr.io/nvidia/tritonserver:26.06-py3@"
"sha256:58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794"
),
"worker_package_id": (
"e15-worker-package-dbf55ccb75664b778a2e0f5d34284af10ec0971945bbbee67b27bc264b765b51"
),
"runner_sha256": "86e9b25c80908a520ed483541b708cde1d1c74605e094053fcb62b610ecefb20",
"orchestrator_sha256": (
"82de50ae83debe26fc5463799b1e1e15217a8db8cc090671ba2993200b8ed95c"
),
"entrypoint": "python3 /runner/run_e15_shadow_inference.py serve",
"observed_container_id": (
"db2024d05098a6beb6b73bbf43f02c88ede586a4664b2c91182f436a42b3e3ff"
),
"observed_at_utc": "2026-08-05T10:30:00Z",
}
class BaselineContractError(ValueError):
@@ -129,25 +186,37 @@ def load_m4_baseline(path: Path) -> BaselineProfile:
raise BaselineContractError("M4 source pack artifact identity changed")
if source.get("camera_stream_sha256") != BASELINE_CAMERA_STREAM_SHA256:
raise BaselineContractError("M4 camera stream identity changed")
modalities = _string_array(source.get("modalities"), "source modalities")
if set(modalities) != {"image", "registered-point-increment", "pose"}:
raise BaselineContractError("baseline source must bind image, points and pose")
if _integer(source.get("frame_count"), "source frame count") != 4489:
if source.get("frame_count") != 4489:
raise BaselineContractError("baseline source frame count changed")
if source.get("duration_seconds") != 448.723:
raise BaselineContractError("baseline source duration changed")
if source.get("frame_rate") != 10.003944527024467:
raise BaselineContractError("baseline source frame rate changed")
modalities = _string_array(source.get("modalities"), "source modalities")
if modalities != ("image", "registered-point-increment", "pose"):
raise BaselineContractError("baseline source must bind image, points and pose")
calibration = _object(document.get("calibration"), "calibration")
if calibration != _EXPECTED_CALIBRATION:
raise BaselineContractError("baseline calibration identity changed")
detector = _object(document.get("detector"), "detector")
if detector != _EXPECTED_DETECTOR:
raise BaselineContractError("baseline detector identity changed")
authority = _object(document.get("authority"), "authority")
if authority.get("mode") != "replay-simulated":
raise BaselineContractError("M4 authority must remain replay-simulated")
for key in (
"ground_truth",
"physical_live",
"physical_collision_accepted",
"commands_enabled",
"actuation_allowed",
"navigation_or_safety_accepted",
):
if authority.get(key) is not False:
raise BaselineContractError(f"baseline authority {key} must remain false")
if authority != {
"mode": "replay-simulated",
"ground_truth": False,
"physical_live": False,
"physical_collision_accepted": False,
"commands_enabled": False,
"actuation_allowed": False,
"navigation_or_safety_accepted": False,
}:
raise BaselineContractError("M4 authority must remain replay-simulated and false")
if _string_array(document.get("non_goals"), "non-goals") != _EXPECTED_NON_GOALS:
raise BaselineContractError("baseline non-goals changed")
evidence_items = document.get("evidence")
if not isinstance(evidence_items, list):
@@ -161,15 +230,8 @@ def load_m4_baseline(path: Path) -> BaselineProfile:
raise BaselineContractError("baseline evidence paths must be unique")
rollback = _object(document.get("rollback"), "rollback")
if rollback.get("worker_id") != "worker-006":
raise BaselineContractError("rollback worker identity changed")
if rollback.get("worker_node") != "DESKTOP-OPJ8J04":
raise BaselineContractError("rollback worker node changed")
entrypoint = rollback.get("entrypoint")
if not isinstance(entrypoint, str) or "run_e15_shadow_inference.py serve" not in entrypoint:
raise BaselineContractError("rollback E15 process identity is missing")
_digest(rollback.get("runner_sha256"), "rollback runner digest")
_digest(rollback.get("orchestrator_sha256"), "rollback orchestrator digest")
if rollback != _EXPECTED_ROLLBACK:
raise BaselineContractError("rollback E15 identity changed")
return BaselineProfile(path=path, document=document, evidence=evidence)
+113
View File
@@ -0,0 +1,113 @@
"""Sequential product runner for the frozen detector replay/capacity gate."""
from __future__ import annotations
import time
from collections.abc import Callable
from pathlib import Path
from threading import Event
from .baseline import BASELINE_SESSION_ID, BASELINE_SOURCE_ID
from .detector import FrozenYoloxDetectorProvider
from .detector_replay_result import (
M4_DETECTOR_REPLAY_GATE,
DetectorReplayFrame,
DetectorReplayGate,
DetectorReplayResult,
DetectorReplayResultError,
DetectorRuntimeIdentity,
build_detector_replay_metrics,
seal_detector_replay_result,
)
from .providers import SourceProvider
from .recorded_source import RECORDED_SOURCE_PROVIDER_ID
def run_detector_replay(
*,
source: SourceProvider,
provider: FrozenYoloxDetectorProvider,
runtime: DetectorRuntimeIdentity,
output_root: Path,
gate: DetectorReplayGate = M4_DETECTOR_REPLAY_GATE,
stop_event: Event | None = None,
clock_ns: Callable[[], int] = time.perf_counter_ns,
created_at_utc: str | None = None,
) -> DetectorReplayResult:
"""Execute one request per admitted frame and seal even a bounded failed run."""
if source.provider_id != RECORDED_SOURCE_PROVIDER_ID:
raise DetectorReplayResultError("detector replay source provider is not admitted")
stop = stop_event or Event()
frames: list[DetectorReplayFrame] = []
source_failure_code: str | None = None
started_ns = int(clock_ns())
packets = source.packets(stop)
try:
for packet in packets:
if len(frames) == gate.expected_frames:
source_failure_code = "source-frame-count-exceeded"
break
if (
packet.envelope.sequence != len(frames)
or packet.envelope.source_id != BASELINE_SOURCE_ID
or packet.envelope.session_id != BASELINE_SESSION_ID
):
source_failure_code = "source-envelope-sequence-or-identity-mismatch"
break
frame_started_ns = int(clock_ns())
try:
proposals = provider.detect(packet)
except Exception as exc:
frames.append(
DetectorReplayFrame(
sequence=packet.envelope.sequence,
envelope=packet.envelope,
outcome="failed",
proposals=(),
duration_ns=max(0, int(clock_ns()) - frame_started_ns),
failure_code=type(exc).__name__,
)
)
break
frames.append(
DetectorReplayFrame(
sequence=packet.envelope.sequence,
envelope=packet.envelope,
outcome="completed",
proposals=proposals,
duration_ns=max(0, int(clock_ns()) - frame_started_ns),
)
)
except Exception as exc:
source_failure_code = type(exc).__name__
finally:
close = getattr(packets, "close", None)
if callable(close):
try:
close()
except Exception as exc:
if source_failure_code is None:
source_failure_code = type(exc).__name__
if stop.is_set() and source_failure_code is None:
source_failure_code = "execution-cancelled"
if len(frames) != gate.expected_frames and source_failure_code is None:
source_failure_code = "source-frame-count-mismatch"
ended_ns = int(clock_ns())
metrics = build_detector_replay_metrics(
tuple(frames),
provider.snapshot(),
run_duration_ns=max(1, ended_ns - started_ns),
)
return seal_detector_replay_result(
output_root=output_root,
frames=tuple(frames),
metrics=metrics,
runtime=runtime,
gate=gate,
source_failure_code=source_failure_code,
created_at_utc=created_at_utc,
)
__all__ = ["run_detector_replay"]
@@ -0,0 +1,111 @@
"""Worker entry point for the canonical frozen detector replay gate."""
from __future__ import annotations
import argparse
import ipaddress
import json
import urllib.parse
from pathlib import Path
from threading import Event
from k1link.compute.yolox_object_detector import (
TritonHttpInferenceBackend,
load_valid_fov_mask,
)
from .baseline import load_m4_baseline, verify_m4_baseline
from .detector import FrozenYoloxDetectorProvider
from .detector_replay import run_detector_replay
from .detector_replay_result import (
DetectorReplayResultError,
DetectorRuntimeIdentity,
require_m4_detector_replay_acceptance,
)
from .recorded_source import (
DecodedRecordedSource,
PyAvRecordedImageDecoder,
RecordedRavnoves00Source,
ReplayPacing,
)
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Run the product-neutral M4 detector replay/capacity gate."
)
parser.add_argument("--repository-root", type=Path, required=True)
parser.add_argument("--video", type=Path, required=True)
parser.add_argument("--valid-fov-mask", type=Path, required=True)
parser.add_argument("--triton-origin", required=True, type=_loopback_triton_origin)
parser.add_argument("--runtime-identity", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
return parser.parse_args()
def _loopback_triton_origin(value: str) -> str:
parsed = urllib.parse.urlsplit(value)
hostname = parsed.hostname
try:
loopback = hostname == "localhost" or (
hostname is not None and ipaddress.ip_address(hostname).is_loopback
)
except ValueError:
loopback = False
if not loopback:
raise argparse.ArgumentTypeError("M4 Triton origin must use worker-local loopback")
return value
def main() -> int:
args = _arguments()
repository_root = args.repository_root.resolve(strict=True)
baseline = load_m4_baseline(
repository_root / "config/perception/m4-recorded-realtime-baseline-v1.json"
)
verify_m4_baseline(repository_root, baseline)
runtime_value = json.loads(args.runtime_identity.resolve(strict=True).read_text("utf-8"))
runtime = DetectorRuntimeIdentity.from_dict(runtime_value)
source = DecodedRecordedSource(
source=RecordedRavnoves00Source.from_repository(
repository_root,
pacing=ReplayPacing.UNCAPPED,
),
decoder=PyAvRecordedImageDecoder(args.video),
)
backend = TritonHttpInferenceBackend(args.triton_origin)
try:
result = run_detector_replay(
source=source,
provider=FrozenYoloxDetectorProvider(
mask=load_valid_fov_mask(args.valid_fov_mask),
backend=backend,
),
runtime=runtime,
output_root=args.output_root,
stop_event=Event(),
)
finally:
backend.close()
try:
require_m4_detector_replay_acceptance(result)
m4_accepted = True
except DetectorReplayResultError:
m4_accepted = False
print(
json.dumps(
{
"result_id": result.result_id,
"result_root": str(result.result_root),
"accepted": m4_accepted,
"metrics": result.metrics.to_dict(),
},
sort_keys=True,
separators=(",", ":"),
)
)
return 0 if m4_accepted else 2
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,493 @@
"""Strict contracts and accounting for detector replay evidence."""
from __future__ import annotations
import math
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Final
from k1link.compute.yolox_object_detector import (
YOLOX_CONFIG_SHA256,
YOLOX_MODEL_SHA256,
YOLOX_VALID_FOV_SHA256,
)
from .contracts import ObjectProposal2D, SourceEnvelope
from .detector import (
FROZEN_YOLOX_MODEL_ID,
FROZEN_YOLOX_PREPROCESS_ID,
FROZEN_YOLOX_PROVIDER_ID,
DetectorProviderSnapshot,
)
DETECTOR_REPLAY_RESULT_SCHEMA: Final = "missioncore.perception-detector-replay-result/v1"
DETECTOR_REPLAY_RECEIPT_SCHEMA: Final = "missioncore.perception-detector-replay-receipt/v1"
DETECTOR_REPLAY_FRAME_SCHEMA: Final = "missioncore.perception-detector-replay-frame/v1"
DETECTOR_RUNTIME_IDENTITY_SCHEMA: Final = "missioncore.perception-runtime-identity/v1"
DETECTOR_REPLAY_RESULT_PREFIX: Final = "m4-detector-replay-"
DETECTOR_REPLAY_MANIFEST_NAME: Final = "manifest.json"
DETECTOR_REPLAY_RECEIPT_NAME: Final = "receipt.json"
DETECTOR_REPLAY_FRAMES_NAME: Final = "frames.jsonl"
_SHA256 = re.compile(r"^[a-f0-9]{64}$")
_GIT_REVISION = re.compile(r"^[a-f0-9]{40}$")
_RESULT_ID = re.compile(r"^m4-detector-replay-[a-f0-9]{64}$")
_DOCKER_IMAGE_ID = re.compile(r"^sha256:[a-f0-9]{64}$")
_IDENTIFIER = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:/-]{0,255}$")
class DetectorReplayResultError(ValueError):
"""Detector replay evidence is incomplete, mutable or internally inconsistent."""
@dataclass(frozen=True, slots=True)
class DetectorReplayGate:
expected_frames: int
minimum_end_to_end_fps: float
def __post_init__(self) -> None:
if self.expected_frames < 1:
raise DetectorReplayResultError("detector replay expected frames must be positive")
if (
not math.isfinite(self.minimum_end_to_end_fps)
or self.minimum_end_to_end_fps <= 0.0
):
raise DetectorReplayResultError("detector replay minimum FPS must be positive")
def to_dict(self) -> dict[str, object]:
return {
"expected_frames": self.expected_frames,
"minimum_end_to_end_fps": self.minimum_end_to_end_fps,
}
@classmethod
def from_dict(cls, value: object) -> DetectorReplayGate:
document = _object(value, "detector replay gate")
_exact_keys(document, {"expected_frames", "minimum_end_to_end_fps"}, "gate")
return cls(
expected_frames=_integer(document.get("expected_frames"), "expected frames"),
minimum_end_to_end_fps=_number(
document.get("minimum_end_to_end_fps"), "minimum end-to-end FPS"
),
)
M4_DETECTOR_REPLAY_GATE: Final = DetectorReplayGate(
expected_frames=4489,
minimum_end_to_end_fps=10.004,
)
@dataclass(frozen=True, slots=True)
class DetectorRuntimeIdentity:
worker_id: str
worker_node: str
worker_container_id: str
worker_image_id: str
triton_container_id: str
triton_image_id: str
triton_model_sha256: str
triton_model_config_sha256: str
valid_fov_mask_sha256: str
artifact_sha256: str
code_revision: str
source_mount_read_only: bool
model_service_reused: bool
public_worker_port_added: bool
same_host_tensor_transport: bool
def __post_init__(self) -> None:
_identifier(self.worker_id, "worker id")
_identifier(self.worker_node, "worker node")
for value, label in (
(self.worker_container_id, "worker container id"),
(self.triton_container_id, "Triton container id"),
):
if _SHA256.fullmatch(value) is None:
raise DetectorReplayResultError(f"{label} must be a full digest")
for value, label in (
(self.worker_image_id, "worker image id"),
(self.triton_image_id, "Triton image id"),
):
if _DOCKER_IMAGE_ID.fullmatch(value) is None:
raise DetectorReplayResultError(f"{label} must be a full image digest")
if self.triton_model_sha256 != YOLOX_MODEL_SHA256:
raise DetectorReplayResultError("runtime Triton model digest changed")
if self.triton_model_config_sha256 != YOLOX_CONFIG_SHA256:
raise DetectorReplayResultError("runtime Triton model config digest changed")
if self.valid_fov_mask_sha256 != YOLOX_VALID_FOV_SHA256:
raise DetectorReplayResultError("runtime valid-FOV mask digest changed")
if _SHA256.fullmatch(self.artifact_sha256) is None:
raise DetectorReplayResultError("worker artifact must be digest-bound")
if _GIT_REVISION.fullmatch(self.code_revision) is None:
raise DetectorReplayResultError("code revision must be a full Git revision")
if not self.source_mount_read_only:
raise DetectorReplayResultError("recorded source mount must be read-only")
if not self.model_service_reused:
raise DetectorReplayResultError("the admitted Triton service must be reused")
if self.public_worker_port_added:
raise DetectorReplayResultError("detector replay must not add a public worker port")
if not self.same_host_tensor_transport:
raise DetectorReplayResultError("full detector tensors must remain on the worker host")
def to_dict(self) -> dict[str, object]:
return {
"schema_version": DETECTOR_RUNTIME_IDENTITY_SCHEMA,
"worker_id": self.worker_id,
"worker_node": self.worker_node,
"worker_container_id": self.worker_container_id,
"worker_image_id": self.worker_image_id,
"triton_container_id": self.triton_container_id,
"triton_image_id": self.triton_image_id,
"triton_model_sha256": self.triton_model_sha256,
"triton_model_config_sha256": self.triton_model_config_sha256,
"valid_fov_mask_sha256": self.valid_fov_mask_sha256,
"artifact_sha256": self.artifact_sha256,
"code_revision": self.code_revision,
"source_mount_read_only": self.source_mount_read_only,
"model_service_reused": self.model_service_reused,
"public_worker_port_added": self.public_worker_port_added,
"same_host_tensor_transport": self.same_host_tensor_transport,
}
@classmethod
def from_dict(cls, value: object) -> DetectorRuntimeIdentity:
document = _object(value, "detector runtime identity")
fields = {
"worker_id",
"worker_node",
"worker_container_id",
"worker_image_id",
"triton_container_id",
"triton_image_id",
"triton_model_sha256",
"triton_model_config_sha256",
"valid_fov_mask_sha256",
"artifact_sha256",
"code_revision",
"source_mount_read_only",
"model_service_reused",
"public_worker_port_added",
"same_host_tensor_transport",
}
_exact_keys(document, fields | {"schema_version"}, "runtime identity")
if document.get("schema_version") != DETECTOR_RUNTIME_IDENTITY_SCHEMA:
raise DetectorReplayResultError("detector runtime identity schema changed")
return cls(
worker_id=_string(document.get("worker_id"), "worker id"),
worker_node=_string(document.get("worker_node"), "worker node"),
worker_container_id=_string(
document.get("worker_container_id"), "worker container id"
),
worker_image_id=_string(document.get("worker_image_id"), "worker image id"),
triton_container_id=_string(
document.get("triton_container_id"), "Triton container id"
),
triton_image_id=_string(document.get("triton_image_id"), "Triton image id"),
triton_model_sha256=_string(
document.get("triton_model_sha256"), "Triton model digest"
),
triton_model_config_sha256=_string(
document.get("triton_model_config_sha256"), "Triton model config digest"
),
valid_fov_mask_sha256=_string(
document.get("valid_fov_mask_sha256"), "valid-FOV mask digest"
),
artifact_sha256=_string(document.get("artifact_sha256"), "artifact digest"),
code_revision=_string(document.get("code_revision"), "code revision"),
source_mount_read_only=_boolean(
document.get("source_mount_read_only"), "source mount read-only"
),
model_service_reused=_boolean(
document.get("model_service_reused"), "model service reused"
),
public_worker_port_added=_boolean(
document.get("public_worker_port_added"), "public worker port added"
),
same_host_tensor_transport=_boolean(
document.get("same_host_tensor_transport"), "same-host tensor transport"
),
)
@dataclass(frozen=True, slots=True)
class DetectorReplayFrame:
sequence: int
envelope: SourceEnvelope
outcome: str
proposals: tuple[ObjectProposal2D, ...]
duration_ns: int
failure_code: str | None = None
def __post_init__(self) -> None:
if self.sequence < 0 or self.envelope.sequence != self.sequence:
raise DetectorReplayResultError("detector replay frame sequence is invalid")
if self.outcome not in {"completed", "failed"}:
raise DetectorReplayResultError("detector replay frame outcome is invalid")
if self.duration_ns < 0:
raise DetectorReplayResultError("detector replay frame duration is invalid")
if self.outcome == "completed" and self.failure_code is not None:
raise DetectorReplayResultError("completed detector frame cannot carry a failure")
if self.outcome == "failed" and (
self.proposals or self.failure_code is None or not self.failure_code
):
raise DetectorReplayResultError("failed detector frame must carry one failure code")
proposal_ids: set[str] = set()
for proposal in self.proposals:
if (
proposal.source_id != self.envelope.source_id
or proposal.frame_id != self.envelope.frame_id
or proposal.provider_id != FROZEN_YOLOX_PROVIDER_ID
or proposal.model_id != FROZEN_YOLOX_MODEL_ID
or proposal.preprocess_id != FROZEN_YOLOX_PREPROCESS_ID
or proposal.provider_tracklet is not None
or proposal.proposal_id in proposal_ids
):
raise DetectorReplayResultError("detector replay proposal ownership changed")
proposal_ids.add(proposal.proposal_id)
def to_dict(self) -> dict[str, object]:
return {
"schema_version": DETECTOR_REPLAY_FRAME_SCHEMA,
"sequence": self.sequence,
"source_envelope": self.envelope.to_dict(),
"outcome": self.outcome,
"detector_request_count": 1,
"class_routing_used": False,
"duration_ns": self.duration_ns,
"failure_code": self.failure_code,
"proposals": [proposal.to_dict() for proposal in self.proposals],
}
@classmethod
def from_dict(cls, value: object) -> DetectorReplayFrame:
document = _object(value, "detector replay frame")
_exact_keys(
document,
{
"schema_version",
"sequence",
"source_envelope",
"outcome",
"detector_request_count",
"class_routing_used",
"duration_ns",
"failure_code",
"proposals",
},
"detector replay frame",
)
if (
document.get("schema_version") != DETECTOR_REPLAY_FRAME_SCHEMA
or document.get("detector_request_count") != 1
or document.get("class_routing_used") is not False
):
raise DetectorReplayResultError("detector replay frame contract changed")
proposals_value = document.get("proposals")
if not isinstance(proposals_value, list):
raise DetectorReplayResultError("detector replay proposals must be an array")
failure = document.get("failure_code")
if failure is not None and not isinstance(failure, str):
raise DetectorReplayResultError("detector replay failure code is invalid")
return cls(
sequence=_integer(document.get("sequence"), "frame sequence"),
envelope=SourceEnvelope.from_dict(document.get("source_envelope")),
outcome=_string(document.get("outcome"), "frame outcome"),
proposals=tuple(ObjectProposal2D.from_dict(item) for item in proposals_value),
duration_ns=_integer(document.get("duration_ns"), "frame duration"),
failure_code=failure,
)
@dataclass(frozen=True, slots=True)
class DetectorReplayMetrics:
frame_count: int
completed_frame_count: int
failed_frame_count: int
proposal_count: int
zero_proposal_frame_count: int
semantic_hint_count: int
provider_tracklet_count: int
run_duration_ns: int
provider_core_duration_ns: int
end_to_end_fps: float
provider_core_fps: float
frame_latency_p50_ms: float
frame_latency_p95_ms: float
frame_latency_max_ms: float
rejected: tuple[tuple[str, int], ...]
def to_dict(self) -> dict[str, object]:
return {
"frame_count": self.frame_count,
"completed_frame_count": self.completed_frame_count,
"failed_frame_count": self.failed_frame_count,
"proposal_count": self.proposal_count,
"zero_proposal_frame_count": self.zero_proposal_frame_count,
"semantic_hint_count": self.semantic_hint_count,
"provider_tracklet_count": self.provider_tracklet_count,
"run_duration_ns": self.run_duration_ns,
"provider_core_duration_ns": self.provider_core_duration_ns,
"end_to_end_fps": self.end_to_end_fps,
"provider_core_fps": self.provider_core_fps,
"frame_latency_p50_ms": self.frame_latency_p50_ms,
"frame_latency_p95_ms": self.frame_latency_p95_ms,
"frame_latency_max_ms": self.frame_latency_max_ms,
"rejected": [{"reason": key, "count": count} for key, count in self.rejected],
}
@dataclass(frozen=True, slots=True)
class DetectorReplayResult:
result_id: str
result_root: Path
accepted: bool
metrics: DetectorReplayMetrics
runtime: DetectorRuntimeIdentity
gate: DetectorReplayGate
frames: tuple[DetectorReplayFrame, ...]
manifest: dict[str, object]
receipt: dict[str, object]
def build_detector_replay_metrics(
frames: tuple[DetectorReplayFrame, ...],
snapshot: DetectorProviderSnapshot,
*,
run_duration_ns: int,
) -> DetectorReplayMetrics:
if run_duration_ns <= 0:
raise DetectorReplayResultError("detector replay duration must be positive")
completed = sum(frame.outcome == "completed" for frame in frames)
failed = len(frames) - completed
proposals = tuple(proposal for frame in frames for proposal in frame.proposals)
latencies_ms = sorted(frame.duration_ns / 1_000_000 for frame in frames)
metrics = DetectorReplayMetrics(
frame_count=len(frames),
completed_frame_count=completed,
failed_frame_count=failed,
proposal_count=len(proposals),
zero_proposal_frame_count=sum(
frame.outcome == "completed" and not frame.proposals for frame in frames
),
semantic_hint_count=sum(
proposal.semantic_hint is not None for proposal in proposals
),
provider_tracklet_count=sum(
proposal.provider_tracklet is not None for proposal in proposals
),
run_duration_ns=run_duration_ns,
provider_core_duration_ns=snapshot.core_duration_ns,
end_to_end_fps=round(completed * 1_000_000_000 / run_duration_ns, 6),
provider_core_fps=(
round(completed * 1_000_000_000 / snapshot.core_duration_ns, 6)
if snapshot.core_duration_ns > 0
else 0.0
),
frame_latency_p50_ms=round(_percentile(latencies_ms, 0.5), 6),
frame_latency_p95_ms=round(_percentile(latencies_ms, 0.95), 6),
frame_latency_max_ms=round(max(latencies_ms, default=0.0), 6),
rejected=snapshot.rejected,
)
if (
snapshot.input_frames != len(frames)
or snapshot.completed_frames != completed
or snapshot.failed_frames != failed
or snapshot.proposal_count != len(proposals)
or snapshot.zero_proposal_frames != metrics.zero_proposal_frame_count
):
raise DetectorReplayResultError("provider and replay accounting disagree")
return metrics
def detector_replay_accepted(
metrics: DetectorReplayMetrics,
gate: DetectorReplayGate,
source_failure_code: str | None,
) -> bool:
return (
source_failure_code is None
and metrics.frame_count == gate.expected_frames
and metrics.completed_frame_count == gate.expected_frames
and metrics.failed_frame_count == 0
and metrics.provider_tracklet_count == 0
and metrics.end_to_end_fps >= gate.minimum_end_to_end_fps
)
def _percentile(values: list[float], fraction: float) -> float:
if not values:
return 0.0
index = (len(values) - 1) * fraction
lower = math.floor(index)
upper = math.ceil(index)
if lower == upper:
return values[lower]
ratio = index - lower
return values[lower] * (1.0 - ratio) + values[upper] * ratio
def _object(value: object, label: str) -> dict[str, object]:
if not isinstance(value, dict) or any(not isinstance(key, str) for key in value):
raise DetectorReplayResultError(f"{label} must be an object")
return value
def _exact_keys(document: dict[str, object], expected: set[str], label: str) -> None:
if set(document) != expected:
raise DetectorReplayResultError(f"{label} fields are incompatible")
def _string(value: object, label: str) -> str:
if not isinstance(value, str) or not value:
raise DetectorReplayResultError(f"{label} must be a nonempty string")
return value
def _identifier(value: str, label: str) -> None:
if _IDENTIFIER.fullmatch(value) is None:
raise DetectorReplayResultError(f"{label} is invalid")
def _integer(value: object, label: str) -> int:
if not isinstance(value, int) or isinstance(value, bool) or value < 0:
raise DetectorReplayResultError(f"{label} must be a nonnegative integer")
return value
def _number(value: object, label: str) -> float:
if not isinstance(value, int | float) or isinstance(value, bool):
raise DetectorReplayResultError(f"{label} must be numeric")
converted = float(value)
if not math.isfinite(converted):
raise DetectorReplayResultError(f"{label} must be finite")
return converted
def _boolean(value: object, label: str) -> bool:
if not isinstance(value, bool):
raise DetectorReplayResultError(f"{label} must be boolean")
return value
__all__ = [
"DETECTOR_REPLAY_FRAME_SCHEMA",
"DETECTOR_REPLAY_FRAMES_NAME",
"DETECTOR_REPLAY_MANIFEST_NAME",
"DETECTOR_REPLAY_RECEIPT_NAME",
"DETECTOR_REPLAY_RECEIPT_SCHEMA",
"DETECTOR_REPLAY_RESULT_PREFIX",
"DETECTOR_REPLAY_RESULT_SCHEMA",
"DETECTOR_RUNTIME_IDENTITY_SCHEMA",
"M4_DETECTOR_REPLAY_GATE",
"DetectorReplayFrame",
"DetectorReplayGate",
"DetectorReplayMetrics",
"DetectorReplayResult",
"DetectorReplayResultError",
"DetectorRuntimeIdentity",
"build_detector_replay_metrics",
"detector_replay_accepted",
]
@@ -0,0 +1,220 @@
"""Atomic publication API for product detector replay evidence."""
from __future__ import annotations
import hashlib
import json
import os
import shutil
import uuid
from datetime import UTC, datetime
from pathlib import Path
from k1link.compute.yolox_object_detector import (
FROZEN_YOLOX_CONFIG,
YOLOX_CONFIG_SHA256,
YOLOX_MODEL_SHA256,
YOLOX_MODEL_VERSION,
YOLOX_VALID_FOV_SHA256,
)
from .baseline import (
BASELINE_CAMERA_STREAM_SHA256,
BASELINE_PREPROCESS_PROFILE_SHA256,
BASELINE_PROFILE_ID,
BASELINE_RECORDED_JOB_ID,
BASELINE_SESSION_ID,
BASELINE_SOURCE_ID,
)
from .contracts import FalseAuthority
from .detector import (
FROZEN_YOLOX_MODEL_ID,
FROZEN_YOLOX_PREPROCESS_ID,
FROZEN_YOLOX_PROVIDER_ID,
)
from .detector_replay_contracts import (
DETECTOR_REPLAY_FRAME_SCHEMA,
DETECTOR_REPLAY_FRAMES_NAME,
DETECTOR_REPLAY_MANIFEST_NAME,
DETECTOR_REPLAY_RECEIPT_NAME,
DETECTOR_REPLAY_RECEIPT_SCHEMA,
DETECTOR_REPLAY_RESULT_PREFIX,
DETECTOR_REPLAY_RESULT_SCHEMA,
DETECTOR_RUNTIME_IDENTITY_SCHEMA,
M4_DETECTOR_REPLAY_GATE,
DetectorReplayFrame,
DetectorReplayGate,
DetectorReplayMetrics,
DetectorReplayResult,
DetectorReplayResultError,
DetectorRuntimeIdentity,
build_detector_replay_metrics,
detector_replay_accepted,
)
from .detector_replay_validation import (
read_detector_replay_result as _read_detector_replay_result,
)
from .detector_replay_validation import validate_frame_accounting
from .recorded_source import RECORDED_SOURCE_PROVIDER_ID
def seal_detector_replay_result(
*,
output_root: Path,
frames: tuple[DetectorReplayFrame, ...],
metrics: DetectorReplayMetrics,
runtime: DetectorRuntimeIdentity,
gate: DetectorReplayGate,
source_failure_code: str | None,
created_at_utc: str | None = None,
) -> DetectorReplayResult:
validate_frame_accounting(frames, metrics)
root = output_root.expanduser().absolute()
root.mkdir(mode=0o700, parents=True, exist_ok=True)
staging = root / f".detector-replay.{uuid.uuid4().hex}.tmp"
staging.mkdir(mode=0o700, exist_ok=False)
try:
frames_path = staging / DETECTOR_REPLAY_FRAMES_NAME
with frames_path.open("wb") as handle:
for frame in frames:
handle.write(_canonical_json(frame.to_dict()) + b"\n")
frames_sha256 = _file_sha256(frames_path)
accepted = detector_replay_accepted(metrics, gate, source_failure_code)
identity = {
"schema_version": DETECTOR_REPLAY_RESULT_SCHEMA,
"baseline_profile_id": BASELINE_PROFILE_ID,
"source": {
"provider_id": RECORDED_SOURCE_PROVIDER_ID,
"source_id": BASELINE_SOURCE_ID,
"session_id": BASELINE_SESSION_ID,
"camera_artifact_id": BASELINE_RECORDED_JOB_ID,
"camera_stream_sha256": BASELINE_CAMERA_STREAM_SHA256,
},
"detector": {
"provider_id": FROZEN_YOLOX_PROVIDER_ID,
"model_id": FROZEN_YOLOX_MODEL_ID,
"model_version": YOLOX_MODEL_VERSION,
"model_sha256": YOLOX_MODEL_SHA256,
"model_config_sha256": YOLOX_CONFIG_SHA256,
"valid_fov_mask_sha256": YOLOX_VALID_FOV_SHA256,
"preprocess_id": FROZEN_YOLOX_PREPROCESS_ID,
"preprocess_profile_sha256": BASELINE_PREPROCESS_PROFILE_SHA256,
"minimum_score": FROZEN_YOLOX_CONFIG.minimum_score,
"nms_iou_threshold": FROZEN_YOLOX_CONFIG.nms_iou_threshold,
"target_class_ids": list(FROZEN_YOLOX_CONFIG.target_class_ids),
"single_inference_per_frame": True,
"class_routing_used": False,
"provider_tracklets_used": False,
},
"runtime": runtime.to_dict(),
"gate": gate.to_dict(),
"metrics": metrics.to_dict(),
"source_failure_code": source_failure_code,
"frames_sha256": frames_sha256,
"accepted": accepted,
"authority": FalseAuthority().to_dict(),
}
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
result_id = f"{DETECTOR_REPLAY_RESULT_PREFIX}{identity_sha256}"
created = created_at_utc or datetime.now(UTC).isoformat(timespec="milliseconds").replace(
"+00:00", "Z"
)
receipt = {
"schema_version": DETECTOR_REPLAY_RECEIPT_SCHEMA,
"result_id": result_id,
"identity_sha256": identity_sha256,
"created_at_utc": created,
"accepted": accepted,
"source_failure_code": source_failure_code,
"gate": gate.to_dict(),
"metrics": metrics.to_dict(),
"authority": FalseAuthority().to_dict(),
}
receipt_path = staging / DETECTOR_REPLAY_RECEIPT_NAME
_write_json(receipt_path, receipt)
manifest = {
"schema_version": DETECTOR_REPLAY_RESULT_SCHEMA,
"result_id": result_id,
"identity_sha256": identity_sha256,
"identity": identity,
"created_at_utc": created,
"accepted": accepted,
"artifacts": [
_artifact(receipt_path, "detector-replay-receipt"),
_artifact(frames_path, "detector-replay-frames"),
],
}
_write_json(staging / DETECTOR_REPLAY_MANIFEST_NAME, manifest)
destination = root / result_id
if destination.exists():
shutil.rmtree(staging)
return read_detector_replay_result(destination)
os.replace(staging, destination)
return read_detector_replay_result(destination)
except BaseException:
shutil.rmtree(staging, ignore_errors=True)
raise
def read_detector_replay_result(root: Path) -> DetectorReplayResult:
return _read_detector_replay_result(root)
def require_m4_detector_replay_acceptance(result: DetectorReplayResult) -> None:
"""Fail unless a result closes the exact M4.3 Worker 006 detector gate."""
if (
not result.accepted
or result.gate != M4_DETECTOR_REPLAY_GATE
or result.runtime.worker_id != "worker-006"
or result.runtime.worker_node != "DESKTOP-OPJ8J04"
):
raise DetectorReplayResultError("result does not close the M4.3 Worker 006 gate")
def _artifact(path: Path, role: str) -> dict[str, object]:
return {
"role": role,
"path": path.name,
"bytes": path.stat().st_size,
"sha256": _file_sha256(path),
}
def _write_json(path: Path, value: object) -> None:
path.write_bytes(_canonical_json(value) + b"\n")
def _canonical_json(value: object) -> bytes:
return json.dumps(
value,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode()
def _file_sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
__all__ = [
"DETECTOR_REPLAY_FRAME_SCHEMA",
"DETECTOR_REPLAY_RESULT_SCHEMA",
"DETECTOR_RUNTIME_IDENTITY_SCHEMA",
"M4_DETECTOR_REPLAY_GATE",
"DetectorReplayFrame",
"DetectorReplayGate",
"DetectorReplayMetrics",
"DetectorReplayResult",
"DetectorReplayResultError",
"DetectorRuntimeIdentity",
"build_detector_replay_metrics",
"read_detector_replay_result",
"require_m4_detector_replay_acceptance",
"seal_detector_replay_result",
]
@@ -0,0 +1,402 @@
"""Fail-closed reader and consistency checks for detector replay evidence."""
from __future__ import annotations
import hashlib
import json
from pathlib import Path
from k1link.compute.yolox_object_detector import (
FROZEN_YOLOX_CONFIG,
YOLOX_CONFIG_SHA256,
YOLOX_MODEL_SHA256,
YOLOX_MODEL_VERSION,
YOLOX_VALID_FOV_SHA256,
)
from .baseline import (
BASELINE_CAMERA_STREAM_SHA256,
BASELINE_PREPROCESS_PROFILE_SHA256,
BASELINE_PROFILE_ID,
BASELINE_RECORDED_JOB_ID,
BASELINE_SESSION_ID,
BASELINE_SOURCE_ID,
)
from .contracts import FalseAuthority
from .detector import (
FROZEN_YOLOX_MODEL_ID,
FROZEN_YOLOX_PREPROCESS_ID,
FROZEN_YOLOX_PROVIDER_ID,
)
from .detector_replay_contracts import (
_RESULT_ID,
_SHA256,
DETECTOR_REPLAY_FRAMES_NAME,
DETECTOR_REPLAY_MANIFEST_NAME,
DETECTOR_REPLAY_RECEIPT_NAME,
DETECTOR_REPLAY_RECEIPT_SCHEMA,
DETECTOR_REPLAY_RESULT_PREFIX,
DETECTOR_REPLAY_RESULT_SCHEMA,
DetectorReplayFrame,
DetectorReplayGate,
DetectorReplayMetrics,
DetectorReplayResult,
DetectorReplayResultError,
DetectorRuntimeIdentity,
_exact_keys,
_integer,
_number,
_object,
_percentile,
_string,
detector_replay_accepted,
)
from .recorded_source import RECORDED_SOURCE_PROVIDER_ID
def read_detector_replay_result(root: Path) -> DetectorReplayResult:
resolved = root.resolve(strict=True)
if resolved.is_symlink() or _RESULT_ID.fullmatch(resolved.name) is None:
raise DetectorReplayResultError("detector replay result root is invalid")
manifest = _read_json(resolved / DETECTOR_REPLAY_MANIFEST_NAME)
_exact_keys(
manifest,
{
"schema_version",
"result_id",
"identity_sha256",
"identity",
"created_at_utc",
"accepted",
"artifacts",
},
"detector replay manifest",
)
identity = _object(manifest.get("identity"), "detector replay identity")
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
if (
manifest.get("schema_version") != DETECTOR_REPLAY_RESULT_SCHEMA
or manifest.get("result_id") != resolved.name
or manifest.get("identity_sha256") != identity_sha256
or resolved.name != f"{DETECTOR_REPLAY_RESULT_PREFIX}{identity_sha256}"
):
raise DetectorReplayResultError("detector replay identity changed")
_validate_identity(identity)
runtime = DetectorRuntimeIdentity.from_dict(identity.get("runtime"))
gate = DetectorReplayGate.from_dict(identity.get("gate"))
artifacts_value = manifest.get("artifacts")
if not isinstance(artifacts_value, list) or len(artifacts_value) != 2:
raise DetectorReplayResultError("detector replay artifact inventory changed")
artifacts = {
_string(_object(item, "artifact").get("role"), "artifact role"): item
for item in artifacts_value
}
if set(artifacts) != {"detector-replay-receipt", "detector-replay-frames"}:
raise DetectorReplayResultError("detector replay artifact roles changed")
receipt_path = _validated_artifact(
resolved, artifacts["detector-replay-receipt"], DETECTOR_REPLAY_RECEIPT_NAME
)
frames_path = _validated_artifact(
resolved, artifacts["detector-replay-frames"], DETECTOR_REPLAY_FRAMES_NAME
)
if _file_sha256(frames_path) != identity.get("frames_sha256"):
raise DetectorReplayResultError("detector replay frame digest changed")
receipt = _read_json(receipt_path)
_validate_receipt(receipt, manifest, identity)
frames = tuple(
DetectorReplayFrame.from_dict(value)
for value in _read_jsonl(frames_path, "detector replay frames")
)
metrics = _metrics_from_dict(identity.get("metrics"))
validate_frame_accounting(frames, metrics)
source_failure = identity.get("source_failure_code")
if source_failure is not None and not isinstance(source_failure, str):
raise DetectorReplayResultError("detector replay source failure code changed")
accepted = detector_replay_accepted(metrics, gate, source_failure)
if manifest.get("accepted") is not accepted or identity.get("accepted") is not accepted:
raise DetectorReplayResultError("detector replay acceptance changed")
return DetectorReplayResult(
result_id=resolved.name,
result_root=resolved,
accepted=accepted,
metrics=metrics,
runtime=runtime,
gate=gate,
frames=frames,
manifest=manifest,
receipt=receipt,
)
def validate_frame_accounting(
frames: tuple[DetectorReplayFrame, ...], metrics: DetectorReplayMetrics
) -> None:
if metrics.run_duration_ns <= 0:
raise DetectorReplayResultError("detector replay duration must be positive")
if any(frame.sequence != index for index, frame in enumerate(frames)):
raise DetectorReplayResultError("detector replay frame sequence is incomplete")
if any(
frame.envelope.source_id != BASELINE_SOURCE_ID
or frame.envelope.session_id != BASELINE_SESSION_ID
for frame in frames
):
raise DetectorReplayResultError("detector replay source ownership changed")
completed = sum(frame.outcome == "completed" for frame in frames)
failed = len(frames) - completed
proposals = tuple(proposal for frame in frames for proposal in frame.proposals)
latencies_ms = sorted(frame.duration_ns / 1_000_000 for frame in frames)
expected_end_to_end_fps = round(
completed * 1_000_000_000 / metrics.run_duration_ns, 6
)
expected_provider_fps = (
round(completed * 1_000_000_000 / metrics.provider_core_duration_ns, 6)
if metrics.provider_core_duration_ns > 0
else 0.0
)
if (
metrics.frame_count != len(frames)
or metrics.completed_frame_count != completed
or metrics.failed_frame_count != failed
or metrics.proposal_count != len(proposals)
or metrics.zero_proposal_frame_count
!= sum(frame.outcome == "completed" and not frame.proposals for frame in frames)
or metrics.semantic_hint_count
!= sum(proposal.semantic_hint is not None for proposal in proposals)
or metrics.provider_tracklet_count
!= sum(proposal.provider_tracklet is not None for proposal in proposals)
or metrics.end_to_end_fps != expected_end_to_end_fps
or metrics.provider_core_fps != expected_provider_fps
or metrics.frame_latency_p50_ms != round(_percentile(latencies_ms, 0.5), 6)
or metrics.frame_latency_p95_ms != round(_percentile(latencies_ms, 0.95), 6)
or metrics.frame_latency_max_ms != round(max(latencies_ms, default=0.0), 6)
):
raise DetectorReplayResultError("detector replay metrics and frames disagree")
def _validate_identity(identity: dict[str, object]) -> None:
_exact_keys(
identity,
{
"schema_version",
"baseline_profile_id",
"source",
"detector",
"runtime",
"gate",
"metrics",
"source_failure_code",
"frames_sha256",
"accepted",
"authority",
},
"detector replay identity",
)
source = _object(identity.get("source"), "detector replay source")
detector = _object(identity.get("detector"), "detector replay provider")
if (
identity.get("schema_version") != DETECTOR_REPLAY_RESULT_SCHEMA
or identity.get("baseline_profile_id") != BASELINE_PROFILE_ID
or source
!= {
"provider_id": RECORDED_SOURCE_PROVIDER_ID,
"source_id": BASELINE_SOURCE_ID,
"session_id": BASELINE_SESSION_ID,
"camera_artifact_id": BASELINE_RECORDED_JOB_ID,
"camera_stream_sha256": BASELINE_CAMERA_STREAM_SHA256,
}
or detector
!= {
"provider_id": FROZEN_YOLOX_PROVIDER_ID,
"model_id": FROZEN_YOLOX_MODEL_ID,
"model_version": YOLOX_MODEL_VERSION,
"model_sha256": YOLOX_MODEL_SHA256,
"model_config_sha256": YOLOX_CONFIG_SHA256,
"valid_fov_mask_sha256": YOLOX_VALID_FOV_SHA256,
"preprocess_id": FROZEN_YOLOX_PREPROCESS_ID,
"preprocess_profile_sha256": BASELINE_PREPROCESS_PROFILE_SHA256,
"minimum_score": FROZEN_YOLOX_CONFIG.minimum_score,
"nms_iou_threshold": FROZEN_YOLOX_CONFIG.nms_iou_threshold,
"target_class_ids": list(FROZEN_YOLOX_CONFIG.target_class_ids),
"single_inference_per_frame": True,
"class_routing_used": False,
"provider_tracklets_used": False,
}
or identity.get("authority") != FalseAuthority().to_dict()
or _SHA256.fullmatch(_string(identity.get("frames_sha256"), "frame digest")) is None
):
raise DetectorReplayResultError("detector replay frozen identity changed")
def _validate_receipt(
receipt: dict[str, object],
manifest: dict[str, object],
identity: dict[str, object],
) -> None:
_exact_keys(
receipt,
{
"schema_version",
"result_id",
"identity_sha256",
"created_at_utc",
"accepted",
"source_failure_code",
"gate",
"metrics",
"authority",
},
"detector replay receipt",
)
if (
receipt.get("schema_version") != DETECTOR_REPLAY_RECEIPT_SCHEMA
or receipt.get("result_id") != manifest.get("result_id")
or receipt.get("identity_sha256") != manifest.get("identity_sha256")
or receipt.get("created_at_utc") != manifest.get("created_at_utc")
or receipt.get("accepted") != manifest.get("accepted")
or receipt.get("source_failure_code") != identity.get("source_failure_code")
or receipt.get("gate") != identity.get("gate")
or receipt.get("metrics") != identity.get("metrics")
or receipt.get("authority") != identity.get("authority")
):
raise DetectorReplayResultError("detector replay receipt changed")
def _metrics_from_dict(value: object) -> DetectorReplayMetrics:
document = _object(value, "detector replay metrics")
fields = {
"frame_count",
"completed_frame_count",
"failed_frame_count",
"proposal_count",
"zero_proposal_frame_count",
"semantic_hint_count",
"provider_tracklet_count",
"run_duration_ns",
"provider_core_duration_ns",
"end_to_end_fps",
"provider_core_fps",
"frame_latency_p50_ms",
"frame_latency_p95_ms",
"frame_latency_max_ms",
"rejected",
}
_exact_keys(document, fields, "detector replay metrics")
rejected_value = document.get("rejected")
if not isinstance(rejected_value, list):
raise DetectorReplayResultError("detector rejected accounting is invalid")
rejected: list[tuple[str, int]] = []
for item in rejected_value:
row = _object(item, "detector rejection")
_exact_keys(row, {"reason", "count"}, "detector rejection")
rejected.append(
(
_string(row.get("reason"), "rejection reason"),
_integer(row.get("count"), "rejection count"),
)
)
if rejected != sorted(rejected) or len({reason for reason, _ in rejected}) != len(rejected):
raise DetectorReplayResultError("detector rejection accounting is not canonical")
metrics = DetectorReplayMetrics(
frame_count=_integer(document.get("frame_count"), "frame count"),
completed_frame_count=_integer(
document.get("completed_frame_count"), "completed frame count"
),
failed_frame_count=_integer(document.get("failed_frame_count"), "failed frame count"),
proposal_count=_integer(document.get("proposal_count"), "proposal count"),
zero_proposal_frame_count=_integer(
document.get("zero_proposal_frame_count"), "zero-proposal frame count"
),
semantic_hint_count=_integer(
document.get("semantic_hint_count"), "semantic hint count"
),
provider_tracklet_count=_integer(
document.get("provider_tracklet_count"), "provider tracklet count"
),
run_duration_ns=_integer(document.get("run_duration_ns"), "run duration"),
provider_core_duration_ns=_integer(
document.get("provider_core_duration_ns"), "provider core duration"
),
end_to_end_fps=_number(document.get("end_to_end_fps"), "end-to-end FPS"),
provider_core_fps=_number(document.get("provider_core_fps"), "provider core FPS"),
frame_latency_p50_ms=_number(
document.get("frame_latency_p50_ms"), "frame p50 latency"
),
frame_latency_p95_ms=_number(
document.get("frame_latency_p95_ms"), "frame p95 latency"
),
frame_latency_max_ms=_number(
document.get("frame_latency_max_ms"), "frame maximum latency"
),
rejected=tuple(rejected),
)
if metrics.run_duration_ns <= 0 or any(
value < 0.0
for value in (
metrics.end_to_end_fps,
metrics.provider_core_fps,
metrics.frame_latency_p50_ms,
metrics.frame_latency_p95_ms,
metrics.frame_latency_max_ms,
)
):
raise DetectorReplayResultError("detector replay runtime metrics are invalid")
return metrics
def _validated_artifact(root: Path, value: object, expected_name: str) -> Path:
artifact = _object(value, "detector replay artifact")
_exact_keys(artifact, {"role", "path", "bytes", "sha256"}, "artifact")
if artifact.get("path") != expected_name:
raise DetectorReplayResultError("detector replay artifact path changed")
path = (root / expected_name).resolve(strict=True)
if path.parent != root or path.is_symlink():
raise DetectorReplayResultError("detector replay artifact escapes its result")
if (
artifact.get("bytes") != path.stat().st_size
or artifact.get("sha256") != _file_sha256(path)
):
raise DetectorReplayResultError("detector replay artifact changed")
return path
def _read_json(path: Path) -> dict[str, object]:
try:
value = json.loads(path.read_text("utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise DetectorReplayResultError(f"cannot read detector replay JSON: {path.name}") from exc
return _object(value, path.name)
def _read_jsonl(path: Path, label: str) -> tuple[dict[str, object], ...]:
documents: list[dict[str, object]] = []
try:
lines = path.read_text("utf-8").splitlines()
except OSError as exc:
raise DetectorReplayResultError(f"cannot read {label}") from exc
for line_number, line in enumerate(lines, start=1):
try:
value = json.loads(line)
except json.JSONDecodeError as exc:
raise DetectorReplayResultError(f"{label} line {line_number} is invalid") from exc
documents.append(_object(value, f"{label} line {line_number}"))
return tuple(documents)
def _canonical_json(value: object) -> bytes:
return json.dumps(
value,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode()
def _file_sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
__all__ = ["read_detector_replay_result", "validate_frame_accounting"]