1650 lines
65 KiB
Python
1650 lines
65 KiB
Python
"""Immutable full-source M4.6 replay threat and visual evidence."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
import math
|
|
import os
|
|
import shutil
|
|
import time
|
|
import uuid
|
|
from collections import Counter
|
|
from collections.abc import Iterator
|
|
from dataclasses import dataclass
|
|
from datetime import UTC, datetime
|
|
from pathlib import Path
|
|
from threading import Event
|
|
from typing import Final
|
|
|
|
import numpy as np
|
|
|
|
from .contracts import (
|
|
BoundingRegion2D,
|
|
GridCell,
|
|
HistorySample,
|
|
LocalObstacleMap,
|
|
MotionState,
|
|
ObjectProposal2D,
|
|
SourceAccounting,
|
|
TemporalObstacle,
|
|
TemporalState,
|
|
ThreatAssessment,
|
|
ThreatDecision,
|
|
)
|
|
from .detector_replay_contracts import DetectorReplayResult
|
|
from .detector_replay_result import read_detector_replay_result
|
|
from .geometry import RecordedGeometryStore
|
|
from .geometry_replay import GeometryReplayResult, read_geometry_replay_result
|
|
from .providers import SourcePacket
|
|
from .recorded_source import RecordedRavnoves00Source, ReplayPacing
|
|
from .spatial_evidence import (
|
|
project_metric_obstacles_to_body,
|
|
sample_points_in_body_frame,
|
|
)
|
|
from .temporal_replay import TemporalReplayResult, read_temporal_replay_result
|
|
from .threat import (
|
|
DEFAULT_REPLAY_THREAT_PROFILE_PATH,
|
|
DualEvidenceReplayThreatProvider,
|
|
RecordedReplayBodyFrameResolver,
|
|
ReplayBodyFrame,
|
|
ReplayThreatProfile,
|
|
load_replay_threat_profile,
|
|
)
|
|
|
|
THREAT_REPLAY_SCHEMA: Final = "missioncore.perception-threat-replay-result/v1"
|
|
THREAT_REPLAY_FRAME_SCHEMA: Final = "missioncore.perception-threat-replay-frame/v1"
|
|
THREAT_REPLAY_VISUAL_SCHEMA: Final = "missioncore.perception-threat-visual-frame/v1"
|
|
THREAT_REPLAY_FIXTURE_SCHEMA: Final = "missioncore.perception-threat-fixtures/v1"
|
|
THREAT_REPLAY_REPORT_SCHEMA: Final = "missioncore.perception-threat-replay-report/v1"
|
|
THREAT_REPLAY_SCHEMA_V2: Final = "missioncore.perception-threat-replay-result/v2"
|
|
THREAT_REPLAY_FRAME_SCHEMA_V2: Final = "missioncore.perception-threat-replay-frame/v2"
|
|
THREAT_REPLAY_VISUAL_SCHEMA_V2: Final = "missioncore.perception-threat-visual-frame/v2"
|
|
THREAT_REPLAY_FIXTURE_SCHEMA_V2: Final = "missioncore.perception-threat-fixtures/v2"
|
|
THREAT_REPLAY_REPORT_SCHEMA_V2: Final = "missioncore.perception-threat-replay-report/v2"
|
|
THREAT_REPLAY_RESULT_PREFIX: Final = "m4-threat-replay-"
|
|
THREAT_REPLAY_FRAMES_NAME: Final = "frames.jsonl"
|
|
THREAT_REPLAY_VISUALS_NAME: Final = "visual-frames.jsonl"
|
|
THREAT_REPLAY_FIXTURES_NAME: Final = "fixtures.json"
|
|
THREAT_REPLAY_REPORT_NAME: Final = "report.json"
|
|
THREAT_REPLAY_MANIFEST_NAME: Final = "manifest.json"
|
|
VISUAL_FRAME_COUNT: Final = 32
|
|
VISUAL_POINT_LIMIT: Final = 4_000
|
|
VISUAL_GEOMETRY_REGRESSION_SEQUENCES: Final = (138, 274, 1880, 2584)
|
|
FRAME_1880_ENGINEERING_ANCHORS: Final = (
|
|
{
|
|
"anchor_id": "near-concrete-hemisphere",
|
|
"x_bounds_m": (0.3, 1.2),
|
|
"y_bounds_m": (-0.8, 0.2),
|
|
"z_bounds_m": (-0.1, 0.9),
|
|
"must_assert_threat": True,
|
|
},
|
|
{
|
|
"anchor_id": "far-concrete-hemisphere",
|
|
"x_bounds_m": (1.5, 2.7),
|
|
"y_bounds_m": (0.6, 1.7),
|
|
"z_bounds_m": (-0.1, 0.9),
|
|
"must_assert_threat": False,
|
|
},
|
|
)
|
|
FRAME_2584_ENGINEERING_ANCHORS: Final = (
|
|
{
|
|
"anchor_id": "near-compact-concrete-hemisphere",
|
|
"x_bounds_m": (1.5, 2.4),
|
|
"y_bounds_m": (-0.7, 0.2),
|
|
"z_bounds_m": (-0.1, 0.9),
|
|
"must_assert_threat": True,
|
|
},
|
|
{
|
|
"anchor_id": "far-concrete-hemisphere-occupancy",
|
|
"x_bounds_m": (2.8, 4.0),
|
|
"y_bounds_m": (1.2, 2.4),
|
|
"z_bounds_m": (-0.1, 1.0),
|
|
"must_assert_threat": False,
|
|
},
|
|
)
|
|
|
|
|
|
class ThreatReplayError(RuntimeError):
|
|
"""The M4.6 replay is incomplete, mutable or source-inconsistent."""
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ThreatReplayResult:
|
|
result_id: str
|
|
result_root: Path
|
|
accepted: bool
|
|
metrics: dict[str, object]
|
|
report: dict[str, object]
|
|
manifest: dict[str, object]
|
|
|
|
|
|
def build_threat_replay(
|
|
*,
|
|
repository_root: Path,
|
|
temporal_result_root: Path,
|
|
geometry_result_root: Path,
|
|
detector_result_root: Path,
|
|
output_root: Path,
|
|
) -> ThreatReplayResult:
|
|
repository = repository_root.resolve()
|
|
profile = load_replay_threat_profile(repository / DEFAULT_REPLAY_THREAT_PROFILE_PATH)
|
|
temporal = read_temporal_replay_result(temporal_result_root)
|
|
geometry = read_geometry_replay_result(geometry_result_root)
|
|
detector = read_detector_replay_result(detector_result_root)
|
|
_validate_upstream(profile, temporal, geometry, detector)
|
|
|
|
store = RecordedGeometryStore.from_repository(repository)
|
|
body_frame_resolver = RecordedReplayBodyFrameResolver(
|
|
store,
|
|
profile=profile.body_frame,
|
|
)
|
|
provider = DualEvidenceReplayThreatProvider(
|
|
body_frame_resolver=body_frame_resolver,
|
|
profile=profile,
|
|
)
|
|
source = RecordedRavnoves00Source.from_repository(
|
|
repository,
|
|
pacing=ReplayPacing.UNCAPPED,
|
|
)
|
|
visual_sequences = _visual_sequences(body_frame_resolver.qualified_frame_indices())
|
|
|
|
root = output_root.expanduser().absolute()
|
|
root.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
staging = root / f".threat-replay.{uuid.uuid4().hex}.tmp"
|
|
staging.mkdir(mode=0o700, exist_ok=False)
|
|
started_ns = time.perf_counter_ns()
|
|
frame_count = 0
|
|
failed_frames = 0
|
|
assessment_counts: Counter[str] = Counter()
|
|
evidence_counts: Counter[str] = Counter()
|
|
motion_decisions: Counter[str] = Counter()
|
|
reason_counts: Counter[str] = Counter()
|
|
latencies_ms: list[float] = []
|
|
visual_count = 0
|
|
frame_1880_regression: dict[str, object] | None = None
|
|
frame_2584_regression: dict[str, object] | None = None
|
|
try:
|
|
temporal_frames_path = temporal.result_root / "frames.jsonl"
|
|
geometry_frames_path = geometry.result_root / "frames.jsonl"
|
|
frames_path = staging / THREAT_REPLAY_FRAMES_NAME
|
|
visuals_path = staging / THREAT_REPLAY_VISUALS_NAME
|
|
with (
|
|
temporal_frames_path.open("rb") as temporal_stream,
|
|
geometry_frames_path.open("rb") as geometry_stream,
|
|
frames_path.open("wb") as output,
|
|
visuals_path.open("wb") as visual_output,
|
|
):
|
|
packets = source.packets(Event())
|
|
if len(detector.frames) != 4489:
|
|
raise ThreatReplayError("detector replay frame count changed")
|
|
for detector_frame, packet in zip(detector.frames, packets, strict=True):
|
|
temporal_frame = _read_json_line(
|
|
temporal_stream.readline(), "temporal frame", frame_count
|
|
)
|
|
geometry_frame = _read_json_line(
|
|
geometry_stream.readline(), "geometry frame", frame_count
|
|
)
|
|
_validate_frame_binding(
|
|
frame_count,
|
|
packet.envelope.frame_id,
|
|
detector_frame.sequence,
|
|
detector_frame.envelope.frame_id,
|
|
temporal_frame,
|
|
geometry_frame,
|
|
)
|
|
current = tuple(
|
|
TemporalObstacle.from_dict(value)
|
|
for value in _array(temporal_frame.get("current"), "current obstacles")
|
|
)
|
|
unknown = tuple(
|
|
TemporalObstacle.from_dict(value)
|
|
for key in ("held", "expired")
|
|
for value in _array(temporal_frame.get(key), f"{key} obstacles")
|
|
)
|
|
rolling_retained = tuple(
|
|
TemporalObstacle.from_dict(value)
|
|
for value in _array(
|
|
temporal_frame.get("rolling_retained"),
|
|
"rolling retained obstacles",
|
|
)
|
|
)
|
|
if any(item.state is not TemporalState.RETAINED for item in rolling_retained):
|
|
raise ThreatReplayError("temporal replay rolling map escaped retained state")
|
|
geometry_observations = _array(
|
|
geometry_frame.get("observations"), "geometry observations"
|
|
)
|
|
associated_proposals = {
|
|
proposal_id
|
|
for raw in geometry_observations
|
|
if isinstance(raw, dict) and raw.get("occupied_support") is True
|
|
for proposal_id in _string_array(
|
|
raw.get("proposal_ids"), "geometry proposal ids"
|
|
)
|
|
}
|
|
proposals = detector_frame.proposals
|
|
camera_uncertainty = tuple(
|
|
proposal
|
|
for proposal in proposals
|
|
if proposal.proposal_id not in associated_proposals
|
|
)
|
|
obstacle_map = LocalObstacleMap(
|
|
source_id=packet.envelope.source_id,
|
|
session_id=packet.envelope.session_id,
|
|
frame_id=packet.envelope.frame_id,
|
|
graph_id="reference-perception-graph/v1",
|
|
generated_monotonic_ns=0,
|
|
output_age_ns=0,
|
|
occupied=(*current, *rolling_retained),
|
|
unknown=unknown,
|
|
camera_uncertainty=camera_uncertainty,
|
|
accounting=SourceAccounting(1, 1, 0, 0),
|
|
)
|
|
frame_started_ns = time.perf_counter_ns()
|
|
assessments = provider.assess(obstacle_map)
|
|
latencies_ms.append((time.perf_counter_ns() - frame_started_ns) / 1_000_000)
|
|
by_id = {item.component_id: item for item in assessments}
|
|
expected_ids = {
|
|
item.component_id for item in (*current, *rolling_retained, *unknown)
|
|
} | {item.proposal_id for item in camera_uncertainty}
|
|
if set(by_id) != expected_ids:
|
|
raise ThreatReplayError("threat assessment coverage is incomplete")
|
|
camera_rows = _camera_rows(
|
|
proposals,
|
|
geometry_observations,
|
|
by_id,
|
|
)
|
|
metric_rows = [
|
|
_metric_row(item, by_id[item.component_id])
|
|
for item in (*current, *rolling_retained, *unknown)
|
|
]
|
|
if frame_count == 1880:
|
|
frame_1880_regression = _frame_1880_regression(
|
|
metric_rows,
|
|
body_frame_resolver.body_frame_for_frame(packet.envelope.frame_id),
|
|
)
|
|
if frame_count == 2584:
|
|
frame_2584_regression = _frame_2584_regression(
|
|
metric_rows,
|
|
body_frame_resolver.body_frame_for_frame(packet.envelope.frame_id),
|
|
voxel_size_m=profile.corridor.occupied_voxel_size_m,
|
|
)
|
|
for item in assessments:
|
|
assessment_counts[item.decision.value] += 1
|
|
reason_counts.update(item.reason_codes)
|
|
evidence_counts["current-metric"] += len(current)
|
|
evidence_counts["rolling-map-retained"] += len(rolling_retained)
|
|
evidence_counts["stale-or-held"] += len(unknown)
|
|
evidence_counts["camera-only"] += len(camera_uncertainty)
|
|
for obstacle in current:
|
|
motion_decisions[
|
|
f"{obstacle.motion.value}:{by_id[obstacle.component_id].decision.value}"
|
|
] += 1
|
|
frame_document = {
|
|
"schema_version": THREAT_REPLAY_FRAME_SCHEMA_V2,
|
|
"sequence": frame_count,
|
|
"frame_id": packet.envelope.frame_id,
|
|
"source_time_ns": packet.envelope.timestamps.source_ns,
|
|
"source_available": (packet.envelope.registered_point_increment.available),
|
|
"body_frame_available": body_frame_resolver.body_frame_for_frame(
|
|
packet.envelope.frame_id
|
|
)
|
|
is not None,
|
|
"metric_obstacles": metric_rows,
|
|
"camera_proposals": camera_rows,
|
|
"assessments": [item.to_dict() for item in assessments],
|
|
"accounting": {
|
|
"metric_obstacles": len(metric_rows),
|
|
"camera_proposals": len(proposals),
|
|
"camera_only": len(camera_uncertainty),
|
|
"current_increment_metric": len(current),
|
|
"rolling_map_retained": len(rolling_retained),
|
|
"assessments": len(assessments),
|
|
},
|
|
"authority": _false_authority(),
|
|
}
|
|
output.write(_canonical_json(frame_document) + b"\n")
|
|
if frame_count in visual_sequences:
|
|
visual_output.write(
|
|
_canonical_json(
|
|
_visual_frame(
|
|
packet=packet,
|
|
store=store,
|
|
body_frame=body_frame_resolver.body_frame_for_frame(
|
|
packet.envelope.frame_id
|
|
),
|
|
metric_rows=metric_rows,
|
|
camera_rows=camera_rows,
|
|
profile=profile,
|
|
)
|
|
)
|
|
+ b"\n"
|
|
)
|
|
visual_count += 1
|
|
frame_count += 1
|
|
if temporal_stream.readline() or geometry_stream.readline():
|
|
raise ThreatReplayError("upstream frame ledger exceeds recorded source")
|
|
if frame_count != 4489 or visual_count != VISUAL_FRAME_COUNT:
|
|
raise ThreatReplayError("full replay or visual sample coverage is incomplete")
|
|
|
|
fixtures = _fixture_document(profile)
|
|
fixtures_path = staging / THREAT_REPLAY_FIXTURES_NAME
|
|
_write_json(fixtures_path, fixtures)
|
|
elapsed_ns = time.perf_counter_ns() - started_ns
|
|
metrics = _metrics(
|
|
frame_count=frame_count,
|
|
failed_frames=failed_frames,
|
|
assessment_counts=assessment_counts,
|
|
evidence_counts=evidence_counts,
|
|
motion_decisions=motion_decisions,
|
|
reason_counts=reason_counts,
|
|
latencies_ms=latencies_ms,
|
|
elapsed_ns=elapsed_ns,
|
|
visual_count=visual_count,
|
|
fixtures=fixtures,
|
|
body_frame=body_frame_resolver.qualification_summary(),
|
|
frame_1880_regression=frame_1880_regression,
|
|
frame_2584_regression=frame_2584_regression,
|
|
)
|
|
requirements = _requirements_v2(metrics, fixtures)
|
|
accepted = all(value is True for value in requirements.values())
|
|
frames_sha256 = _file_sha256(frames_path)
|
|
visuals_sha256 = _file_sha256(visuals_path)
|
|
fixtures_sha256 = _file_sha256(fixtures_path)
|
|
identity = {
|
|
"schema_version": THREAT_REPLAY_SCHEMA_V2,
|
|
"profile_id": profile.profile_id,
|
|
"profile_sha256": profile.profile_sha256,
|
|
"provider_id": provider.provider_id,
|
|
"source_id": profile.source_id,
|
|
"source_session_id": profile.session_id,
|
|
"temporal_result_id": temporal.result_id,
|
|
"temporal_frames_sha256": profile.temporal_frames_sha256,
|
|
"geometry_result_id": geometry.result_id,
|
|
"geometry_frames_sha256": profile.geometry_frames_sha256,
|
|
"detector_result_id": detector.result_id,
|
|
"detector_frames_sha256": profile.detector_frames_sha256,
|
|
"source_pack_id": profile.source_pack_id,
|
|
"source_pack_sha256": profile.source_pack_sha256,
|
|
"calibration_id": profile.calibration_id,
|
|
"calibration_content_sha256": profile.calibration_content_sha256,
|
|
"body_frame": {
|
|
"schema_version": profile.body_frame.schema_version,
|
|
"origin": profile.body_frame.origin,
|
|
"up": profile.body_frame.up,
|
|
"forward": profile.body_frame.forward,
|
|
},
|
|
"rig_profile_id": profile.rig.profile_id,
|
|
"corridor_profile_id": profile.corridor.profile_id,
|
|
"producer_sha256": _producer_hashes(repository),
|
|
"frames_sha256": frames_sha256,
|
|
"visuals_sha256": visuals_sha256,
|
|
"fixtures_sha256": fixtures_sha256,
|
|
"metrics": metrics,
|
|
"acceptance_requirements": requirements,
|
|
"accepted": accepted,
|
|
"authority": _false_authority(),
|
|
}
|
|
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
|
result_id = f"{THREAT_REPLAY_RESULT_PREFIX}{identity_sha256}"
|
|
report = {
|
|
"schema_version": THREAT_REPLAY_REPORT_SCHEMA_V2,
|
|
"result_id": result_id,
|
|
"identity_sha256": identity_sha256,
|
|
"status": "accepted" if accepted else "rejected",
|
|
"metrics": metrics,
|
|
"acceptance_requirements": requirements,
|
|
"configuration": {
|
|
"virtual_body_m": [
|
|
profile.rig.body_length_m,
|
|
profile.rig.body_width_m,
|
|
],
|
|
"nominal_sensor_height_m": profile.rig.nominal_sensor_height_m,
|
|
"body_frame": {
|
|
"origin": profile.body_frame.origin,
|
|
"up": profile.body_frame.up,
|
|
"forward": profile.body_frame.forward,
|
|
},
|
|
"forward_corridor_m": profile.corridor.forward_length_m,
|
|
"prediction_horizon_seconds": (profile.corridor.prediction_horizon_seconds),
|
|
},
|
|
"limitations": [
|
|
"The body and corridor are replay-simulated, not a measured physical mount.",
|
|
(
|
|
"The replay base_footprint uses SLAM trajectory and map gravity; "
|
|
"a mounted vehicle replaces it with calibrated T_body_from_sensor."
|
|
),
|
|
"The LiDAR archive is the vendor mapped point increment, not every raw beam.",
|
|
"TTC uses bounded constant-relative-velocity replay extrapolation.",
|
|
"Camera-only evidence remains unknown and cannot establish metric clearance.",
|
|
"M4.8 independent object-centric labels remain the correctness gate.",
|
|
],
|
|
"authority": _false_authority(),
|
|
}
|
|
report_path = staging / THREAT_REPLAY_REPORT_NAME
|
|
_write_json(report_path, report)
|
|
manifest = {
|
|
"schema_version": THREAT_REPLAY_SCHEMA_V2,
|
|
"result_id": result_id,
|
|
"identity_sha256": identity_sha256,
|
|
"identity": identity,
|
|
"created_at_utc": datetime.now(UTC)
|
|
.isoformat(timespec="milliseconds")
|
|
.replace("+00:00", "Z"),
|
|
"accepted": accepted,
|
|
"artifacts": [
|
|
_artifact(frames_path, "threat-replay-frames"),
|
|
_artifact(visuals_path, "threat-visual-frames"),
|
|
_artifact(fixtures_path, "threat-deterministic-fixtures"),
|
|
_artifact(report_path, "threat-replay-report"),
|
|
],
|
|
}
|
|
_write_json(staging / THREAT_REPLAY_MANIFEST_NAME, manifest)
|
|
destination = root / result_id
|
|
if destination.exists():
|
|
shutil.rmtree(staging)
|
|
return read_threat_replay_result(destination)
|
|
os.replace(staging, destination)
|
|
return read_threat_replay_result(destination)
|
|
except BaseException:
|
|
shutil.rmtree(staging, ignore_errors=True)
|
|
raise
|
|
|
|
|
|
def read_threat_replay_result(root: Path) -> ThreatReplayResult:
|
|
resolved = root.resolve(strict=True)
|
|
if resolved.is_symlink() or not resolved.name.startswith(THREAT_REPLAY_RESULT_PREFIX):
|
|
raise ThreatReplayError("threat replay result root is invalid")
|
|
manifest = _read_json(resolved / THREAT_REPLAY_MANIFEST_NAME)
|
|
_exact_keys(
|
|
manifest,
|
|
{
|
|
"schema_version",
|
|
"result_id",
|
|
"identity_sha256",
|
|
"identity",
|
|
"created_at_utc",
|
|
"accepted",
|
|
"artifacts",
|
|
},
|
|
"threat replay manifest",
|
|
)
|
|
schema_version = manifest.get("schema_version")
|
|
if schema_version not in {THREAT_REPLAY_SCHEMA, THREAT_REPLAY_SCHEMA_V2}:
|
|
raise ThreatReplayError("threat replay schema is incompatible")
|
|
is_v2 = schema_version == THREAT_REPLAY_SCHEMA_V2
|
|
identity = _object(manifest.get("identity"), "threat replay identity")
|
|
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
|
if (
|
|
manifest.get("result_id") != resolved.name
|
|
or manifest.get("identity_sha256") != identity_sha256
|
|
or resolved.name != f"{THREAT_REPLAY_RESULT_PREFIX}{identity_sha256}"
|
|
):
|
|
raise ThreatReplayError("threat replay identity changed")
|
|
artifacts = _array(manifest.get("artifacts"), "threat artifacts")
|
|
by_role = {_object(item, "threat artifact").get("role"): item for item in artifacts}
|
|
expected = {
|
|
"threat-replay-frames": (THREAT_REPLAY_FRAMES_NAME, "frames_sha256"),
|
|
"threat-visual-frames": (THREAT_REPLAY_VISUALS_NAME, "visuals_sha256"),
|
|
"threat-deterministic-fixtures": (
|
|
THREAT_REPLAY_FIXTURES_NAME,
|
|
"fixtures_sha256",
|
|
),
|
|
"threat-replay-report": (THREAT_REPLAY_REPORT_NAME, None),
|
|
}
|
|
if set(by_role) != set(expected):
|
|
raise ThreatReplayError("threat artifact inventory changed")
|
|
paths: dict[str, Path] = {}
|
|
for role, (name, identity_key) in expected.items():
|
|
path = _validated_artifact(resolved, by_role[role], name)
|
|
paths[role] = path
|
|
if identity_key is not None and _file_sha256(path) != identity.get(identity_key):
|
|
raise ThreatReplayError("threat artifact identity changed")
|
|
report = _read_json(paths["threat-replay-report"])
|
|
metrics = _object(identity.get("metrics"), "threat metrics")
|
|
requirements = _object(identity.get("acceptance_requirements"), "threat requirements")
|
|
fixtures = _read_json(paths["threat-deterministic-fixtures"])
|
|
accepted = all(value is True for value in requirements.values())
|
|
expected_requirements = (
|
|
_requirements_v2(metrics, fixtures) if is_v2 else _requirements_v1(metrics, fixtures)
|
|
)
|
|
if (
|
|
report.get("schema_version")
|
|
!= (THREAT_REPLAY_REPORT_SCHEMA_V2 if is_v2 else THREAT_REPLAY_REPORT_SCHEMA)
|
|
or report.get("result_id") != resolved.name
|
|
or report.get("identity_sha256") != identity_sha256
|
|
or report.get("metrics") != metrics
|
|
or report.get("acceptance_requirements") != requirements
|
|
or report.get("authority") != _false_authority()
|
|
or identity.get("authority") != _false_authority()
|
|
or manifest.get("accepted") is not accepted
|
|
or identity.get("accepted") is not accepted
|
|
or requirements != expected_requirements
|
|
):
|
|
raise ThreatReplayError("threat replay report or acceptance changed")
|
|
_validate_ledgers(
|
|
paths["threat-replay-frames"],
|
|
paths["threat-visual-frames"],
|
|
metrics,
|
|
is_v2=is_v2,
|
|
)
|
|
return ThreatReplayResult(
|
|
result_id=resolved.name,
|
|
result_root=resolved,
|
|
accepted=accepted,
|
|
metrics=metrics,
|
|
report=report,
|
|
manifest=manifest,
|
|
)
|
|
|
|
|
|
def _validate_upstream(
|
|
profile: ReplayThreatProfile,
|
|
temporal: TemporalReplayResult,
|
|
geometry: GeometryReplayResult,
|
|
detector: DetectorReplayResult,
|
|
) -> None:
|
|
if not temporal.accepted or not geometry.accepted or not detector.accepted:
|
|
raise ThreatReplayError("an upstream M4 result is not accepted")
|
|
detector_identity = _object(detector.manifest.get("identity"), "detector identity")
|
|
geometry_identity = _object(geometry.manifest.get("identity"), "geometry identity")
|
|
temporal_identity = _object(temporal.manifest.get("identity"), "temporal identity")
|
|
if (
|
|
temporal.result_id != profile.temporal_result_id
|
|
or temporal_identity.get("frames_sha256") != profile.temporal_frames_sha256
|
|
or geometry.result_id != profile.geometry_result_id
|
|
or geometry_identity.get("frames_sha256") != profile.geometry_frames_sha256
|
|
or detector.result_id != profile.detector_result_id
|
|
or detector_identity.get("frames_sha256") != profile.detector_frames_sha256
|
|
):
|
|
raise ThreatReplayError("upstream M4 evidence escaped the threat profile")
|
|
|
|
|
|
def _validate_frame_binding(
|
|
sequence: int,
|
|
frame_id: str,
|
|
detector_sequence: int,
|
|
detector_frame_id: str,
|
|
temporal_frame: dict[str, object],
|
|
geometry_frame: dict[str, object],
|
|
) -> None:
|
|
if (
|
|
detector_sequence != sequence
|
|
or detector_frame_id != frame_id
|
|
or temporal_frame.get("sequence") != sequence
|
|
or temporal_frame.get("frame_id") != frame_id
|
|
or geometry_frame.get("sequence") != sequence
|
|
or geometry_frame.get("frame_id") != frame_id
|
|
):
|
|
raise ThreatReplayError("M4 frame ledgers are not source-aligned")
|
|
|
|
|
|
def _metric_row(
|
|
obstacle: TemporalObstacle,
|
|
assessment: ThreatAssessment,
|
|
) -> dict[str, object]:
|
|
return {
|
|
"component_id": obstacle.component_id,
|
|
"state": obstacle.state.value,
|
|
"motion": obstacle.motion.value,
|
|
"motion_reason": obstacle.motion_reason,
|
|
"semantic_hint": obstacle.semantic_hint,
|
|
"centroid_map_xyz_m": (
|
|
None if obstacle.last_centroid_xyz_m is None else list(obstacle.last_centroid_xyz_m)
|
|
),
|
|
"cells": [item.to_dict() for item in obstacle.cells],
|
|
"history": [item.to_dict() for item in obstacle.history],
|
|
"assessment": assessment.to_dict(),
|
|
}
|
|
|
|
|
|
def _camera_rows(
|
|
proposals: tuple[ObjectProposal2D, ...],
|
|
observations: list[object],
|
|
assessments: dict[str, ThreatAssessment],
|
|
) -> list[dict[str, object]]:
|
|
support: dict[str, dict[str, object]] = {}
|
|
for raw in observations:
|
|
if not isinstance(raw, dict):
|
|
raise ThreatReplayError("geometry observation is not an object")
|
|
for proposal_id in _string_array(raw.get("proposal_ids"), "proposal ids"):
|
|
metric = raw.get("metric_geometry")
|
|
range_m = metric.get("range_m") if isinstance(metric, dict) else None
|
|
support[proposal_id] = {
|
|
"occupied_support": raw.get("occupied_support") is True,
|
|
"range_m": range_m,
|
|
"reason_codes": _string_array(raw.get("reason_codes"), "reason codes"),
|
|
}
|
|
rows = []
|
|
for proposal in proposals:
|
|
geometry = support.get(
|
|
proposal.proposal_id,
|
|
{
|
|
"occupied_support": False,
|
|
"range_m": None,
|
|
"reason_codes": ["proposal-without-metric-observation"],
|
|
},
|
|
)
|
|
assessment = assessments.get(proposal.proposal_id)
|
|
rows.append(
|
|
{
|
|
"proposal_id": proposal.proposal_id,
|
|
"bbox_xyxy": list(proposal.region.as_tuple()),
|
|
"objectness": proposal.objectness,
|
|
"semantic_hint": proposal.semantic_hint,
|
|
"occupied_support": geometry["occupied_support"],
|
|
"range_m": geometry["range_m"],
|
|
"geometry_reason_codes": geometry["reason_codes"],
|
|
"threat_decision": (None if assessment is None else assessment.decision.value),
|
|
"threat_reason_codes": (
|
|
[] if assessment is None else list(assessment.reason_codes)
|
|
),
|
|
}
|
|
)
|
|
return rows
|
|
|
|
|
|
def _visual_frame(
|
|
*,
|
|
packet: SourcePacket,
|
|
store: RecordedGeometryStore,
|
|
body_frame: ReplayBodyFrame | None,
|
|
metric_rows: list[dict[str, object]],
|
|
camera_rows: list[dict[str, object]],
|
|
profile: ReplayThreatProfile,
|
|
) -> dict[str, object]:
|
|
if body_frame is None:
|
|
raise ThreatReplayError("visual frame has no qualified body frame")
|
|
points = store.current_points(packet)
|
|
if points is None:
|
|
raise ThreatReplayError("visual frame has no current point cloud")
|
|
sampled, source_count = sample_points_in_body_frame(
|
|
points,
|
|
body_frame,
|
|
point_limit=VISUAL_POINT_LIMIT,
|
|
)
|
|
metric_visuals = project_metric_obstacles_to_body(
|
|
metric_rows,
|
|
body_frame,
|
|
occupied_voxel_size_m=profile.corridor.occupied_voxel_size_m,
|
|
)
|
|
return {
|
|
"schema_version": THREAT_REPLAY_VISUAL_SCHEMA_V2,
|
|
"sequence": packet.envelope.sequence,
|
|
"frame_id": packet.envelope.frame_id,
|
|
"source_time_ns": packet.envelope.timestamps.source_ns,
|
|
"point_cloud_body_xyz_m": sampled,
|
|
"point_cloud_source_count": source_count,
|
|
"point_cloud_sample_count": len(sampled),
|
|
"point_cloud_layer": "current-increment",
|
|
"rolling_map_component_count": sum(
|
|
row.get("state") == TemporalState.RETAINED.value for row in metric_rows
|
|
),
|
|
"metric_obstacles": metric_visuals,
|
|
"camera_proposals": camera_rows,
|
|
"body_frame": {
|
|
"origin_map_xyz_m": list(body_frame.origin_map_xyz_m),
|
|
"basis_map_from_body": [list(row) for row in body_frame.basis_map_from_body],
|
|
"sensor_height_m": body_frame.sensor_height_m,
|
|
"surface_slope_deg": body_frame.surface_slope_deg,
|
|
"forward_source": body_frame.forward_source,
|
|
"camera_forward_alignment_deg": body_frame.camera_forward_alignment_deg,
|
|
},
|
|
"rig": {
|
|
"length_m": profile.rig.body_length_m,
|
|
"width_m": profile.rig.body_width_m,
|
|
"nominal_sensor_height_m": profile.rig.nominal_sensor_height_m,
|
|
},
|
|
"corridor": {
|
|
"forward_length_m": profile.corridor.forward_length_m,
|
|
"rear_margin_m": profile.corridor.rear_margin_m,
|
|
"half_width_m": (profile.rig.body_width_m / 2 + profile.corridor.lateral_clearance_m),
|
|
"prediction_horizon_seconds": (profile.corridor.prediction_horizon_seconds),
|
|
},
|
|
"authority": _false_authority(),
|
|
}
|
|
|
|
|
|
def _frame_1880_regression(
|
|
metric_rows: list[dict[str, object]],
|
|
body_frame: ReplayBodyFrame | None,
|
|
) -> dict[str, object]:
|
|
if body_frame is None:
|
|
raise ThreatReplayError("frame 1880 has no qualified body frame")
|
|
retained_threats = 0
|
|
retained_components = 0
|
|
retained_rows: list[tuple[dict[str, object], tuple[float, float, float]]] = []
|
|
for row in metric_rows:
|
|
if row.get("state") != TemporalState.RETAINED.value:
|
|
continue
|
|
retained_components += 1
|
|
assessment = _object(row.get("assessment"), "frame 1880 assessment")
|
|
if assessment.get("decision") == ThreatDecision.THREAT.value:
|
|
retained_threats += 1
|
|
centroid_map = _array(
|
|
row.get("centroid_map_xyz_m"),
|
|
"frame 1880 retained centroid",
|
|
)
|
|
if len(centroid_map) != 3:
|
|
raise ThreatReplayError("frame 1880 retained centroid is invalid")
|
|
centroid_values = tuple(
|
|
_number_value(value, "frame 1880 centroid") for value in centroid_map
|
|
)
|
|
centroid_body = body_frame.map_point_to_body(
|
|
(centroid_values[0], centroid_values[1], centroid_values[2])
|
|
)
|
|
retained_rows.append((row, centroid_body))
|
|
|
|
anchors: list[dict[str, object]] = []
|
|
matched_ids: set[str] = set()
|
|
for raw_anchor in FRAME_1880_ENGINEERING_ANCHORS:
|
|
anchor = _object(raw_anchor, "frame 1880 engineering anchor")
|
|
x_bounds = _bounds(anchor.get("x_bounds_m"), "frame 1880 x bounds")
|
|
y_bounds = _bounds(anchor.get("y_bounds_m"), "frame 1880 y bounds")
|
|
z_bounds = _bounds(anchor.get("z_bounds_m"), "frame 1880 z bounds")
|
|
match = next(
|
|
(
|
|
(row, centroid)
|
|
for row, centroid in retained_rows
|
|
if row.get("component_id") not in matched_ids
|
|
and x_bounds[0] <= centroid[0] <= x_bounds[1]
|
|
and y_bounds[0] <= centroid[1] <= y_bounds[1]
|
|
and z_bounds[0] <= centroid[2] <= z_bounds[1]
|
|
),
|
|
None,
|
|
)
|
|
component_id = None if match is None else str(match[0]["component_id"])
|
|
if component_id is not None:
|
|
matched_ids.add(component_id)
|
|
anchor_assessment = (
|
|
None
|
|
if match is None
|
|
else _object(match[0].get("assessment"), "frame 1880 anchor assessment")
|
|
)
|
|
anchors.append(
|
|
{
|
|
"anchor_id": anchor["anchor_id"],
|
|
"bounds_body_xyz_m": [list(x_bounds), list(y_bounds), list(z_bounds)],
|
|
"must_assert_threat": anchor["must_assert_threat"],
|
|
"matched": match is not None,
|
|
"component_id": component_id,
|
|
"centroid_body_xyz_m": (None if match is None else list(match[1])),
|
|
"decision": (
|
|
None if anchor_assessment is None else anchor_assessment.get("decision")
|
|
),
|
|
}
|
|
)
|
|
required_threats_passed = all(
|
|
item["matched"] is True
|
|
and (item["must_assert_threat"] is False or item["decision"] == ThreatDecision.THREAT.value)
|
|
for item in anchors
|
|
)
|
|
return {
|
|
"sequence": 1880,
|
|
"retained_components": retained_components,
|
|
"retained_threat_components": retained_threats,
|
|
"engineering_anchors": anchors,
|
|
"matched_anchor_count": sum(item["matched"] is True for item in anchors),
|
|
"required_threats_passed": required_threats_passed,
|
|
"camera_visible_hemispheres_independent_truth": False,
|
|
"gate": "two-visible-hemisphere-regression",
|
|
}
|
|
|
|
|
|
def _frame_2584_regression(
|
|
metric_rows: list[dict[str, object]],
|
|
body_frame: ReplayBodyFrame | None,
|
|
*,
|
|
voxel_size_m: float,
|
|
) -> dict[str, object]:
|
|
"""Bind both visible hemispheres to produced occupancy without injecting it."""
|
|
|
|
if body_frame is None:
|
|
raise ThreatReplayError("frame 2584 has no qualified body frame")
|
|
candidates: list[
|
|
tuple[dict[str, object], tuple[float, float, float], tuple[float, float, float]]
|
|
] = []
|
|
for row in metric_rows:
|
|
assessment = _object(row.get("assessment"), "frame 2584 assessment")
|
|
centroid_map = _array(row.get("centroid_map_xyz_m"), "frame 2584 centroid")
|
|
if len(centroid_map) != 3:
|
|
raise ThreatReplayError("frame 2584 centroid is invalid")
|
|
centroid_body = body_frame.map_point_to_body(
|
|
tuple(_number_value(value, "frame 2584 centroid") for value in centroid_map)
|
|
)
|
|
for raw_cell in _array(row.get("cells"), "frame 2584 cells"):
|
|
cell = _object(raw_cell, "frame 2584 cell")
|
|
point_map = tuple(
|
|
(_signed_integer(cell.get(key), f"frame 2584 cell {key}") + 0.5) * voxel_size_m
|
|
for key in ("x", "y", "z")
|
|
)
|
|
candidates.append(
|
|
(
|
|
row,
|
|
centroid_body,
|
|
body_frame.map_point_to_body(point_map),
|
|
)
|
|
)
|
|
if not row.get("cells"):
|
|
raise ThreatReplayError("frame 2584 metric component has no occupied cells")
|
|
if assessment.get("component_id") != row.get("component_id"):
|
|
raise ThreatReplayError("frame 2584 assessment identity changed")
|
|
|
|
anchors: list[dict[str, object]] = []
|
|
matched_ids: set[str] = set()
|
|
for raw_anchor in FRAME_2584_ENGINEERING_ANCHORS:
|
|
anchor = _object(raw_anchor, "frame 2584 engineering anchor")
|
|
x_bounds = _bounds(anchor.get("x_bounds_m"), "frame 2584 x bounds")
|
|
y_bounds = _bounds(anchor.get("y_bounds_m"), "frame 2584 y bounds")
|
|
z_bounds = _bounds(anchor.get("z_bounds_m"), "frame 2584 z bounds")
|
|
match = next(
|
|
(
|
|
(row, centroid, cell)
|
|
for row, centroid, cell in candidates
|
|
if row.get("component_id") not in matched_ids
|
|
and x_bounds[0] <= cell[0] <= x_bounds[1]
|
|
and y_bounds[0] <= cell[1] <= y_bounds[1]
|
|
and z_bounds[0] <= cell[2] <= z_bounds[1]
|
|
),
|
|
None,
|
|
)
|
|
component_id = None if match is None else str(match[0]["component_id"])
|
|
if component_id is not None:
|
|
matched_ids.add(component_id)
|
|
assessment = (
|
|
None
|
|
if match is None
|
|
else _object(match[0].get("assessment"), "frame 2584 anchor assessment")
|
|
)
|
|
anchors.append(
|
|
{
|
|
"anchor_id": anchor["anchor_id"],
|
|
"bounds_body_xyz_m": [list(x_bounds), list(y_bounds), list(z_bounds)],
|
|
"must_assert_threat": anchor["must_assert_threat"],
|
|
"matched": match is not None,
|
|
"component_id": component_id,
|
|
"component_state": None if match is None else match[0].get("state"),
|
|
"centroid_body_xyz_m": None if match is None else list(match[1]),
|
|
"matched_cell_body_xyz_m": None if match is None else list(match[2]),
|
|
"decision": None if assessment is None else assessment.get("decision"),
|
|
}
|
|
)
|
|
required_threats_passed = all(
|
|
item["matched"] is True
|
|
and (item["must_assert_threat"] is False or item["decision"] == ThreatDecision.THREAT.value)
|
|
for item in anchors
|
|
)
|
|
return {
|
|
"sequence": 2584,
|
|
"engineering_anchors": anchors,
|
|
"matched_anchor_count": sum(item["matched"] is True for item in anchors),
|
|
"required_threats_passed": required_threats_passed,
|
|
"camera_visible_hemispheres_independent_truth": False,
|
|
"matching_basis": "produced-occupied-cell-inside-camera-reviewed-body-window",
|
|
"gate": "compact-and-merged-hemisphere-occupancy-regression",
|
|
}
|
|
|
|
|
|
class _FixtureBodyFrames:
|
|
def body_frame_for_frame(self, frame_id: str) -> ReplayBodyFrame:
|
|
return ReplayBodyFrame(
|
|
frame_id=frame_id,
|
|
origin_map_xyz_m=(0.0, 0.0, 0.0),
|
|
basis_map_from_body=((1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)),
|
|
sensor_height_m=1.25,
|
|
surface_slope_deg=0.0,
|
|
forward_source="fixture",
|
|
camera_forward_alignment_deg=0.0,
|
|
)
|
|
|
|
|
|
def _fixture_document(profile: ReplayThreatProfile) -> dict[str, object]:
|
|
provider = DualEvidenceReplayThreatProvider(
|
|
body_frame_resolver=_FixtureBodyFrames(),
|
|
profile=profile,
|
|
)
|
|
frame_id = "frame-000002"
|
|
cases = [
|
|
_fixture_case(
|
|
provider,
|
|
"static-in-corridor",
|
|
_fixture_obstacle(
|
|
"fixture-static-in",
|
|
GridCell(6, 0, 0),
|
|
MotionState.STATIONARY,
|
|
(
|
|
("frame-000000", 0, (2.925, 0.225, 0.225)),
|
|
(frame_id, 300_000_000, (2.925, 0.225, 0.225)),
|
|
),
|
|
),
|
|
ThreatDecision.THREAT,
|
|
critical=True,
|
|
),
|
|
_fixture_case(
|
|
provider,
|
|
"static-outside",
|
|
_fixture_obstacle(
|
|
"fixture-static-out",
|
|
GridCell(6, 7, 0),
|
|
MotionState.STATIONARY,
|
|
(
|
|
("frame-000000", 0, (2.925, 3.375, 0.225)),
|
|
(frame_id, 300_000_000, (2.925, 3.375, 0.225)),
|
|
),
|
|
),
|
|
ThreatDecision.NOT_THREAT,
|
|
),
|
|
_fixture_case(
|
|
provider,
|
|
"crossing",
|
|
_fixture_obstacle(
|
|
"fixture-crossing",
|
|
GridCell(6, 3, 0),
|
|
MotionState.MOVING,
|
|
(
|
|
("frame-000000", 0, (2.925, 2.575, 0.225)),
|
|
(frame_id, 300_000_000, (2.925, 1.575, 0.225)),
|
|
),
|
|
),
|
|
ThreatDecision.THREAT,
|
|
critical=True,
|
|
),
|
|
_fixture_case(
|
|
provider,
|
|
"approaching",
|
|
_fixture_obstacle(
|
|
"fixture-approaching",
|
|
GridCell(9, 0, 0),
|
|
MotionState.MOVING,
|
|
(
|
|
("frame-000000", 0, (6.275, 0.225, 0.225)),
|
|
(frame_id, 300_000_000, (4.275, 0.225, 0.225)),
|
|
),
|
|
),
|
|
ThreatDecision.THREAT,
|
|
critical=True,
|
|
),
|
|
_fixture_case(
|
|
provider,
|
|
"receding",
|
|
_fixture_obstacle(
|
|
"fixture-receding",
|
|
GridCell(-5, 0, 0),
|
|
MotionState.MOVING,
|
|
(
|
|
("frame-000000", 0, (-1.025, 0.225, 0.225)),
|
|
(frame_id, 300_000_000, (-2.025, 0.225, 0.225)),
|
|
),
|
|
),
|
|
ThreatDecision.NOT_THREAT,
|
|
),
|
|
_fixture_case(
|
|
provider,
|
|
"occluded-held",
|
|
_fixture_obstacle(
|
|
"fixture-held",
|
|
GridCell(6, 0, 0),
|
|
MotionState.UNKNOWN,
|
|
((frame_id, 300_000_000, (2.925, 0.225, 0.225)),),
|
|
state=TemporalState.HELD,
|
|
),
|
|
ThreatDecision.UNKNOWN,
|
|
),
|
|
_fixture_case(
|
|
provider,
|
|
"stale-expired",
|
|
_fixture_obstacle(
|
|
"fixture-expired",
|
|
GridCell(6, 0, 0),
|
|
MotionState.UNKNOWN,
|
|
((frame_id, 300_000_000, (2.925, 0.225, 0.225)),),
|
|
state=TemporalState.EXPIRED,
|
|
),
|
|
ThreatDecision.UNKNOWN,
|
|
),
|
|
_fixture_case(
|
|
provider,
|
|
"retained-in-corridor",
|
|
_fixture_obstacle(
|
|
"fixture-retained-in",
|
|
GridCell(6, 0, 0),
|
|
MotionState.UNKNOWN,
|
|
((frame_id, 200_000_000, (2.925, 0.225, 0.225)),),
|
|
state=TemporalState.RETAINED,
|
|
),
|
|
ThreatDecision.THREAT,
|
|
critical=True,
|
|
),
|
|
_fixture_camera_case(provider, frame_id),
|
|
_fixture_case(
|
|
provider,
|
|
"geometry-only",
|
|
_fixture_obstacle(
|
|
"fixture-geometry-only",
|
|
GridCell(4, 0, 0),
|
|
MotionState.STATIONARY,
|
|
(
|
|
("frame-000000", 0, (2.025, 0.225, 0.225)),
|
|
(frame_id, 300_000_000, (2.025, 0.225, 0.225)),
|
|
),
|
|
),
|
|
ThreatDecision.THREAT,
|
|
critical=True,
|
|
),
|
|
]
|
|
return {
|
|
"schema_version": THREAT_REPLAY_FIXTURE_SCHEMA_V2,
|
|
"cases": cases,
|
|
"critical_case_count": sum(item["critical"] is True for item in cases),
|
|
"critical_false_not_threat_count": sum(
|
|
item["critical"] is True and item["actual"] == "not-threat" for item in cases
|
|
),
|
|
"passed_count": sum(item["passed"] is True for item in cases),
|
|
"total_count": len(cases),
|
|
"authority": _false_authority(),
|
|
}
|
|
|
|
|
|
def _fixture_obstacle(
|
|
component_id: str,
|
|
cell: GridCell,
|
|
motion: MotionState,
|
|
history: tuple[tuple[str, int, tuple[float, float, float]], ...],
|
|
*,
|
|
state: TemporalState = TemporalState.CURRENT,
|
|
) -> TemporalObstacle:
|
|
samples = tuple(HistorySample(*item) for item in history)
|
|
last = samples[-1]
|
|
return TemporalObstacle(
|
|
component_id=component_id,
|
|
identity_scope="ephemeral",
|
|
state=state,
|
|
ttl_ns=(3_000_000_000 if state is TemporalState.RETAINED else 750_000_000),
|
|
last_hit_ns=last.evidence_time_ns,
|
|
age_ns=0 if state is TemporalState.CURRENT else 100_000_000,
|
|
association_basis="deterministic-fixture",
|
|
history=samples,
|
|
cells=() if state is TemporalState.EXPIRED else (cell,),
|
|
coordinate_frame=None if state is TemporalState.EXPIRED else "map",
|
|
last_centroid_xyz_m=None if state is TemporalState.EXPIRED else last.centroid_xyz_m,
|
|
motion=motion if state is TemporalState.CURRENT else MotionState.UNKNOWN,
|
|
motion_confidence=(
|
|
0.0 if state is not TemporalState.CURRENT or motion is MotionState.UNKNOWN else 1.0
|
|
),
|
|
motion_reason=(
|
|
"stale-support"
|
|
if state is not TemporalState.CURRENT
|
|
else "bounded-map-history-moving"
|
|
if motion is MotionState.MOVING
|
|
else "bounded-map-history-stationary"
|
|
if motion is MotionState.STATIONARY
|
|
else "insufficient-history"
|
|
),
|
|
)
|
|
|
|
|
|
def _fixture_case(
|
|
provider: DualEvidenceReplayThreatProvider,
|
|
name: str,
|
|
obstacle: TemporalObstacle,
|
|
expected: ThreatDecision,
|
|
*,
|
|
critical: bool = False,
|
|
) -> dict[str, object]:
|
|
obstacle_map = LocalObstacleMap(
|
|
source_id="RAVNOVES00",
|
|
session_id="20260720T065719Z_viewer_live",
|
|
frame_id="frame-000002",
|
|
graph_id="reference-perception-graph/v1",
|
|
generated_monotonic_ns=0,
|
|
output_age_ns=0,
|
|
occupied=(obstacle,)
|
|
if obstacle.state in {TemporalState.CURRENT, TemporalState.RETAINED}
|
|
else (),
|
|
unknown=(obstacle,)
|
|
if obstacle.state not in {TemporalState.CURRENT, TemporalState.RETAINED}
|
|
else (),
|
|
camera_uncertainty=(),
|
|
accounting=SourceAccounting(1, 1, 0, 0),
|
|
)
|
|
assessment = provider.assess(obstacle_map)[0]
|
|
return {
|
|
"name": name,
|
|
"expected": expected.value,
|
|
"actual": assessment.decision.value,
|
|
"critical": critical,
|
|
"passed": assessment.decision is expected,
|
|
"assessment": assessment.to_dict(),
|
|
}
|
|
|
|
|
|
def _fixture_camera_case(
|
|
provider: DualEvidenceReplayThreatProvider,
|
|
frame_id: str,
|
|
) -> dict[str, object]:
|
|
proposal = ObjectProposal2D(
|
|
proposal_id="fixture-camera-only",
|
|
source_id="RAVNOVES00",
|
|
frame_id=frame_id,
|
|
region=BoundingRegion2D(10.0, 10.0, 20.0, 20.0),
|
|
objectness=0.9,
|
|
provider_id="fixture-detector/v1",
|
|
model_id="fixture-model/v1",
|
|
preprocess_id="fixture-preprocess/v1",
|
|
)
|
|
obstacle_map = LocalObstacleMap(
|
|
source_id="RAVNOVES00",
|
|
session_id="20260720T065719Z_viewer_live",
|
|
frame_id=frame_id,
|
|
graph_id="reference-perception-graph/v1",
|
|
generated_monotonic_ns=0,
|
|
output_age_ns=0,
|
|
occupied=(),
|
|
unknown=(),
|
|
camera_uncertainty=(proposal,),
|
|
accounting=SourceAccounting(1, 1, 0, 0),
|
|
)
|
|
assessment = provider.assess(obstacle_map)[0]
|
|
return {
|
|
"name": "camera-only",
|
|
"expected": "unknown",
|
|
"actual": assessment.decision.value,
|
|
"critical": False,
|
|
"passed": assessment.decision is ThreatDecision.UNKNOWN,
|
|
"assessment": assessment.to_dict(),
|
|
}
|
|
|
|
|
|
def _metrics(
|
|
*,
|
|
frame_count: int,
|
|
failed_frames: int,
|
|
assessment_counts: Counter[str],
|
|
evidence_counts: Counter[str],
|
|
motion_decisions: Counter[str],
|
|
reason_counts: Counter[str],
|
|
latencies_ms: list[float],
|
|
elapsed_ns: int,
|
|
visual_count: int,
|
|
fixtures: dict[str, object],
|
|
body_frame: dict[str, object],
|
|
frame_1880_regression: dict[str, object] | None,
|
|
frame_2584_regression: dict[str, object] | None,
|
|
) -> dict[str, object]:
|
|
values = np.asarray(latencies_ms, dtype=np.float64)
|
|
return {
|
|
"frames": {"total": frame_count, "failed": failed_frames},
|
|
"evidence": dict(sorted(evidence_counts.items())),
|
|
"decisions": dict(sorted(assessment_counts.items())),
|
|
"motion_decisions": dict(sorted(motion_decisions.items())),
|
|
"reason_counts": dict(sorted(reason_counts.items())),
|
|
"body_frame": body_frame,
|
|
"visual_evidence": {
|
|
"frame_count": visual_count,
|
|
"point_limit_per_frame": VISUAL_POINT_LIMIT,
|
|
"video_overlay_available": True,
|
|
"camera_boxes_available": True,
|
|
"point_cloud_available": True,
|
|
"metric_distance_available": True,
|
|
"virtual_corridor_available": True,
|
|
"qualified_base_footprint_available": True,
|
|
"geometry_regression_sequences": list(VISUAL_GEOMETRY_REGRESSION_SEQUENCES),
|
|
"frame_1880_regression": frame_1880_regression,
|
|
"frame_2584_regression": frame_2584_regression,
|
|
},
|
|
"fixtures": {
|
|
"passed": fixtures["passed_count"],
|
|
"total": fixtures["total_count"],
|
|
"critical": fixtures["critical_case_count"],
|
|
"critical_false_not_threat": fixtures["critical_false_not_threat_count"],
|
|
},
|
|
"runtime": {
|
|
"elapsed_ns": elapsed_ns,
|
|
"frames_per_second": round(frame_count / (elapsed_ns / 1_000_000_000), 6),
|
|
"provider_latency_p50_ms": round(float(np.percentile(values, 50)), 6),
|
|
"provider_latency_p95_ms": round(float(np.percentile(values, 95)), 6),
|
|
"provider_latency_max_ms": round(float(np.max(values)), 6),
|
|
},
|
|
}
|
|
|
|
|
|
def _requirements_v1(
|
|
metrics: dict[str, object],
|
|
fixtures: dict[str, object],
|
|
) -> dict[str, bool]:
|
|
frames = _object(metrics.get("frames"), "frame metrics")
|
|
evidence = _object(metrics.get("evidence"), "evidence metrics")
|
|
decisions = _object(metrics.get("decisions"), "decision metrics")
|
|
visual = _object(metrics.get("visual_evidence"), "visual metrics")
|
|
body_frame = _object(metrics.get("body_frame"), "body frame metrics")
|
|
total_evidence = sum(_integer(value, "evidence count") for value in evidence.values())
|
|
total_decisions = sum(_integer(value, "decision count") for value in decisions.values())
|
|
cases = _array(fixtures.get("cases"), "fixture cases")
|
|
camera_case = next(
|
|
(
|
|
_object(item, "fixture")
|
|
for item in cases
|
|
if isinstance(item, dict) and item.get("name") == "camera-only"
|
|
),
|
|
{},
|
|
)
|
|
stale_cases = [
|
|
_object(item, "fixture")
|
|
for item in cases
|
|
if isinstance(item, dict) and item.get("name") in {"occluded-held", "stale-expired"}
|
|
]
|
|
return {
|
|
"full_ravnoves00_replay_completed": (
|
|
frames.get("total") == 4489 and frames.get("failed") == 0
|
|
),
|
|
"every_metric_or_camera_evidence_received_one_assessment": (
|
|
total_evidence == total_decisions and total_evidence > 0
|
|
),
|
|
"camera_only_is_unknown_never_safe": camera_case.get("actual") == "unknown",
|
|
"held_and_stale_are_unknown_never_safe": (
|
|
len(stale_cases) == 2 and all(item.get("actual") == "unknown" for item in stale_cases)
|
|
),
|
|
"geometry_only_evidence_is_assessed": (
|
|
_integer(
|
|
_object(metrics.get("reason_counts"), "reason metrics").get(
|
|
"geometry-only-evidence", 0
|
|
),
|
|
"geometry-only count",
|
|
)
|
|
> 0
|
|
),
|
|
"deterministic_fixture_matrix_passed": (
|
|
fixtures.get("passed_count") == fixtures.get("total_count") == 9
|
|
),
|
|
"zero_critical_fixture_false_not_threat": (
|
|
fixtures.get("critical_false_not_threat_count") == 0
|
|
),
|
|
"visual_video_camera_cloud_distance_and_corridor_are_available": (
|
|
visual.get("frame_count") == VISUAL_FRAME_COUNT
|
|
and all(
|
|
visual.get(key) is True
|
|
for key in (
|
|
"video_overlay_available",
|
|
"camera_boxes_available",
|
|
"point_cloud_available",
|
|
"metric_distance_available",
|
|
"virtual_corridor_available",
|
|
"qualified_base_footprint_available",
|
|
)
|
|
)
|
|
and visual.get("geometry_regression_sequences") == [138, 274]
|
|
),
|
|
"body_frame_is_grounded_gravity_stable_and_route_aligned": (
|
|
body_frame.get("available")
|
|
== _integer(body_frame.get("qualified"), "qualified body frames")
|
|
+ _integer(body_frame.get("rejected"), "rejected body frames")
|
|
and _integer(body_frame.get("qualified"), "qualified body frames")
|
|
>= math.ceil(_integer(body_frame.get("available"), "available body frames") * 0.95)
|
|
and body_frame.get("origin") == "local-surface-vertical-projection"
|
|
and body_frame.get("up") == "vendor-slam-map-gravity-axis"
|
|
and body_frame.get("forward") == "smoothed-slam-trajectory-validated-by-camera-axis"
|
|
and _number_value(
|
|
_object(
|
|
body_frame.get("camera_forward_alignment_deg"),
|
|
"body alignment metrics",
|
|
).get("maximum"),
|
|
"maximum body alignment",
|
|
)
|
|
<= 25.0
|
|
),
|
|
"physical_collision_and_actuation_authority_remain_false": (
|
|
fixtures.get("authority") == _false_authority()
|
|
),
|
|
}
|
|
|
|
|
|
def _requirements_v2(
|
|
metrics: dict[str, object],
|
|
fixtures: dict[str, object],
|
|
) -> dict[str, bool]:
|
|
frames = _object(metrics.get("frames"), "frame metrics")
|
|
evidence = _object(metrics.get("evidence"), "evidence metrics")
|
|
decisions = _object(metrics.get("decisions"), "decision metrics")
|
|
visual = _object(metrics.get("visual_evidence"), "visual metrics")
|
|
body_frame = _object(metrics.get("body_frame"), "body frame metrics")
|
|
total_evidence = sum(_integer(value, "evidence count") for value in evidence.values())
|
|
total_decisions = sum(_integer(value, "decision count") for value in decisions.values())
|
|
cases = _array(fixtures.get("cases"), "fixture cases")
|
|
camera_case = next(
|
|
(
|
|
_object(item, "fixture")
|
|
for item in cases
|
|
if isinstance(item, dict) and item.get("name") == "camera-only"
|
|
),
|
|
{},
|
|
)
|
|
stale_cases = [
|
|
_object(item, "fixture")
|
|
for item in cases
|
|
if isinstance(item, dict) and item.get("name") in {"occluded-held", "stale-expired"}
|
|
]
|
|
return {
|
|
"full_ravnoves00_replay_completed": (
|
|
frames.get("total") == 4489 and frames.get("failed") == 0
|
|
),
|
|
"every_metric_or_camera_evidence_received_one_assessment": (
|
|
total_evidence == total_decisions and total_evidence > 0
|
|
),
|
|
"camera_only_is_unknown_never_safe": camera_case.get("actual") == "unknown",
|
|
"held_and_stale_are_unknown_never_safe": (
|
|
len(stale_cases) == 2 and all(item.get("actual") == "unknown" for item in stale_cases)
|
|
),
|
|
"geometry_only_evidence_is_assessed": (
|
|
_integer(
|
|
_object(metrics.get("reason_counts"), "reason metrics").get(
|
|
"geometry-only-evidence", 0
|
|
),
|
|
"geometry-only count",
|
|
)
|
|
> 0
|
|
),
|
|
"deterministic_fixture_matrix_passed": (
|
|
fixtures.get("passed_count") == fixtures.get("total_count") == 10
|
|
),
|
|
"zero_critical_fixture_false_not_threat": (
|
|
fixtures.get("critical_false_not_threat_count") == 0
|
|
),
|
|
"visual_video_camera_cloud_distance_and_corridor_are_available": (
|
|
visual.get("frame_count") == VISUAL_FRAME_COUNT
|
|
and all(
|
|
visual.get(key) is True
|
|
for key in (
|
|
"video_overlay_available",
|
|
"camera_boxes_available",
|
|
"point_cloud_available",
|
|
"metric_distance_available",
|
|
"virtual_corridor_available",
|
|
"qualified_base_footprint_available",
|
|
)
|
|
)
|
|
and visual.get("geometry_regression_sequences")
|
|
== list(VISUAL_GEOMETRY_REGRESSION_SEQUENCES)
|
|
),
|
|
"frame_1880_retains_two_hemispheres_and_blocks_near_corridor": (
|
|
isinstance(visual.get("frame_1880_regression"), dict)
|
|
and _object(
|
|
visual.get("frame_1880_regression"),
|
|
"frame 1880 regression",
|
|
).get("matched_anchor_count")
|
|
== len(FRAME_1880_ENGINEERING_ANCHORS)
|
|
and _object(
|
|
visual.get("frame_1880_regression"),
|
|
"frame 1880 regression",
|
|
).get("required_threats_passed")
|
|
is True
|
|
),
|
|
"frame_2584_retains_compact_hemisphere_and_accounts_for_far_occupancy": (
|
|
isinstance(visual.get("frame_2584_regression"), dict)
|
|
and _object(
|
|
visual.get("frame_2584_regression"),
|
|
"frame 2584 regression",
|
|
).get("matched_anchor_count")
|
|
== len(FRAME_2584_ENGINEERING_ANCHORS)
|
|
and _object(
|
|
visual.get("frame_2584_regression"),
|
|
"frame 2584 regression",
|
|
).get("required_threats_passed")
|
|
is True
|
|
),
|
|
"body_frame_is_grounded_gravity_stable_and_route_aligned": (
|
|
body_frame.get("available")
|
|
== _integer(body_frame.get("qualified"), "qualified body frames")
|
|
+ _integer(body_frame.get("rejected"), "rejected body frames")
|
|
and _integer(body_frame.get("qualified"), "qualified body frames")
|
|
>= math.ceil(_integer(body_frame.get("available"), "available body frames") * 0.95)
|
|
and body_frame.get("origin") == "local-surface-vertical-projection"
|
|
and body_frame.get("up") == "vendor-slam-map-gravity-axis"
|
|
and body_frame.get("forward") == "smoothed-slam-trajectory-validated-by-camera-axis"
|
|
and _number_value(
|
|
_object(
|
|
body_frame.get("camera_forward_alignment_deg"),
|
|
"body alignment metrics",
|
|
).get("maximum"),
|
|
"maximum body alignment",
|
|
)
|
|
<= 25.0
|
|
),
|
|
"physical_collision_and_actuation_authority_remain_false": (
|
|
fixtures.get("authority") == _false_authority()
|
|
),
|
|
}
|
|
|
|
|
|
def _validate_ledgers(
|
|
frames_path: Path,
|
|
visuals_path: Path,
|
|
metrics: dict[str, object],
|
|
*,
|
|
is_v2: bool,
|
|
) -> None:
|
|
frame_count = 0
|
|
assessment_count = 0
|
|
for sequence, frame in enumerate(_read_jsonl(frames_path)):
|
|
if (
|
|
frame.get("schema_version")
|
|
!= (THREAT_REPLAY_FRAME_SCHEMA_V2 if is_v2 else THREAT_REPLAY_FRAME_SCHEMA)
|
|
or frame.get("sequence") != sequence
|
|
or frame.get("authority") != _false_authority()
|
|
):
|
|
raise ThreatReplayError("threat frame ledger changed")
|
|
accounting = _object(frame.get("accounting"), "threat frame accounting")
|
|
assessments = _array(frame.get("assessments"), "threat assessments")
|
|
if accounting.get("assessments") != len(assessments):
|
|
raise ThreatReplayError("threat frame accounting changed")
|
|
assessment_count += len(assessments)
|
|
frame_count += 1
|
|
visuals = list(_read_jsonl(visuals_path))
|
|
if (
|
|
frame_count != _object(metrics.get("frames"), "frames").get("total")
|
|
or assessment_count
|
|
!= sum(
|
|
_integer(value, "decision count")
|
|
for value in _object(metrics.get("decisions"), "decisions").values()
|
|
)
|
|
or len(visuals) != VISUAL_FRAME_COUNT
|
|
or any(
|
|
item.get("schema_version")
|
|
!= (THREAT_REPLAY_VISUAL_SCHEMA_V2 if is_v2 else THREAT_REPLAY_VISUAL_SCHEMA)
|
|
for item in visuals
|
|
)
|
|
):
|
|
raise ThreatReplayError("threat replay ledger and metrics disagree")
|
|
|
|
|
|
def _visual_sequences(available: tuple[int, ...]) -> frozenset[int]:
|
|
if len(available) < VISUAL_FRAME_COUNT:
|
|
raise ThreatReplayError("not enough available source frames for visual evidence")
|
|
selected = {
|
|
available[round(index * (len(available) - 1) / (VISUAL_FRAME_COUNT - 1))]
|
|
for index in range(VISUAL_FRAME_COUNT)
|
|
}
|
|
available_set = frozenset(available)
|
|
for anchor in VISUAL_GEOMETRY_REGRESSION_SEQUENCES:
|
|
if anchor not in available_set:
|
|
raise ThreatReplayError("geometry regression frame is not qualified")
|
|
if anchor in selected:
|
|
continue
|
|
replaceable = selected.difference(
|
|
{
|
|
available[0],
|
|
available[-1],
|
|
*VISUAL_GEOMETRY_REGRESSION_SEQUENCES,
|
|
}
|
|
)
|
|
if not replaceable:
|
|
raise ThreatReplayError("visual regression sample cannot be inserted")
|
|
selected.remove(min(replaceable, key=lambda value: abs(value - anchor)))
|
|
selected.add(anchor)
|
|
if len(selected) != VISUAL_FRAME_COUNT:
|
|
raise ThreatReplayError("visual sample selection is not unique")
|
|
return frozenset(selected)
|
|
|
|
|
|
def _producer_hashes(repository: Path) -> dict[str, str]:
|
|
return {
|
|
name: _file_sha256(repository / "src/k1link/perception" / name)
|
|
for name in ("threat.py", "threat_replay.py")
|
|
}
|
|
|
|
|
|
def _false_authority() -> dict[str, object]:
|
|
return {
|
|
"mode": "replay-simulated",
|
|
"ground_truth": False,
|
|
"physical_live": False,
|
|
"physical_collision_accepted": False,
|
|
"commands_enabled": False,
|
|
"actuation_allowed": False,
|
|
"navigation_or_safety_accepted": False,
|
|
}
|
|
|
|
|
|
def _artifact(path: Path, role: str) -> dict[str, object]:
|
|
return {
|
|
"role": role,
|
|
"path": path.name,
|
|
"byte_length": path.stat().st_size,
|
|
"sha256": _file_sha256(path),
|
|
}
|
|
|
|
|
|
def _validated_artifact(root: Path, raw: object, expected_name: str) -> Path:
|
|
item = _object(raw, "threat artifact")
|
|
_exact_keys(
|
|
item,
|
|
{"role", "path", "byte_length", "sha256"},
|
|
"threat artifact",
|
|
)
|
|
if item.get("path") != expected_name:
|
|
raise ThreatReplayError("threat artifact path changed")
|
|
path = (root / expected_name).resolve(strict=True)
|
|
if (
|
|
path.parent != root
|
|
or path.is_symlink()
|
|
or not path.is_file()
|
|
or path.stat().st_size != item.get("byte_length")
|
|
or _file_sha256(path) != item.get("sha256")
|
|
):
|
|
raise ThreatReplayError("threat artifact content changed")
|
|
return path
|
|
|
|
|
|
def _read_json_line(raw: bytes, label: str, sequence: int) -> dict[str, object]:
|
|
if not raw:
|
|
raise ThreatReplayError(f"{label} ended before frame {sequence}")
|
|
try:
|
|
return _object(json.loads(raw), label)
|
|
except json.JSONDecodeError as exc:
|
|
raise ThreatReplayError(f"{label} is invalid JSON") from exc
|
|
|
|
|
|
def _read_json(path: Path) -> dict[str, object]:
|
|
try:
|
|
return _object(json.loads(path.read_bytes()), path.name)
|
|
except json.JSONDecodeError as exc:
|
|
raise ThreatReplayError(f"{path.name} is invalid JSON") from exc
|
|
|
|
|
|
def _read_jsonl(path: Path) -> Iterator[dict[str, object]]:
|
|
with path.open("rb") as handle:
|
|
for line in handle:
|
|
if line.strip():
|
|
try:
|
|
yield _object(json.loads(line), path.name)
|
|
except json.JSONDecodeError as exc:
|
|
raise ThreatReplayError(f"{path.name} is invalid JSONL") from exc
|
|
|
|
|
|
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=(",", ":"),
|
|
ensure_ascii=False,
|
|
allow_nan=False,
|
|
).encode()
|
|
|
|
|
|
def _file_sha256(path: Path) -> str:
|
|
digest = hashlib.sha256()
|
|
with path.open("rb") as handle:
|
|
for block in iter(lambda: handle.read(1024 * 1024), b""):
|
|
digest.update(block)
|
|
return digest.hexdigest()
|
|
|
|
|
|
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 ThreatReplayError(f"{label} must be an object")
|
|
return value
|
|
|
|
|
|
def _array(value: object, label: str) -> list[object]:
|
|
if not isinstance(value, list):
|
|
raise ThreatReplayError(f"{label} must be an array")
|
|
return value
|
|
|
|
|
|
def _string_array(value: object, label: str) -> list[str]:
|
|
values = _array(value, label)
|
|
if any(not isinstance(item, str) for item in values):
|
|
raise ThreatReplayError(f"{label} must contain strings")
|
|
return [str(item) for item in values]
|
|
|
|
|
|
def _integer(value: object, label: str) -> int:
|
|
if not isinstance(value, int) or isinstance(value, bool) or value < 0:
|
|
raise ThreatReplayError(f"{label} must be a nonnegative integer")
|
|
return value
|
|
|
|
|
|
def _number_value(value: object, label: str) -> float:
|
|
if not isinstance(value, int | float) or isinstance(value, bool) or not math.isfinite(value):
|
|
raise ThreatReplayError(f"{label} is not finite")
|
|
return float(value)
|
|
|
|
|
|
def _bounds(value: object, label: str) -> tuple[float, float]:
|
|
if not isinstance(value, tuple) or len(value) != 2:
|
|
raise ThreatReplayError(f"{label} must contain two values")
|
|
lower = _number_value(value[0], label)
|
|
upper = _number_value(value[1], label)
|
|
if lower >= upper:
|
|
raise ThreatReplayError(f"{label} must be ordered")
|
|
return lower, upper
|
|
|
|
|
|
def _signed_integer(value: object, label: str) -> int:
|
|
if not isinstance(value, int) or isinstance(value, bool):
|
|
raise ThreatReplayError(f"{label} must be an integer")
|
|
return value
|
|
|
|
|
|
def _exact_keys(document: dict[str, object], expected: set[str], label: str) -> None:
|
|
if set(document) != expected:
|
|
raise ThreatReplayError(f"{label} fields are incompatible")
|
|
|
|
|
|
__all__ = [
|
|
"THREAT_REPLAY_FIXTURE_SCHEMA",
|
|
"THREAT_REPLAY_FRAME_SCHEMA",
|
|
"THREAT_REPLAY_MANIFEST_NAME",
|
|
"THREAT_REPLAY_REPORT_SCHEMA",
|
|
"THREAT_REPLAY_RESULT_PREFIX",
|
|
"THREAT_REPLAY_SCHEMA",
|
|
"THREAT_REPLAY_VISUAL_SCHEMA",
|
|
"ThreatReplayError",
|
|
"ThreatReplayResult",
|
|
"build_threat_replay",
|
|
"read_threat_replay_result",
|
|
]
|