feat(lab): bind sealed component adapter images

This commit is contained in:
DCCONSTRUCTIONS
2026-08-31 19:19:47 +03:00
parent 35d99e40d5
commit 19c0bd70d0
4 changed files with 210 additions and 64 deletions
@@ -59,8 +59,6 @@ PORTABLE_LAB_V1_COMPONENT_INSTALLATION_SCHEMA: Final = (
_SETUP_ID: Final = "lab-v1-eomt-ddrnet-portable-v1"
_DEFINITION_ID: Final = "lab-v1-eomt-ddrnet-portable"
_DEFINITION_VERSION: Final = 2
_EOMT_IMAGE_SHA256: Final = "58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794"
_DDRNET_IMAGE_SHA256: Final = "591cb382c099eeb05e7ec16e2371e0b2da54d2bb5c49ec0f4ac88dbf72b0f0cd"
_EOMT_PIPELINE: Final = "recorded-semantic-eomt-fisheye-mask/v1"
_DDRNET_CHECKPOINT_SHA256: Final = (
"b99c2838051bcd7b092fd3970aa62a77d5c0bbb809c9b9afb2ff4b0ebdaa4ee6"
@@ -80,54 +78,56 @@ type PortableLabV1Component = Literal["eomt", "ddrnet"]
type PortableLabV1AssetKind = Literal["file", "tree"]
type PortableLabV1AssetVerification = Literal["sha256", "identity-sha256"]
_EXPECTED_ASSET_IDENTITIES: Final[dict[PortableLabV1Component, dict[str, str]]] = {
_EXPECTED_ASSET_TARGETS: Final[dict[PortableLabV1Component, dict[str, str]]] = {
"eomt": {
"eomt-config-json": ("7f4aa94fa4e43c0dbd79a5420edb511120aef62bd82bfbcbcece79948286a650"),
"eomt-dependency-set": ("4eb1f8d33236806e74f9e5bb96b7dce2ac37623dc39b2184be2aa8d7d00e983e"),
"eomt-evaluation-helper": (
"25baf30c0df564734e08f38ace88cc4bc147cacf240c761622279511e361daa4"
),
"eomt-model-weights": ("c265da9a74f58f5c3f4826d23ca4ca78beac0b106cca5842beca61580de5b782"),
"eomt-preprocessor-config": (
"97e2fbf7f0bdba2cfc90251c5133bae9c27ddc9c4410509f40670be2332854e7"
),
"eomt-profile": ("ea583966bc3409f5cf563cbf4fad05e366907e67187082eb692aff53d9f5d875"),
"eomt-profile-runtime": (
"01881862d4eaa218955f776a948124bf19c34be2b5ec282115daeacb15c53ae6"
),
"eomt-recorded-runtime": (
"4dcc4fc8bdf33702651a199be69d0dd4fadb243d2e65aee1c3d1ae7a58fdf675"
),
"eomt-runner": ("651e8e06c3912dffb036b7fd08f2c0623f7563d8306cc7aee05db562798518f4"),
"k1-valid-fov-identity": (
"b4dd8ddf2b87c1d520ee8a0868c4fea062d7c14d1bae73ccabd3abe1f3acbac2"
),
"k1-valid-fov-mask": ("a40cee06b7c6f69b6a09a11563dcfd237f3de833b1ccd31459e66692e528ba63"),
"eomt-environment": "/environment",
"eomt-ffmpeg-runtime": "/opt/ffmpeg",
"eomt-model-cache": "/cache",
"eomt-python-environment": "/opt/env",
"eomt-runner-bundle": "/runner",
"eomt-transformers-environment": "/opt/transformers",
"k1-valid-fov-root": "/valid-fov",
},
"ddrnet": {
"ddrnet-checkpoint": _DDRNET_CHECKPOINT_SHA256,
"ddrnet-goose-mapping": _GOOSE_MAPPING_SHA256,
"ddrnet-goose-runner": ("b18ad60f277eea69a240a28f290611b94627fb9707faf1bb3e6e22102dad67c1"),
"vegetation-policy": ("b75c4ac841d7b4bcc57f7a9c8417ca2317d8ecfa499e72a9af8a8591a2ec0d35"),
"vegetation-provider-map": (
"f2b69046b6a740fd9532d2d88e7fabae7c20fb662f783c9502adc9026406f352"
),
"ddrnet-checkpoint": "/opt/nodedc/assets/ddrnet-checkpoint",
"ddrnet-goose-mapping": "/opt/nodedc/assets/ddrnet-goose-mapping",
"ddrnet-goose-runner": "/opt/nodedc/assets/ddrnet-goose-runner",
"vegetation-policy": "/opt/nodedc/assets/vegetation-policy",
"vegetation-provider-map": "/opt/nodedc/assets/vegetation-provider-map",
},
}
_EXPECTED_FIXED_ASSET_IDENTITIES: Final[dict[str, str]] = {
"eomt-ffmpeg-runtime": "03651449fdcccec847a0f1241e1663a82cf374bd94e7470b4ddb0c0e46d88c69",
"ddrnet-checkpoint": _DDRNET_CHECKPOINT_SHA256,
"ddrnet-goose-mapping": _GOOSE_MAPPING_SHA256,
"ddrnet-goose-runner": "b18ad60f277eea69a240a28f290611b94627fb9707faf1bb3e6e22102dad67c1",
"vegetation-policy": "b75c4ac841d7b4bcc57f7a9c8417ca2317d8ecfa499e72a9af8a8591a2ec0d35",
"vegetation-provider-map": (
"f2b69046b6a740fd9532d2d88e7fabae7c20fb662f783c9502adc9026406f352"
),
}
_EXPECTED_ASSET_LENGTHS: Final[dict[str, int]] = {
"eomt-ffmpeg-runtime": 256_208_352,
"ddrnet-checkpoint": 259_419_077,
"ddrnet-goose-runner": 32_877,
"eomt-config-json": 1_575,
"eomt-evaluation-helper": 28_915,
"eomt-model-weights": 1_276_175_488,
"eomt-preprocessor-config": 666,
"eomt-profile": 3_805,
"eomt-profile-runtime": 45_789,
"eomt-recorded-runtime": 34_899,
"eomt-runner": 30_720,
"vegetation-policy": 3_022,
"vegetation-provider-map": 2_756,
}
_EXPECTED_ENTRYPOINTS: Final[dict[PortableLabV1Component, tuple[str, ...]]] = {
"eomt": ("python3",),
"ddrnet": (
"conda",
"run",
"--no-capture-output",
"--name",
"goose",
"python",
),
}
_EXPECTED_COMMANDS: Final[dict[PortableLabV1Component, tuple[str, ...]]] = {
"eomt": ("/opt/nodedc/adapter/run_portable_lab_v1_eomt_component.py",),
"ddrnet": ("/opt/nodedc/adapter/run_portable_lab_v1_ddrnet_component.py",),
}
class PortableLabV1LocalRunnerError(PortableLabV1WorkerError):
@@ -154,8 +154,18 @@ class PortableLabV1HostAsset:
_safe_token(self.asset_id, "asset id")
_host_path(self.host_path, "asset host path")
target = _container_path(self.container_path, "asset container path")
if target.parts[:4] != ("/", "opt", "nodedc", "assets"):
raise PortableLabV1LocalRunnerError("asset target is outside /opt/nodedc/assets")
expected_target = next(
(
paths[self.asset_id]
for paths in _EXPECTED_ASSET_TARGETS.values()
if self.asset_id in paths
),
None,
)
if expected_target is None or target.as_posix() != expected_target:
raise PortableLabV1LocalRunnerError(
"asset target differs from its fixed component path"
)
_digest(self.identity_sha256, "asset identity sha256")
if self.kind == "file" and self.verification != "sha256":
raise PortableLabV1LocalRunnerError(
@@ -210,11 +220,15 @@ class PortableLabV1ComponentInstallation:
if self.component not in ("eomt", "ddrnet"):
raise PortableLabV1LocalRunnerError("component identity is invalid")
_digest(self.image_sha256, "component image sha256")
expected_image = _EOMT_IMAGE_SHA256 if self.component == "eomt" else _DDRNET_IMAGE_SHA256
if self.image_sha256 != expected_image:
raise PortableLabV1LocalRunnerError(f"{self.component} image identity changed")
_argv(self.entrypoint, "component entrypoint")
_argv(self.command, "component command")
if (
self.entrypoint != _EXPECTED_ENTRYPOINTS[self.component]
or self.command != _EXPECTED_COMMANDS[self.component]
):
raise PortableLabV1LocalRunnerError(
f"{self.component} adapter entrypoint changed"
)
if not (1.0 <= self.timeout_seconds <= 24 * 60 * 60):
raise PortableLabV1LocalRunnerError("component timeout is invalid")
if not (1024**3 <= self.memory_bytes <= 128 * 1024**3):
@@ -227,10 +241,31 @@ class PortableLabV1ComponentInstallation:
targets = tuple(asset.container_path for asset in self.assets)
if len(targets) != len(set(targets)):
raise PortableLabV1LocalRunnerError("component asset targets overlap")
expected = _EXPECTED_ASSET_IDENTITIES[self.component]
expected = _EXPECTED_ASSET_TARGETS[self.component]
if set(asset_ids) != set(expected):
raise PortableLabV1LocalRunnerError(f"{self.component} installed asset set changed")
if any(asset.identity_sha256 != expected[asset.asset_id] for asset in self.assets):
if any(asset.container_path != expected[asset.asset_id] for asset in self.assets):
raise PortableLabV1LocalRunnerError(
f"{self.component} installed asset target changed"
)
expected_kind: PortableLabV1AssetKind = (
"tree" if self.component == "eomt" else "file"
)
expected_verification: PortableLabV1AssetVerification = (
"identity-sha256" if self.component == "eomt" else "sha256"
)
if any(
asset.kind != expected_kind or asset.verification != expected_verification
for asset in self.assets
):
raise PortableLabV1LocalRunnerError(
f"{self.component} installed asset verification changed"
)
if any(
asset.asset_id in _EXPECTED_FIXED_ASSET_IDENTITIES
and asset.identity_sha256 != _EXPECTED_FIXED_ASSET_IDENTITIES[asset.asset_id]
for asset in self.assets
):
raise PortableLabV1LocalRunnerError(
f"{self.component} installed asset identity changed"
)
@@ -440,11 +475,14 @@ class PortableLabV1DockerLaunch:
if self.component not in ("eomt", "ddrnet"):
raise PortableLabV1LocalRunnerError("launch component is invalid")
_digest(self.image_sha256, "launch image sha256")
expected_image = _EOMT_IMAGE_SHA256 if self.component == "eomt" else _DDRNET_IMAGE_SHA256
if self.image_sha256 != expected_image:
raise PortableLabV1LocalRunnerError("launch image identity changed")
_argv(self.entrypoint, "launch entrypoint")
_argv(self.command, "launch command")
if (
self.entrypoint != _EXPECTED_ENTRYPOINTS[self.component]
or self.command[:-2] != _EXPECTED_COMMANDS[self.component]
or self.command[-2:] != ("--request", "/run/nodedc/request.json")
):
raise PortableLabV1LocalRunnerError("launch adapter command changed")
_safe_token(self.name_token, "launch name token")
if not (1.0 <= self.timeout_seconds <= 24 * 60 * 60):
raise PortableLabV1LocalRunnerError("launch timeout is invalid")
@@ -458,10 +496,11 @@ class PortableLabV1DockerLaunch:
writable = tuple(mount for mount in self.mounts if not mount.read_only)
if len(writable) != 1 or writable[0].container_path != "/run/nodedc/output":
raise PortableLabV1LocalRunnerError("Docker writable mount surface changed")
allowed_asset_targets = set(_EXPECTED_ASSET_TARGETS[self.component].values())
if any(
not (
mount.container_path.startswith("/run/nodedc/")
or mount.container_path.startswith("/opt/nodedc/assets/")
or mount.container_path in allowed_asset_targets
)
for mount in self.mounts
):