feat(lab): seal native risk review evidence
This commit is contained in:
@@ -153,8 +153,8 @@ from k1link.web.runtime_readiness import (
|
||||
build_runtime_readiness,
|
||||
)
|
||||
from k1link.web.session_api import build_session_router
|
||||
from k1link.web.simulation_world_provider_api import build_simulation_world_provider_router
|
||||
from k1link.web.simulation_projects_api import build_simulation_projects_router
|
||||
from k1link.web.simulation_world_provider_api import build_simulation_world_provider_router
|
||||
from k1link.web.system_telemetry_api import build_system_telemetry_router
|
||||
from k1link.web.viewer_diagnostics_api import build_viewer_diagnostics_router
|
||||
|
||||
@@ -980,6 +980,13 @@ app.include_router(
|
||||
/ "m48t-risk-quality"
|
||||
/ "lab-results"
|
||||
),
|
||||
native_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "m48t-risk-quality"
|
||||
/ "native-lab-results"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Read-only API for the sealed M4.8T quality and temporal LAB."""
|
||||
"""Read-only API for the M4.8T legacy quality and M4.8Q native review lifecycle."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -8,7 +8,7 @@ import json
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path, PurePosixPath
|
||||
from typing import Any, Final
|
||||
from typing import Any, Final, Literal
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi.responses import FileResponse
|
||||
@@ -18,30 +18,61 @@ from k1link.laboratory.evidence_report import (
|
||||
LaboratoryEvidenceReportError,
|
||||
verify_laboratory_evidence_result,
|
||||
)
|
||||
from k1link.laboratory.m48q_native_risk_quality_lab import (
|
||||
CATALOG_SCHEMA as NATIVE_CATALOG_SCHEMA,
|
||||
)
|
||||
from k1link.laboratory.m48q_native_risk_quality_lab import (
|
||||
LAB_SCHEMA as NATIVE_LAB_SCHEMA,
|
||||
)
|
||||
from k1link.laboratory.m48q_native_risk_quality_lab import (
|
||||
REPORT_SCHEMA as NATIVE_REPORT_SCHEMA,
|
||||
)
|
||||
from k1link.laboratory.m48q_native_risk_quality_lab import (
|
||||
RESULT_PREFIX as NATIVE_RESULT_PREFIX,
|
||||
)
|
||||
from k1link.laboratory.m48t_risk_quality_lab import (
|
||||
CATALOG_SCHEMA,
|
||||
LAB_SCHEMA,
|
||||
REPORT_SCHEMA,
|
||||
RESULT_PREFIX,
|
||||
CATALOG_SCHEMA as LEGACY_CATALOG_SCHEMA,
|
||||
)
|
||||
from k1link.laboratory.m48t_risk_quality_lab import (
|
||||
LAB_SCHEMA as LEGACY_LAB_SCHEMA,
|
||||
)
|
||||
from k1link.laboratory.m48t_risk_quality_lab import (
|
||||
REPORT_SCHEMA as LEGACY_REPORT_SCHEMA,
|
||||
)
|
||||
from k1link.laboratory.m48t_risk_quality_lab import (
|
||||
RESULT_PREFIX as LEGACY_RESULT_PREFIX,
|
||||
)
|
||||
from k1link.perception.fixed_class_detector_tournament import false_authority
|
||||
|
||||
RootProvider = Callable[[], Path | None]
|
||||
RESULT_ID: Final = re.compile(rf"^{re.escape(RESULT_PREFIX)}[a-f0-9]{{64}}$")
|
||||
CASE_ID: Final = re.compile(r"^[0-9]{12}$")
|
||||
VIEW_SCHEMA: Final = "missioncore.m48t-risk-quality-temporal-view/v1"
|
||||
CATALOG_VIEW_SCHEMA: Final = "missioncore.m48t-risk-quality-temporal-catalog/v1"
|
||||
_DEFINITION: Final = LaboratoryEvidenceDefinition(
|
||||
Variant = Literal["legacy", "native"]
|
||||
LEGACY_RESULT_ID: Final = re.compile(rf"^{re.escape(LEGACY_RESULT_PREFIX)}[a-f0-9]{{64}}$")
|
||||
NATIVE_RESULT_ID: Final = re.compile(rf"^{re.escape(NATIVE_RESULT_PREFIX)}[a-f0-9]{{64}}$")
|
||||
LEGACY_CASE_ID: Final = re.compile(r"^[0-9]{12}$")
|
||||
NATIVE_CASE_ID: Final = re.compile(r"^[0-9]{6}$")
|
||||
LEGACY_VIEW_SCHEMA: Final = "missioncore.m48t-risk-quality-temporal-view/v1"
|
||||
NATIVE_VIEW_SCHEMA: Final = "missioncore.m48q-native-risk-quality-view/v1"
|
||||
CATALOG_VIEW_SCHEMA: Final = "missioncore.m48t-risk-quality-lifecycle-catalog/v1"
|
||||
_LEGACY_DEFINITION: Final = LaboratoryEvidenceDefinition(
|
||||
work_id="m48t-risk-quality-temporal",
|
||||
runtime_relative_root=PurePosixPath("m48t-risk-quality/lab-results"),
|
||||
result_id_prefix="m48t-risk-quality-temporal-lab",
|
||||
document_name="manifest.json",
|
||||
result_schema_version=LAB_SCHEMA,
|
||||
result_schema_version=LEGACY_LAB_SCHEMA,
|
||||
)
|
||||
_NATIVE_DEFINITION: Final = LaboratoryEvidenceDefinition(
|
||||
work_id="m48t-risk-quality-temporal",
|
||||
runtime_relative_root=PurePosixPath("m48t-risk-quality/native-lab-results"),
|
||||
result_id_prefix="m48q-native-risk-quality-lab",
|
||||
document_name="manifest.json",
|
||||
result_schema_version=NATIVE_LAB_SCHEMA,
|
||||
)
|
||||
|
||||
|
||||
def build_m48t_risk_quality_lab_router(
|
||||
*, root_provider: RootProvider = lambda: None
|
||||
*,
|
||||
root_provider: RootProvider = lambda: None,
|
||||
native_root_provider: RootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(
|
||||
prefix="/api/v1/laboratory/m48t/risk-quality",
|
||||
@@ -50,15 +81,23 @@ def build_m48t_risk_quality_lab_router(
|
||||
|
||||
@router.get("/results")
|
||||
def list_results(limit: int = Query(default=1, ge=1, le=10)) -> dict[str, object]:
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
return _empty_catalog(False)
|
||||
candidates = _candidates(root)
|
||||
configured = False
|
||||
candidates: list[tuple[Path, Variant]] = []
|
||||
providers: tuple[tuple[RootProvider, Variant, re.Pattern[str]], ...] = (
|
||||
(root_provider, "legacy", LEGACY_RESULT_ID),
|
||||
(native_root_provider, "native", NATIVE_RESULT_ID),
|
||||
)
|
||||
for provider, variant, pattern in providers:
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
continue
|
||||
configured = True
|
||||
candidates.extend((item, variant) for item in _candidates(root, pattern))
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
for candidate in candidates:
|
||||
for candidate, variant in candidates:
|
||||
try:
|
||||
items.append(_project_result(candidate))
|
||||
items.append(_project_result(candidate, variant))
|
||||
except RuntimeError:
|
||||
invalid_total += 1
|
||||
items.sort(
|
||||
@@ -67,7 +106,7 @@ def build_m48t_risk_quality_lab_router(
|
||||
)
|
||||
return {
|
||||
"schema_version": CATALOG_VIEW_SCHEMA,
|
||||
"configured": True,
|
||||
"configured": configured,
|
||||
"items": items[:limit],
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
@@ -77,18 +116,37 @@ def build_m48t_risk_quality_lab_router(
|
||||
@router.get("/results/{result_id}")
|
||||
def get_result(result_id: str) -> dict[str, object]:
|
||||
try:
|
||||
return _project_result(_resolve_candidate(root_provider, result_id))
|
||||
candidate, variant = _resolve_candidate(
|
||||
root_provider,
|
||||
native_root_provider,
|
||||
result_id,
|
||||
)
|
||||
return _project_result(candidate, variant)
|
||||
except RuntimeError:
|
||||
raise HTTPException(status_code=404, detail="M4.8T result not found") from None
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="M4.8T/M4.8Q result not found",
|
||||
) from None
|
||||
|
||||
@router.get("/results/{result_id}/review/{case_id}.jpg")
|
||||
def get_review_image(result_id: str, case_id: str) -> FileResponse:
|
||||
if CASE_ID.fullmatch(case_id) is None:
|
||||
raise HTTPException(status_code=404, detail="M4.8T review case not found")
|
||||
try:
|
||||
loaded = _load_result(_resolve_candidate(root_provider, result_id))
|
||||
candidate, variant = _resolve_candidate(
|
||||
root_provider,
|
||||
native_root_provider,
|
||||
result_id,
|
||||
)
|
||||
invalid_case = (variant == "legacy" and LEGACY_CASE_ID.fullmatch(case_id) is None) or (
|
||||
variant == "native" and NATIVE_CASE_ID.fullmatch(case_id) is None
|
||||
)
|
||||
if invalid_case:
|
||||
raise RuntimeError("case identity is invalid")
|
||||
loaded = _load_result(candidate, variant)
|
||||
except RuntimeError:
|
||||
raise HTTPException(status_code=404, detail="M4.8T result not found") from None
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="M4.8T/M4.8Q review case not found",
|
||||
) from None
|
||||
descriptor = next(
|
||||
(
|
||||
item
|
||||
@@ -98,8 +156,10 @@ def build_m48t_risk_quality_lab_router(
|
||||
None,
|
||||
)
|
||||
if not isinstance(descriptor, dict):
|
||||
raise HTTPException(status_code=404, detail="M4.8T review case not found")
|
||||
candidate = loaded["root"]
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="M4.8T/M4.8Q review case not found",
|
||||
)
|
||||
path = (candidate / str(descriptor["path"])).resolve()
|
||||
if (
|
||||
not path.is_relative_to(candidate)
|
||||
@@ -108,7 +168,10 @@ def build_m48t_risk_quality_lab_router(
|
||||
or descriptor.get("byte_length") != path.stat().st_size
|
||||
or descriptor.get("sha256") != _sha256(path)
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="M4.8T review case not found")
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="M4.8T/M4.8Q review case not found",
|
||||
)
|
||||
return FileResponse(
|
||||
path,
|
||||
media_type="image/jpeg",
|
||||
@@ -122,8 +185,12 @@ def build_m48t_risk_quality_lab_router(
|
||||
return router
|
||||
|
||||
|
||||
def _project_result(candidate: Path) -> dict[str, object]:
|
||||
loaded = _load_result(candidate)
|
||||
def _project_result(candidate: Path, variant: Variant) -> dict[str, object]:
|
||||
loaded = _load_result(candidate, variant)
|
||||
return _project_native(loaded) if variant == "native" else _project_legacy(loaded)
|
||||
|
||||
|
||||
def _project_legacy(loaded: dict[str, Any]) -> dict[str, object]:
|
||||
manifest = loaded["manifest"]
|
||||
report = loaded["report"]
|
||||
catalog = loaded["catalog"]
|
||||
@@ -143,7 +210,8 @@ def _project_result(candidate: Path) -> dict[str, object]:
|
||||
for item in catalog["cases"]
|
||||
]
|
||||
return {
|
||||
"schema_version": VIEW_SCHEMA,
|
||||
"schema_version": LEGACY_VIEW_SCHEMA,
|
||||
"variant": "legacy-coco-quality",
|
||||
"result_id": result_id,
|
||||
"created_at_utc": manifest["created_at_utc"],
|
||||
"status": manifest["status"],
|
||||
@@ -165,26 +233,97 @@ def _project_result(candidate: Path) -> dict[str, object]:
|
||||
}
|
||||
|
||||
|
||||
def _load_result(candidate: Path) -> dict[str, Any]:
|
||||
def _project_native(loaded: dict[str, Any]) -> dict[str, object]:
|
||||
manifest = loaded["manifest"]
|
||||
report = loaded["report"]
|
||||
catalog = loaded["catalog"]
|
||||
result_id = str(manifest["result_id"])
|
||||
review_cases = [
|
||||
{
|
||||
"case_id": item["case_id"],
|
||||
"sequence": item["sequence"],
|
||||
"frame_id": item["frame_id"],
|
||||
"evidence_time_ns": item["evidence_time_ns"],
|
||||
"image_url": (
|
||||
f"/api/v1/laboratory/m48t/risk-quality/results/{result_id}"
|
||||
f"/review/{item['case_id']}.jpg"
|
||||
),
|
||||
"media_type": item["media_type"],
|
||||
"width": item["width"],
|
||||
"height": item["height"],
|
||||
"byte_length": item["byte_length"],
|
||||
"sha256": item["sha256"],
|
||||
"geometric_resampling": item["geometric_resampling"],
|
||||
"selection_buckets": copy.deepcopy(item["selection_buckets"]),
|
||||
"comparison": copy.deepcopy(item["comparison"]),
|
||||
"proposals": copy.deepcopy(item["proposals"]),
|
||||
}
|
||||
for item in catalog["cases"]
|
||||
]
|
||||
return {
|
||||
"schema_version": NATIVE_VIEW_SCHEMA,
|
||||
"variant": "native-risk-review",
|
||||
"result_id": result_id,
|
||||
"created_at_utc": manifest["created_at_utc"],
|
||||
"status": manifest["status"],
|
||||
"source": copy.deepcopy(report["source"]),
|
||||
"configuration": copy.deepcopy(report["configuration"]),
|
||||
"method": copy.deepcopy(report["method"]),
|
||||
"execution": copy.deepcopy(report["execution"]),
|
||||
"metrics": copy.deepcopy(report["metrics"]),
|
||||
"acceptance": copy.deepcopy(report["acceptance"]),
|
||||
"decision": copy.deepcopy(report["decision"]),
|
||||
"limitations": copy.deepcopy(report["limitations"]),
|
||||
"review": {
|
||||
"source_raster": copy.deepcopy(catalog["source_raster"]),
|
||||
"overlay": copy.deepcopy(catalog["overlay"]),
|
||||
"cases": review_cases,
|
||||
},
|
||||
"ground_truth": False,
|
||||
"authority": copy.deepcopy(report["authority"]),
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
|
||||
def _load_result(candidate: Path, variant: Variant) -> dict[str, Any]:
|
||||
pattern = NATIVE_RESULT_ID if variant == "native" else LEGACY_RESULT_ID
|
||||
definition = _NATIVE_DEFINITION if variant == "native" else _LEGACY_DEFINITION
|
||||
if (
|
||||
not candidate.is_dir()
|
||||
or candidate.is_symlink()
|
||||
or RESULT_ID.fullmatch(candidate.name) is None
|
||||
or pattern.fullmatch(candidate.name) is None
|
||||
):
|
||||
raise RuntimeError("M4.8T result candidate is invalid")
|
||||
raise RuntimeError("M4.8 result candidate is invalid")
|
||||
try:
|
||||
verify_laboratory_evidence_result(_DEFINITION, candidate)
|
||||
verify_laboratory_evidence_result(definition, candidate)
|
||||
manifest = _read_object(candidate / "manifest.json")
|
||||
report = _read_object(candidate / "report.json")
|
||||
catalog = _read_object(candidate / "catalog.json")
|
||||
except (LaboratoryEvidenceReportError, OSError, ValueError) as exc:
|
||||
raise RuntimeError("M4.8T result integrity failed") from exc
|
||||
raise RuntimeError("M4.8 result integrity failed") from exc
|
||||
if variant == "native":
|
||||
_validate_native(candidate, manifest, report, catalog)
|
||||
else:
|
||||
_validate_legacy(candidate, manifest, report, catalog)
|
||||
return {
|
||||
"root": candidate,
|
||||
"manifest": manifest,
|
||||
"report": report,
|
||||
"catalog": catalog,
|
||||
}
|
||||
|
||||
|
||||
def _validate_legacy(
|
||||
candidate: Path,
|
||||
manifest: dict[str, Any],
|
||||
report: dict[str, Any],
|
||||
catalog: dict[str, Any],
|
||||
) -> None:
|
||||
identity = manifest.get("identity")
|
||||
if (
|
||||
manifest.get("schema_version") != LAB_SCHEMA
|
||||
manifest.get("schema_version") != LEGACY_LAB_SCHEMA
|
||||
or manifest.get("result_id") != candidate.name
|
||||
or manifest.get("status")
|
||||
!= "complete-quality-gate-failed-temporal-invariant-passed"
|
||||
or manifest.get("status") != "complete-quality-gate-failed-temporal-invariant-passed"
|
||||
or manifest.get("completed") is not True
|
||||
or manifest.get("bounded_question_accepted") is not False
|
||||
or manifest.get("ground_truth") is not False
|
||||
@@ -194,47 +333,143 @@ def _load_result(candidate: Path) -> dict[str, Any]:
|
||||
or not candidate.name.endswith(str(manifest.get("identity_sha256")))
|
||||
or identity.get("authority") != false_authority()
|
||||
or manifest.get("authority") != false_authority()
|
||||
or report.get("schema_version") != REPORT_SCHEMA
|
||||
or report.get("schema_version") != LEGACY_REPORT_SCHEMA
|
||||
or report.get("result_id") != candidate.name
|
||||
or report.get("authority") != false_authority()
|
||||
or report.get("decision", {}).get("quality_accepted") is not False
|
||||
or report.get("decision", {}).get("temporal_invariant_passed") is not True
|
||||
or catalog.get("schema_version") != CATALOG_SCHEMA
|
||||
or catalog.get("schema_version") != LEGACY_CATALOG_SCHEMA
|
||||
or catalog.get("result_id") != candidate.name
|
||||
or catalog.get("case_count") != 16
|
||||
or not isinstance(catalog.get("cases"), list)
|
||||
or len(catalog["cases"]) != 16
|
||||
or any(not _valid_case(item) for item in catalog["cases"])
|
||||
or any(not _valid_legacy_case(item) for item in catalog["cases"])
|
||||
):
|
||||
raise RuntimeError("M4.8T result contract changed")
|
||||
return {"root": candidate, "manifest": manifest, "report": report, "catalog": catalog}
|
||||
|
||||
|
||||
def _valid_case(value: object) -> bool:
|
||||
def _validate_native(
|
||||
candidate: Path,
|
||||
manifest: dict[str, Any],
|
||||
report: dict[str, Any],
|
||||
catalog: dict[str, Any],
|
||||
) -> None:
|
||||
identity = manifest.get("identity")
|
||||
cases = catalog.get("cases")
|
||||
if (
|
||||
manifest.get("schema_version") != NATIVE_LAB_SCHEMA
|
||||
or manifest.get("result_id") != candidate.name
|
||||
or manifest.get("status") != "complete-review-ready-quality-not-adjudicated"
|
||||
or manifest.get("completed") is not True
|
||||
or manifest.get("bounded_question_accepted") is not True
|
||||
or manifest.get("ground_truth") is not False
|
||||
or not isinstance(manifest.get("created_at_utc"), str)
|
||||
or not isinstance(identity, dict)
|
||||
or manifest.get("identity_sha256") != _canonical_sha256(identity)
|
||||
or not candidate.name.endswith(str(manifest.get("identity_sha256")))
|
||||
or identity.get("authority") != false_authority()
|
||||
or manifest.get("authority") != false_authority()
|
||||
or report.get("schema_version") != NATIVE_REPORT_SCHEMA
|
||||
or report.get("result_id") != candidate.name
|
||||
or report.get("authority") != false_authority()
|
||||
or report.get("decision", {}).get("review_ready") is not True
|
||||
or report.get("decision", {}).get("quality_evaluated") is not False
|
||||
or report.get("decision", {}).get("candidate_accepted") is not False
|
||||
or catalog.get("schema_version") != NATIVE_CATALOG_SCHEMA
|
||||
or catalog.get("result_id") != candidate.name
|
||||
or catalog.get("case_count") != 24
|
||||
or catalog.get("ground_truth") is not False
|
||||
or not isinstance(cases, list)
|
||||
or len(cases) != 24
|
||||
or any(not _valid_native_case(item) for item in cases)
|
||||
):
|
||||
raise RuntimeError("M4.8Q result contract changed")
|
||||
|
||||
|
||||
def _valid_legacy_case(value: object) -> bool:
|
||||
return (
|
||||
isinstance(value, dict)
|
||||
and isinstance(value.get("case_id"), str)
|
||||
and CASE_ID.fullmatch(value["case_id"]) is not None
|
||||
and LEGACY_CASE_ID.fullmatch(value["case_id"]) is not None
|
||||
and value.get("image_id") == int(value["case_id"])
|
||||
and value.get("path") == f"review/review-{value['case_id']}.jpg"
|
||||
and value.get("media_type") == "image/jpeg"
|
||||
and isinstance(value.get("byte_length"), int)
|
||||
and _valid_file_proof(value)
|
||||
)
|
||||
|
||||
|
||||
def _valid_native_case(value: object) -> bool:
|
||||
if not isinstance(value, dict) or not isinstance(value.get("case_id"), str):
|
||||
return False
|
||||
case_id = value["case_id"]
|
||||
proposals = value.get("proposals")
|
||||
return (
|
||||
NATIVE_CASE_ID.fullmatch(case_id) is not None
|
||||
and value.get("sequence") == int(case_id)
|
||||
and value.get("frame_id") == f"frame-{case_id}"
|
||||
and isinstance(value.get("evidence_time_ns"), int)
|
||||
and value.get("path") == f"cases/frame-{case_id}.jpg"
|
||||
and value.get("media_type") == "image/jpeg"
|
||||
and value.get("width") == 800
|
||||
and value.get("height") == 600
|
||||
and value.get("geometric_resampling") is False
|
||||
and isinstance(value.get("selection_buckets"), list)
|
||||
and isinstance(value.get("comparison"), dict)
|
||||
and isinstance(proposals, list)
|
||||
and len(proposals) > 0
|
||||
and all(_valid_native_proposal(item) for item in proposals)
|
||||
and _valid_file_proof(value)
|
||||
)
|
||||
|
||||
|
||||
def _valid_native_proposal(value: object) -> bool:
|
||||
if not isinstance(value, dict):
|
||||
return False
|
||||
box = value.get("box_xyxy")
|
||||
return (
|
||||
isinstance(value.get("proposal_id"), str)
|
||||
and isinstance(value.get("class_name"), str)
|
||||
and isinstance(value.get("risk_family"), str)
|
||||
and isinstance(value.get("score"), (int, float))
|
||||
and not isinstance(value.get("score"), bool)
|
||||
and 0.25 <= value["score"] <= 1
|
||||
and isinstance(box, list)
|
||||
and len(box) == 4
|
||||
and all(isinstance(item, (int, float)) and not isinstance(item, bool) for item in box)
|
||||
and 0 <= box[0] < box[2] <= 800
|
||||
and 0 <= box[1] < box[3] <= 600
|
||||
)
|
||||
|
||||
|
||||
def _valid_file_proof(value: dict[str, Any]) -> bool:
|
||||
return (
|
||||
isinstance(value.get("byte_length"), int)
|
||||
and value["byte_length"] > 0
|
||||
and isinstance(value.get("sha256"), str)
|
||||
and re.fullmatch(r"[a-f0-9]{64}", value["sha256"]) is not None
|
||||
)
|
||||
|
||||
|
||||
def _resolve_candidate(provider: RootProvider, result_id: str) -> Path:
|
||||
if RESULT_ID.fullmatch(result_id) is None:
|
||||
raise HTTPException(status_code=404, detail="M4.8T result not found")
|
||||
def _resolve_candidate(
|
||||
legacy_provider: RootProvider,
|
||||
native_provider: RootProvider,
|
||||
result_id: str,
|
||||
) -> tuple[Path, Variant]:
|
||||
provider: RootProvider
|
||||
variant: Variant
|
||||
if NATIVE_RESULT_ID.fullmatch(result_id) is not None:
|
||||
provider, variant = native_provider, "native"
|
||||
elif LEGACY_RESULT_ID.fullmatch(result_id) is not None:
|
||||
provider, variant = legacy_provider, "legacy"
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail="M4.8 result not found")
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="M4.8T result not found")
|
||||
raise HTTPException(status_code=404, detail="M4.8 result not found")
|
||||
candidate = (root / result_id).resolve()
|
||||
if candidate.parent != root or candidate.is_symlink() or not candidate.is_dir():
|
||||
raise HTTPException(status_code=404, detail="M4.8T result not found")
|
||||
return candidate
|
||||
raise HTTPException(status_code=404, detail="M4.8 result not found")
|
||||
return candidate, variant
|
||||
|
||||
|
||||
def _configured_root(provider: RootProvider) -> Path | None:
|
||||
@@ -251,29 +486,18 @@ def _configured_root(provider: RootProvider) -> Path | None:
|
||||
return root if root.is_dir() else None
|
||||
|
||||
|
||||
def _candidates(root: Path) -> list[Path]:
|
||||
def _candidates(root: Path, pattern: re.Pattern[str]) -> list[Path]:
|
||||
return sorted(
|
||||
(
|
||||
item
|
||||
for item in root.iterdir()
|
||||
if item.is_dir() and not item.is_symlink() and RESULT_ID.fullmatch(item.name)
|
||||
if item.is_dir() and not item.is_symlink() and pattern.fullmatch(item.name)
|
||||
),
|
||||
key=lambda item: item.stat().st_mtime_ns,
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
|
||||
def _empty_catalog(configured: bool) -> dict[str, object]:
|
||||
return {
|
||||
"schema_version": CATALOG_VIEW_SCHEMA,
|
||||
"configured": configured,
|
||||
"items": [],
|
||||
"candidate_total": 0,
|
||||
"invalid_total": 0,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
|
||||
def _read_object(path: Path) -> dict[str, Any]:
|
||||
value = json.loads(path.read_text("utf-8"))
|
||||
if not isinstance(value, dict):
|
||||
|
||||
Reference in New Issue
Block a user