feat(lab): seal native risk review evidence
This commit is contained in:
@@ -0,0 +1,471 @@
|
||||
"""Seal native raw-fisheye M4.8Q risk review evidence as a terminal LAB phase."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
from k1link.perception.fixed_class_detector_tournament import (
|
||||
canonical_json,
|
||||
false_authority,
|
||||
sha256_path,
|
||||
)
|
||||
|
||||
LAB_SCHEMA: Final = "missioncore.m48q-native-risk-quality-lab/v1"
|
||||
REPORT_SCHEMA: Final = "missioncore.m48q-native-risk-quality-report/v1"
|
||||
CATALOG_SCHEMA: Final = "missioncore.m48q-native-risk-review-catalog/v1"
|
||||
CASE_SCHEMA: Final = "missioncore.m48q-native-risk-review-case/v1"
|
||||
RESULT_PREFIX: Final = "m48q-native-risk-quality-lab-"
|
||||
PROFILE_RELATIVE_PATH: Final = Path("config/perception/m48q-native-risk-case-mining-v1.json")
|
||||
DETECTOR_PROFILE_RELATIVE_PATH: Final = Path(
|
||||
"config/perception/rf-detr-large-native-kb4-risk-shadow-v0.json"
|
||||
)
|
||||
WORKER_RELATIVE_ROOT: Final = Path(
|
||||
".runtime/worker-results/m48q-native-risk-case-mining/ravnoves00-native-raw-review-v3"
|
||||
)
|
||||
EXPECTED_PROFILE_SHA256: Final = "c455055e63578d505edc80b66d67e795916a3d0297cc18bfee5a6af7b032ceda"
|
||||
EXPECTED_DETECTOR_PROFILE_SHA256: Final = (
|
||||
"dbf4da5dbad6c3c22b1280b46ffcad81719bd183c81c263a4859847d829019b6"
|
||||
)
|
||||
EXPECTED_WORKER_RESULT_SHA256: Final = (
|
||||
"a8f98d6d68abd6fd627caa05aaa3f048d69279a989b07fc2552b508af909a623"
|
||||
)
|
||||
EXPECTED_CASES_SHA256: Final = "9cea2a4a4558d761bc76b0d70474673ff07210473f53bd32cf178e8e0c8e179c"
|
||||
EXPECTED_REPORT_IDENTITY_SHA256: Final = (
|
||||
"b744775c0e08d18d728f04021cd2acf9b1ed6fcacf1520352ee04e06a6d5d324"
|
||||
)
|
||||
CASE_ID: Final = re.compile(r"^[0-9]{6}$")
|
||||
|
||||
|
||||
class M48QNativeRiskQualityLabError(RuntimeError):
|
||||
"""Raised when native M4.8Q evidence cannot be sealed honestly."""
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class M48QNativeRiskQualityLabResult:
|
||||
result_root: Path
|
||||
result_id: str
|
||||
manifest: dict[str, Any]
|
||||
|
||||
|
||||
def build_m48q_native_risk_quality_lab(
|
||||
*,
|
||||
repository_root: Path,
|
||||
output_root: Path,
|
||||
) -> M48QNativeRiskQualityLabResult:
|
||||
repository = repository_root.expanduser().resolve(strict=True)
|
||||
profile_path = repository / PROFILE_RELATIVE_PATH
|
||||
detector_profile_path = repository / DETECTOR_PROFILE_RELATIVE_PATH
|
||||
worker_root = repository / WORKER_RELATIVE_ROOT
|
||||
worker_result_path = worker_root / "result.json"
|
||||
cases_path = worker_root / "cases.jsonl"
|
||||
images_root = worker_root / "cases"
|
||||
for path in (
|
||||
profile_path,
|
||||
detector_profile_path,
|
||||
worker_result_path,
|
||||
cases_path,
|
||||
):
|
||||
if path.is_symlink() or not path.is_file():
|
||||
raise M48QNativeRiskQualityLabError(f"required M4.8Q evidence is missing: {path.name}")
|
||||
if images_root.is_symlink() or not images_root.is_dir():
|
||||
raise M48QNativeRiskQualityLabError("M4.8Q image evidence is missing")
|
||||
expected_hashes = {
|
||||
profile_path: EXPECTED_PROFILE_SHA256,
|
||||
detector_profile_path: EXPECTED_DETECTOR_PROFILE_SHA256,
|
||||
worker_result_path: EXPECTED_WORKER_RESULT_SHA256,
|
||||
cases_path: EXPECTED_CASES_SHA256,
|
||||
}
|
||||
if any(sha256_path(path) != expected for path, expected in expected_hashes.items()):
|
||||
raise M48QNativeRiskQualityLabError("M4.8Q source evidence identity changed")
|
||||
|
||||
profile = _read_object(profile_path)
|
||||
detector_profile = _read_object(detector_profile_path)
|
||||
worker_result = _read_object(worker_result_path)
|
||||
cases = _read_cases(cases_path)
|
||||
_validate_inputs(
|
||||
profile=profile,
|
||||
detector_profile=detector_profile,
|
||||
worker_result=worker_result,
|
||||
cases=cases,
|
||||
images_root=images_root,
|
||||
)
|
||||
method = _method(profile, detector_profile, worker_result)
|
||||
identity = {
|
||||
"schema_version": LAB_SCHEMA,
|
||||
"profile": {
|
||||
"profile_id": profile["profile_id"],
|
||||
"sha256": EXPECTED_PROFILE_SHA256,
|
||||
},
|
||||
"detector_profile": {
|
||||
"profile_id": detector_profile["profile_id"],
|
||||
"sha256": EXPECTED_DETECTOR_PROFILE_SHA256,
|
||||
},
|
||||
"source": {
|
||||
"source_id": "RAVNOVES00",
|
||||
"video_sha256": profile["source"]["video_sha256"],
|
||||
"graph_result_sha256": worker_result["source"]["graph_result_sha256"],
|
||||
"graph_frames_sha256": worker_result["source"]["graph_frames_sha256"],
|
||||
"comparison_result_sha256": worker_result["source"]["comparison_result_sha256"],
|
||||
"comparison_frames_sha256": worker_result["source"]["comparison_frames_sha256"],
|
||||
"worker_result_sha256": EXPECTED_WORKER_RESULT_SHA256,
|
||||
"cases_sha256": EXPECTED_CASES_SHA256,
|
||||
},
|
||||
"method": method,
|
||||
"authority": false_authority(),
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(canonical_json(identity)).hexdigest()
|
||||
result_id = RESULT_PREFIX + identity_sha256
|
||||
root = output_root.expanduser().absolute()
|
||||
root.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
destination = root / result_id
|
||||
if destination.exists():
|
||||
manifest = _read_object(destination / "manifest.json")
|
||||
if (
|
||||
manifest.get("schema_version") != LAB_SCHEMA
|
||||
or manifest.get("identity_sha256") != identity_sha256
|
||||
or manifest.get("result_id") != result_id
|
||||
):
|
||||
raise M48QNativeRiskQualityLabError("existing M4.8Q LAB identity conflicts")
|
||||
return M48QNativeRiskQualityLabResult(destination, result_id, manifest)
|
||||
|
||||
created_at_utc = datetime.now(UTC).isoformat(timespec="microseconds").replace("+00:00", "Z")
|
||||
temporary = Path(tempfile.mkdtemp(prefix=".m48q-native-risk-lab-", dir=root))
|
||||
try:
|
||||
(temporary / "cases").mkdir(mode=0o700)
|
||||
shutil.copyfile(profile_path, temporary / "profile.json")
|
||||
shutil.copyfile(detector_profile_path, temporary / "detector-profile.json")
|
||||
shutil.copyfile(worker_result_path, temporary / "worker-result.json")
|
||||
shutil.copyfile(cases_path, temporary / "cases.jsonl")
|
||||
|
||||
catalog_cases: list[dict[str, object]] = []
|
||||
for case in cases:
|
||||
image = case["image"]
|
||||
source_image = worker_root / image["path"]
|
||||
destination_image = temporary / image["path"]
|
||||
shutil.copyfile(source_image, destination_image)
|
||||
catalog_cases.append(
|
||||
{
|
||||
"case_id": case["case_id"],
|
||||
"sequence": case["sequence"],
|
||||
"frame_id": case["frame_id"],
|
||||
"evidence_time_ns": case["evidence_time_ns"],
|
||||
"path": image["path"],
|
||||
"media_type": "image/jpeg",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"byte_length": destination_image.stat().st_size,
|
||||
"sha256": sha256_path(destination_image),
|
||||
"geometric_resampling": False,
|
||||
"selection_buckets": case["selection_buckets"],
|
||||
"comparison": case["comparison"],
|
||||
"proposals": case["proposals"],
|
||||
}
|
||||
)
|
||||
catalog = {
|
||||
"schema_version": CATALOG_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"case_count": len(catalog_cases),
|
||||
"source_raster": {"width": 800, "height": 600},
|
||||
"overlay": {
|
||||
"client_rendered": True,
|
||||
"toggleable": True,
|
||||
"box_coordinates": "source-pixel-xyxy",
|
||||
"class_names": True,
|
||||
"scores": True,
|
||||
},
|
||||
"ground_truth": False,
|
||||
"cases": catalog_cases,
|
||||
}
|
||||
catalog_path = temporary / "catalog.json"
|
||||
catalog_path.write_bytes(canonical_json(catalog) + b"\n")
|
||||
|
||||
graph_qualification = detector_profile["qualification"][
|
||||
"full_ravnoves00_integrated_reference_graph"
|
||||
]
|
||||
decision = {
|
||||
"review_ready": True,
|
||||
"quality_evaluated": False,
|
||||
"ground_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"production_accepted": False,
|
||||
"next_action": "operator-adjudication-in-existing-m48-review-instrument",
|
||||
}
|
||||
report = {
|
||||
"schema_version": REPORT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"source": {
|
||||
**profile["source"],
|
||||
"graph_result_sha256": worker_result["source"]["graph_result_sha256"],
|
||||
"graph_frames_sha256": worker_result["source"]["graph_frames_sha256"],
|
||||
"independent_route_truth_available": False,
|
||||
},
|
||||
"configuration": {
|
||||
"candidate": profile["candidate"],
|
||||
"selection": profile["selection"],
|
||||
},
|
||||
"method": method,
|
||||
"execution": {
|
||||
"worker": worker_result["worker"],
|
||||
"full_graph_frames": graph_qualification["frame_count"],
|
||||
"requested_source_rate_hz": graph_qualification["requested_source_rate_hz"],
|
||||
"effective_world_state_fps": graph_qualification["effective_world_state_fps"],
|
||||
"world_state_completion_p95_ms": graph_qualification[
|
||||
"world_state_completion_p95_ms"
|
||||
],
|
||||
"detector_total_p95_ms": graph_qualification["detector_total_p95_ms"],
|
||||
"gpu_utilization_p95_percent": graph_qualification["gpu_utilization_p95_percent"],
|
||||
"gpu_memory_used_maximum_mib": graph_qualification["gpu_memory_used_maximum_mib"],
|
||||
"additional_inference_passes": 0,
|
||||
},
|
||||
"metrics": {
|
||||
"selection": worker_result["selection"],
|
||||
"runtime": {
|
||||
"delivery_ratio": graph_qualification["delivery_ratio"],
|
||||
"integrated_runtime_gate_passed": graph_qualification[
|
||||
"integrated_runtime_gate_passed"
|
||||
],
|
||||
"operating_target_gate_passed": graph_qualification[
|
||||
"operating_target_gate_passed"
|
||||
],
|
||||
},
|
||||
"native_tensor_parity": detector_profile["qualification"][
|
||||
"native_pytorch_tensorrt_parity"
|
||||
],
|
||||
"native_vs_legacy_704": detector_profile["qualification"][
|
||||
"full_ravnoves00_native_vs_legacy_704"
|
||||
],
|
||||
},
|
||||
"acceptance": {
|
||||
"review_ready": True,
|
||||
"integrated_runtime_gate_passed": True,
|
||||
"independent_quality_evaluated": False,
|
||||
"semantic_candidate_accepted": False,
|
||||
},
|
||||
"decision": decision,
|
||||
"limitations": worker_result["limitations"],
|
||||
"authority": false_authority(),
|
||||
"visual_evidence": {
|
||||
"kind": "native-raw-fisheye-risk-case-review",
|
||||
"case_count": 24,
|
||||
"source_raster": "800x600",
|
||||
"geometric_resampling": False,
|
||||
"boxes_baked_into_images": False,
|
||||
"ground_truth": False,
|
||||
},
|
||||
}
|
||||
report_path = temporary / "report.json"
|
||||
report_path.write_bytes(canonical_json(report) + b"\n")
|
||||
artifacts = _artifact_manifest(temporary)
|
||||
manifest = {
|
||||
"schema_version": LAB_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": created_at_utc,
|
||||
"status": "complete-review-ready-quality-not-adjudicated",
|
||||
"completed": True,
|
||||
"bounded_question_accepted": True,
|
||||
"ground_truth": False,
|
||||
"method": method,
|
||||
"metrics": report["metrics"],
|
||||
"decision": decision,
|
||||
"limitations": worker_result["limitations"],
|
||||
"authority": false_authority(),
|
||||
"artifacts": artifacts,
|
||||
}
|
||||
(temporary / "manifest.json").write_bytes(canonical_json(manifest) + b"\n")
|
||||
temporary.replace(destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(temporary, ignore_errors=True)
|
||||
raise
|
||||
return M48QNativeRiskQualityLabResult(destination, result_id, manifest)
|
||||
|
||||
|
||||
def _validate_inputs(
|
||||
*,
|
||||
profile: dict[str, Any],
|
||||
detector_profile: dict[str, Any],
|
||||
worker_result: dict[str, Any],
|
||||
cases: list[dict[str, Any]],
|
||||
images_root: Path,
|
||||
) -> None:
|
||||
if (
|
||||
profile.get("schema_version") != "missioncore.m48q-native-risk-case-mining-profile/v1"
|
||||
or profile.get("source", {}).get("geometric_resampling") is not False
|
||||
or profile.get("source", {}).get("raster_width") != 800
|
||||
or profile.get("source", {}).get("raster_height") != 600
|
||||
or profile.get("authority") != false_authority()
|
||||
or detector_profile.get("schema_version")
|
||||
!= "missioncore.rf-detr-native-risk-shadow-profile/v0"
|
||||
or detector_profile.get("preprocessing", {}).get("resize") is not False
|
||||
or detector_profile.get("preprocessing", {}).get("geometric_resampling") is not False
|
||||
or detector_profile.get("status", {}).get("integrated_world_state_gate_passed") is not True
|
||||
or worker_result.get("schema_version")
|
||||
!= "missioncore.m48q-native-risk-case-mining-result/v1"
|
||||
or worker_result.get("status") != "complete-review-ready-quality-not-adjudicated"
|
||||
or worker_result.get("completed") is not True
|
||||
or worker_result.get("report_identity_sha256") != EXPECTED_REPORT_IDENTITY_SHA256
|
||||
or worker_result.get("artifacts", {}).get("cases", {}).get("sha256")
|
||||
!= EXPECTED_CASES_SHA256
|
||||
or worker_result.get("selection", {}).get("case_count") != 24
|
||||
or worker_result.get("decision", {}).get("quality_evaluated") is not False
|
||||
or worker_result.get("authority") != false_authority()
|
||||
or len(cases) != 24
|
||||
or len({case.get("case_id") for case in cases}) != 24
|
||||
):
|
||||
raise M48QNativeRiskQualityLabError("M4.8Q evidence contract changed")
|
||||
for case in cases:
|
||||
image = case.get("image")
|
||||
proposals = case.get("proposals")
|
||||
if (
|
||||
case.get("schema_version") != CASE_SCHEMA
|
||||
or not isinstance(case.get("case_id"), str)
|
||||
or CASE_ID.fullmatch(case["case_id"]) is None
|
||||
or case.get("case_id") != f"{case.get('sequence'):06d}"
|
||||
or case.get("frame_id") != f"frame-{case.get('sequence'):06d}"
|
||||
or case.get("ground_truth") is not False
|
||||
or case.get("quality_evaluated") is not False
|
||||
or case.get("authority") != false_authority()
|
||||
or not isinstance(image, dict)
|
||||
or image.get("path") != f"cases/frame-{case['case_id']}.jpg"
|
||||
or image.get("width") != 800
|
||||
or image.get("height") != 600
|
||||
or image.get("geometric_resampling") is not False
|
||||
or not isinstance(proposals, list)
|
||||
or not proposals
|
||||
):
|
||||
raise M48QNativeRiskQualityLabError("M4.8Q review case contract changed")
|
||||
image_path = images_root.parent / image["path"]
|
||||
if (
|
||||
image_path.is_symlink()
|
||||
or not image_path.is_file()
|
||||
or image_path.stat().st_size != image.get("byte_length")
|
||||
or sha256_path(image_path) != image.get("sha256")
|
||||
):
|
||||
raise M48QNativeRiskQualityLabError("M4.8Q review image proof changed")
|
||||
|
||||
|
||||
def _method(
|
||||
profile: dict[str, Any],
|
||||
detector_profile: dict[str, Any],
|
||||
worker_result: dict[str, Any],
|
||||
) -> dict[str, object]:
|
||||
return {
|
||||
"schema_version": "missioncore.laboratory-method/v1",
|
||||
"completeness": "complete",
|
||||
"execution_class": "hybrid",
|
||||
"pipeline_id": "m48q-native-raw-fisheye-risk-case-review/v1",
|
||||
"components": [
|
||||
{
|
||||
"kind": "source",
|
||||
"name": "RAVNOVES00 raw KB4 video",
|
||||
"version": "800x600 immutable recording",
|
||||
"role": "unrectified review raster",
|
||||
"identity_sha256": profile["source"]["video_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "model",
|
||||
"name": "RF-DETR-L native KB4 TensorRT",
|
||||
"version": profile["candidate"]["model_id"],
|
||||
"role": "fixed-class behavior-risk shadow proposals",
|
||||
"identity_sha256": profile["candidate"]["engine_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "runtime",
|
||||
"name": "M4.7 native reference graph",
|
||||
"version": "full RAVNOVES00 at recorded 12 FPS",
|
||||
"role": "proposal and realtime evidence source",
|
||||
"identity_sha256": worker_result["source"]["graph_result_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "algorithm",
|
||||
"name": "bounded diagnostic case miner",
|
||||
"version": profile["profile_id"],
|
||||
"role": "deterministic risk-family and edge-case sampling",
|
||||
"identity_sha256": worker_result["identity"]["runner_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "tool",
|
||||
"name": "M4.8 existing image-case review instrument",
|
||||
"version": "client-rendered source-pixel overlay/v1",
|
||||
"role": "operator review without baked boxes",
|
||||
"identity_sha256": EXPECTED_CASES_SHA256,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _artifact_manifest(root: Path) -> list[dict[str, object]]:
|
||||
artifacts: list[dict[str, object]] = []
|
||||
for path in sorted(item for item in root.rglob("*") if item.is_file()):
|
||||
relative = path.relative_to(root).as_posix()
|
||||
if relative == "manifest.json":
|
||||
continue
|
||||
media_type = "application/json"
|
||||
schema_version: str | None = None
|
||||
role = "supporting-evidence"
|
||||
if relative == "report.json":
|
||||
role = "laboratory-report"
|
||||
schema_version = REPORT_SCHEMA
|
||||
elif relative == "catalog.json":
|
||||
role = "visual-evidence-catalog"
|
||||
schema_version = CATALOG_SCHEMA
|
||||
elif relative == "profile.json":
|
||||
role = "predeclared-native-risk-review-profile"
|
||||
schema_version = "missioncore.m48q-native-risk-case-mining-profile/v1"
|
||||
elif relative == "detector-profile.json":
|
||||
role = "qualified-native-detector-profile"
|
||||
schema_version = "missioncore.rf-detr-native-risk-shadow-profile/v0"
|
||||
elif relative == "worker-result.json":
|
||||
role = "upstream-worker-case-mining-result"
|
||||
schema_version = "missioncore.m48q-native-risk-case-mining-result/v1"
|
||||
elif relative == "cases.jsonl":
|
||||
media_type = "application/x-ndjson"
|
||||
role = "native-risk-review-case-ledger"
|
||||
schema_version = CASE_SCHEMA
|
||||
elif relative.endswith(".jpg"):
|
||||
media_type = "image/jpeg"
|
||||
role = "visual-evidence-native-raw-fisheye-frame"
|
||||
artifacts.append(
|
||||
{
|
||||
"role": role,
|
||||
"path": relative,
|
||||
"byte_length": path.stat().st_size,
|
||||
"sha256": sha256_path(path),
|
||||
"media_type": media_type,
|
||||
"schema_version": schema_version,
|
||||
}
|
||||
)
|
||||
return artifacts
|
||||
|
||||
|
||||
def _read_object(path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
value: object = json.loads(path.read_text("utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise M48QNativeRiskQualityLabError(f"invalid JSON evidence: {path.name}") from exc
|
||||
if not isinstance(value, dict):
|
||||
raise M48QNativeRiskQualityLabError(f"JSON evidence must be an object: {path.name}")
|
||||
return value
|
||||
|
||||
|
||||
def _read_cases(path: Path) -> list[dict[str, Any]]:
|
||||
cases: list[dict[str, Any]] = []
|
||||
try:
|
||||
with path.open("r", encoding="utf-8") as stream:
|
||||
for line in stream:
|
||||
value: object = json.loads(line)
|
||||
if not isinstance(value, dict):
|
||||
raise M48QNativeRiskQualityLabError("M4.8Q case ledger row is not an object")
|
||||
cases.append(value)
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise M48QNativeRiskQualityLabError("M4.8Q case ledger cannot be read") from exc
|
||||
return cases
|
||||
Reference in New Issue
Block a user