refactor(platform): freeze laboratory and telemetry boundaries

This commit is contained in:
DCCONSTRUCTIONS
2026-08-05 12:29:06 +03:00
parent 6d0abbc569
commit 1b3e0b3406
22 changed files with 1657 additions and 148 deletions
+50 -2
View File
@@ -8,6 +8,7 @@ No sink is created implicitly and telemetry never grants command authority.
from __future__ import annotations
import hashlib
import json
import os
import re
@@ -30,6 +31,8 @@ SAFE_TOPIC_IDENTIFIER: Final = re.compile(
)
MAX_TEXT_LENGTH: Final = 256
MAX_PAYLOAD_BYTES: Final = 1024 * 1024
DEFAULT_JOURNAL_MAX_BYTES: Final = 64 * 1024 * 1024
DEFAULT_JOURNAL_MAX_SEGMENTS: Final = 8
STAGE_STATES: Final = frozenset({"started", "completed", "failed"})
RUN_STATES: Final = STAGE_STATES
_AUTHORITY: Final = {
@@ -243,10 +246,27 @@ class PipelineTelemetryEmitter:
class JsonlPipelineTelemetrySink:
"""Append topic-bound telemetry records for local, auditable execution evidence."""
"""Append to a bounded, fail-closed local outbox for auditable execution evidence.
def __init__(self, path: Path) -> None:
Completed segments are content-addressed and never pruned implicitly. When the
segment bound is reached, telemetry publication fails observably instead of
deleting an event that Telegraf may not have acknowledged yet.
"""
def __init__(
self,
path: Path,
*,
max_bytes: int = DEFAULT_JOURNAL_MAX_BYTES,
max_segments: int = DEFAULT_JOURNAL_MAX_SEGMENTS,
) -> None:
if max_bytes < MAX_PAYLOAD_BYTES + 4096:
raise PipelineTelemetryError("pipeline journal max_bytes is too small")
if not 1 <= max_segments <= 64:
raise PipelineTelemetryError("pipeline journal max_segments is invalid")
self.path = path.expanduser().absolute()
self.max_bytes = max_bytes
self.max_segments = max_segments
self._lock = threading.Lock()
def publish(self, topic: str, payload: bytes) -> None:
@@ -261,6 +281,10 @@ class JsonlPipelineTelemetrySink:
encoded = _canonical_json(record) + b"\n"
with self._lock:
self.path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
if self.path.is_symlink():
raise PipelineTelemetryError("pipeline journal must not be a symlink")
if self.path.exists() and self.path.stat().st_size + len(encoded) > self.max_bytes:
self._rotate()
descriptor = os.open(
self.path,
os.O_APPEND | os.O_CREAT | os.O_WRONLY,
@@ -272,6 +296,30 @@ class JsonlPipelineTelemetrySink:
finally:
os.close(descriptor)
def _rotate(self) -> None:
if not self.path.is_file() or self.path.stat().st_size == 0:
return
segments = tuple(self.path.parent.glob(f"{self.path.stem}.*{self.path.suffix}"))
if len(segments) >= self.max_segments:
raise PipelineTelemetryError(
"pipeline journal segment bound reached; acknowledged segments require review"
)
digest = _file_sha256(self.path)
destination = self.path.with_name(
f"{self.path.stem}.{digest}{self.path.suffix}"
)
if destination.exists() or destination.is_symlink():
raise PipelineTelemetryError("pipeline journal segment identity already exists")
os.replace(self.path, destination)
def _file_sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
class MqttPipelineTelemetrySink:
"""Publish through a worker-owned, already-connected Paho-compatible client."""
+22
View File
@@ -11,6 +11,18 @@ from k1link.laboratory.evidence_report import (
LaboratoryEvidenceReportError,
LaboratoryEvidenceReportNotFound,
LaboratoryEvidenceReportService,
verify_laboratory_evidence_result,
)
from k1link.laboratory.execution import (
LABORATORY_EXECUTION_REGISTRY_SCHEMA,
LABORATORY_RUN_RECEIPT_SCHEMA,
LaboratoryAdapterResult,
LaboratoryExecutionDefinition,
LaboratoryExecutionError,
LaboratoryExecutionRegistry,
LaboratoryRunner,
LaboratoryRunRequest,
LaboratoryRunResult,
)
from k1link.laboratory.value_review_registry import (
LABORATORY_VALUE_REVIEW_INDEX_SCHEMA,
@@ -28,7 +40,17 @@ __all__ = [
"LaboratoryEvidenceReportError",
"LaboratoryEvidenceReportNotFound",
"LaboratoryEvidenceReportService",
"verify_laboratory_evidence_result",
"LaboratoryRegistryError",
"LABORATORY_EXECUTION_REGISTRY_SCHEMA",
"LABORATORY_RUN_RECEIPT_SCHEMA",
"LaboratoryAdapterResult",
"LaboratoryExecutionDefinition",
"LaboratoryExecutionError",
"LaboratoryExecutionRegistry",
"LaboratoryRunRequest",
"LaboratoryRunResult",
"LaboratoryRunner",
"LABORATORY_VALUE_REVIEW_INDEX_SCHEMA",
"LABORATORY_VALUE_REVIEW_REGISTRY_SCHEMA",
"LaboratoryValueReviewEntry",
+37
View File
@@ -27,6 +27,43 @@ class LaboratoryEvidenceReportNotFound(LaboratoryEvidenceReportError):
"""Raised when the requested evidence identity is not available."""
def verify_laboratory_evidence_result(
definition: LaboratoryEvidenceDefinition,
result_root: Path,
) -> dict[str, object]:
"""Verify one immutable result without projecting it into a UI report."""
candidate = result_root.expanduser().absolute()
if candidate.is_symlink():
raise LaboratoryEvidenceReportError("LAB result must not be a symlink")
try:
resolved = candidate.resolve(strict=True)
except OSError as exc:
raise LaboratoryEvidenceReportError("LAB evidence result is unavailable") from exc
if not resolved.is_dir() or definition.result_id_pattern.fullmatch(resolved.name) is None:
raise LaboratoryEvidenceReportError("LAB evidence result path is invalid")
document_path = _safe_file(resolved, definition.document_name)
document_bytes = _read_bounded(document_path, _DOCUMENT_MAX_BYTES, "LAB document")
document = _json_object(document_bytes, "LAB document")
_validate_document(document, definition, resolved.name)
identity = _object_or_none(document.get("identity"))
identity_sha256 = document.get("identity_sha256")
if identity is None or not isinstance(identity_sha256, str):
raise LaboratoryEvidenceReportError("LAB identity proof is missing")
if (
_canonical_sha256(identity) != identity_sha256
or not resolved.name.endswith(identity_sha256)
):
raise LaboratoryEvidenceReportError("LAB identity proof is invalid")
artifacts = _verified_artifacts(resolved, document.get("artifacts"))
return {
"result_id": resolved.name,
"identity_sha256": identity_sha256,
"document_sha256": hashlib.sha256(document_bytes).hexdigest(),
"artifact_count": len(artifacts),
}
class LaboratoryEvidenceReportService:
def __init__(
self,
+544
View File
@@ -0,0 +1,544 @@
"""Admission and execution boundary for reproducible Mission Core laboratories.
Legacy evidence remains readable but is not executable through this boundary. A
canonical or experimental laboratory must declare its inputs and contracts before
an adapter can run it. The boundary adds telemetry and one immutable receipt; it
does not duplicate the laboratory algorithm.
"""
from __future__ import annotations
import hashlib
import json
import os
import re
import time
import uuid
from collections.abc import Callable, Mapping
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Final, Literal, cast
from k1link.compute.pipeline_telemetry import (
PipelineTelemetryEmitter,
PipelineTelemetryIdentity,
PipelineTelemetrySink,
)
from k1link.laboratory.evidence_registry import (
LaboratoryEvidenceRegistry,
)
from k1link.laboratory.evidence_report import verify_laboratory_evidence_result
LABORATORY_EXECUTION_REGISTRY_SCHEMA: Final = (
"missioncore.laboratory-execution-registry/v1"
)
LABORATORY_RUN_RECEIPT_SCHEMA: Final = "missioncore.laboratory-run-receipt/v1"
LABORATORY_RUN_CONTRACT: Final = "missioncore.laboratory-run/v1"
_MAX_REGISTRY_BYTES: Final = 128 * 1024
_IDENTIFIER: Final = re.compile(r"^[a-z][a-z0-9-]{2,95}$")
_INPUT_ROLE: Final = re.compile(r"^[a-z][a-z0-9_]{2,63}$")
_RUN_IDENTIFIER: Final = re.compile(r"^[a-z0-9][a-z0-9._-]{2,95}$")
_ADAPTER_IDENTIFIER: Final = re.compile(
r"^(?:canonical|experimental)\.[a-z][a-z0-9-]{2,95}/v[1-9][0-9]*$"
)
_CONTRACT: Final = re.compile(r"^missioncore\.[a-z0-9.-]+/v[1-9][0-9]*$")
_DEFINITION_KEYS: Final = frozenset(
{"work_id", "lifecycle", "isolation", "adapter_id", "input_roles", "contracts"}
)
_CONTRACT_KEYS: Final = frozenset({"source", "provider", "graph", "run", "evidence"})
_AUTHORITY: Final = {
"commands_enabled": False,
"navigation_or_safety_accepted": False,
}
LaboratoryLifecycle = Literal["canonical", "experimental"]
class LaboratoryExecutionError(RuntimeError):
"""The execution registry, admission request, or immutable receipt is invalid."""
@dataclass(frozen=True, slots=True)
class LaboratoryExecutionDefinition:
work_id: str
lifecycle: LaboratoryLifecycle
isolation: str
adapter_id: str
input_roles: tuple[str, ...]
source_contract: str
provider_contract: str
graph_contract: str
run_contract: str
evidence_contract: str
def __post_init__(self) -> None:
_identifier(self.work_id, "work_id")
if self.lifecycle not in {"canonical", "experimental"}:
raise LaboratoryExecutionError("laboratory lifecycle is invalid")
expected_isolation = (
"core-adapter" if self.lifecycle == "canonical" else "bounded-adapter"
)
if self.isolation != expected_isolation:
raise LaboratoryExecutionError(
f"{self.lifecycle} laboratory isolation must be {expected_isolation}"
)
if _ADAPTER_IDENTIFIER.fullmatch(self.adapter_id) is None or not self.adapter_id.startswith(
f"{self.lifecycle}."
):
raise LaboratoryExecutionError("laboratory adapter_id is invalid")
if not self.input_roles or len(self.input_roles) != len(set(self.input_roles)):
raise LaboratoryExecutionError("laboratory input_roles must be unique and non-empty")
for role in self.input_roles:
if _INPUT_ROLE.fullmatch(role) is None:
raise LaboratoryExecutionError("input role is invalid")
for value in (
self.source_contract,
self.provider_contract,
self.graph_contract,
self.run_contract,
self.evidence_contract,
):
if _CONTRACT.fullmatch(value) is None:
raise LaboratoryExecutionError("laboratory contract identity is invalid")
if self.run_contract != LABORATORY_RUN_CONTRACT:
raise LaboratoryExecutionError("laboratory run contract is unsupported")
@dataclass(frozen=True, slots=True)
class LaboratoryExecutionRegistry:
definitions: tuple[LaboratoryExecutionDefinition, ...]
legacy_work_ids: tuple[str, ...]
def __post_init__(self) -> None:
work_ids = [definition.work_id for definition in self.definitions]
if len(work_ids) != len(set(work_ids)):
raise LaboratoryExecutionError("duplicate executable laboratory work_id")
if len(self.legacy_work_ids) != len(set(self.legacy_work_ids)):
raise LaboratoryExecutionError("duplicate legacy laboratory work_id")
for work_id in self.legacy_work_ids:
_identifier(work_id, "legacy work_id")
if set(work_ids).intersection(self.legacy_work_ids):
raise LaboratoryExecutionError("a laboratory cannot be executable and legacy")
@classmethod
def from_file(
cls,
path: Path,
evidence_registry: LaboratoryEvidenceRegistry,
) -> LaboratoryExecutionRegistry:
candidate = path.expanduser().absolute()
if candidate.is_symlink() or not candidate.is_file():
raise LaboratoryExecutionError("laboratory execution registry must be a regular file")
if candidate.stat().st_size > _MAX_REGISTRY_BYTES:
raise LaboratoryExecutionError("laboratory execution registry is too large")
try:
payload: object = json.loads(candidate.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise LaboratoryExecutionError("laboratory execution registry is unreadable") from exc
document = _object(payload, "laboratory execution registry")
_exact_keys(
document,
frozenset({"schema_version", "definitions", "legacy_work_ids"}),
"laboratory execution registry",
)
if document["schema_version"] != LABORATORY_EXECUTION_REGISTRY_SCHEMA:
raise LaboratoryExecutionError("laboratory execution registry schema is invalid")
rows = document["definitions"]
legacy_rows = document["legacy_work_ids"]
if not isinstance(rows, list) or not isinstance(legacy_rows, list):
raise LaboratoryExecutionError("laboratory registry rows must be arrays")
definitions = tuple(_definition(row) for row in rows)
if not all(isinstance(item, str) for item in legacy_rows):
raise LaboratoryExecutionError("legacy work IDs must be strings")
registry = cls(definitions=definitions, legacy_work_ids=tuple(legacy_rows))
registry.validate_evidence_registry(evidence_registry)
return registry
def validate_evidence_registry(self, evidence_registry: LaboratoryEvidenceRegistry) -> None:
evidence_by_work_id = {
definition.work_id: definition for definition in evidence_registry.definitions
}
classified = {definition.work_id for definition in self.definitions}.union(
self.legacy_work_ids
)
if classified != set(evidence_by_work_id):
missing = sorted(set(evidence_by_work_id) - classified)
unknown = sorted(classified - set(evidence_by_work_id))
raise LaboratoryExecutionError(
f"laboratory classification is incomplete; missing={missing}, unknown={unknown}"
)
for definition in self.definitions:
if (
evidence_by_work_id[definition.work_id].result_schema_version
!= definition.evidence_contract
):
raise LaboratoryExecutionError(
f"laboratory evidence contract mismatch: {definition.work_id}"
)
def executable(self, work_id: str) -> LaboratoryExecutionDefinition:
for definition in self.definitions:
if definition.work_id == work_id:
return definition
if work_id in self.legacy_work_ids:
raise LaboratoryExecutionError(
f"legacy laboratory is read-only and cannot be executed: {work_id}"
)
raise LaboratoryExecutionError(f"laboratory work_id is not classified: {work_id}")
@dataclass(frozen=True, slots=True)
class LaboratoryRunRequest:
work_id: str
run_id: str
request_id: str
contour_id: str
agent_id: str
node_id: str
source_id: str
source_package_id: str
method_id: str
inputs: Mapping[str, Path]
output_root: Path
receipt_root: Path
@dataclass(frozen=True, slots=True)
class LaboratoryAdapterResult:
result_root: Path
result_id: str
@dataclass(frozen=True, slots=True)
class LaboratoryRunResult:
result_root: Path
result_id: str
receipt_root: Path
receipt_id: str
receipt: dict[str, Any]
LaboratoryAdapter = Callable[[LaboratoryRunRequest], LaboratoryAdapterResult]
class LaboratoryRunner:
"""Execute an admitted adapter and publish one common proof and telemetry shape."""
def __init__(
self,
*,
registry: LaboratoryExecutionRegistry,
evidence_registry: LaboratoryEvidenceRegistry,
sink: PipelineTelemetrySink,
adapters: Mapping[str, LaboratoryAdapter] | None = None,
clock_ns: Callable[[], int] = time.monotonic_ns,
) -> None:
self._registry = registry
self._evidence = {
definition.work_id: definition for definition in evidence_registry.definitions
}
self._sink = sink
self._adapters = dict(adapters or canonical_laboratory_adapters())
self._clock_ns = clock_ns
def run(self, request: LaboratoryRunRequest) -> LaboratoryRunResult:
definition = self._registry.executable(request.work_id)
_validate_request(request, definition)
adapter = self._adapters.get(definition.adapter_id)
if adapter is None:
raise LaboratoryExecutionError(
f"laboratory adapter is not installed: {definition.adapter_id}"
)
telemetry = PipelineTelemetryEmitter(
identity=PipelineTelemetryIdentity(
contour_id=request.contour_id,
agent_id=request.agent_id,
node_id=request.node_id,
lab_id=request.work_id,
run_id=request.run_id,
request_id=request.request_id,
source_id=request.source_id,
source_package_id=request.source_package_id,
method_id=request.method_id,
),
sink=self._sink,
clock_ns=self._clock_ns,
)
started_ns = self._clock_ns()
telemetry.run("started")
try:
with telemetry.stage("execute-adapter"):
result = adapter(request)
with telemetry.stage("verify-evidence") as outcome:
proof = verify_laboratory_evidence_result(
self._evidence[request.work_id],
result.result_root,
)
if result.result_id != proof["result_id"]:
raise LaboratoryExecutionError("adapter result_id does not match evidence")
artifact_count = proof["artifact_count"]
if not isinstance(artifact_count, int) or isinstance(artifact_count, bool):
raise LaboratoryExecutionError("evidence artifact count is invalid")
outcome.output_count = artifact_count
with telemetry.stage("publish-receipt"):
receipt_root, receipt_id, receipt = _publish_receipt(
request=request,
definition=definition,
proof=proof,
)
except BaseException as exc:
telemetry.run(
"failed",
duration_ms=max(0.0, (self._clock_ns() - started_ns) / 1_000_000),
exit_code=1,
error_type=type(exc).__name__,
)
raise
telemetry.run(
"completed",
duration_ms=max(0.0, (self._clock_ns() - started_ns) / 1_000_000),
exit_code=0,
)
return LaboratoryRunResult(
result_root=result.result_root,
result_id=result.result_id,
receipt_root=receipt_root,
receipt_id=receipt_id,
receipt=receipt,
)
def canonical_laboratory_adapters() -> dict[str, LaboratoryAdapter]:
return {
"canonical.e33-worker-shadow/v1": _run_e33,
"canonical.e35-degradation-recovery/v1": _run_e35,
"canonical.e46j-raw-fisheye-realtime/v1": _run_e46j,
}
def _run_e33(request: LaboratoryRunRequest) -> LaboratoryAdapterResult:
from k1link.compute.e33_worker_shadow import run_e33_worker_shadow
result = run_e33_worker_shadow(
request.inputs["package_root"],
request.output_root,
execution_id=request.run_id,
)
return LaboratoryAdapterResult(result_root=result.result_root, result_id=result.result_id)
def _run_e35(request: LaboratoryRunRequest) -> LaboratoryAdapterResult:
from k1link.compute.e35_degradation_replay import build_e35_degradation_replay
result = build_e35_degradation_replay(
e32_result_root=request.inputs["e32_result_root"],
e33_result_root=request.inputs["e33_result_root"],
e34_result_root=request.inputs["e34_result_root"],
e34_profile_path=request.inputs["e34_profile_path"],
profile_path=request.inputs["profile_path"],
output_root=request.output_root,
)
return LaboratoryAdapterResult(result_root=result.result_root, result_id=result.result_id)
def _run_e46j(request: LaboratoryRunRequest) -> LaboratoryAdapterResult:
from k1link.compute.e46j_raw_fisheye_realtime import (
build_e46j_raw_fisheye_realtime,
)
result = build_e46j_raw_fisheye_realtime(
raw_root=request.inputs["raw_root"],
profile_path=request.inputs["profile_path"],
output_root=request.output_root,
)
return LaboratoryAdapterResult(
result_root=Path(result["result_root"]),
result_id=str(result["result_id"]),
)
def _validate_request(
request: LaboratoryRunRequest,
definition: LaboratoryExecutionDefinition,
) -> None:
if _RUN_IDENTIFIER.fullmatch(request.run_id) is None:
raise LaboratoryExecutionError("laboratory run_id is invalid")
for value, label in (
(request.request_id, "request_id"),
(request.node_id, "node_id"),
(request.source_id, "source_id"),
(request.source_package_id, "source_package_id"),
(request.method_id, "method_id"),
):
_bounded_text(value, label)
actual_roles = set(request.inputs)
expected_roles = set(definition.input_roles)
if actual_roles != expected_roles:
raise LaboratoryExecutionError(
"laboratory input roles are invalid; "
f"missing={sorted(expected_roles - actual_roles)}, "
f"unexpected={sorted(actual_roles - expected_roles)}"
)
for role, path in request.inputs.items():
if not isinstance(path, Path):
raise LaboratoryExecutionError(f"laboratory input must be a Path: {role}")
candidate = path.expanduser().absolute()
if candidate.is_symlink():
raise LaboratoryExecutionError(f"laboratory input must not be a symlink: {role}")
try:
candidate.resolve(strict=True)
except OSError as exc:
raise LaboratoryExecutionError(f"laboratory input is unavailable: {role}") from exc
for path, label in (
(request.output_root, "output_root"),
(request.receipt_root, "receipt_root"),
):
if not isinstance(path, Path):
raise LaboratoryExecutionError(f"{label} must be a Path")
candidate = path.expanduser().absolute()
if candidate.is_symlink():
raise LaboratoryExecutionError(f"{label} must not be a symlink")
def _publish_receipt(
*,
request: LaboratoryRunRequest,
definition: LaboratoryExecutionDefinition,
proof: dict[str, object],
) -> tuple[Path, str, dict[str, Any]]:
identity = {
"schema_version": LABORATORY_RUN_RECEIPT_SCHEMA,
"work_id": request.work_id,
"lifecycle": definition.lifecycle,
"adapter_id": definition.adapter_id,
"run_id": request.run_id,
"request_id": request.request_id,
"contour_id": request.contour_id,
"agent_id": request.agent_id,
"node_id": request.node_id,
"source_id": request.source_id,
"source_package_id": request.source_package_id,
"method_id": request.method_id,
"contracts": {
"source": definition.source_contract,
"provider": definition.provider_contract,
"graph": definition.graph_contract,
"run": definition.run_contract,
"evidence": definition.evidence_contract,
},
"result_id": proof["result_id"],
"result_identity_sha256": proof["identity_sha256"],
"evidence_document_sha256": proof["document_sha256"],
"artifact_count": proof["artifact_count"],
"authority": _AUTHORITY,
}
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
receipt_id = f"laboratory-run-receipt-{identity_sha256}"
receipt = {
**identity,
"receipt_id": receipt_id,
"identity_sha256": identity_sha256,
}
destination = request.receipt_root.expanduser().absolute() / receipt_id
document_path = destination / "receipt.json"
encoded = _canonical_json(receipt) + b"\n"
if destination.exists():
if (
destination.is_symlink()
or document_path.is_symlink()
or not document_path.is_file()
):
raise LaboratoryExecutionError("existing laboratory receipt is invalid")
if document_path.read_bytes() != encoded:
raise LaboratoryExecutionError("immutable laboratory receipt changed")
return destination, receipt_id, receipt
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
staging = destination.parent / f".{receipt_id}.{uuid.uuid4().hex}.tmp"
staging.mkdir(mode=0o700, exist_ok=False)
try:
descriptor = os.open(staging / "receipt.json", os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
try:
os.write(descriptor, encoded)
os.fsync(descriptor)
finally:
os.close(descriptor)
os.replace(staging, destination)
except BaseException:
if staging.exists():
for child in staging.iterdir():
child.unlink()
staging.rmdir()
raise
return destination, receipt_id, receipt
def _definition(value: object) -> LaboratoryExecutionDefinition:
row = _object(value, "laboratory execution definition")
_exact_keys(row, _DEFINITION_KEYS, "laboratory execution definition")
contracts = _object(row["contracts"], "laboratory contracts")
_exact_keys(contracts, _CONTRACT_KEYS, "laboratory contracts")
input_roles = row["input_roles"]
if not isinstance(input_roles, list) or not all(
isinstance(item, str) for item in input_roles
):
raise LaboratoryExecutionError("laboratory input_roles must be strings")
lifecycle = row["lifecycle"]
if lifecycle not in {"canonical", "experimental"}:
raise LaboratoryExecutionError("laboratory lifecycle is invalid")
return LaboratoryExecutionDefinition(
work_id=_text(row["work_id"], "work_id"),
lifecycle=cast(LaboratoryLifecycle, lifecycle),
isolation=_text(row["isolation"], "isolation"),
adapter_id=_text(row["adapter_id"], "adapter_id"),
input_roles=tuple(input_roles),
source_contract=_text(contracts["source"], "source contract"),
provider_contract=_text(contracts["provider"], "provider contract"),
graph_contract=_text(contracts["graph"], "graph contract"),
run_contract=_text(contracts["run"], "run contract"),
evidence_contract=_text(contracts["evidence"], "evidence contract"),
)
def _object(value: object, label: str) -> dict[str, object]:
if not isinstance(value, dict) or not all(isinstance(key, str) for key in value):
raise LaboratoryExecutionError(f"{label} must be an object")
return value
def _exact_keys(document: dict[str, object], expected: frozenset[str], label: str) -> None:
actual = frozenset(document)
if actual != expected:
raise LaboratoryExecutionError(
f"{label} keys are invalid; missing={sorted(expected - actual)}, "
f"unexpected={sorted(actual - expected)}"
)
def _text(value: object, label: str) -> str:
if not isinstance(value, str) or not value or value != value.strip():
raise LaboratoryExecutionError(f"{label} must be a non-empty trimmed string")
return value
def _identifier(value: object, label: str) -> str:
text = _text(value, label)
if _IDENTIFIER.fullmatch(text) is None:
raise LaboratoryExecutionError(f"{label} is invalid")
return text
def _bounded_text(value: object, label: str) -> str:
text = _text(value, label)
if len(text) > 256 or any(ord(character) < 32 for character in text):
raise LaboratoryExecutionError(f"{label} is invalid")
return text
def _canonical_json(value: object) -> bytes:
return json.dumps(
value,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
).encode("utf-8")
+4 -4
View File
@@ -910,7 +910,7 @@ def build_advanced_laboratory_router(
if not isinstance(items, list):
raise RuntimeError("advanced LAB index items are invalid")
l3_identity = latest_l3_visual_identity(l3_visual_root_provider)
if l3_identity is not None:
if evidence_registry is None and l3_identity is not None:
items.append(
{
"work_id": "l3-pointpillars-visual-audit",
@@ -919,7 +919,7 @@ def build_advanced_laboratory_router(
}
)
l31_identity = latest_l31_identity(l31_ravnoves_root_provider)
if l31_identity is not None:
if evidence_registry is None and l31_identity is not None:
items.append(
{
"work_id": "l31-pointpillars-ravnoves",
@@ -928,7 +928,7 @@ def build_advanced_laboratory_router(
}
)
l32_identity = latest_l32_identity(l32_camera_review_root_provider)
if l32_identity is not None:
if evidence_registry is None and l32_identity is not None:
items.append(
{
"work_id": "l32-pointpillars-camera-review",
@@ -937,7 +937,7 @@ def build_advanced_laboratory_router(
}
)
l33_identity = latest_l33_identity(l33_camera_first_review_root_provider)
if l33_identity is not None:
if evidence_registry is None and l33_identity is not None:
items.append(
{
"work_id": "l33-camera-first-detector-review",
+5
View File
@@ -26,6 +26,7 @@ from k1link.compute import (
from k1link.laboratory import (
LaboratoryEvidenceRegistry,
LaboratoryEvidenceReportService,
LaboratoryExecutionRegistry,
LaboratoryValueReviewRegistry,
)
from k1link.sessions import (
@@ -142,6 +143,10 @@ INVALID_REQUEST_DETAIL = "Некорректные параметры запро
LABORATORY_EVIDENCE_REGISTRY = LaboratoryEvidenceRegistry.from_directory(
REPOSITORY_ROOT / "config" / "laboratories"
)
LABORATORY_EXECUTION_REGISTRY = LaboratoryExecutionRegistry.from_file(
REPOSITORY_ROOT / "config" / "laboratory-execution.json",
LABORATORY_EVIDENCE_REGISTRY,
)
LABORATORY_VALUE_REVIEW_REGISTRY = LaboratoryValueReviewRegistry.from_file(
REPOSITORY_ROOT / "config" / "laboratory-value-review.json"
)