feat(lab): separate evidence catalogs and record methods

This commit is contained in:
DCCONSTRUCTIONS
2026-07-26 17:56:23 +03:00
parent e9ff29297b
commit 4db00b53f7
17 changed files with 670 additions and 24 deletions
+96
View File
@@ -83,6 +83,53 @@ class PublishedCameraEgoMotionLabInstance:
build: CameraEgoMotionBuild
def _laboratory_method(
*,
pipeline_id: str,
execution_class: str,
algorithm: str,
profile_sha256: str | None,
source_result_id: str,
) -> dict[str, object]:
source_identity: str | None = source_result_id.rsplit("-", 1)[-1]
if len(source_identity) != 64 or any(
character not in "0123456789abcdef" for character in source_identity
):
source_identity = None
return {
"schema_version": "missioncore.laboratory-method/v1",
# E19-E26 predate the method manifest. The publisher now records the
# exact known identities, but does not invent historical model/runtime
# versions that were absent from their original accepted evidence.
"completeness": "legacy-partial",
"execution_class": execution_class,
"pipeline_id": pipeline_id,
"components": [
{
"kind": "source",
"name": "immutable accepted upstream result",
"version": "content-addressed",
"role": "read-only input evidence",
"identity_sha256": source_identity,
},
{
"kind": "algorithm",
"name": algorithm,
"version": pipeline_id,
"role": "laboratory derivative",
"identity_sha256": profile_sha256,
},
{
"kind": "tool",
"name": "Mission Core LAB publisher",
"version": "missioncore.lab-instance/v1",
"role": "immutable catalog projection",
"identity_sha256": _sha256(Path(__file__).resolve(strict=True)),
},
],
}
def publish_integrated_lab_instance(
*,
repository_root: Path,
@@ -156,6 +203,13 @@ def publish_integrated_lab_instance(
run_created_at_utc=source.created_at_utc,
provenance={
"schema_version": "missioncore.integrated-lab-publication/v1",
"method": _laboratory_method(
pipeline_id="integrated-perception/v1",
execution_class="hybrid",
algorithm="camera semantics + LiDAR metric fusion",
profile_sha256=profile_sha256,
source_result_id=source.result_id,
),
"storage_mode": "hard-linked-immutable-payloads",
"source_job_id": source.job.job_id,
"projected_job_id": lab_job.job_id,
@@ -263,6 +317,13 @@ def publish_e21_lab_instance(
include_recorded_media=False,
provenance={
"schema_version": "missioncore.e21-lab-publication/v1",
"method": _laboratory_method(
pipeline_id="realtime-envelope/v1",
execution_class="hybrid",
algorithm="bounded real-time perception replay",
profile_sha256=str(e21_report["identity"]["profile_sha256"]),
source_result_id=str(e21_document["result_id"]),
),
"storage_mode": "bounded-derived-replay-and-projection",
"e21_result_id": e21_document["result_id"],
"worker_result_id": worker_document["result_id"],
@@ -361,6 +422,13 @@ def publish_e22_lab_instance(
include_recorded_media=False,
provenance={
"schema_version": "missioncore.e22-lab-publication/v1",
"method": _laboratory_method(
pipeline_id="temporal-stability/v1",
execution_class="hybrid",
algorithm="bounded temporal 2D/3D/semantic stabilization",
profile_sha256=build.profile_sha256,
source_result_id=source.result_id,
),
"storage_mode": "bounded-derived-replay-and-temporal-projection",
"source_result_id": source.result_id,
"source_lab_session_id": (None if source_lab is None else source_lab.session_id),
@@ -487,6 +555,13 @@ def publish_e23_lab_instance(
include_recorded_media=False,
provenance={
"schema_version": "missioncore.e23-lab-publication/v1",
"method": _laboratory_method(
pipeline_id="inline-temporal-stability/v1",
execution_class="hybrid",
algorithm="warm-worker inline temporal stabilization",
profile_sha256=profile_sha256,
source_result_id=str(worker_document["result_id"]),
),
"storage_mode": "bounded-inline-worker-result-and-immutable-source-replay",
"worker_result_id": worker_document["result_id"],
"source_report_sha256": _sha256(source_path),
@@ -589,6 +664,13 @@ def publish_e24_lab_instance(
include_recorded_media=False,
provenance={
"schema_version": "missioncore.e24-lab-publication/v1",
"method": _laboratory_method(
pipeline_id="world-motion/v1",
execution_class="hybrid",
algorithm="world-frame motion tracking",
profile_sha256=build.profile_sha256,
source_result_id=source.result_id,
),
"storage_mode": "bounded-world-frame-tracking-and-immutable-source-replay",
"source_result_id": source.result_id,
"source_lab_session_id": (None if source_lab is None else source_lab.session_id),
@@ -695,6 +777,13 @@ def publish_e25_lab_instance(
include_recorded_media=False,
provenance={
"schema_version": "missioncore.e25-lab-publication/v1",
"method": _laboratory_method(
pipeline_id="persistent-support-motion/v1",
execution_class="hybrid",
algorithm="persistent occupied-support tracking",
profile_sha256=build.profile_sha256,
source_result_id=source.result_id,
),
"storage_mode": "bounded-persistent-support-and-immutable-source-replay",
"source_result_id": source.result_id,
"source_lab_session_id": (None if source_lab is None else source_lab.session_id),
@@ -826,6 +915,13 @@ def publish_e26_lab_instance(
include_recorded_media=False,
provenance={
"schema_version": "missioncore.e26-lab-publication/v1",
"method": _laboratory_method(
pipeline_id="camera-ego-motion-fusion/v1",
execution_class="hybrid",
algorithm="KB4 multiview ego-motion + persistent LiDAR support",
profile_sha256=build.profile_sha256,
source_result_id=lidar_source.result_id,
),
"storage_mode": (
"bounded-camera-ego-motion-and-immutable-lidar-source-replay"
),
@@ -1375,6 +1375,7 @@ def k1_local_surface_catalog_item(model: K1LocalSurfaceV1) -> dict[str, object]:
"status": model.report["status"],
"source": model.report["source"],
"surface_model": model.report["surface_model"],
"producer_sha256": model.identity["producer_sha256"],
"occupancy_policy": model.report["occupancy_policy"],
"metrics": model.report["metrics"],
"anchors": model.report["anchors"],
+86 -9
View File
@@ -11,7 +11,7 @@ import threading
from collections.abc import Iterator
from contextlib import contextmanager
from pathlib import Path
from typing import Any, cast
from typing import Any, Literal, cast
from uuid import uuid4
from k1link.artifacts import utc_now_iso
@@ -45,6 +45,8 @@ LAB_ARCHIVE_ID = "missioncore.lab-instances"
LAB_ORIGIN = "missioncore.lab-instance/v1"
LAB_ID_PATTERN = re.compile(r"^LAB [A-Z][A-Z0-9._-]{0,31}$")
SHA256_PATTERN = re.compile(r"^[a-f0-9]{64}$")
LAB_METHOD_SCHEMA = "missioncore.laboratory-method/v1"
SessionScope = Literal["all", "source", "laboratory"]
SCHEMA_SQL = """
CREATE TABLE IF NOT EXISTS observation_sessions (
@@ -205,27 +207,51 @@ class SessionStore:
connection.commit()
return tuple(imported)
def list_recent(self, *, limit: int = 20, cursor: str | None = None) -> SessionPage:
def list_recent(
self,
*,
limit: int = 20,
cursor: str | None = None,
scope: SessionScope = "all",
) -> SessionPage:
if not 1 <= limit <= 100:
raise ValueError("limit must be within 1..100")
scope_clause = {
"all": "1 = 1",
"source": (
"NOT EXISTS (SELECT 1 FROM observation_lab_instances AS lab "
"WHERE lab.session_id = sessions.session_id)"
),
"laboratory": (
"EXISTS (SELECT 1 FROM observation_lab_instances AS lab "
"WHERE lab.session_id = sessions.session_id)"
),
}.get(scope)
if scope_clause is None:
raise ValueError("scope must be all, source, or laboratory")
parameters: list[object] = []
where = ""
where = f"WHERE {scope_clause}" # noqa: S608 - closed static scope clauses
with self._connect() as connection:
if cursor is not None:
_validate_identifier(cursor, "session cursor")
cursor_row = connection.execute(
"SELECT started_at_utc, session_id FROM observation_sessions "
"WHERE session_id = ?",
"SELECT sessions.started_at_utc, sessions.session_id "
"FROM observation_sessions AS sessions "
f"WHERE {scope_clause} AND sessions.session_id = ?", # noqa: S608
(cursor,),
).fetchone()
if cursor_row is None:
raise SessionNotFoundError("observation session cursor was not found")
where = "WHERE (COALESCE(started_at_utc, ''), session_id) < (COALESCE(?, ''), ?)"
where += (
" AND (COALESCE(sessions.started_at_utc, ''), sessions.session_id) "
"< (COALESCE(?, ''), ?)"
)
parameters.extend((cursor_row["started_at_utc"], cursor_row["session_id"]))
parameters.append(limit + 1)
rows = connection.execute(
f"SELECT * FROM observation_sessions {where} " # noqa: S608 - static clause
"ORDER BY COALESCE(started_at_utc, '') DESC, session_id DESC LIMIT ?",
f"SELECT sessions.* FROM observation_sessions AS sessions {where} " # noqa: S608
"ORDER BY COALESCE(sessions.started_at_utc, '') DESC, "
"sessions.session_id DESC LIMIT ?",
parameters,
).fetchall()
lab_rows = (
@@ -366,7 +392,9 @@ class SessionStore:
or duration_seconds <= 0
):
raise ValueError("LAB duration must be a positive finite value")
serialized_provenance = _serialize_provenance(provenance or {})
normalized_provenance = provenance or {}
_validate_lab_method(normalized_provenance)
serialized_provenance = _serialize_provenance(normalized_provenance)
published_at = utc_now_iso()
with self._lock, self._connect() as connection:
@@ -1078,6 +1106,55 @@ def _serialize_provenance(value: dict[str, Any]) -> str:
return serialized
def _validate_lab_method(provenance: dict[str, Any]) -> None:
method = provenance.get("method")
if not isinstance(method, dict):
raise ValueError("LAB provenance must include a method manifest")
if method.get("schema_version") != LAB_METHOD_SCHEMA:
raise ValueError("LAB method schema is invalid")
if method.get("completeness") not in {"complete", "legacy-partial"}:
raise ValueError("LAB method completeness is invalid")
if method.get("execution_class") not in {
"deterministic",
"ai-inference",
"hybrid",
}:
raise ValueError("LAB method execution class is invalid")
pipeline_id = method.get("pipeline_id")
if (
not isinstance(pipeline_id, str)
or not pipeline_id.strip()
or len(pipeline_id) > 160
):
raise ValueError("LAB method pipeline id is invalid")
components = method.get("components")
if not isinstance(components, list) or not 1 <= len(components) <= 32:
raise ValueError("LAB method components are invalid")
identities = 0
for component in components:
if not isinstance(component, dict):
raise ValueError("LAB method component is invalid")
if component.get("kind") not in {"source", "tool", "model", "algorithm", "runtime"}:
raise ValueError("LAB method component kind is invalid")
for field in ("name", "version", "role"):
value = component.get(field)
if not isinstance(value, str) or not value.strip() or len(value) > 240:
raise ValueError(f"LAB method component {field} is invalid")
identity = component.get("identity_sha256")
if identity is not None:
if not isinstance(identity, str) or SHA256_PATTERN.fullmatch(identity) is None:
raise ValueError("LAB method component identity is invalid")
identities += 1
if identities == 0:
raise ValueError("LAB method must bind at least one component identity")
if method["completeness"] == "complete" and any(
component.get("identity_sha256") is None
for component in components
if component.get("kind") in {"model", "algorithm"}
):
raise ValueError("complete LAB method must identify every model and algorithm")
def _require_utc_timestamp(value: str, field: str) -> None:
from datetime import datetime
+2 -1
View File
@@ -312,10 +312,11 @@ def build_session_router(
def list_observation_sessions(
limit: int = Query(default=20, ge=1, le=100),
cursor: str | None = Query(default=None, max_length=128),
scope: Literal["all", "source", "laboratory"] = "all",
) -> dict[str, Any]:
try:
_refresh_catalog(catalog_refresher)
page = store.list_recent(limit=limit, cursor=cursor)
page = store.list_recent(limit=limit, cursor=cursor, scope=scope)
return {
"items": [
{