from __future__ import annotations import hashlib import json import math import os import re import secrets from collections.abc import Callable from datetime import UTC, datetime from pathlib import Path from typing import Annotated, Any, Final import numpy as np from fastapi import APIRouter, Header, HTTPException, Query from fastapi import Path as PathParameter from pydantic import BaseModel, ConfigDict, Field from k1link.simulation import ( QualificationRun, QualificationRunIntegrityError, QualificationRunNotFoundError, QualificationRunStore, ) from k1link.simulation.worker_gateway import ( STATUS_SCHEMA, PolygonWorkerGateway, SimulationWorkerGatewayError, SimulationWorkerRejectedError, SimulationWorkerUnavailableError, UnixSocketWorkerGateway, ) POLYGON_RUNS_ROOT_ENV: Final = "MISSIONCORE_POLYGON_RUNS_ROOT" POLYGON_REVIEW_ROOT_ENV: Final = "MISSIONCORE_POLYGON_REVIEW_ROOT" POLYGON_WORKER_SOCKET_ENV: Final = "MISSIONCORE_POLYGON_WORKER_SOCKET" POLYGON_WORKER_CONTROL_ENV: Final = "MISSIONCORE_POLYGON_WORKER_CONTROL" MISSION_CORE_COMMIT_ENV: Final = "MISSIONCORE_COMMIT" CATALOG_SCHEMA: Final = "missioncore.polygon-run-catalog/v1" DETAIL_SCHEMA: Final = "missioncore.polygon-run-detail/v1" GROUND_QUALIFICATION_SCHEMA: Final = "missioncore.polygon-ground-qualification/v1" GROUND_FAILURE_PREVIEW_SCHEMA: Final = "missioncore.polygon-ground-failure-preview/v1" GROUND_REPORT_ARTIFACT_KIND: Final = "goose-ground-qualification-report" RELLIS_REPORT_ARTIFACT_KIND: Final = "rellis-ground-qualification-report" GROUND_FAILURE_ARTIFACT_KIND: Final = "goose-ground-qualification-failure-preview" GROUND_REPORT_SCHEMA: Final = "missioncore.goose-ground-qualification-report/v1" RELLIS_REPORT_SCHEMA: Final = "missioncore.rellis-ground-qualification-report/v1" GROUND_FAILURE_SCHEMA: Final = "missioncore.goose-ground-qualification-failure-preview/v1" GROUND_REVIEW_SCHEMA: Final = "missioncore.polygon-ground-review/v2" GROUND_REVIEW_FRAME_SCHEMA: Final = "missioncore.polygon-ground-review-frame/v1" GOOSE_REVIEW_PACK_SCHEMA: Final = "missioncore.goose-ground-review-pack/v1" GOOSE_REVIEW_FRAME_SCHEMA: Final = "missioncore.goose-ground-review-frame/v1" RELLIS_REVIEW_PACK_SCHEMA: Final = "missioncore.rellis-ground-review-pack/v1" RELLIS_REVIEW_FRAME_SCHEMA: Final = "missioncore.rellis-ground-review-frame/v1" MAX_QUALIFICATION_ARTIFACT_BYTES: Final = 32 * 1024**2 MAX_REVIEW_MANIFEST_BYTES: Final = 8 * 1024**2 MAX_REVIEW_FRAME_BYTES: Final = 2 * 1024**2 MAX_REVIEW_FRAMES: Final = 3_000 MAX_REVIEW_POINTS: Final = 20_000 COMMIT_PATTERN: Final = re.compile(r"^[a-f0-9]{40}$") GOOSE_FRAME_ID_PATTERN: Final = re.compile( r"^(?P[A-Za-z0-9][A-Za-z0-9_-]{0,95})" r"__(?P[0-9]{4})" r"_(?P[0-9]{16,20})$" ) READ_ONLY_LIMITATIONS: Final[tuple[str, ...]] = ( "Архивный UI-0 публикует только квалификационные доказательства; " "содержимое команд и mutation-маршруты в архиве отсутствуют.", "Артефакты представлены относительными метаданными; их содержимое не публикуется этим API.", "Терминальное состояние прогона не означает приёмку навигации, " "восприятия или физической безопасности.", ) RootProvider = Callable[[], Path | None] WorkerProvider = Callable[[], PolygonWorkerGateway | None] ControlProvider = Callable[[], bool] CommitProvider = Callable[[], str | None] class StartStockRoverRequest(BaseModel): model_config = ConfigDict(extra="forbid", frozen=True) scenario_id: str class AckermannCommandRequest(BaseModel): model_config = ConfigDict(extra="forbid", frozen=True) speed_mps: float = Field(ge=-1.5, le=1.5, allow_inf_nan=False) steering_normalized: float = Field(ge=-1.0, le=1.0, allow_inf_nan=False) def configured_polygon_runs_root() -> Path | None: raw = os.environ.get(POLYGON_RUNS_ROOT_ENV) if raw is None or not raw.strip(): return None return Path(raw.strip()).expanduser() def configured_polygon_worker() -> PolygonWorkerGateway | None: raw = os.environ.get(POLYGON_WORKER_SOCKET_ENV) if raw is None or not raw.strip(): return None return UnixSocketWorkerGateway(Path(raw.strip()).expanduser()) def configured_polygon_control() -> bool: return os.environ.get(POLYGON_WORKER_CONTROL_ENV, "").strip() == "internal-virtual-only" def configured_mission_core_commit() -> str | None: raw = os.environ.get(MISSION_CORE_COMMIT_ENV) if raw is None: return None normalized = raw.strip() return normalized if COMMIT_PATTERN.fullmatch(normalized) else None def build_polygon_router( *, root_provider: RootProvider = configured_polygon_runs_root, worker_provider: WorkerProvider = configured_polygon_worker, control_provider: ControlProvider = configured_polygon_control, commit_provider: CommitProvider = configured_mission_core_commit, ) -> APIRouter: router = APIRouter(prefix="/api/v1/polygon", tags=["polygon"]) @router.get("/runs") def list_polygon_runs( limit: Annotated[int, Query(ge=1, le=100)] = 20, ) -> dict[str, Any]: store = _open_read_only_store(root_provider) try: ordered = sorted( store.list_runs(), key=lambda run: (run.created_at_utc, run.run_id), reverse=True, ) except QualificationRunIntegrityError as exc: raise HTTPException( status_code=500, detail="Журнал прогонов Полигона не прошёл проверку целостности.", ) from exc return { "schema_version": CATALOG_SCHEMA, "access": "read-only", "items": [_run_summary(run) for run in ordered[:limit]], "total": len(ordered), "limitations": list(READ_ONLY_LIMITATIONS), } @router.get("/runs/{run_id}") def get_polygon_run( run_id: Annotated[ str, PathParameter(pattern=r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"), ], event_limit: Annotated[int, Query(ge=1, le=500)] = 200, ) -> dict[str, Any]: store = _open_read_only_store(root_provider) try: run = store.load(run_id) events = store.list_events(run_id) commands = store.list_commands(run_id) except QualificationRunNotFoundError as exc: raise HTTPException(status_code=404, detail="Прогон Полигона не найден.") from exc except QualificationRunIntegrityError as exc: raise HTTPException( status_code=500, detail="Доказательства прогона не прошли проверку целостности.", ) from exc visible_events = events[-event_limit:] return { "schema_version": DETAIL_SCHEMA, "access": "read-only", "run": { **_run_summary(run), "scenario_sha256": run.scenario_sha256, "profile_sha256": run.profile_sha256, "host_profile_sha256": run.host_profile_sha256, "seed": run.seed, "parent_run_id": run.parent_run_id, "providers": [provider.to_dict() for provider in run.providers], "authority": run.authority.to_dict(), }, "events": [event.to_dict() for event in visible_events], "events_total": len(events), "events_truncated": len(visible_events) != len(events), "commands": { "count": len(commands), "content_exposed": False, }, "artifacts": [artifact.to_dict() for artifact in run.artifacts], "limitations": list(READ_ONLY_LIMITATIONS), } @router.get("/runs/{run_id}/qualification") def get_polygon_ground_qualification( run_id: Annotated[ str, PathParameter(pattern=r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"), ], ) -> dict[str, Any]: store, run = _load_run(root_provider, run_id) report = _read_registered_json( store, run, artifact_kind=(GROUND_REPORT_ARTIFACT_KIND, RELLIS_REPORT_ARTIFACT_KIND), expected_schema=(GROUND_REPORT_SCHEMA, RELLIS_REPORT_SCHEMA), ) required = ( "identity_sha256", "source_id", "split", "frame_count", "aggregates", "degradations", "checks", "worst_frames", "decision", "safety", ) if any(key not in report for key in required): raise HTTPException( status_code=500, detail="Отчёт квалификации имеет неполный контракт.", ) return { "schema_version": GROUND_QUALIFICATION_SCHEMA, "access": "read-only", "run_id": run.run_id, **{key: report[key] for key in required}, } @router.get("/runs/{run_id}/qualification/failures/{frame_id}") def get_polygon_ground_failure_preview( run_id: Annotated[ str, PathParameter(pattern=r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"), ], frame_id: Annotated[ str, PathParameter(pattern=r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"), ], ) -> dict[str, Any]: store, run = _load_run(root_provider, run_id) preview = _read_registered_json( store, run, artifact_kind=GROUND_FAILURE_ARTIFACT_KIND, expected_schema=GROUND_FAILURE_SCHEMA, frame_id=frame_id, ) required = ( "source_id", "frame_id", "source_point_count", "point_count", "sampling", "points_xyz_m", "ground_truth_ground", "evaluated", "current_ground", "patchwork_ground", "current_disagreement", "patchwork_disagreement", "safety", ) if any(key not in preview for key in required) or preview["frame_id"] != frame_id: raise HTTPException( status_code=500, detail="Preview проблемного кадра имеет неполный контракт.", ) return { "schema_version": GROUND_FAILURE_PREVIEW_SCHEMA, "access": "read-only", "run_id": run.run_id, **{key: preview[key] for key in required}, } @router.get("/runs/{run_id}/qualification/review") def get_polygon_ground_review( run_id: Annotated[ str, PathParameter(pattern=r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"), ], ) -> dict[str, Any]: _, run = _load_run(root_provider, run_id) _, manifest = _read_ground_review_manifest(root_provider, run.run_id) return { "schema_version": GROUND_REVIEW_SCHEMA, "access": "read-only", "run_id": run.run_id, "identity_sha256": manifest["identity_sha256"], "source_id": manifest["source_id"], "frame_count": manifest["frame_count"], "preview_points": manifest["preview_points"], "frames": [ { **{ key: frame[key] for key in ( "sequence", "frame_id", "source_point_count", "point_count", "current", "patchworkpp", "ground_iou_delta", ) }, **_dataset_frame_identity(frame), } for frame in manifest["frames"] ], "safety": manifest["safety"], } @router.get("/runs/{run_id}/qualification/review/frames/{frame_id}") def get_polygon_ground_review_frame( run_id: Annotated[ str, PathParameter(pattern=r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"), ], frame_id: Annotated[ str, PathParameter(pattern=r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"), ], ) -> dict[str, Any]: _, run = _load_run(root_provider, run_id) pack_root, manifest = _read_ground_review_manifest(root_provider, run.run_id) matches = [frame for frame in manifest["frames"] if frame.get("frame_id") == frame_id] if len(matches) != 1: raise HTTPException( status_code=404, detail="Кадр визуального прогона не найден.", ) frame = matches[0] path = _verified_review_frame_path(pack_root, frame) try: with np.load(path, allow_pickle=False) as source: schema = str(source["schema"][0]) stored_frame_id = str(source["frame_id"][0]) source_point_count = int(source["source_point_count"][0]) xyz_cm = np.asarray(source["xyz_cm"]) remission = np.asarray(source["remission_u8"]) flags = np.asarray(source["flags"]) except (OSError, ValueError, KeyError, IndexError) as exc: raise HTTPException( status_code=500, detail="Кадр визуального прогона повреждён.", ) from exc point_count = int(frame["point_count"]) if ( schema not in { GOOSE_REVIEW_FRAME_SCHEMA, RELLIS_REVIEW_FRAME_SCHEMA, } or stored_frame_id != frame_id or source_point_count != frame["source_point_count"] or xyz_cm.dtype != np.dtype(" 15) ): raise HTTPException( status_code=500, detail="Кадр визуального прогона нарушает контракт.", ) evaluated = (flags & 1) != 0 ground_truth = (flags & 2) != 0 current = (flags & 4) != 0 patchwork = (flags & 8) != 0 return { "schema_version": GROUND_REVIEW_FRAME_SCHEMA, "access": "read-only", "run_id": run.run_id, "source_id": manifest["source_id"], "frame_id": frame_id, "source_point_count": source_point_count, "point_count": point_count, "sampling": "deterministic-even-index", "points_xyz_m": (xyz_cm.astype(np.float32) / 100.0).tolist(), "intensity_0_255": remission.tolist(), "ground_truth_ground": ground_truth.astype(np.uint8).tolist(), "evaluated": evaluated.astype(np.uint8).tolist(), "current_ground": current.astype(np.uint8).tolist(), "patchwork_ground": patchwork.astype(np.uint8).tolist(), "current_disagreement": (evaluated & (current != ground_truth)) .astype(np.uint8) .tolist(), "patchwork_disagreement": (evaluated & (patchwork != ground_truth)) .astype(np.uint8) .tolist(), "safety": manifest["safety"], } @router.get("/worker") def get_polygon_worker() -> dict[str, Any]: gateway = worker_provider() if gateway is None: return _unavailable_worker_status() try: status = gateway.status() except SimulationWorkerGatewayError: return _unavailable_worker_status() return { **status, "control_available": bool(status["control_available"] and control_provider()), } @router.get("/worker/live") def get_polygon_worker_live() -> dict[str, Any]: gateway = _required_worker(worker_provider) try: return gateway.live() except SimulationWorkerUnavailableError as exc: raise HTTPException( status_code=503, detail="Simulation Worker недоступен.", ) from exc except SimulationWorkerRejectedError as exc: raise HTTPException(status_code=409, detail=str(exc)) from exc except SimulationWorkerGatewayError as exc: raise HTTPException( status_code=502, detail="Simulation Worker вернул некорректное состояние.", ) from exc @router.post("/worker/runs") def start_polygon_worker_run( request: StartStockRoverRequest, idempotency_key: Annotated[ str, Header(alias="Idempotency-Key", min_length=1, max_length=160), ], ) -> dict[str, Any]: if request.scenario_id != "stock-rover-ackermann": raise HTTPException(status_code=422, detail="Сценарий Полигона не поддерживается.") gateway, commit = _control_context( worker_provider, control_provider, commit_provider, ) run_id = _new_run_id(commit) try: return gateway.start( run_id=run_id, mission_core_commit=commit, idempotency_key=idempotency_key, ) except SimulationWorkerUnavailableError as exc: raise HTTPException(status_code=503, detail="Simulation Worker недоступен.") from exc except SimulationWorkerRejectedError as exc: raise HTTPException(status_code=409, detail=str(exc)) from exc except SimulationWorkerGatewayError as exc: raise HTTPException( status_code=502, detail="Simulation Worker не подтвердил запуск.", ) from exc @router.post("/worker/runs/{run_id}/stop") def stop_polygon_worker_run( run_id: Annotated[ str, PathParameter(pattern=r"^[a-z0-9][a-z0-9-]{0,63}$"), ], idempotency_key: Annotated[ str, Header(alias="Idempotency-Key", min_length=1, max_length=160), ], ) -> dict[str, Any]: gateway, _ = _control_context( worker_provider, control_provider, commit_provider, ) try: return gateway.stop(run_id=run_id, idempotency_key=idempotency_key) except SimulationWorkerUnavailableError as exc: raise HTTPException(status_code=503, detail="Simulation Worker недоступен.") from exc except SimulationWorkerRejectedError as exc: raise HTTPException(status_code=409, detail=str(exc)) from exc except SimulationWorkerGatewayError as exc: raise HTTPException( status_code=502, detail="Simulation Worker не подтвердил остановку.", ) from exc @router.post("/worker/runs/{run_id}/commands") def command_polygon_worker_run( run_id: Annotated[ str, PathParameter(pattern=r"^[a-z0-9][a-z0-9-]{0,63}$"), ], request: AckermannCommandRequest, idempotency_key: Annotated[ str, Header(alias="Idempotency-Key", min_length=1, max_length=160), ], ) -> dict[str, Any]: gateway, _ = _control_context( worker_provider, control_provider, commit_provider, ) try: return gateway.command( run_id=run_id, speed_mps=request.speed_mps, steering_normalized=request.steering_normalized, idempotency_key=idempotency_key, ) except SimulationWorkerUnavailableError as exc: raise HTTPException(status_code=503, detail="Simulation Worker недоступен.") from exc except SimulationWorkerRejectedError as exc: raise HTTPException(status_code=409, detail=str(exc)) from exc except SimulationWorkerGatewayError as exc: raise HTTPException( status_code=502, detail="Simulation Worker не подтвердил команду ровера.", ) from exc return router def _open_read_only_store(root_provider: RootProvider) -> QualificationRunStore: root = root_provider() if root is None: raise HTTPException( status_code=503, detail="Источник журналов Полигона не настроен на этом экземпляре Mission Core.", ) if not root.is_absolute(): raise HTTPException( status_code=503, detail="Источник журналов Полигона настроен некорректно.", ) try: return QualificationRunStore(root, read_only=True) except QualificationRunIntegrityError as exc: raise HTTPException( status_code=503, detail="Источник журналов Полигона недоступен или настроен некорректно.", ) from exc def _load_run( root_provider: RootProvider, run_id: str, ) -> tuple[QualificationRunStore, QualificationRun]: store = _open_read_only_store(root_provider) try: return store, store.load(run_id) except QualificationRunNotFoundError as exc: raise HTTPException(status_code=404, detail="Прогон Полигона не найден.") from exc except QualificationRunIntegrityError as exc: raise HTTPException( status_code=500, detail="Доказательства прогона не прошли проверку целостности.", ) from exc def _read_registered_json( store: QualificationRunStore, run: QualificationRun, *, artifact_kind: str | tuple[str, ...], expected_schema: str | tuple[str, ...], frame_id: str | None = None, ) -> dict[str, Any]: artifact_kinds = (artifact_kind,) if isinstance(artifact_kind, str) else artifact_kind expected_schemas = ( (expected_schema,) if isinstance(expected_schema, str) else expected_schema ) candidates = [ artifact for artifact in run.artifacts if artifact.kind in artifact_kinds ] if frame_id is not None: candidates = [ artifact for artifact in candidates if Path(artifact.relative_path).stem == frame_id ] if len(candidates) != 1: raise HTTPException( status_code=404, detail="Квалификационный артефакт для этого прогона не найден.", ) artifact = candidates[0] if artifact.byte_length > MAX_QUALIFICATION_ARTIFACT_BYTES: raise HTTPException(status_code=500, detail="Квалификационный артефакт слишком велик.") run_root = (store.root / run.run_id).resolve() path = run_root / artifact.relative_path try: resolved = path.resolve(strict=True) resolved.relative_to(run_root) except (OSError, ValueError) as exc: raise HTTPException( status_code=500, detail="Путь квалификационного артефакта нарушает границу прогона.", ) from exc if ( path.is_symlink() or not resolved.is_file() or resolved.stat().st_size != artifact.byte_length ): raise HTTPException( status_code=500, detail="Квалификационный артефакт не прошёл проверку файла.", ) digest = hashlib.sha256(resolved.read_bytes()).hexdigest() if digest != artifact.sha256: raise HTTPException( status_code=500, detail="Квалификационный артефакт не прошёл проверку SHA-256.", ) try: value = json.loads(resolved.read_text(encoding="utf-8")) except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc: raise HTTPException( status_code=500, detail="Квалификационный артефакт не является допустимым JSON.", ) from exc if not isinstance(value, dict) or value.get("schema_version") not in expected_schemas: raise HTTPException( status_code=500, detail="Квалификационный артефакт имеет неизвестную схему.", ) return value def _read_ground_review_manifest( root_provider: RootProvider, run_id: str, ) -> tuple[Path, dict[str, Any]]: runs_root = root_provider() if runs_root is None or not runs_root.is_absolute(): raise HTTPException( status_code=503, detail="Источник визуальных прогонов не настроен.", ) configured = os.environ.get(POLYGON_REVIEW_ROOT_ENV, "").strip() review_root = ( Path(configured).expanduser() if configured else runs_root.parent / "polygon-review-packs" ) if not review_root.is_absolute(): raise HTTPException( status_code=503, detail="Источник визуальных прогонов настроен некорректно.", ) run_review_root = review_root / run_id try: resolved_review_root = review_root.resolve() resolved_run_root = run_review_root.resolve(strict=True) resolved_run_root.relative_to(resolved_review_root) except (OSError, ValueError) as exc: raise HTTPException( status_code=404, detail="Покадровый просмотр для прогона ещё не опубликован.", ) from exc candidates = sorted( path for path in resolved_run_root.iterdir() if path.is_dir() and not path.is_symlink() and re.fullmatch(r"review-[a-f0-9]{64}", path.name) and (path / "manifest.json").is_file() ) if not candidates: raise HTTPException( status_code=404, detail="Покадровый просмотр для прогона ещё не опубликован.", ) pack_root = candidates[-1] manifest_path = pack_root / "manifest.json" try: if manifest_path.is_symlink() or manifest_path.stat().st_size > MAX_REVIEW_MANIFEST_BYTES: raise OSError manifest = json.loads(manifest_path.read_text(encoding="utf-8")) except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc: raise HTTPException( status_code=500, detail="Манифест визуального прогона повреждён.", ) from exc frames = manifest.get("frames") if isinstance(manifest, dict) else None safety = manifest.get("safety") if isinstance(manifest, dict) else None if ( not isinstance(manifest, dict) or manifest.get("schema_version") not in {GOOSE_REVIEW_PACK_SCHEMA, RELLIS_REVIEW_PACK_SCHEMA} or manifest.get("source_run_id") != run_id or not isinstance(manifest.get("identity_sha256"), str) or not re.fullmatch(r"[a-f0-9]{64}", manifest["identity_sha256"]) or pack_root.name != f"review-{manifest['identity_sha256']}" or not isinstance(manifest.get("source_id"), str) or not isinstance(manifest.get("preview_points"), int) or not isinstance(frames, list) or not 1 <= len(frames) <= MAX_REVIEW_FRAMES or manifest.get("frame_count") != len(frames) or not isinstance(safety, dict) or safety.get("visualization_only") is not True or safety.get("navigation_or_safety_accepted") is not False ): raise HTTPException( status_code=500, detail="Манифест визуального прогона нарушает контракт.", ) seen: set[str] = set() for expected_sequence, frame in enumerate(frames): if ( not isinstance(frame, dict) or frame.get("sequence") != expected_sequence or not isinstance(frame.get("frame_id"), str) or not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,127}", frame["frame_id"]) or frame["frame_id"] in seen or not isinstance(frame.get("source_point_count"), int) or not isinstance(frame.get("point_count"), int) or not 1 <= frame["point_count"] <= MAX_REVIEW_POINTS or frame["source_point_count"] < frame["point_count"] or not isinstance(frame.get("relative_path"), str) or not isinstance(frame.get("sha256"), str) or not re.fullmatch(r"[a-f0-9]{64}", frame["sha256"]) or not isinstance(frame.get("byte_length"), int) or not 1 <= frame["byte_length"] <= MAX_REVIEW_FRAME_BYTES or not _valid_review_metrics(frame.get("current")) or not _valid_review_metrics(frame.get("patchworkpp")) or not isinstance(frame.get("ground_iou_delta"), (int, float)) or not math.isfinite(frame["ground_iou_delta"]) or not -1 <= frame["ground_iou_delta"] <= 1 ): raise HTTPException( status_code=500, detail="Индекс кадров визуального прогона нарушает контракт.", ) seen.add(frame["frame_id"]) return pack_root, manifest def _dataset_frame_identity(frame: dict[str, Any]) -> dict[str, str | int | None]: frame_id = frame["frame_id"] explicit_sequence = frame.get("dataset_sequence_id") explicit_number = frame.get("dataset_frame_number") if explicit_sequence is not None or explicit_number is not None: if ( not isinstance(explicit_sequence, str) or re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_-]{0,95}", explicit_sequence) is None or not isinstance(explicit_number, int) or explicit_number < 0 ): raise HTTPException( status_code=500, detail="Идентичность dataset-кадра нарушает контракт.", ) return { "dataset_sequence_id": explicit_sequence, "dataset_frame_number": explicit_number, "sensor_timestamp_ns": None, } match = GOOSE_FRAME_ID_PATTERN.fullmatch(frame_id) if match is None: raise HTTPException( status_code=500, detail="Идентичность GOOSE-кадра нарушает контракт.", ) return { "dataset_sequence_id": match.group("dataset_sequence_id"), "dataset_frame_number": int(match.group("dataset_frame_number")), # Nanosecond Unix time exceeds JavaScript's safe integer range. "sensor_timestamp_ns": match.group("sensor_timestamp_ns"), } def _valid_review_metrics(value: object) -> bool: if not isinstance(value, dict): return False for key in ( "ground_iou", "natural_ground_recall", "obstacle_non_ground_recall", ): metric = value.get(key) if ( not isinstance(metric, (int, float)) or not math.isfinite(metric) or not 0 <= metric <= 1 ): return False latency = value.get("latency_ms") return isinstance(latency, (int, float)) and math.isfinite(latency) and latency >= 0 def _verified_review_frame_path(pack_root: Path, frame: dict[str, Any]) -> Path: relative = Path(frame["relative_path"]) if relative.is_absolute() or relative.parts[:1] != ("frames",): raise HTTPException( status_code=500, detail="Кадр визуального прогона нарушает границу.", ) path = pack_root / relative try: resolved = path.resolve(strict=True) resolved.relative_to(pack_root) if ( path.is_symlink() or not resolved.is_file() or resolved.stat().st_size != frame["byte_length"] ): raise OSError except (OSError, ValueError) as exc: raise HTTPException( status_code=500, detail="Файл визуального прогона нарушает границу.", ) from exc if hashlib.sha256(resolved.read_bytes()).hexdigest() != frame["sha256"]: raise HTTPException( status_code=500, detail="Кадр визуального прогона не прошёл SHA-256.", ) return resolved def _run_summary(run: QualificationRun) -> dict[str, Any]: return { "run_id": run.run_id, "episode_id": run.episode_id, "kind": run.kind.value, "state": run.state.value, "created_at_utc": run.created_at_utc, "started_at_utc": run.started_at_utc, "ended_at_utc": run.ended_at_utc, "terminal_reason": run.terminal_reason, "scenario_generation": run.scenario_generation, "profile_generation": run.profile_generation, "mission_core_commit": run.mission_core_commit, "host_profile_id": run.host_profile_id, "reproducibility_tier": run.reproducibility_tier.value, "clock_domain": run.clock_domain, "revision": run.revision, "provider_ids": [provider.identifier for provider in run.providers], "artifact_count": len(run.artifacts), } def _required_worker(provider: WorkerProvider) -> PolygonWorkerGateway: gateway = provider() if gateway is None: raise HTTPException( status_code=503, detail="Simulation Worker не зарегистрирован на этом экземпляре Mission Core.", ) return gateway def _control_context( worker_provider: WorkerProvider, control_provider: ControlProvider, commit_provider: CommitProvider, ) -> tuple[PolygonWorkerGateway, str]: if not control_provider(): raise HTTPException( status_code=403, detail="Lifecycle Полигона отключён на этом экземпляре Mission Core.", ) gateway = _required_worker(worker_provider) commit = commit_provider() if commit is None: raise HTTPException( status_code=503, detail="Mission Core не привязан к точной Git-ревизии.", ) return gateway, commit def _unavailable_worker_status() -> dict[str, Any]: return { "schema_version": STATUS_SCHEMA, "worker_id": "mission-gpu-s1", "transport": "unix", "mode": "simulation", "available": False, "control_available": False, "active_run_id": None, "run_state": None, "active_provider_ids": [], "provider_profile": None, "isolation": { "network": "unavailable", "process_identity": "missioncore", "artifact_policy": "d-only", }, "authority": { "scope": "virtual-only", "actuator_authority": False, "direct_actuator_setpoints_allowed": False, }, } def _new_run_id(commit: str) -> str: stamp = datetime.now(UTC).strftime("%Y%m%dt%H%M%Sz").lower() return f"s1c-{commit[:7]}-{stamp}-{secrets.token_hex(3)}"