feat(observatory): add laboratory setup preflight

This commit is contained in:
DCCONSTRUCTIONS
2026-08-30 22:21:58 +03:00
parent be83283ee0
commit 8d5aeb0533
15 changed files with 2491 additions and 9 deletions
+19
View File
@@ -0,0 +1,19 @@
"""Observation-only laboratory orchestration contracts."""
from k1link.observatory.canonical_result import (
is_admitted_observatory_recorded_result,
)
from k1link.observatory.setups import (
LABORATORY_SETUP_CATALOG_SCHEMA,
LABORATORY_SETUP_REGISTRY_SCHEMA,
LaboratorySetupRegistry,
LaboratorySetupRegistryError,
)
__all__ = [
"LABORATORY_SETUP_CATALOG_SCHEMA",
"LABORATORY_SETUP_REGISTRY_SCHEMA",
"LaboratorySetupRegistry",
"LaboratorySetupRegistryError",
"is_admitted_observatory_recorded_result",
]
@@ -0,0 +1,93 @@
"""Exact admission for the current canonical recorded Observatory result."""
from __future__ import annotations
import re
from typing import Final
from k1link.sessions.models import SessionSummary
_SHA256: Final = re.compile(r"^[a-f0-9]{64}$")
_CANONICAL_RESULT: Final = re.compile(r"^lab-v1-vegetation-shadow-([a-f0-9]{64})$")
_CANONICAL_SOURCE_SESSION_ID: Final = "20260828T130511Z_viewer_live"
_CANONICAL_PIPELINE_ID: Final = (
"ravnoves004tree-full-eomt-ddrnet-recorded-review/v1"
)
def is_admitted_observatory_recorded_result(
summary: SessionSummary,
*,
expected_result_id: str,
expected_source_session_id: str,
expected_result_kind: str,
) -> bool:
"""Return true only for the exact capability-owned canonical projection."""
lab = summary.lab
match = _CANONICAL_RESULT.fullmatch(expected_result_id)
if (
lab is None
or match is None
or expected_source_session_id != _CANONICAL_SOURCE_SESSION_ID
or summary.session_id != expected_result_id
or summary.origin != "missioncore.lab-instance/v1"
or summary.status != "ready"
or not summary.replayable
or lab.session_id != expected_result_id
or lab.result_id != expected_result_id
or lab.source_session_id != expected_source_session_id
or lab.lab_id != "LAB V1"
or lab.result_kind != expected_result_kind
or lab.result_kind != "recorded-perception-qualification"
or lab.config_sha256 is not None
or not isinstance(lab.source_result_id, str)
or _CANONICAL_RESULT.fullmatch(lab.source_result_id) is None
or lab.replay_capability is None
):
return False
capability = lab.replay_capability.as_dict()
provenance = lab.provenance
evidence_identity = match.group(1)
if set(provenance) != {
"schema_version",
"evidence_identity_sha256",
"result_document_sha256",
"replay_capability",
"authority",
"method",
}:
return False
result_document_sha256 = provenance.get("result_document_sha256")
if (
provenance.get("schema_version")
!= "missioncore.canonical-recorded-lab-projection/v1"
or provenance.get("evidence_identity_sha256") != evidence_identity
or not isinstance(result_document_sha256, str)
or _SHA256.fullmatch(result_document_sha256) is None
or provenance.get("replay_capability") != capability
or provenance.get("authority")
!= {
"commands_enabled": False,
"navigation_or_safety_accepted": False,
"actuation_accepted": False,
}
):
return False
return provenance.get("method") == {
"schema_version": "missioncore.laboratory-method/v1",
"completeness": "legacy-partial",
"execution_class": "ai-inference",
"pipeline_id": _CANONICAL_PIPELINE_ID,
"components": [
{
"kind": "source",
"name": "sealed full-route LAB result",
"version": "missioncore.lab-v1-vegetation-shadow/v1",
"role": "immutable Session catalog projection",
"identity_sha256": evidence_identity,
}
],
}
+524
View File
@@ -0,0 +1,524 @@
"""Strict setup catalog for the Observatory laboratory configurator.
The catalog can represent both a real historical RunDefinition and an exact
sealed result that predates RunDefinitions. It never upgrades the latter into
a fabricated executable configuration.
"""
from __future__ import annotations
import hashlib
import json
import re
from dataclasses import dataclass
from pathlib import Path, PurePosixPath
from typing import Any, Final, Literal
from k1link.sessions.models import SessionSummary
LABORATORY_SETUP_REGISTRY_SCHEMA: Final = (
"missioncore.observatory-laboratory-setup-registry/v1"
)
LABORATORY_SETUP_CATALOG_SCHEMA: Final = (
"missioncore.observatory-laboratory-setup-catalog/v1"
)
_MAX_REGISTRY_BYTES: Final = 256 * 1024
_MAX_CONFIGURATION_BYTES: Final = 4 * 1024 * 1024
_IDENTIFIER: Final = re.compile(r"^[a-z][a-z0-9-]{2,95}$")
_RESULT_ID: Final = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{2,159}$")
_SHA256: Final = re.compile(r"^[a-f0-9]{64}$")
_MODALITIES: Final = frozenset({"point-cloud", "trajectory", "video"})
_AUTHORITY: Final = {
"commands_enabled": False,
"actuation_allowed": False,
"navigation_or_safety_accepted": False,
"production_accepted": False,
}
SetupOrigin = Literal["archived-definition", "existing-result"]
ResultAccess = Literal["legacy-lab", "observatory", "evidence-only"]
class LaboratorySetupRegistryError(ValueError):
"""A setup registry or its immutable references are invalid."""
@dataclass(frozen=True, slots=True)
class _ConfigurationReference:
role: str
path: PurePosixPath
sha256: str
@dataclass(frozen=True, slots=True)
class _RunDefinition:
definition_id: str
version: int
work_id: str
configuration: tuple[_ConfigurationReference, ...]
@dataclass(frozen=True, slots=True)
class _Executor:
contour_id: str
state: Literal["not-installed"]
reason_code: str
reason: str
@dataclass(frozen=True, slots=True)
class _PreservedResult:
result_id: str
result_kind: str
relation: str
access: ResultAccess
created_at_utc: str
@dataclass(frozen=True, slots=True)
class LaboratorySetup:
setup_id: str
display_name: str
description: str
origin: SetupOrigin
source_session_id: str
source_label: str
required_modalities: tuple[str, ...]
run_definition: _RunDefinition | None
executor: _Executor
preserved_results: tuple[_PreservedResult, ...]
def project(
self,
source: SessionSummary,
*,
available_observatory_result_ids: frozenset[str],
) -> dict[str, object]:
reasons: list[dict[str, str]] = []
if source.lab is not None:
reasons.append(_reason("source-is-lab", "Нужна исходная, а не LAB-сессия."))
if source.session_id != self.source_session_id:
reasons.append(
_reason(
"source-session-not-admitted",
"Эта версия привязана к другой запечатанной исходной сессии.",
)
)
if source.display_name != self.source_label:
reasons.append(
_reason(
"source-label-mismatch",
"Идентичность источника не совпадает с сохранённым сетапом.",
)
)
if source.status != "ready":
reasons.append(
_reason(
"source-not-ready",
"Исходная сессия не находится в запечатанном состоянии ready.",
)
)
if not source.replayable:
reasons.append(
_reason(
"source-not-replayable",
"Для исходной сессии недоступно воспроизводимое чтение.",
)
)
missing = sorted(set(self.required_modalities) - set(source.modalities))
if missing:
reasons.append(
_reason(
"required-modalities-unavailable",
"Не хватает каналов: " + ", ".join(missing) + ".",
)
)
compatible = not reasons
ready_result_ids = [
result.result_id
for result in self.preserved_results
if result.access == "observatory"
and result.result_id in available_observatory_result_ids
]
if compatible and ready_result_ids:
action = "open-existing"
action_reason = "Точный запечатанный результат уже опубликован в Обсерватории."
elif compatible and any(
result.access == "legacy-lab" for result in self.preserved_results
):
action = "open-legacy"
action_reason = "Точный результат сохранён в legacy LAB."
else:
action = "blocked"
action_reason = reasons[0]["message"] if reasons else self.executor.reason
return {
"setup_id": self.setup_id,
"display_name": self.display_name,
"description": self.description,
"origin": self.origin,
"source": {
"session_id": self.source_session_id,
"label": self.source_label,
"required_modalities": list(self.required_modalities),
},
"run_definition": _project_definition(self, self.run_definition),
"compatibility": {"compatible": compatible, "reasons": reasons},
"executor": {
"contour_id": self.executor.contour_id,
"state": self.executor.state,
"reason_code": self.executor.reason_code,
"reason": self.executor.reason,
},
"preserved_results": [
{
"result_id": result.result_id,
"result_kind": result.result_kind,
"relation": result.relation,
"access": result.access,
"created_at_utc": result.created_at_utc,
"observatory_projection_available": (
result.result_id in available_observatory_result_ids
),
}
for result in self.preserved_results
],
"preflight": {
"outcome": "existing" if action == "open-existing" else "blocked",
"action": action,
"reason": action_reason,
"submission_allowed": False,
"existing_result_ids": ready_result_ids,
},
"authority": dict(_AUTHORITY),
}
@dataclass(frozen=True, slots=True)
class LaboratorySetupRegistry:
setups: tuple[LaboratorySetup, ...]
def __post_init__(self) -> None:
setup_ids = [setup.setup_id for setup in self.setups]
if not setup_ids or len(setup_ids) != len(set(setup_ids)):
raise LaboratorySetupRegistryError("setup IDs must be unique and non-empty")
definition_versions = [
(setup.run_definition.definition_id, setup.run_definition.version)
for setup in self.setups
if setup.run_definition is not None
]
if len(definition_versions) != len(set(definition_versions)):
raise LaboratorySetupRegistryError("RunDefinition versions must be unique")
result_ids = [
result.result_id
for setup in self.setups
for result in setup.preserved_results
]
if len(result_ids) != len(set(result_ids)):
raise LaboratorySetupRegistryError("preserved result IDs must be globally unique")
@classmethod
def from_file(
cls,
path: Path,
*,
repository_root: Path,
) -> LaboratorySetupRegistry:
candidate = path.expanduser().absolute()
root = repository_root.expanduser().resolve(strict=True)
if candidate.is_symlink() or not candidate.is_file():
raise LaboratorySetupRegistryError("setup registry must be a regular file")
if candidate.stat().st_size > _MAX_REGISTRY_BYTES:
raise LaboratorySetupRegistryError("setup registry is too large")
try:
document = _object(json.loads(candidate.read_text(encoding="utf-8")), "registry")
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
raise LaboratorySetupRegistryError("setup registry is unreadable") from exc
_exact_keys(document, {"schema_version", "setups"}, "registry")
if document["schema_version"] != LABORATORY_SETUP_REGISTRY_SCHEMA:
raise LaboratorySetupRegistryError("setup registry schema is invalid")
rows = document["setups"]
if not isinstance(rows, list):
raise LaboratorySetupRegistryError("setup registry rows must be an array")
return cls(tuple(_setup(row, repository_root=root) for row in rows))
def catalog(
self,
source: SessionSummary,
*,
available_observatory_result_ids: frozenset[str] = frozenset(),
) -> dict[str, object]:
return {
"schema_version": LABORATORY_SETUP_CATALOG_SCHEMA,
"source_session_id": source.session_id,
"setups": [
setup.project(
source,
available_observatory_result_ids=available_observatory_result_ids,
)
for setup in self.setups
],
"authority": dict(_AUTHORITY),
}
def setup(self, setup_id: str) -> LaboratorySetup:
for setup in self.setups:
if setup.setup_id == setup_id:
return setup
raise KeyError(setup_id)
@property
def observatory_result_ids(self) -> frozenset[str]:
return frozenset(
result.result_id
for setup in self.setups
for result in setup.preserved_results
if result.access == "observatory"
)
def observatory_result_kind(self, result_id: str) -> str | None:
for setup in self.setups:
for result in setup.preserved_results:
if result.access == "observatory" and result.result_id == result_id:
return result.result_kind
return None
def _setup(value: object, *, repository_root: Path) -> LaboratorySetup:
row = _object(value, "setup")
_exact_keys(
row,
{
"setup_id",
"display_name",
"description",
"origin",
"source",
"run_definition",
"executor",
"preserved_results",
"authority",
},
"setup",
)
origin = row["origin"]
if not isinstance(origin, str) or origin not in (
"archived-definition",
"existing-result",
):
raise LaboratorySetupRegistryError("setup origin is invalid")
source = _object(row["source"], "source")
_exact_keys(source, {"session_id", "label", "required_modalities"}, "source")
modalities = _text_array(source["required_modalities"], "required_modalities")
if not set(modalities).issubset(_MODALITIES):
raise LaboratorySetupRegistryError("setup modalities are invalid")
definition = (
None
if row["run_definition"] is None
else _definition(row["run_definition"], repository_root=repository_root)
)
if (origin == "archived-definition") != (definition is not None):
raise LaboratorySetupRegistryError("setup origin and RunDefinition disagree")
if row["authority"] != _AUTHORITY:
raise LaboratorySetupRegistryError("setup authority must remain observation-only")
results = row["preserved_results"]
if not isinstance(results, list) or not results:
raise LaboratorySetupRegistryError("preserved results must be non-empty")
preserved = tuple(_preserved_result(item) for item in results)
if len({item.result_id for item in preserved}) != len(preserved):
raise LaboratorySetupRegistryError("preserved result IDs must be unique")
return LaboratorySetup(
setup_id=_identifier(row["setup_id"], "setup_id"),
display_name=_text(row["display_name"], "display_name"),
description=_text(row["description"], "description"),
origin=origin,
source_session_id=_text(source["session_id"], "source session_id"),
source_label=_text(source["label"], "source label"),
required_modalities=modalities,
run_definition=definition,
executor=_executor(row["executor"]),
preserved_results=preserved,
)
def _definition(value: object, *, repository_root: Path) -> _RunDefinition:
row = _object(value, "RunDefinition")
_exact_keys(
row,
{"definition_id", "version", "work_id", "configuration"},
"RunDefinition",
)
version = row["version"]
if not isinstance(version, int) or isinstance(version, bool) or version < 1:
raise LaboratorySetupRegistryError("RunDefinition version is invalid")
configuration = row["configuration"]
if not isinstance(configuration, list) or not configuration:
raise LaboratorySetupRegistryError("RunDefinition configuration is empty")
references = tuple(
_configuration(item, repository_root=repository_root) for item in configuration
)
if len({item.role for item in references}) != len(references):
raise LaboratorySetupRegistryError("configuration roles must be unique")
return _RunDefinition(
definition_id=_identifier(row["definition_id"], "definition_id"),
version=version,
work_id=_identifier(row["work_id"], "work_id"),
configuration=references,
)
def _configuration(value: object, *, repository_root: Path) -> _ConfigurationReference:
row = _object(value, "configuration")
_exact_keys(row, {"role", "path", "sha256"}, "configuration")
relative = PurePosixPath(_text(row["path"], "configuration path"))
if relative.is_absolute() or ".." in relative.parts:
raise LaboratorySetupRegistryError("configuration path is unsafe")
candidate = repository_root.joinpath(*relative.parts)
if candidate.is_symlink() or not candidate.is_file():
raise LaboratorySetupRegistryError("configuration file is unavailable")
try:
resolved = candidate.resolve(strict=True)
if resolved.stat().st_size > _MAX_CONFIGURATION_BYTES:
raise LaboratorySetupRegistryError("configuration file is too large")
except OSError as exc:
raise LaboratorySetupRegistryError("configuration file is unavailable") from exc
if not resolved.is_relative_to(repository_root):
raise LaboratorySetupRegistryError("configuration escaped repository root")
digest = _digest(row["sha256"], "configuration sha256")
if _file_sha256(resolved) != digest:
raise LaboratorySetupRegistryError("configuration digest changed")
return _ConfigurationReference(
role=_identifier(row["role"], "configuration role"),
path=relative,
sha256=digest,
)
def _executor(value: object) -> _Executor:
row = _object(value, "executor")
_exact_keys(row, {"contour_id", "state", "reason_code", "reason"}, "executor")
if row["state"] != "not-installed":
raise LaboratorySetupRegistryError("only fail-closed executors are admitted in v1")
return _Executor(
contour_id=_identifier(row["contour_id"], "contour_id"),
state="not-installed",
reason_code=_identifier(row["reason_code"], "reason_code"),
reason=_text(row["reason"], "executor reason"),
)
def _preserved_result(value: object) -> _PreservedResult:
row = _object(value, "preserved result")
_exact_keys(
row,
{"result_id", "result_kind", "relation", "access", "created_at_utc"},
"preserved result",
)
result_id = _text(row["result_id"], "result_id")
if _RESULT_ID.fullmatch(result_id) is None:
raise LaboratorySetupRegistryError("result_id is invalid")
access = row["access"]
if not isinstance(access, str) or access not in (
"legacy-lab",
"observatory",
"evidence-only",
):
raise LaboratorySetupRegistryError("result access is invalid")
return _PreservedResult(
result_id=result_id,
result_kind=_identifier(row["result_kind"], "result_kind"),
relation=_identifier(row["relation"], "result relation"),
access=access,
created_at_utc=_text(row["created_at_utc"], "created_at_utc"),
)
def _project_definition(
setup: LaboratorySetup,
definition: _RunDefinition | None,
) -> dict[str, object] | None:
if definition is None:
return None
identity = {
"schema_version": "missioncore.observatory-run-definition/v1",
"definition_id": definition.definition_id,
"version": definition.version,
"work_id": definition.work_id,
"source": {
"session_id": setup.source_session_id,
"label": setup.source_label,
"required_modalities": list(setup.required_modalities),
},
"configuration": [
{"role": item.role, "sha256": item.sha256}
for item in definition.configuration
],
"authority": dict(_AUTHORITY),
}
return {
**identity,
"definition_sha256": hashlib.sha256(_canonical_json(identity)).hexdigest(),
}
def _canonical_json(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
def _file_sha256(path: Path) -> str:
digest = hashlib.sha256()
try:
with path.open("rb") as stream:
while chunk := stream.read(128 * 1024):
digest.update(chunk)
except OSError as exc:
raise LaboratorySetupRegistryError("configuration file is unavailable") from exc
return digest.hexdigest()
def _reason(code: str, message: str) -> dict[str, str]:
return {"code": code, "message": message}
def _object(value: object, label: str) -> dict[str, Any]:
if not isinstance(value, dict) or not all(isinstance(key, str) for key in value):
raise LaboratorySetupRegistryError(f"{label} must be an object")
return value
def _exact_keys(value: dict[str, Any], expected: set[str], label: str) -> None:
if set(value) != expected:
raise LaboratorySetupRegistryError(f"{label} keys are invalid")
def _text(value: object, label: str) -> str:
if not isinstance(value, str) or not value.strip() or value != value.strip():
raise LaboratorySetupRegistryError(f"{label} must be non-empty text")
return value
def _identifier(value: object, label: str) -> str:
text = _text(value, label)
if _IDENTIFIER.fullmatch(text) is None:
raise LaboratorySetupRegistryError(f"{label} is invalid")
return text
def _digest(value: object, label: str) -> str:
text = _text(value, label)
if _SHA256.fullmatch(text) is None:
raise LaboratorySetupRegistryError(f"{label} is invalid")
return text
def _text_array(value: object, label: str) -> tuple[str, ...]:
if not isinstance(value, list) or not value:
raise LaboratorySetupRegistryError(f"{label} must be a non-empty array")
result = tuple(_text(item, label) for item in value)
if len(result) != len(set(result)):
raise LaboratorySetupRegistryError(f"{label} must contain unique values")
return result