feat: add Polygon UI-0 run view

This commit is contained in:
DCCONSTRUCTIONS
2026-07-24 19:02:10 +03:00
parent a78c8c83f5
commit b790f29d59
18 changed files with 2193 additions and 43 deletions
+157
View File
@@ -0,0 +1,157 @@
from __future__ import annotations
import os
from collections.abc import Callable
from pathlib import Path
from typing import Annotated, Any, Final
from fastapi import APIRouter, HTTPException, Query
from fastapi import Path as PathParameter
from k1link.simulation import (
QualificationRun,
QualificationRunIntegrityError,
QualificationRunNotFoundError,
QualificationRunStore,
)
POLYGON_RUNS_ROOT_ENV: Final = "MISSIONCORE_POLYGON_RUNS_ROOT"
CATALOG_SCHEMA: Final = "missioncore.polygon-run-catalog/v1"
DETAIL_SCHEMA: Final = "missioncore.polygon-run-detail/v1"
READ_ONLY_LIMITATIONS: Final[tuple[str, ...]] = (
"UI-0 публикует только квалификационные доказательства; "
"lifecycle-операции и команды отсутствуют.",
"Артефакты представлены относительными метаданными; их содержимое не публикуется этим API.",
"Терминальное состояние прогона не означает приёмку навигации, "
"восприятия или физической безопасности.",
)
RootProvider = Callable[[], Path | None]
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 build_polygon_router(
*,
root_provider: RootProvider = configured_polygon_runs_root,
) -> 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),
}
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 _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),
}