From 732cc139fd29107901e19ee3df69f00d7c26aea0 Mon Sep 17 00:00:00 2001 From: DCCONSTRUCTIONS Date: Mon, 31 Aug 2026 19:47:07 +0300 Subject: [PATCH] refactor(worker): externalize runtime registries --- .../Dockerfile.worker-006-agent | 6 +- .../worker-006-agent-build-context.json | 32 +++-- ...orker-006-agent-install-plan.template.json | 70 +++++++-- ...observatory_worker_agent_image_artifact.py | 136 ++++++++++++++---- 4 files changed, 194 insertions(+), 50 deletions(-) diff --git a/experiments/perception/worker/observatory_portable/Dockerfile.worker-006-agent b/experiments/perception/worker/observatory_portable/Dockerfile.worker-006-agent index 22a495f..fd73e4e 100644 --- a/experiments/perception/worker/observatory_portable/Dockerfile.worker-006-agent +++ b/experiments/perception/worker/observatory_portable/Dockerfile.worker-006-agent @@ -12,11 +12,12 @@ ENV PYTHONPATH=/opt/nodedc/mission-core/src \ WORKDIR /opt/nodedc/mission-core COPY src/k1link ./src/k1link -COPY config ./config COPY experiments/perception/worker/observatory_portable/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 ;; \ * ) ;; \ esac \ @@ -45,6 +46,7 @@ LABEL org.opencontainers.image.title="NODE.DC Observatory Worker 006 agent" \ com.nodedc.worker-contour="worker-006" \ com.nodedc.authority="observation-only" \ com.nodedc.models="external" \ + com.nodedc.runtime-registries="external-read-only" \ com.nodedc.base-image.sha256="58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794" \ com.nodedc.build-context.sha256="${NODEDC_BUILD_CONTEXT_SHA256}" diff --git a/experiments/perception/worker/observatory_portable/worker-006-agent-build-context.json b/experiments/perception/worker/observatory_portable/worker-006-agent-build-context.json index ba75be0..3a6fe67 100644 --- a/experiments/perception/worker/observatory_portable/worker-006-agent-build-context.json +++ b/experiments/perception/worker/observatory_portable/worker-006-agent-build-context.json @@ -27,11 +27,6 @@ "kind": "tree", "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", "kind": "file", @@ -43,11 +38,24 @@ "image_path": "/opt/nodedc/mission-core/release/worker-006-agent-build-context.json" } ], - "required_configuration": [ - "config/observatory-portable-run-definitions.json", - "config/observatory-worker-runtime-candidates.json", - "config/perception/m49-tgs-portable-v2.json", - "config/perception/lab-v1-eomt-ddrnet-portable-v2.json" + "embedded_configuration": [], + "external_runtime_files": [ + { + "role": "portable-run-definition-registry", + "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": { "pythonpath": "/opt/nodedc/mission-core/src", @@ -57,11 +65,15 @@ "k1link.observatory.m49_worker_container_main" ], "composition_module": "k1link.observatory.m49_worker_service", + "configuration_baked_into_image": false, + "runtime_registries_baked_into_image": false, "models_baked_into_image": false, "executor_releases_baked_into_image": false }, "forbidden_context": [ "model weights", + "run-definition registries", + "runtime-candidate registries", "recorded K1 sessions", "worker bearer credentials", "executor release payloads", diff --git a/experiments/perception/worker/observatory_portable/worker-006-agent-install-plan.template.json b/experiments/perception/worker/observatory_portable/worker-006-agent-install-plan.template.json index 4c6feba..ad55fbf 100644 --- a/experiments/perception/worker/observatory_portable/worker-006-agent-install-plan.template.json +++ b/experiments/perception/worker/observatory_portable/worker-006-agent-install-plan.template.json @@ -55,6 +55,7 @@ "com.nodedc.worker-contour": "worker-006", "com.nodedc.authority": "observation-only", "com.nodedc.models": "external", + "com.nodedc.runtime-registries": "external-read-only", "com.nodedc.base-image.sha256": "58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794", "com.nodedc.build-context.sha256": "" }, @@ -76,9 +77,41 @@ "MISSIONCORE_OBSERVATORY_WORKER_BASE_URL": "http://127.0.0.1:18080", "MISSIONCORE_OBSERVATORY_WORKER_TOKEN_FILE": "/run/secrets/observatory-worker.token", "MISSIONCORE_OBSERVATORY_WORKER_WORK_ROOT": "/work", - "MISSIONCORE_OBSERVATORY_WORKER_DEFINITIONS_FILE": "/opt/nodedc/mission-core/config/observatory-portable-run-definitions.json", - "MISSIONCORE_OBSERVATORY_WORKER_RUNTIME_REGISTRY_FILE": "/opt/nodedc/mission-core/config/observatory-worker-runtime-candidates.json", - "MISSIONCORE_OBSERVATORY_M49_INSTALLATION_RECEIPT_FILE": "/release/worker-installation-receipt.json" + "MISSIONCORE_OBSERVATORY_WORKER_DEFINITIONS_FILE": "/run/nodedc/registries/observatory-portable-run-definitions.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_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": { "uid": 0, @@ -121,9 +154,13 @@ "force-delete exact created container ID" ], "sibling_runtime_fences": { - "exact_image_sha256s": { - "eomt": "58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794", - "ddrnet": "591cb382c099eeb05e7ec16e2371e0b2da54d2bb5c49ec0f4ac88dbf72b0f0cd" + "component_image_identity": { + "source": "/release/lab-v1-worker-installation-receipt.json", + "schema_version": "missioncore.observatory-portable-lab-v1-worker-installation-ready-receipt/v1", + "fields": "component_installations..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", "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", "mode": "read-only", "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": { @@ -188,10 +235,12 @@ "queued_jobs_may_select_paths_or_commands": false }, "lab_v1_runner_installation_input": { - "state": "loader-contract-not-yet-defined", - "environment_variable": null, - "container_path": null, - "reason": "the current local runner module defines an in-memory sealed installation receipt but no environment loader or installed receipt path" + "state": "external-installed-receipt-required", + "environment_variable": "MISSIONCORE_OBSERVATORY_LAB_V1_INSTALLATION_RECEIPT_FILE", + "container_path": "/release/lab-v1-worker-installation-receipt.json", + "mode": "read-only", + "owns_component_image_identities": true, + "queued_jobs_may_override_component_images": false }, "protected_contours": [ "K1 connection and control", @@ -217,6 +266,7 @@ "result": "not-run" }, "models_baked_into_image": false, + "runtime_registries_baked_into_image": false, "authority": { "commands_enabled": false, "actuation_allowed": false, diff --git a/tests/test_observatory_worker_agent_image_artifact.py b/tests/test_observatory_worker_agent_image_artifact.py index b95580c..1dc5fc7 100644 --- a/tests/test_observatory_worker_agent_image_artifact.py +++ b/tests/test_observatory_worker_agent_image_artifact.py @@ -5,8 +5,6 @@ import re from pathlib import Path from typing import cast -from k1link.observatory import portable_lab_v1_local_runners as local_runners - REPOSITORY_ROOT = Path(__file__).resolve().parents[1] ARTIFACT_ROOT = ( REPOSITORY_ROOT / "experiments" / "perception" / "worker" / "observatory_portable" @@ -23,6 +21,14 @@ AUTHORITY = { "navigation_or_safety_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]: @@ -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 "CMD []" 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 "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 "curl " 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] assert paths == [ "src/k1link", - "config", "experiments/perception/worker/observatory_portable/Dockerfile.worker-006-agent", "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") ) - required = cast(list[str], document["required_configuration"]) - assert required == [ - "config/observatory-portable-run-definitions.json", - "config/observatory-worker-runtime-candidates.json", - "config/perception/m49-tgs-portable-v2.json", - "config/perception/lab-v1-eomt-ddrnet-portable-v2.json", + assert document["embedded_configuration"] == [] + assert "required_configuration" not in document + external_runtime_files = cast( + list[dict[str, object]], document["external_runtime_files"] + ) + 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"]) assert runtime["entrypoint"] == ENTRYPOINT 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["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: @@ -162,6 +192,7 @@ def test_install_plan_requires_offline_build_hardening_smoke_and_unfilled_receip assert labels["org.opencontainers.image.revision"] == "" assert labels["com.nodedc.base-image.sha256"] == BASE_SHA256 assert labels["com.nodedc.models"] == "external" + assert labels["com.nodedc.runtime-registries"] == "external-read-only" smoke = cast(dict[str, object], acceptance["smoke"]) assert smoke["network"] == "none" 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_TOKEN_FILE": "/run/secrets/observatory-worker.token", "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": DEFINITIONS_CONTAINER_PATH, "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": ( "/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"]) assert (user["uid"], user["gid"]) == (0, 0) 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"]) assert socket_mount == { "type": "bind", - "host_path": str(local_runners._DOCKER_SOCKET), - "container_path": str(local_runners._DOCKER_SOCKET), + "host_path": DOCKER_SOCKET, + "container_path": DOCKER_SOCKET, "mode": "read-write", "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", ] fences = cast(dict[str, object], docker_control["sibling_runtime_fences"]) - assert fences["exact_image_sha256s"] == { - "eomt": local_runners._EOMT_IMAGE_SHA256, - "ddrnet": local_runners._DDRNET_IMAGE_SHA256, + assert "exact_image_sha256s" not in fences + assert fences["component_image_identity"] == { + "source": LAB_V1_RECEIPT_CONTAINER_PATH, + "schema_version": ( + "missioncore.observatory-portable-lab-v1-worker-installation-ready-" + "receipt/v1" + ), + "fields": "component_installations..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 "exact Worker-host asset roots" in cast(str, fences["bind_sources"]) 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"]) assert external["baked_into_agent_image"] 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"]) assert lab_v1_input == { - "state": "loader-contract-not-yet-defined", - "environment_variable": None, - "container_path": None, - "reason": ( - "the current local runner module defines an in-memory sealed installation " - "receipt but no environment loader or installed receipt path" - ), + "state": "external-installed-receipt-required", + "environment_variable": "MISSIONCORE_OBSERVATORY_LAB_V1_INSTALLATION_RECEIPT_FILE", + "container_path": LAB_V1_RECEIPT_CONTAINER_PATH, + "mode": "read-only", + "owns_component_image_identities": True, + "queued_jobs_may_override_component_images": False, } 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"]) assert image == {"tag": None, "id": None, "size_bytes": None} assert receipt["models_baked_into_image"] is False + assert receipt["runtime_registries_baked_into_image"] is False assert receipt["authority"] == AUTHORITY