feat(worker): install combined observatory profiles

This commit is contained in:
DCCONSTRUCTIONS
2026-08-31 20:56:29 +03:00
parent 44ebbe7ca2
commit 11a146011f
14 changed files with 3878 additions and 59 deletions
+435 -4
View File
@@ -1,7 +1,7 @@
"""Fixed POSIX service composition for the sealed portable M4.9 executor.
"""Fixed POSIX service composition for installed M4.9 and LAB V1 executors.
The entrypoint reads only immutable registries, one installation receipt and
path-only Worker service settings. Jobs cannot select commands, providers,
The entrypoint reads only immutable registries, exact external release files
and path-only Worker service settings. Jobs cannot select commands, providers,
modules, images or filesystem locations. Every runtime asset is resolved from
the fixed release layout and re-verified against the ready runtime candidate
before the first queue claim.
@@ -15,6 +15,7 @@ import json
import os
import re
import signal
import socket
import stat
from collections.abc import Iterator, Mapping, Sequence
from contextlib import contextmanager
@@ -35,6 +36,22 @@ from k1link.observatory.m49_portable_executor import (
M49PortableRunnerInstallation,
compose_m49_portable_executor_adapter,
)
from k1link.observatory.portable_lab_v1_executor import (
PortableLabV1ReleaseAsset,
PortableLabV1ReleaseCandidate,
PortableLabV1ReleaseInspection,
)
from k1link.observatory.portable_lab_v1_worker import (
PortableLabV1RunnerInstallation as PortableLabV1ReleaseInstallation,
)
from k1link.observatory.portable_lab_v1_worker_service import (
PORTABLE_LAB_V1_ADAPTER_ID,
PORTABLE_LAB_V1_SETUP_ID,
PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_ASSET_ID,
PortableLabV1WorkerInstallationReceipt,
compose_installed_lab_v1_executor_builder,
load_portable_lab_v1_worker_installation_receipt,
)
from k1link.observatory.portable_result_contract import OBSERVATION_ONLY_AUTHORITY
from k1link.observatory.portable_run_definitions import (
PortableRunDefinition,
@@ -64,6 +81,12 @@ M49_WORKER_RUNTIME_REGISTRY_FILE_ENV: Final = "MISSIONCORE_OBSERVATORY_WORKER_RU
M49_WORKER_INSTALLATION_RECEIPT_FILE_ENV: Final = (
"MISSIONCORE_OBSERVATORY_M49_INSTALLATION_RECEIPT_FILE"
)
LAB_V1_WORKER_INSTALLATION_RECEIPT_FILE_ENV: Final = (
"MISSIONCORE_OBSERVATORY_LAB_V1_INSTALLATION_RECEIPT_FILE"
)
LAB_V1_WORKER_RELEASE_CANDIDATE_FILE_ENV: Final = (
"MISSIONCORE_OBSERVATORY_LAB_V1_RELEASE_CANDIDATE_FILE"
)
M49_WORKER_INSTALLATION_RECEIPT_SCHEMA: Final = (
"missioncore.m49-tgs-portable-worker-installation-ready-receipt/v1"
@@ -74,12 +97,24 @@ M49_WORKER_RELEASE_ID: Final = "m49-tgs-portable-executor-v1"
M49_EXECUTOR_RELEASE_ASSET_ID: Final = "m49-portable-executor-release"
M49_WORKER_INSTALLATION_RECEIPT_ASSET_ID: Final = "m49-portable-worker-installation-receipt"
LAB_V1_PORTABLE_CONFIG_ASSET_ID: Final = "ddrnet-portable-config"
LAB_V1_WORKER_AGENT_IMAGE_ASSET_ID: Final = "worker-006-agent-image"
_MAX_RECEIPT_BYTES: Final = 256 * 1024
_MAX_DOCKER_INSPECTION_BYTES: Final = 1024 * 1024
_SHA256: Final = re.compile(r"^[a-f0-9]{64}$")
_CONTAINER_HOSTNAME: Final = re.compile(r"^[a-f0-9]{12,64}$")
_SOURCE_REVISION: Final = re.compile(r"^[a-f0-9]{40}$")
_WORKER_COMPUTER_NAME: Final = "DESKTOP-OPJ8J04"
_DOCKER_SOCKET: Final = Path("/var/run/docker.sock")
_DOCKER_API_VERSION: Final = "v1.47"
_FIXED_RUNTIME_RELEASE_ROOT: Final = Path("/release")
_FIXED_LAB_V1_INSTALLATION_RECEIPT_FILE: Final = (
_FIXED_RUNTIME_RELEASE_ROOT / "lab-v1-worker-installation-receipt.json"
)
_FIXED_LAB_V1_RELEASE_CANDIDATE_FILE: Final = (
_FIXED_RUNTIME_RELEASE_ROOT / "lab-v1-executor-release.json"
)
_PROTECTED_RUNTIME_NAMES: Final = (
"ndc-mission-core-triton",
"ndc-mission-core-perception-worker",
@@ -119,6 +154,8 @@ class M49WorkerEntrypointConfiguration:
definitions_file: Path
runtime_registry_file: Path
installation_receipt_file: Path
lab_v1_installation_receipt_file: Path | None = None
lab_v1_release_candidate_file: Path | None = None
def __post_init__(self) -> None:
for path, label in (
@@ -127,6 +164,33 @@ class M49WorkerEntrypointConfiguration:
(self.installation_receipt_file, "M4.9 installation receipt"),
):
_absolute_path(path, label)
if (self.lab_v1_installation_receipt_file is None) != (
self.lab_v1_release_candidate_file is None
):
raise M49WorkerCompositionError(
"LAB V1 installation receipt and release candidate must be configured together"
)
for optional_path, label in (
(
self.lab_v1_installation_receipt_file,
"LAB V1 installation receipt",
),
(self.lab_v1_release_candidate_file, "LAB V1 release candidate"),
):
if optional_path is not None:
_absolute_path(optional_path, label)
if (
self.lab_v1_installation_receipt_file is not None
and (
self.lab_v1_installation_receipt_file
!= _FIXED_LAB_V1_INSTALLATION_RECEIPT_FILE
or self.lab_v1_release_candidate_file
!= _FIXED_LAB_V1_RELEASE_CANDIDATE_FILE
)
):
raise M49WorkerCompositionError(
"LAB V1 production inputs must use the fixed /release files"
)
@classmethod
def from_environment(
@@ -148,6 +212,14 @@ class M49WorkerEntrypointConfiguration:
values,
M49_WORKER_INSTALLATION_RECEIPT_FILE_ENV,
),
lab_v1_installation_receipt_file=_required_environment_path(
values,
LAB_V1_WORKER_INSTALLATION_RECEIPT_FILE_ENV,
),
lab_v1_release_candidate_file=_required_environment_path(
values,
LAB_V1_WORKER_RELEASE_CANDIDATE_FILE_ENV,
),
)
@@ -483,15 +555,374 @@ def compose_installed_m49_worker_service(
installation=installation,
),
)
installed_builders = list(executor_builders)
if configuration.lab_v1_installation_receipt_file is not None:
installed_builders.insert(
0,
_compose_installed_lab_v1_builder(
configuration=configuration,
definitions=definitions,
runtime_registry=runtime_registry,
),
)
return compose_installed_observatory_worker_service_from_builders(
configuration=configuration.worker,
definitions=definitions,
runtime_registry=runtime_registry,
builders=(m49_builder, *executor_builders),
builders=(m49_builder, *installed_builders),
http_transport=http_transport,
)
def _compose_installed_lab_v1_builder(
*,
configuration: M49WorkerEntrypointConfiguration,
definitions: PortableRunDefinitionRegistry,
runtime_registry: PortableWorkerRuntimeRegistry,
) -> ObservatoryWorkerExecutorBuilderRegistration:
receipt_path = configuration.lab_v1_installation_receipt_file
release_path = configuration.lab_v1_release_candidate_file
if receipt_path is None or release_path is None:
raise M49WorkerCompositionError("LAB V1 production inputs are incomplete")
definition = definitions.resolve_setup(PORTABLE_LAB_V1_SETUP_ID)
candidate = runtime_registry.resolve(
definition.setup_id,
definition.definition_sha256,
)
_verify_ready_lab_v1_identity(definitions, definition, candidate)
receipt = load_portable_lab_v1_worker_installation_receipt(receipt_path)
release_file = _regular_file(release_path, "LAB V1 release candidate")
release = PortableLabV1ReleaseCandidate.from_file(
release_file,
repository_root=_real_directory(
release_file.parent,
"LAB V1 release repository root",
),
)
if receipt.release_candidate_sha256 != release.candidate_sha256:
raise M49WorkerCompositionError(
"LAB V1 installation receipt belongs to another release candidate"
)
release.bind_definition(definition)
inspection = _inspect_installed_lab_v1_release(
release=release,
receipt=receipt,
definition=definition,
running_worker_image_sha256=(
_inspect_running_worker_container_image_sha256()
),
)
bindings = _lab_v1_runtime_asset_bindings(
candidate=candidate,
release=release,
receipt=receipt,
)
admission = inspect_runtime_candidate(candidate, bindings)
if not admission.ready:
raise M49WorkerCompositionError(
"portable LAB V1 local asset admission is not ready"
)
portable_config_path = _lab_v1_release_repository_file(
release,
LAB_V1_PORTABLE_CONFIG_ASSET_ID,
)
release_installation = PortableLabV1ReleaseInstallation(
release=release,
inspection=inspection,
portable_ddrnet_config_path=portable_config_path,
output_parent=(
configuration.worker.work_root / "lab-v1-portable" / "runner-output"
),
)
return compose_installed_lab_v1_executor_builder(
receipt=receipt,
definition=definition,
candidate=candidate,
admission=admission,
release_installation=release_installation,
)
def _verify_ready_lab_v1_identity(
definitions: PortableRunDefinitionRegistry,
definition: PortableRunDefinition,
candidate: PortableWorkerRuntimeCandidate,
) -> None:
ready_identities = {
(item.setup_id, item.definition_sha256)
for item in definitions.ready_recorded_definitions()
}
if (
(definition.setup_id, definition.definition_sha256) not in ready_identities
or definition.setup_id != PORTABLE_LAB_V1_SETUP_ID
or definition.executor.contour_id != WORKER_006_CONTOUR_ID
or candidate.adapter_id != PORTABLE_LAB_V1_ADAPTER_ID
or not definition.executor.ready
or not candidate.ready
):
raise M49WorkerCompositionError(
"fixed Worker requires the exact ready LAB V1 definition"
)
def _lab_v1_runtime_asset_bindings(
*,
candidate: PortableWorkerRuntimeCandidate,
release: PortableLabV1ReleaseCandidate,
receipt: PortableLabV1WorkerInstallationReceipt,
) -> dict[str, PortableWorkerLocalAssetBinding]:
release_assets = {asset.asset_id: asset for asset in release.assets}
bindings: dict[str, PortableWorkerLocalAssetBinding] = {}
for requirement in candidate.reusable_assets:
if (
requirement.asset_id
== PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_ASSET_ID
):
if (
requirement.kind != "local-file"
or requirement.sha256 != receipt.file_sha256
or requirement.byte_length != receipt.file_byte_length
):
raise M49WorkerCompositionError(
"LAB V1 runtime installation receipt anchor changed"
)
bindings[requirement.asset_id] = PortableWorkerLocalAssetBinding(
asset_id=requirement.asset_id,
file_path=receipt.path,
)
continue
asset = release_assets.get(requirement.asset_id)
if (
asset is None
or asset.sha256 != requirement.sha256
or asset.byte_length != requirement.byte_length
):
raise M49WorkerCompositionError(
"LAB V1 runtime asset differs from its exact release candidate"
)
if requirement.kind == "container-image" and asset.kind == "container-image":
bindings[requirement.asset_id] = PortableWorkerLocalAssetBinding(
asset_id=requirement.asset_id,
image_sha256=asset.sha256,
)
continue
if asset.repository_path is None:
raise M49WorkerCompositionError(
"LAB V1 non-image runtime asset has no exact release file"
)
bindings[requirement.asset_id] = PortableWorkerLocalAssetBinding(
asset_id=requirement.asset_id,
file_path=_lab_v1_release_repository_file(
release,
requirement.asset_id,
),
)
return bindings
def _inspect_installed_lab_v1_release(
*,
release: PortableLabV1ReleaseCandidate,
receipt: PortableLabV1WorkerInstallationReceipt,
definition: PortableRunDefinition,
running_worker_image_sha256: str,
) -> PortableLabV1ReleaseInspection:
if release.declared_blockers or release.executor_image_sha256 is None:
raise M49WorkerCompositionError("LAB V1 release candidate is not ready")
_digest(
running_worker_image_sha256,
"running Worker container image SHA-256",
)
worker_image_assets = tuple(
asset
for asset in release.assets
if asset.asset_id == LAB_V1_WORKER_AGENT_IMAGE_ASSET_ID
)
if (
len(worker_image_assets) != 1
or worker_image_assets[0].kind != "container-image"
or worker_image_assets[0].sha256 != running_worker_image_sha256
or worker_image_assets[0].byte_length is not None
or worker_image_assets[0].repository_path is not None
or release.executor_image_sha256 != running_worker_image_sha256
):
raise M49WorkerCompositionError(
"LAB V1 executor image differs from the running Worker container"
)
installed_evidence = _lab_v1_installed_release_evidence(receipt)
installed_images = {
receipt.eomt_image_build.base_image_sha256,
receipt.eomt_image_build.derived_image_sha256,
receipt.ddrnet_image_build.base_image_sha256,
receipt.ddrnet_image_build.derived_image_sha256,
running_worker_image_sha256,
}
definition_components = {component.sha256 for component in definition.components}
matched: list[str] = []
for asset in release.assets:
if (
asset.asset_id
== PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_ASSET_ID
):
raise M49WorkerCompositionError(
"LAB V1 installation receipt must remain a runtime-only anchor"
)
if asset.kind == "repository-file":
_lab_v1_release_repository_file(release, asset.asset_id)
elif asset.kind == "container-image":
if asset.byte_length is not None or asset.sha256 not in installed_images:
raise M49WorkerCompositionError(
"LAB V1 release contains an uninstalled container image"
)
elif asset.kind == "definition-component":
if asset.byte_length is not None or asset.sha256 not in definition_components:
raise M49WorkerCompositionError(
"LAB V1 release contains an unknown definition component"
)
elif not _lab_v1_release_evidence_matches(asset, installed_evidence):
raise M49WorkerCompositionError(
"LAB V1 release contains an asset absent from its installation receipt"
)
matched.append(asset.asset_id)
return PortableLabV1ReleaseInspection(
candidate_sha256=release.candidate_sha256,
matched_assets=tuple(matched),
blockers=(),
ready=True,
)
def _inspect_running_worker_container_image_sha256(
*,
container_hostname: str | None = None,
transport: httpx.BaseTransport | None = None,
docker_socket: Path = _DOCKER_SOCKET,
) -> str:
"""Return the exact image ID of this running Worker container."""
hostname = socket.gethostname() if container_hostname is None else container_hostname
if _CONTAINER_HOSTNAME.fullmatch(hostname) is None:
raise M49WorkerCompositionError(
"running Worker container hostname is not an exact container ID"
)
selected_transport = transport
if selected_transport is None:
candidate = docker_socket.expanduser().absolute()
try:
metadata = candidate.lstat()
except OSError as exc:
raise M49WorkerCompositionError(
"running Worker Docker Engine socket is unavailable"
) from exc
if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISSOCK(metadata.st_mode):
raise M49WorkerCompositionError(
"running Worker Docker Engine socket is unsafe"
)
selected_transport = httpx.HTTPTransport(uds=str(candidate))
try:
with httpx.Client(
base_url="http://docker",
transport=selected_transport,
timeout=5.0,
) as client:
response = client.get(
f"/{_DOCKER_API_VERSION}/containers/{hostname}/json"
)
payload = response.content
except (httpx.HTTPError, OSError) as exc:
raise M49WorkerCompositionError(
"running Worker container inspection failed"
) from exc
if response.status_code != 200 or not 0 < len(payload) <= _MAX_DOCKER_INSPECTION_BYTES:
raise M49WorkerCompositionError(
"running Worker container inspection is unavailable"
)
try:
document = json.loads(payload.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise M49WorkerCompositionError(
"running Worker container inspection is invalid"
) from exc
row = _object(document, "running Worker container inspection")
container_id = _string(row.get("Id"), "running Worker container ID")
image_reference = _string(
row.get("Image"),
"running Worker container image ID",
)
state = _object(row.get("State"), "running Worker container state")
image_sha256 = image_reference.removeprefix("sha256:")
if (
_SHA256.fullmatch(container_id) is None
or not container_id.startswith(hostname)
or image_reference != f"sha256:{image_sha256}"
or _SHA256.fullmatch(image_sha256) is None
or state.get("Running") is not True
):
raise M49WorkerCompositionError(
"running Worker container identity changed"
)
return image_sha256
def _lab_v1_installed_release_evidence(
receipt: PortableLabV1WorkerInstallationReceipt,
) -> dict[str, set[int | None]]:
evidence: dict[str, set[int | None]] = {}
def add(digest: str, byte_length: int | None) -> None:
evidence.setdefault(digest, set()).add(byte_length)
for component in (
receipt.runner_installation.eomt,
receipt.runner_installation.ddrnet,
):
add(component.image_sha256, None)
add(component.installation_sha256, None)
for asset in component.assets:
add(asset.identity_sha256, asset.byte_length)
add(receipt.runner_installation.receipt_sha256, None)
for build in (receipt.eomt_image_build, receipt.ddrnet_image_build):
for key, value in build.identity_document().items():
if key.endswith("_sha256") and isinstance(value, str):
add(value, None)
add(build.seal_sha256, None)
return evidence
def _lab_v1_release_evidence_matches(
asset: PortableLabV1ReleaseAsset,
evidence: Mapping[str, set[int | None]],
) -> bool:
lengths = evidence.get(asset.sha256)
if lengths is None:
return False
return asset.byte_length is None or asset.byte_length in lengths
def _lab_v1_release_repository_file(
release: PortableLabV1ReleaseCandidate,
asset_id: str,
) -> Path:
asset = next((row for row in release.assets if row.asset_id == asset_id), None)
if asset is None or asset.kind != "repository-file" or asset.repository_path is None:
raise M49WorkerCompositionError("LAB V1 exact release file is unavailable")
relative = _safe_relative_path(asset.repository_path)
path = _regular_file(
release.repository_root.joinpath(*relative.parts),
"LAB V1 release file",
)
if not path.is_relative_to(release.repository_root):
raise M49WorkerCompositionError("LAB V1 release file escapes its release root")
if (
(asset.byte_length is not None and path.stat().st_size != asset.byte_length)
or _sha256_file(path) != asset.sha256
):
raise M49WorkerCompositionError(
"LAB V1 release file differs from its release candidate"
)
return path
def run_installed_m49_worker(
service: InstalledObservatoryWorkerService,
*,
@@ -94,6 +94,9 @@ _EXPECTED_RESULT_CONTRACT_SHA256: Final = (
"b3dfaa8e20a0f22fc510d062ac469f010a3281c650059d9ea134f0b3ccb38d9a"
)
_PORTABLE_CONFIG_ASSET_ID: Final = "ddrnet-portable-config"
_WORKER_INSTALLATION_RECEIPT_ASSET_ID: Final = (
"lab-v1-worker-installation-receipt"
)
_MAX_SOURCE_DOCUMENT_BYTES: Final = 8 * 1024 * 1024
_MAX_MATERIALIZATION_MANIFEST_BYTES: Final = 64 * 1024 * 1024
_MAX_SOURCE_MEMBERS: Final = 100_000
@@ -582,6 +585,12 @@ def _verify_candidate_release(
):
raise PortableLabV1WorkerError("portable LAB V1 runtime and release candidates disagree")
for requirement in candidate.reusable_assets:
if requirement.asset_id == _WORKER_INSTALLATION_RECEIPT_ASSET_ID:
if requirement.kind != "local-file":
raise PortableLabV1WorkerError(
"portable LAB V1 installation receipt runtime anchor changed"
)
continue
asset = release_assets.get(requirement.asset_id)
if (
asset is None
@@ -63,10 +63,13 @@ from k1link.observatory.worker_service import (
)
PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_SCHEMA: Final = (
"missioncore.observatory-portable-lab-v1-worker-installation-ready-receipt/v1"
"missioncore.observatory-portable-lab-v1-worker-installation-ready-receipt/v2"
)
PORTABLE_LAB_V1_COMPONENT_IMAGE_BUILD_SEAL_SCHEMA: Final = (
"missioncore.observatory-portable-lab-v1-component-image-build-seal/v1"
"missioncore.observatory-portable-lab-v1-component-image-build-seal/v2"
)
PORTABLE_LAB_V1_COMPONENT_IMAGE_BUILD_METHOD: Final = (
"docker-commit-exact-layer-v1"
)
PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_ASSET_ID: Final = (
"lab-v1-worker-installation-receipt"
@@ -98,6 +101,8 @@ class PortableLabV1ComponentImageBuildSeal:
base_image_sha256: str
derived_image_sha256: str
dockerfile_sha256: str
build_method: str
installer_sha256: str
shared_adapter_sha256: str
component_adapter_sha256: str
network: str
@@ -112,6 +117,7 @@ class PortableLabV1ComponentImageBuildSeal:
(self.base_image_sha256, "LAB V1 component base image SHA-256"),
(self.derived_image_sha256, "LAB V1 derived component image SHA-256"),
(self.dockerfile_sha256, "LAB V1 component Dockerfile SHA-256"),
(self.installer_sha256, "LAB V1 component image installer SHA-256"),
(self.shared_adapter_sha256, "LAB V1 shared adapter SHA-256"),
(self.component_adapter_sha256, "LAB V1 component adapter SHA-256"),
(self.seal_sha256, "LAB V1 component image build seal SHA-256"),
@@ -120,6 +126,8 @@ class PortableLabV1ComponentImageBuildSeal:
if (
self.base_image_sha256
!= _COMPONENT_BASE_IMAGE_SHA256S[self.component]
or self.build_method
!= PORTABLE_LAB_V1_COMPONENT_IMAGE_BUILD_METHOD
or self.network != "none"
or self.seal_sha256
!= hashlib.sha256(canonical_json(self.identity_document())).hexdigest()
@@ -136,6 +144,8 @@ class PortableLabV1ComponentImageBuildSeal:
base_image_sha256: str,
derived_image_sha256: str,
dockerfile_sha256: str,
build_method: str,
installer_sha256: str,
shared_adapter_sha256: str,
component_adapter_sha256: str,
) -> PortableLabV1ComponentImageBuildSeal:
@@ -144,6 +154,8 @@ class PortableLabV1ComponentImageBuildSeal:
base_image_sha256=base_image_sha256,
derived_image_sha256=derived_image_sha256,
dockerfile_sha256=dockerfile_sha256,
build_method=build_method,
installer_sha256=installer_sha256,
shared_adapter_sha256=shared_adapter_sha256,
component_adapter_sha256=component_adapter_sha256,
)
@@ -152,6 +164,8 @@ class PortableLabV1ComponentImageBuildSeal:
base_image_sha256=base_image_sha256,
derived_image_sha256=derived_image_sha256,
dockerfile_sha256=dockerfile_sha256,
build_method=build_method,
installer_sha256=installer_sha256,
shared_adapter_sha256=shared_adapter_sha256,
component_adapter_sha256=component_adapter_sha256,
network="none",
@@ -164,6 +178,8 @@ class PortableLabV1ComponentImageBuildSeal:
base_image_sha256=self.base_image_sha256,
derived_image_sha256=self.derived_image_sha256,
dockerfile_sha256=self.dockerfile_sha256,
build_method=self.build_method,
installer_sha256=self.installer_sha256,
shared_adapter_sha256=self.shared_adapter_sha256,
component_adapter_sha256=self.component_adapter_sha256,
)
@@ -181,6 +197,7 @@ class PortableLabV1WorkerInstallationReceipt:
runner_installation: PortableLabV1ComponentRunnerInstallation
eomt_image_build: PortableLabV1ComponentImageBuildSeal
ddrnet_image_build: PortableLabV1ComponentImageBuildSeal
installation_evidence_sha256: str
receipt_sha256: str
def __post_init__(self) -> None:
@@ -193,6 +210,10 @@ class PortableLabV1WorkerInstallationReceipt:
"LAB V1 installation receipt size is invalid"
)
_digest(self.file_sha256, "LAB V1 installation receipt file SHA-256")
_digest(
self.installation_evidence_sha256,
"LAB V1 external installation evidence SHA-256",
)
if _SOURCE_REVISION.fullmatch(self.source_revision) is None:
raise PortableLabV1WorkerCompositionError(
"LAB V1 installation receipt source revision is invalid"
@@ -223,6 +244,7 @@ class PortableLabV1WorkerInstallationReceipt:
runner_installation=self.runner_installation,
eomt_image_build=self.eomt_image_build,
ddrnet_image_build=self.ddrnet_image_build,
installation_evidence_sha256=self.installation_evidence_sha256,
):
raise PortableLabV1WorkerCompositionError(
"LAB V1 installation receipt identity changed"
@@ -309,7 +331,8 @@ def load_portable_lab_v1_worker_installation_receipt(
"runner_installation",
"component_installations",
"component_image_build_seals",
"fixture_smoke",
"installation_evidence_sha256",
"installation_smoke",
"blockers",
"authority",
"receipt_sha256",
@@ -322,7 +345,7 @@ def load_portable_lab_v1_worker_installation_receipt(
!= PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_SCHEMA
or row["receipt_state"] != "installed-ready"
or row["worker_id"] != _WORKER_ID
or row["fixture_smoke"] != "passed"
or row["installation_smoke"] != "offline-import-passed"
or blockers
or row["authority"] != OBSERVATION_ONLY_AUTHORITY
):
@@ -385,6 +408,10 @@ def load_portable_lab_v1_worker_installation_receipt(
runner_installation=runner,
eomt_image_build=eomt_image_build,
ddrnet_image_build=ddrnet_image_build,
installation_evidence_sha256=_string(
row["installation_evidence_sha256"],
"LAB V1 external installation evidence SHA-256",
),
receipt_sha256=_string(
row["receipt_sha256"],
"LAB V1 installation receipt SHA-256",
@@ -501,6 +528,7 @@ def portable_lab_v1_worker_receipt_document(
runner_installation: PortableLabV1ComponentRunnerInstallation,
eomt_image_build: PortableLabV1ComponentImageBuildSeal,
ddrnet_image_build: PortableLabV1ComponentImageBuildSeal,
installation_evidence_sha256: str,
) -> dict[str, object]:
"""Return the canonical serializable receipt document for an installer."""
@@ -510,6 +538,7 @@ def portable_lab_v1_worker_receipt_document(
runner_installation=runner_installation,
eomt_image_build=eomt_image_build,
ddrnet_image_build=ddrnet_image_build,
installation_evidence_sha256=installation_evidence_sha256,
)
return {**identity, "receipt_sha256": hashlib.sha256(canonical_json(identity)).hexdigest()}
@@ -521,6 +550,7 @@ def _receipt_identity_sha256(
runner_installation: PortableLabV1ComponentRunnerInstallation,
eomt_image_build: PortableLabV1ComponentImageBuildSeal,
ddrnet_image_build: PortableLabV1ComponentImageBuildSeal,
installation_evidence_sha256: str,
) -> str:
return hashlib.sha256(
canonical_json(
@@ -530,6 +560,7 @@ def _receipt_identity_sha256(
runner_installation=runner_installation,
eomt_image_build=eomt_image_build,
ddrnet_image_build=ddrnet_image_build,
installation_evidence_sha256=installation_evidence_sha256,
)
)
).hexdigest()
@@ -542,7 +573,12 @@ def _receipt_identity_document(
runner_installation: PortableLabV1ComponentRunnerInstallation,
eomt_image_build: PortableLabV1ComponentImageBuildSeal,
ddrnet_image_build: PortableLabV1ComponentImageBuildSeal,
installation_evidence_sha256: str,
) -> dict[str, object]:
_digest(
installation_evidence_sha256,
"LAB V1 external installation evidence SHA-256",
)
return {
"schema_version": PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_SCHEMA,
"receipt_state": "installed-ready",
@@ -561,7 +597,8 @@ def _receipt_identity_document(
"eomt": _component_image_build_document(eomt_image_build),
"ddrnet": _component_image_build_document(ddrnet_image_build),
},
"fixture_smoke": "passed",
"installation_evidence_sha256": installation_evidence_sha256,
"installation_smoke": "offline-import-passed",
"blockers": [],
"authority": dict(OBSERVATION_ONLY_AUTHORITY),
}
@@ -582,6 +619,8 @@ def _component_image_build_identity(
base_image_sha256: str,
derived_image_sha256: str,
dockerfile_sha256: str,
build_method: str,
installer_sha256: str,
shared_adapter_sha256: str,
component_adapter_sha256: str,
) -> dict[str, object]:
@@ -591,6 +630,8 @@ def _component_image_build_identity(
"base_image_sha256": base_image_sha256,
"derived_image_sha256": derived_image_sha256,
"dockerfile_sha256": dockerfile_sha256,
"build_method": build_method,
"installer_sha256": installer_sha256,
"shared_adapter_sha256": shared_adapter_sha256,
"component_adapter_sha256": component_adapter_sha256,
"network": "none",
@@ -618,6 +659,8 @@ def _component_image_build_seal(
"base_image_sha256",
"derived_image_sha256",
"dockerfile_sha256",
"build_method",
"installer_sha256",
"shared_adapter_sha256",
"component_adapter_sha256",
"network",
@@ -649,6 +692,14 @@ def _component_image_build_seal(
row["dockerfile_sha256"],
"LAB V1 component Dockerfile SHA-256",
),
build_method=_string(
row["build_method"],
"LAB V1 component image build method",
),
installer_sha256=_string(
row["installer_sha256"],
"LAB V1 component image installer SHA-256",
),
shared_adapter_sha256=_string(
row["shared_adapter_sha256"],
"LAB V1 shared adapter SHA-256",
@@ -694,6 +745,7 @@ def _verify_release_covers_runner_installation(
for build in image_builds
for digest in (
build.dockerfile_sha256,
build.installer_sha256,
build.shared_adapter_sha256,
build.component_adapter_sha256,
build.seal_sha256,
@@ -969,6 +1021,7 @@ def _utc_now() -> str:
__all__ = [
"PORTABLE_LAB_V1_COMPONENT_IMAGE_BUILD_METHOD",
"PORTABLE_LAB_V1_COMPONENT_IMAGE_BUILD_SEAL_SCHEMA",
"PORTABLE_LAB_V1_ADAPTER_ID",
"PORTABLE_LAB_V1_SETUP_ID",