refactor(worker): externalize runtime registries

This commit is contained in:
DCCONSTRUCTIONS
2026-08-31 19:47:07 +03:00
parent 104e54f4dd
commit 732cc139fd
4 changed files with 194 additions and 50 deletions
@@ -12,11 +12,12 @@ ENV PYTHONPATH=/opt/nodedc/mission-core/src \
WORKDIR /opt/nodedc/mission-core WORKDIR /opt/nodedc/mission-core
COPY src/k1link ./src/k1link COPY src/k1link ./src/k1link
COPY config ./config
COPY experiments/perception/worker/observatory_portable/worker-006-agent-build-context.json \ COPY experiments/perception/worker/observatory_portable/worker-006-agent-build-context.json \
./release/worker-006-agent-build-context.json ./release/worker-006-agent-build-context.json
RUN case "${NODEDC_SOURCE_REVISION}" in \ RUN mkdir -p /run/nodedc/registries \
&& chmod 0555 /run/nodedc /run/nodedc/registries \
&& case "${NODEDC_SOURCE_REVISION}" in \
*[!0-9a-f]* ) exit 64 ;; \ *[!0-9a-f]* ) exit 64 ;; \
* ) ;; \ * ) ;; \
esac \ esac \
@@ -45,6 +46,7 @@ LABEL org.opencontainers.image.title="NODE.DC Observatory Worker 006 agent" \
com.nodedc.worker-contour="worker-006" \ com.nodedc.worker-contour="worker-006" \
com.nodedc.authority="observation-only" \ com.nodedc.authority="observation-only" \
com.nodedc.models="external" \ com.nodedc.models="external" \
com.nodedc.runtime-registries="external-read-only" \
com.nodedc.base-image.sha256="58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794" \ com.nodedc.base-image.sha256="58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794" \
com.nodedc.build-context.sha256="${NODEDC_BUILD_CONTEXT_SHA256}" com.nodedc.build-context.sha256="${NODEDC_BUILD_CONTEXT_SHA256}"
@@ -27,11 +27,6 @@
"kind": "tree", "kind": "tree",
"image_path": "/opt/nodedc/mission-core/src/k1link" "image_path": "/opt/nodedc/mission-core/src/k1link"
}, },
{
"path": "config",
"kind": "tree",
"image_path": "/opt/nodedc/mission-core/config"
},
{ {
"path": "experiments/perception/worker/observatory_portable/Dockerfile.worker-006-agent", "path": "experiments/perception/worker/observatory_portable/Dockerfile.worker-006-agent",
"kind": "file", "kind": "file",
@@ -43,11 +38,24 @@
"image_path": "/opt/nodedc/mission-core/release/worker-006-agent-build-context.json" "image_path": "/opt/nodedc/mission-core/release/worker-006-agent-build-context.json"
} }
], ],
"required_configuration": [ "embedded_configuration": [],
"config/observatory-portable-run-definitions.json", "external_runtime_files": [
"config/observatory-worker-runtime-candidates.json", {
"config/perception/m49-tgs-portable-v2.json", "role": "portable-run-definition-registry",
"config/perception/lab-v1-eomt-ddrnet-portable-v2.json" "environment_variable": "MISSIONCORE_OBSERVATORY_WORKER_DEFINITIONS_FILE",
"container_path": "/run/nodedc/registries/observatory-portable-run-definitions.json",
"binding": "exact-read-only-regular-file",
"identity_owner": "post-image runtime installation",
"baked_into_image": false
},
{
"role": "portable-worker-runtime-registry",
"environment_variable": "MISSIONCORE_OBSERVATORY_WORKER_RUNTIME_REGISTRY_FILE",
"container_path": "/run/nodedc/registries/observatory-worker-runtime-candidates.json",
"binding": "exact-read-only-regular-file",
"identity_owner": "post-image runtime installation",
"baked_into_image": false
}
], ],
"runtime": { "runtime": {
"pythonpath": "/opt/nodedc/mission-core/src", "pythonpath": "/opt/nodedc/mission-core/src",
@@ -57,11 +65,15 @@
"k1link.observatory.m49_worker_container_main" "k1link.observatory.m49_worker_container_main"
], ],
"composition_module": "k1link.observatory.m49_worker_service", "composition_module": "k1link.observatory.m49_worker_service",
"configuration_baked_into_image": false,
"runtime_registries_baked_into_image": false,
"models_baked_into_image": false, "models_baked_into_image": false,
"executor_releases_baked_into_image": false "executor_releases_baked_into_image": false
}, },
"forbidden_context": [ "forbidden_context": [
"model weights", "model weights",
"run-definition registries",
"runtime-candidate registries",
"recorded K1 sessions", "recorded K1 sessions",
"worker bearer credentials", "worker bearer credentials",
"executor release payloads", "executor release payloads",
@@ -55,6 +55,7 @@
"com.nodedc.worker-contour": "worker-006", "com.nodedc.worker-contour": "worker-006",
"com.nodedc.authority": "observation-only", "com.nodedc.authority": "observation-only",
"com.nodedc.models": "external", "com.nodedc.models": "external",
"com.nodedc.runtime-registries": "external-read-only",
"com.nodedc.base-image.sha256": "58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794", "com.nodedc.base-image.sha256": "58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794",
"com.nodedc.build-context.sha256": "<build-context-sha256>" "com.nodedc.build-context.sha256": "<build-context-sha256>"
}, },
@@ -76,9 +77,41 @@
"MISSIONCORE_OBSERVATORY_WORKER_BASE_URL": "http://127.0.0.1:18080", "MISSIONCORE_OBSERVATORY_WORKER_BASE_URL": "http://127.0.0.1:18080",
"MISSIONCORE_OBSERVATORY_WORKER_TOKEN_FILE": "/run/secrets/observatory-worker.token", "MISSIONCORE_OBSERVATORY_WORKER_TOKEN_FILE": "/run/secrets/observatory-worker.token",
"MISSIONCORE_OBSERVATORY_WORKER_WORK_ROOT": "/work", "MISSIONCORE_OBSERVATORY_WORKER_WORK_ROOT": "/work",
"MISSIONCORE_OBSERVATORY_WORKER_DEFINITIONS_FILE": "/opt/nodedc/mission-core/config/observatory-portable-run-definitions.json", "MISSIONCORE_OBSERVATORY_WORKER_DEFINITIONS_FILE": "/run/nodedc/registries/observatory-portable-run-definitions.json",
"MISSIONCORE_OBSERVATORY_WORKER_RUNTIME_REGISTRY_FILE": "/opt/nodedc/mission-core/config/observatory-worker-runtime-candidates.json", "MISSIONCORE_OBSERVATORY_WORKER_RUNTIME_REGISTRY_FILE": "/run/nodedc/registries/observatory-worker-runtime-candidates.json",
"MISSIONCORE_OBSERVATORY_M49_INSTALLATION_RECEIPT_FILE": "/release/worker-installation-receipt.json" "MISSIONCORE_OBSERVATORY_M49_INSTALLATION_RECEIPT_FILE": "/release/worker-installation-receipt.json",
"MISSIONCORE_OBSERVATORY_LAB_V1_INSTALLATION_RECEIPT_FILE": "/release/lab-v1-worker-installation-receipt.json"
},
"runtime_registry_files": {
"binding": "individual read-only bind files",
"identity_timing": "filled and verified after coordinator image build",
"included_in_coordinator_image_or_image_receipt": false,
"required": [
{
"role": "portable-run-definition-registry",
"environment_variable": "MISSIONCORE_OBSERVATORY_WORKER_DEFINITIONS_FILE",
"host_path": null,
"container_path": "/run/nodedc/registries/observatory-portable-run-definitions.json",
"byte_length": null,
"sha256": null,
"mode": "read-only"
},
{
"role": "portable-worker-runtime-registry",
"environment_variable": "MISSIONCORE_OBSERVATORY_WORKER_RUNTIME_REGISTRY_FILE",
"host_path": null,
"container_path": "/run/nodedc/registries/observatory-worker-runtime-candidates.json",
"byte_length": null,
"sha256": null,
"mode": "read-only"
}
],
"required_preflight": [
"fill one exact absolute host path, byte length and SHA-256 for each registry after the coordinator image identity exists",
"reject missing, symbolic-link, non-regular or identity-mismatched registry files before container start",
"bind each registry as its own read-only file at the fixed container path",
"reject any registry path inside the coordinator image"
]
}, },
"user": { "user": {
"uid": 0, "uid": 0,
@@ -121,9 +154,13 @@
"force-delete exact created container ID" "force-delete exact created container ID"
], ],
"sibling_runtime_fences": { "sibling_runtime_fences": {
"exact_image_sha256s": { "component_image_identity": {
"eomt": "58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794", "source": "/release/lab-v1-worker-installation-receipt.json",
"ddrnet": "591cb382c099eeb05e7ec16e2371e0b2da54d2bb5c49ec0f4ac88dbf72b0f0cd" "schema_version": "missioncore.observatory-portable-lab-v1-worker-installation-ready-receipt/v1",
"fields": "component_installations.<eomt-or-ddrnet>.image_sha256",
"image_kind": "derived adapter image",
"base_image_ids_are_component_ids": false,
"queued_job_override_allowed": false
}, },
"commands_and_entrypoints": "exact values sealed in the LAB V1 runner installation receipt; never supplied by a queued job", "commands_and_entrypoints": "exact values sealed in the LAB V1 runner installation receipt; never supplied by a queued job",
"bind_sources": "only exact Worker-host asset roots sealed in the LAB V1 runner installation receipt and descendants of its exact work-root binding", "bind_sources": "only exact Worker-host asset roots sealed in the LAB V1 runner installation receipt and descendants of its exact work-root binding",
@@ -179,6 +216,16 @@
"container_path": "/run/secrets", "container_path": "/run/secrets",
"mode": "read-only", "mode": "read-only",
"purpose": "private bearer token file" "purpose": "private bearer token file"
},
{
"container_path": "/run/nodedc/registries/observatory-portable-run-definitions.json",
"mode": "read-only",
"purpose": "exact external portable RunDefinition registry file"
},
{
"container_path": "/run/nodedc/registries/observatory-worker-runtime-candidates.json",
"mode": "read-only",
"purpose": "exact external portable runtime candidate registry file"
} }
], ],
"external_assets": { "external_assets": {
@@ -188,10 +235,12 @@
"queued_jobs_may_select_paths_or_commands": false "queued_jobs_may_select_paths_or_commands": false
}, },
"lab_v1_runner_installation_input": { "lab_v1_runner_installation_input": {
"state": "loader-contract-not-yet-defined", "state": "external-installed-receipt-required",
"environment_variable": null, "environment_variable": "MISSIONCORE_OBSERVATORY_LAB_V1_INSTALLATION_RECEIPT_FILE",
"container_path": null, "container_path": "/release/lab-v1-worker-installation-receipt.json",
"reason": "the current local runner module defines an in-memory sealed installation receipt but no environment loader or installed receipt path" "mode": "read-only",
"owns_component_image_identities": true,
"queued_jobs_may_override_component_images": false
}, },
"protected_contours": [ "protected_contours": [
"K1 connection and control", "K1 connection and control",
@@ -217,6 +266,7 @@
"result": "not-run" "result": "not-run"
}, },
"models_baked_into_image": false, "models_baked_into_image": false,
"runtime_registries_baked_into_image": false,
"authority": { "authority": {
"commands_enabled": false, "commands_enabled": false,
"actuation_allowed": false, "actuation_allowed": false,
@@ -5,8 +5,6 @@ import re
from pathlib import Path from pathlib import Path
from typing import cast from typing import cast
from k1link.observatory import portable_lab_v1_local_runners as local_runners
REPOSITORY_ROOT = Path(__file__).resolve().parents[1] REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
ARTIFACT_ROOT = ( ARTIFACT_ROOT = (
REPOSITORY_ROOT / "experiments" / "perception" / "worker" / "observatory_portable" REPOSITORY_ROOT / "experiments" / "perception" / "worker" / "observatory_portable"
@@ -23,6 +21,14 @@ AUTHORITY = {
"navigation_or_safety_accepted": False, "navigation_or_safety_accepted": False,
"production_accepted": False, "production_accepted": False,
} }
DOCKER_SOCKET = "/var/run/docker.sock"
DEFINITIONS_CONTAINER_PATH = (
"/run/nodedc/registries/observatory-portable-run-definitions.json"
)
RUNTIME_REGISTRY_CONTAINER_PATH = (
"/run/nodedc/registries/observatory-worker-runtime-candidates.json"
)
LAB_V1_RECEIPT_CONTAINER_PATH = "/release/lab-v1-worker-installation-receipt.json"
def _document(path: Path) -> dict[str, object]: def _document(path: Path) -> dict[str, object]:
@@ -45,8 +51,11 @@ def test_agent_dockerfile_is_offline_commit_bound_and_model_free() -> None:
assert 'ENTRYPOINT ["python3", "-m", "k1link.observatory.m49_worker_container_main"]' in payload assert 'ENTRYPOINT ["python3", "-m", "k1link.observatory.m49_worker_container_main"]' in payload
assert "CMD []" in payload assert "CMD []" in payload
assert "COPY src/k1link ./src/k1link" in payload assert "COPY src/k1link ./src/k1link" in payload
assert "COPY config ./config" in payload assert "COPY config" not in payload
assert "COPY . ." not in payload assert "COPY . ." not in payload
assert "mkdir -p /run/nodedc/registries" in payload
assert "chmod 0555 /run/nodedc /run/nodedc/registries" in payload
assert 'com.nodedc.runtime-registries="external-read-only"' in payload
assert re.search(r"(?im)^\s*(?:run\s+)?(?:apt|apk|yum|dnf|pip|uv)\b", payload) is None assert re.search(r"(?im)^\s*(?:run\s+)?(?:apt|apk|yum|dnf|pip|uv)\b", payload) is None
assert "curl " not in payload.lower() assert "curl " not in payload.lower()
assert "wget " not in payload.lower() assert "wget " not in payload.lower()
@@ -85,7 +94,6 @@ def test_build_context_is_only_repo_source_configuration_and_artifact_contract()
paths = [cast(str, row["path"]) for row in entries] paths = [cast(str, row["path"]) for row in entries]
assert paths == [ assert paths == [
"src/k1link", "src/k1link",
"config",
"experiments/perception/worker/observatory_portable/Dockerfile.worker-006-agent", "experiments/perception/worker/observatory_portable/Dockerfile.worker-006-agent",
"experiments/perception/worker/observatory_portable/worker-006-agent-build-context.json", "experiments/perception/worker/observatory_portable/worker-006-agent-build-context.json",
] ]
@@ -97,19 +105,41 @@ def test_build_context_is_only_repo_source_configuration_and_artifact_contract()
for token in ("model", "weight", "session", "credential", "runtime") for token in ("model", "weight", "session", "credential", "runtime")
) )
required = cast(list[str], document["required_configuration"]) assert document["embedded_configuration"] == []
assert required == [ assert "required_configuration" not in document
"config/observatory-portable-run-definitions.json", external_runtime_files = cast(
"config/observatory-worker-runtime-candidates.json", list[dict[str, object]], document["external_runtime_files"]
"config/perception/m49-tgs-portable-v2.json", )
"config/perception/lab-v1-eomt-ddrnet-portable-v2.json", assert external_runtime_files == [
{
"role": "portable-run-definition-registry",
"environment_variable": "MISSIONCORE_OBSERVATORY_WORKER_DEFINITIONS_FILE",
"container_path": DEFINITIONS_CONTAINER_PATH,
"binding": "exact-read-only-regular-file",
"identity_owner": "post-image runtime installation",
"baked_into_image": False,
},
{
"role": "portable-worker-runtime-registry",
"environment_variable": (
"MISSIONCORE_OBSERVATORY_WORKER_RUNTIME_REGISTRY_FILE"
),
"container_path": RUNTIME_REGISTRY_CONTAINER_PATH,
"binding": "exact-read-only-regular-file",
"identity_owner": "post-image runtime installation",
"baked_into_image": False,
},
] ]
assert all((REPOSITORY_ROOT / relative).is_file() for relative in required)
runtime = cast(dict[str, object], document["runtime"]) runtime = cast(dict[str, object], document["runtime"])
assert runtime["entrypoint"] == ENTRYPOINT assert runtime["entrypoint"] == ENTRYPOINT
assert runtime["composition_module"] == "k1link.observatory.m49_worker_service" assert runtime["composition_module"] == "k1link.observatory.m49_worker_service"
assert runtime["configuration_baked_into_image"] is False
assert runtime["runtime_registries_baked_into_image"] is False
assert runtime["models_baked_into_image"] is False assert runtime["models_baked_into_image"] is False
assert runtime["executor_releases_baked_into_image"] is False assert runtime["executor_releases_baked_into_image"] is False
forbidden = cast(list[str], document["forbidden_context"])
assert "run-definition registries" in forbidden
assert "runtime-candidate registries" in forbidden
def test_install_plan_requires_offline_build_hardening_smoke_and_unfilled_receipt() -> None: def test_install_plan_requires_offline_build_hardening_smoke_and_unfilled_receipt() -> None:
@@ -162,6 +192,7 @@ def test_install_plan_requires_offline_build_hardening_smoke_and_unfilled_receip
assert labels["org.opencontainers.image.revision"] == "<source-revision>" assert labels["org.opencontainers.image.revision"] == "<source-revision>"
assert labels["com.nodedc.base-image.sha256"] == BASE_SHA256 assert labels["com.nodedc.base-image.sha256"] == BASE_SHA256
assert labels["com.nodedc.models"] == "external" assert labels["com.nodedc.models"] == "external"
assert labels["com.nodedc.runtime-registries"] == "external-read-only"
smoke = cast(dict[str, object], acceptance["smoke"]) smoke = cast(dict[str, object], acceptance["smoke"])
assert smoke["network"] == "none" assert smoke["network"] == "none"
assert smoke["read_only_rootfs"] is True assert smoke["read_only_rootfs"] is True
@@ -174,16 +205,40 @@ def test_install_plan_requires_offline_build_hardening_smoke_and_unfilled_receip
"MISSIONCORE_OBSERVATORY_WORKER_BASE_URL": "http://127.0.0.1:18080", "MISSIONCORE_OBSERVATORY_WORKER_BASE_URL": "http://127.0.0.1:18080",
"MISSIONCORE_OBSERVATORY_WORKER_TOKEN_FILE": "/run/secrets/observatory-worker.token", "MISSIONCORE_OBSERVATORY_WORKER_TOKEN_FILE": "/run/secrets/observatory-worker.token",
"MISSIONCORE_OBSERVATORY_WORKER_WORK_ROOT": "/work", "MISSIONCORE_OBSERVATORY_WORKER_WORK_ROOT": "/work",
"MISSIONCORE_OBSERVATORY_WORKER_DEFINITIONS_FILE": ( "MISSIONCORE_OBSERVATORY_WORKER_DEFINITIONS_FILE": DEFINITIONS_CONTAINER_PATH,
"/opt/nodedc/mission-core/config/observatory-portable-run-definitions.json"
),
"MISSIONCORE_OBSERVATORY_WORKER_RUNTIME_REGISTRY_FILE": ( "MISSIONCORE_OBSERVATORY_WORKER_RUNTIME_REGISTRY_FILE": (
"/opt/nodedc/mission-core/config/observatory-worker-runtime-candidates.json" RUNTIME_REGISTRY_CONTAINER_PATH
), ),
"MISSIONCORE_OBSERVATORY_M49_INSTALLATION_RECEIPT_FILE": ( "MISSIONCORE_OBSERVATORY_M49_INSTALLATION_RECEIPT_FILE": (
"/release/worker-installation-receipt.json" "/release/worker-installation-receipt.json"
), ),
"MISSIONCORE_OBSERVATORY_LAB_V1_INSTALLATION_RECEIPT_FILE": (
LAB_V1_RECEIPT_CONTAINER_PATH
),
} }
registry_files = cast(dict[str, object], runtime["runtime_registry_files"])
assert registry_files["binding"] == "individual read-only bind files"
assert registry_files["identity_timing"] == (
"filled and verified after coordinator image build"
)
assert registry_files["included_in_coordinator_image_or_image_receipt"] is False
registry_rows = cast(list[dict[str, object]], registry_files["required"])
assert [row["container_path"] for row in registry_rows] == [
DEFINITIONS_CONTAINER_PATH,
RUNTIME_REGISTRY_CONTAINER_PATH,
]
assert all(
row["host_path"] is None
and row["byte_length"] is None
and row["sha256"] is None
and row["mode"] == "read-only"
for row in registry_rows
)
registry_preflight = cast(list[str], registry_files["required_preflight"])
assert any("after the coordinator image identity exists" in row for row in registry_preflight)
assert any("symbolic-link" in row for row in registry_preflight)
assert any("read-only file" in row for row in registry_preflight)
assert any("inside the coordinator image" in row for row in registry_preflight)
user = cast(dict[str, object], runtime["user"]) user = cast(dict[str, object], runtime["user"])
assert (user["uid"], user["gid"]) == (0, 0) assert (user["uid"], user["gid"]) == (0, 0)
assert cast(str, user["reason"]).strip() assert cast(str, user["reason"]).strip()
@@ -205,8 +260,8 @@ def test_install_plan_requires_offline_build_hardening_smoke_and_unfilled_receip
socket_mount = cast(dict[str, object], docker_control["socket_mount"]) socket_mount = cast(dict[str, object], docker_control["socket_mount"])
assert socket_mount == { assert socket_mount == {
"type": "bind", "type": "bind",
"host_path": str(local_runners._DOCKER_SOCKET), "host_path": DOCKER_SOCKET,
"container_path": str(local_runners._DOCKER_SOCKET), "container_path": DOCKER_SOCKET,
"mode": "read-write", "mode": "read-write",
"exception_to_read_only_mount_policy": True, "exception_to_read_only_mount_policy": True,
} }
@@ -219,11 +274,18 @@ def test_install_plan_requires_offline_build_hardening_smoke_and_unfilled_receip
"force-delete exact created container ID", "force-delete exact created container ID",
] ]
fences = cast(dict[str, object], docker_control["sibling_runtime_fences"]) fences = cast(dict[str, object], docker_control["sibling_runtime_fences"])
assert fences["exact_image_sha256s"] == { assert "exact_image_sha256s" not in fences
"eomt": local_runners._EOMT_IMAGE_SHA256, assert fences["component_image_identity"] == {
"ddrnet": local_runners._DDRNET_IMAGE_SHA256, "source": LAB_V1_RECEIPT_CONTAINER_PATH,
"schema_version": (
"missioncore.observatory-portable-lab-v1-worker-installation-ready-"
"receipt/v1"
),
"fields": "component_installations.<eomt-or-ddrnet>.image_sha256",
"image_kind": "derived adapter image",
"base_image_ids_are_component_ids": False,
"queued_job_override_allowed": False,
} }
assert local_runners._EOMT_IMAGE_SHA256 == BASE_SHA256
assert "queued job" in cast(str, fences["commands_and_entrypoints"]) assert "queued job" in cast(str, fences["commands_and_entrypoints"])
assert "exact Worker-host asset roots" in cast(str, fences["bind_sources"]) assert "exact Worker-host asset roots" in cast(str, fences["bind_sources"])
assert fences["network"] == "none" assert fences["network"] == "none"
@@ -254,15 +316,32 @@ def test_install_plan_requires_offline_build_hardening_smoke_and_unfilled_receip
external = cast(dict[str, object], runtime["external_assets"]) external = cast(dict[str, object], runtime["external_assets"])
assert external["baked_into_agent_image"] is False assert external["baked_into_agent_image"] is False
assert external["queued_jobs_may_select_paths_or_commands"] is False assert external["queued_jobs_may_select_paths_or_commands"] is False
mounts = cast(list[dict[str, object]], runtime["mounts"])
registry_mounts = [
row
for row in mounts
if cast(str, row["container_path"]).startswith("/run/nodedc/registries/")
]
assert registry_mounts == [
{
"container_path": DEFINITIONS_CONTAINER_PATH,
"mode": "read-only",
"purpose": "exact external portable RunDefinition registry file",
},
{
"container_path": RUNTIME_REGISTRY_CONTAINER_PATH,
"mode": "read-only",
"purpose": "exact external portable runtime candidate registry file",
},
]
lab_v1_input = cast(dict[str, object], runtime["lab_v1_runner_installation_input"]) lab_v1_input = cast(dict[str, object], runtime["lab_v1_runner_installation_input"])
assert lab_v1_input == { assert lab_v1_input == {
"state": "loader-contract-not-yet-defined", "state": "external-installed-receipt-required",
"environment_variable": None, "environment_variable": "MISSIONCORE_OBSERVATORY_LAB_V1_INSTALLATION_RECEIPT_FILE",
"container_path": None, "container_path": LAB_V1_RECEIPT_CONTAINER_PATH,
"reason": ( "mode": "read-only",
"the current local runner module defines an in-memory sealed installation " "owns_component_image_identities": True,
"receipt but no environment loader or installed receipt path" "queued_jobs_may_override_component_images": False,
),
} }
receipt = cast(dict[str, object], document["receipt_skeleton"]) receipt = cast(dict[str, object], document["receipt_skeleton"])
@@ -274,4 +353,5 @@ def test_install_plan_requires_offline_build_hardening_smoke_and_unfilled_receip
image = cast(dict[str, object], receipt["image"]) image = cast(dict[str, object], receipt["image"])
assert image == {"tag": None, "id": None, "size_bytes": None} assert image == {"tag": None, "id": None, "size_bytes": None}
assert receipt["models_baked_into_image"] is False assert receipt["models_baked_into_image"] is False
assert receipt["runtime_registries_baked_into_image"] is False
assert receipt["authority"] == AUTHORITY assert receipt["authority"] == AUTHORITY