fix(deploy): version device manager edge release contract

This commit is contained in:
Codex
2026-08-11 20:43:07 +03:00
parent 724e745628
commit 0f44cca27d
5 changed files with 283 additions and 59 deletions
@@ -11,9 +11,6 @@
"publicIngress": "reverse-proxy-only", "publicIngress": "reverse-proxy-only",
"deviceCoreManagementApi": "file-token-authenticated", "deviceCoreManagementApi": "file-token-authenticated",
"launcherTrust": "file-token-scoped-to-device-core-handoff", "launcherTrust": "file-token-scoped-to-device-core-handoff",
"edgeChannel": "core-initiated-pinned-mtls-enabled-zero-or-more-registered-edges",
"edgeChannelIdentity": "runner-managed-host-local-private-key-public-certificate-export",
"edgeChannelEgress": "dedicated-core-only-bridge-no-host-ingress-public-ipv4-tcp-8443-registration-policy",
"healthGate": "bounded-container-grace+core-contract", "healthGate": "bounded-container-grace+core-contract",
"commandTransport": "disabled", "commandTransport": "disabled",
"gelios": "untouched", "gelios": "untouched",
@@ -0,0 +1,21 @@
{
"schemaVersion": "nodedc.device-plane.device-manager-release.v2",
"releaseId": "__PATCH_ID__",
"action": "upgrade",
"predecessor": {
"kind": "release",
"patchId": "device-manager-release-20260811-010",
"artifactSha256": "d4132993216eb674967dc6fc65d9670cfc2a9efdf46186ca019030f259de2d0e"
},
"service": "device-manager",
"publicIngress": "reverse-proxy-only",
"deviceCoreManagementApi": "file-token-authenticated",
"launcherTrust": "file-token-scoped-to-device-core-handoff",
"edgeChannel": "core-initiated-pinned-mtls-enabled-zero-or-more-registered-edges",
"edgeChannelIdentity": "runner-managed-host-local-private-key-public-certificate-export",
"edgeChannelEgress": "dedicated-core-only-bridge-no-host-ingress-public-ipv4-tcp-8443-registration-policy",
"healthGate": "bounded-container-grace+core-contract",
"commandTransport": "disabled",
"gelios": "untouched",
"rollback": "restore-preapply-snapshot"
}
@@ -12,7 +12,7 @@ const devicePlaneRoot = resolve(platformRoot, "device-plane");
const designRoot = resolve(process.env.NODEDC_DEVICE_MANAGER_SOURCE_ROOT || resolve(platformRoot, "../NODEDC_DESIGN_GUIDELINE")); const designRoot = resolve(process.env.NODEDC_DEVICE_MANAGER_SOURCE_ROOT || resolve(platformRoot, "../NODEDC_DESIGN_GUIDELINE"));
const managerRoot = resolve(designRoot, "apps/device-manager"); const managerRoot = resolve(designRoot, "apps/device-manager");
const artifactDir = resolve(process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(scriptDir, "../deploy-artifacts")); const artifactDir = resolve(process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(scriptDir, "../deploy-artifacts"));
const [patchId = "device-manager-release-20260811-011", ...extra] = process.argv.slice(2); const [patchId = "device-manager-release-20260811-012", ...extra] = process.argv.slice(2);
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) throw new Error("usage: build-device-manager-control-plane-artifact.mjs [patch-id]"); if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) throw new Error("usage: build-device-manager-control-plane-artifact.mjs [patch-id]");
const entries = [ const entries = [
@@ -26,7 +26,7 @@ const entries = [
"services/device-gateway/package.json", "services/device-gateway/package.json",
"services/device-edge-relay/package.json", "services/device-edge-relay/package.json",
"services/device-manager", "services/device-manager",
"deployment/device-manager-release-v1.json", "deployment/device-manager-release-v2.json",
]; ];
const stage = await mkdtemp(join(tmpdir(), "nodedc-device-manager-control-plane-")); const stage = await mkdtemp(join(tmpdir(), "nodedc-device-manager-control-plane-"));
const payload = join(stage, "payload"); const payload = join(stage, "payload");
@@ -40,7 +40,7 @@ try {
if (build.status !== 0) throw new Error(`device_manager_build_failed:${build.stderr || build.stdout}`); if (build.status !== 0) throw new Error(`device_manager_build_failed:${build.stderr || build.stdout}`);
await mkdir(payload, { recursive: true }); await mkdir(payload, { recursive: true });
for (const entry of entries) { for (const entry of entries) {
if (entry === "deployment/device-manager-release-v1.json") { if (entry === "deployment/device-manager-release-v2.json") {
const descriptor = JSON.parse(await readFile(resolve(devicePlaneRoot, entry), "utf8")); const descriptor = JSON.parse(await readFile(resolve(devicePlaneRoot, entry), "utf8"));
if (descriptor.releaseId !== "__PATCH_ID__") { if (descriptor.releaseId !== "__PATCH_ID__") {
throw new Error("device_manager_release_template_id_mismatch"); throw new Error("device_manager_release_template_id_mismatch");
@@ -109,12 +109,12 @@ try {
if (compose.includes(forbidden)) throw new Error(`device_manager_compose_boundary_violation:${forbidden}`); if (compose.includes(forbidden)) throw new Error(`device_manager_compose_boundary_violation:${forbidden}`);
} }
const descriptor = JSON.parse(await readFile( const descriptor = JSON.parse(await readFile(
join(payload, "deployment/device-manager-release-v1.json"), join(payload, "deployment/device-manager-release-v2.json"),
"utf8", "utf8",
)); ));
const predecessor = descriptor.predecessor; const predecessor = descriptor.predecessor;
if ( if (
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-release.v1" descriptor.schemaVersion !== "nodedc.device-plane.device-manager-release.v2"
|| descriptor.releaseId !== patchId || descriptor.releaseId !== patchId
|| !["activate", "upgrade"].includes(descriptor.action) || !["activate", "upgrade"].includes(descriptor.action)
|| !predecessor || !predecessor
+164 -31
View File
@@ -240,10 +240,19 @@ DEVICE_PLANE_MANAGER_V2_CONTROL_PLANE_REL = (
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL = ( DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL = (
"deployment/device-manager-release-v1.json" "deployment/device-manager-release-v1.json"
) )
DEVICE_PLANE_MANAGER_RELEASE_V2_REL = (
"deployment/device-manager-release-v2.json"
)
DEVICE_PLANE_MANAGER_COMPOSE_REL = "docker-compose.device-manager.yml" DEVICE_PLANE_MANAGER_COMPOSE_REL = "docker-compose.device-manager.yml"
DEVICE_PLANE_MANAGER_COMPOSE_SHA256 = ( DEVICE_PLANE_MANAGER_RELEASE_V1_COMPOSE_SHA256 = (
"4954120aaddc999798b64c304d8cf692b79714feb727d873117bd1f3434e865e"
)
DEVICE_PLANE_MANAGER_RELEASE_V2_COMPOSE_SHA256 = (
"369a2acf9c1a1030b9e1c6c366144b1eaf8900aef0ee59bb6bf23250b7b371b9" "369a2acf9c1a1030b9e1c6c366144b1eaf8900aef0ee59bb6bf23250b7b371b9"
) )
DEVICE_PLANE_MANAGER_COMPOSE_SHA256 = (
DEVICE_PLANE_MANAGER_RELEASE_V2_COMPOSE_SHA256
)
DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_ENTRIES = ( DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_ENTRIES = (
".dockerignore", ".dockerignore",
"package.json", "package.json",
@@ -283,6 +292,19 @@ DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES = (
"services/device-manager", "services/device-manager",
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL, DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
) )
DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES = (
".dockerignore",
"package.json",
"package-lock.json",
DEVICE_PLANE_MANAGER_COMPOSE_REL,
"packages/device-protocol-contract",
"packages/arusnavi-b2-adapter",
"services/device-control-core",
"services/device-gateway/package.json",
"services/device-edge-relay/package.json",
"services/device-manager",
DEVICE_PLANE_MANAGER_RELEASE_V2_REL,
)
DEVICE_PLANE_MANAGER_RECONCILIATION_REL = ( DEVICE_PLANE_MANAGER_RECONCILIATION_REL = (
"deployment/device-manager-control-plane-reconciliation-v1.json" "deployment/device-manager-control-plane-reconciliation-v1.json"
) )
@@ -3505,6 +3527,7 @@ def allowed_payload_path(component, rel):
DEVICE_PLANE_BACKHAUL_TARGET_REL, DEVICE_PLANE_BACKHAUL_TARGET_REL,
DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_REL, DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_REL,
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL, DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
DEVICE_PLANE_MANAGER_RELEASE_V2_REL,
DEVICE_PLANE_MANAGER_RECONCILIATION_REL, DEVICE_PLANE_MANAGER_RECONCILIATION_REL,
DEVICE_PLANE_MANAGER_V2_RECONCILIATION_REL, DEVICE_PLANE_MANAGER_V2_RECONCILIATION_REL,
"packages/device-protocol-contract", "packages/device-protocol-contract",
@@ -8551,7 +8574,7 @@ def load_artifact(artifact, work_dir):
manifest["component"], manifest["component"],
entries, entries,
): ):
validate_device_plane_manager_control_plane_payload( validate_device_plane_manager_release_payload(
payload_dir, payload_dir,
expected_release_id=manifest["id"], expected_release_id=manifest["id"],
) )
@@ -9023,7 +9046,18 @@ def is_device_plane_manager_control_plane_slice(component, entries):
return ( return (
component == "device-plane" component == "device-plane"
and entries is not None and entries is not None
and tuple(entries) == DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES and tuple(entries) in (
DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES,
DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES,
)
)
def is_device_plane_manager_release_v2_slice(component, entries):
return (
component == "device-plane"
and entries is not None
and tuple(entries) == DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES
) )
@@ -9083,12 +9117,22 @@ def expected_platform_device_manager_public_route_descriptor():
} }
def expected_device_plane_manager_release_boundaries(): def expected_device_plane_manager_release_v1_boundaries():
return { return {
"service": "device-manager", "service": "device-manager",
"publicIngress": "reverse-proxy-only", "publicIngress": "reverse-proxy-only",
"deviceCoreManagementApi": "file-token-authenticated", "deviceCoreManagementApi": "file-token-authenticated",
"launcherTrust": "file-token-scoped-to-device-core-handoff", "launcherTrust": "file-token-scoped-to-device-core-handoff",
"healthGate": "bounded-container-grace+core-contract",
"commandTransport": "disabled",
"gelios": "untouched",
"rollback": "restore-preapply-snapshot",
}
def expected_device_plane_manager_release_v2_boundaries():
return {
**expected_device_plane_manager_release_v1_boundaries(),
"edgeChannel": ( "edgeChannel": (
"core-initiated-pinned-mtls-enabled-zero-or-more-registered-edges" "core-initiated-pinned-mtls-enabled-zero-or-more-registered-edges"
), ),
@@ -9099,13 +9143,15 @@ def expected_device_plane_manager_release_boundaries():
"dedicated-core-only-bridge-no-host-ingress-" "dedicated-core-only-bridge-no-host-ingress-"
"public-ipv4-tcp-8443-registration-policy" "public-ipv4-tcp-8443-registration-policy"
), ),
"healthGate": "bounded-container-grace+core-contract",
"commandTransport": "disabled",
"gelios": "untouched",
"rollback": "restore-preapply-snapshot",
} }
def expected_device_plane_manager_release_boundaries():
# Compatibility name for the current release builder/tests. Immutable v1
# predecessors always use expected_device_plane_manager_release_v1_boundaries.
return expected_device_plane_manager_release_v2_boundaries()
def expected_device_plane_manager_failed_control_plane_descriptor(): def expected_device_plane_manager_failed_control_plane_descriptor():
return { return {
"schemaVersion": ( "schemaVersion": (
@@ -9231,29 +9277,28 @@ def validate_platform_device_manager_public_route_payload(payload_dir):
return descriptor return descriptor
def validate_device_plane_manager_control_plane_payload( def validate_device_plane_manager_release_descriptor(
payload_dir, descriptor,
*, *,
schema_version,
boundaries,
expected_release_id=None, expected_release_id=None,
): ):
descriptor = read_strict_json(
payload_dir / DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
"Device Manager release descriptor",
max_bytes=16 * 1024,
)
required_keys = { required_keys = {
"schemaVersion", "schemaVersion",
"releaseId", "releaseId",
"action", "action",
"predecessor", "predecessor",
*expected_device_plane_manager_release_boundaries(), *boundaries,
} }
if set(descriptor) != required_keys: if set(descriptor) != required_keys:
die("Device Manager release descriptor key set mismatch") missing = ",".join(sorted(required_keys - set(descriptor))) or "none"
if ( extra = ",".join(sorted(set(descriptor) - required_keys)) or "none"
descriptor.get("schemaVersion") die(
!= "nodedc.device-plane.device-manager-release.v1" "Device Manager release descriptor key set mismatch: "
): f"schema={schema_version} missing={missing} extra={extra}"
)
if descriptor.get("schemaVersion") != schema_version:
die("Device Manager release descriptor schema mismatch") die("Device Manager release descriptor schema mismatch")
release_id = descriptor.get("releaseId") release_id = descriptor.get("releaseId")
if ( if (
@@ -9284,16 +9329,39 @@ def validate_device_plane_manager_control_plane_payload(
!= (predecessor["kind"] == "reconciliation") != (predecessor["kind"] == "reconciliation")
): ):
die("Device Manager release predecessor mismatch") die("Device Manager release predecessor mismatch")
boundaries = expected_device_plane_manager_release_boundaries()
if any(descriptor.get(key) != value for key, value in boundaries.items()): if any(descriptor.get(key) != value for key, value in boundaries.items()):
die("Device Manager release security boundary mismatch") die("Device Manager release security boundary mismatch")
return descriptor
def validate_device_plane_manager_release_payload_contract(
payload_dir,
*,
descriptor_rel,
schema_version,
boundaries,
compose_sha256,
edge_channel,
expected_release_id=None,
):
descriptor = read_strict_json(
payload_dir / descriptor_rel,
"Device Manager release descriptor",
max_bytes=16 * 1024,
)
validate_device_plane_manager_release_descriptor(
descriptor,
schema_version=schema_version,
boundaries=boundaries,
expected_release_id=expected_release_id,
)
compose_path = payload_dir / DEVICE_PLANE_MANAGER_COMPOSE_REL compose_path = payload_dir / DEVICE_PLANE_MANAGER_COMPOSE_REL
if sha256_file(compose_path) != DEVICE_PLANE_MANAGER_COMPOSE_SHA256: if sha256_file(compose_path) != compose_sha256:
die("Device Manager control-plane Compose mismatch") die("Device Manager control-plane Compose mismatch")
compose = compose_path.read_text( compose = compose_path.read_text(
encoding="utf-8" encoding="utf-8"
) )
for required in ( required_compose = [
"device-manager:", "device-manager:",
'DEVICE_MANAGEMENT_API_ENABLED: "true"', 'DEVICE_MANAGEMENT_API_ENABLED: "true"',
"DEVICE_MANAGEMENT_CORE_TOKEN_FILE: " "DEVICE_MANAGEMENT_CORE_TOKEN_FILE: "
@@ -9302,6 +9370,10 @@ def validate_device_plane_manager_control_plane_payload(
"/run/nodedc-secrets/device-core-internal-token", "/run/nodedc-secrets/device-core-internal-token",
"NODEDC_DEVICE_CORE_TOKEN_FILE: " "NODEDC_DEVICE_CORE_TOKEN_FILE: "
"/run/nodedc-secrets/management-core-token", "/run/nodedc-secrets/management-core-token",
"name: nodedc-platform_edge",
]
if edge_channel:
required_compose.extend((
'DEVICE_EDGE_CHANNEL_ENABLED: "true"', 'DEVICE_EDGE_CHANNEL_ENABLED: "true"',
"DEVICE_EDGE_CHANNEL_CORE_KEY_FILE: " "DEVICE_EDGE_CHANNEL_CORE_KEY_FILE: "
"/run/nodedc-secrets/device-edge-channel/core-private-key.pem", "/run/nodedc-secrets/device-edge-channel/core-private-key.pem",
@@ -9316,8 +9388,8 @@ def validate_device_plane_manager_control_plane_payload(
"source: /volume1/docker/nodedc-device-plane/secrets/" "source: /volume1/docker/nodedc-device-plane/secrets/"
"device-edge-channel/peers", "device-edge-channel/peers",
"name: nodedc-device-plane-egress", "name: nodedc-device-plane-egress",
"name: nodedc-platform_edge", ))
): for required in required_compose:
if required not in compose: if required not in compose:
die(f"Device Manager control-plane boundary missing: {required}") die(f"Device Manager control-plane boundary missing: {required}")
for forbidden in ( for forbidden in (
@@ -9330,6 +9402,59 @@ def validate_device_plane_manager_control_plane_payload(
return descriptor return descriptor
def validate_device_plane_manager_control_plane_payload(
payload_dir,
*,
expected_release_id=None,
):
return validate_device_plane_manager_release_payload_contract(
payload_dir,
descriptor_rel=DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
schema_version="nodedc.device-plane.device-manager-release.v1",
boundaries=expected_device_plane_manager_release_v1_boundaries(),
compose_sha256=DEVICE_PLANE_MANAGER_RELEASE_V1_COMPOSE_SHA256,
edge_channel=False,
expected_release_id=expected_release_id,
)
def validate_device_plane_manager_release_v2_payload(
payload_dir,
*,
expected_release_id=None,
):
return validate_device_plane_manager_release_payload_contract(
payload_dir,
descriptor_rel=DEVICE_PLANE_MANAGER_RELEASE_V2_REL,
schema_version="nodedc.device-plane.device-manager-release.v2",
boundaries=expected_device_plane_manager_release_v2_boundaries(),
compose_sha256=DEVICE_PLANE_MANAGER_RELEASE_V2_COMPOSE_SHA256,
edge_channel=True,
expected_release_id=expected_release_id,
)
def validate_device_plane_manager_release_payload(
payload_dir,
*,
expected_release_id=None,
):
v1 = payload_dir / DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL
v2 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V2_REL
present = [path for path in (v1, v2) if path.exists() or path.is_symlink()]
if len(present) != 1:
die("Device Manager release descriptor cardinality mismatch")
if present[0] == v2:
return validate_device_plane_manager_release_v2_payload(
payload_dir,
expected_release_id=expected_release_id,
)
return validate_device_plane_manager_control_plane_payload(
payload_dir,
expected_release_id=expected_release_id,
)
def validate_device_plane_manager_failed_control_plane_payload(payload_dir): def validate_device_plane_manager_failed_control_plane_payload(payload_dir):
descriptor = read_strict_json( descriptor = read_strict_json(
payload_dir / DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_REL, payload_dir / DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_REL,
@@ -10802,7 +10927,7 @@ def validate_device_plane_manager_reconciliation_evidence(payload_dir):
def validate_device_plane_manager_activation_predecessor(payload_dir): def validate_device_plane_manager_activation_predecessor(payload_dir):
descriptor = validate_device_plane_manager_control_plane_payload( descriptor = validate_device_plane_manager_release_payload(
payload_dir payload_dir
) )
predecessor = descriptor["predecessor"] predecessor = descriptor["predecessor"]
@@ -10904,18 +11029,26 @@ def validate_device_plane_manager_activation_predecessor(payload_dir):
else: else:
if ( if (
descriptor["action"] != "upgrade" descriptor["action"] != "upgrade"
or tuple(predecessor_entries) or tuple(predecessor_entries) not in (
!= DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES,
DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES,
)
): ):
die("Device Manager release predecessor type mismatch") die("Device Manager release predecessor type mismatch")
predecessor_descriptor = ( predecessor_descriptor = (
validate_device_plane_manager_control_plane_payload( validate_device_plane_manager_release_payload(
predecessor_payload, predecessor_payload,
expected_release_id=patch_id, expected_release_id=patch_id,
) )
) )
predecessor_descriptor_rel = (
DEVICE_PLANE_MANAGER_RELEASE_V2_REL
if tuple(predecessor_entries)
== DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES
else DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL
)
installed_descriptor = read_strict_json( installed_descriptor = read_strict_json(
root / DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL, root / predecessor_descriptor_rel,
"installed Device Manager release predecessor", "installed Device Manager release predecessor",
max_bytes=16 * 1024, max_bytes=16 * 1024,
) )
@@ -69,6 +69,27 @@ def device_manager_release_descriptor(
predecessor_kind="reconciliation", predecessor_kind="reconciliation",
predecessor_patch="device-manager-reconciliation-unit-001", predecessor_patch="device-manager-reconciliation-unit-001",
predecessor_sha="a" * 64, predecessor_sha="a" * 64,
):
return {
"schemaVersion": "nodedc.device-plane.device-manager-release.v2",
"releaseId": release_id,
"action": action,
"predecessor": {
"kind": predecessor_kind,
"patchId": predecessor_patch,
"artifactSha256": predecessor_sha,
},
**RUNNER.expected_device_plane_manager_release_v2_boundaries(),
}
def device_manager_release_v1_descriptor(
release_id="device-manager-release-v1-unit-001",
*,
action="upgrade",
predecessor_kind="release",
predecessor_patch="device-manager-release-v1-unit-000",
predecessor_sha="a" * 64,
): ):
return { return {
"schemaVersion": "nodedc.device-plane.device-manager-release.v1", "schemaVersion": "nodedc.device-plane.device-manager-release.v1",
@@ -79,7 +100,7 @@ def device_manager_release_descriptor(
"patchId": predecessor_patch, "patchId": predecessor_patch,
"artifactSha256": predecessor_sha, "artifactSha256": predecessor_sha,
}, },
**RUNNER.expected_device_plane_manager_release_boundaries(), **RUNNER.expected_device_plane_manager_release_v1_boundaries(),
} }
@@ -164,7 +185,7 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
manifest, entries, names, result = self.assert_deterministic_artifact( manifest, entries, names, result = self.assert_deterministic_artifact(
"build-device-manager-control-plane-artifact.mjs", "build-device-manager-control-plane-artifact.mjs",
"device-manager-control-plane-unit-001", "device-manager-control-plane-unit-001",
RUNNER.DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES, RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES,
) )
self.assertEqual(manifest["component"], "device-plane") self.assertEqual(manifest["component"], "device-plane")
self.assertEqual( self.assertEqual(
@@ -205,6 +226,58 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
self.assertEqual(checks[0]["expected_json"]["managementApi"], "enabled") self.assertEqual(checks[0]["expected_json"]["managementApi"], "enabled")
self.assertEqual(checks[0]["expected_json"]["discoveryIngest"], "enabled") self.assertEqual(checks[0]["expected_json"]["discoveryIngest"], "enabled")
def test_release_v2_keeps_release_v1_predecessor_contract_immutable(self):
predecessor = device_manager_release_v1_descriptor(
release_id="device-manager-release-20260811-010",
)
candidate = device_manager_release_descriptor(
release_id="device-manager-release-20260811-012",
action="upgrade",
predecessor_kind="release",
predecessor_patch="device-manager-release-20260811-010",
predecessor_sha="d" * 64,
)
self.assertIs(
RUNNER.validate_device_plane_manager_release_descriptor(
predecessor,
schema_version=(
"nodedc.device-plane.device-manager-release.v1"
),
boundaries=(
RUNNER.expected_device_plane_manager_release_v1_boundaries()
),
expected_release_id="device-manager-release-20260811-010",
),
predecessor,
)
self.assertIs(
RUNNER.validate_device_plane_manager_release_descriptor(
candidate,
schema_version=(
"nodedc.device-plane.device-manager-release.v2"
),
boundaries=(
RUNNER.expected_device_plane_manager_release_v2_boundaries()
),
expected_release_id="device-manager-release-20260811-012",
),
candidate,
)
with self.assertRaisesRegex(
RUNNER.DeployError,
r"schema=.*release\.v2 missing=edgeChannel,edgeChannelEgress,"
r"edgeChannelIdentity extra=none",
):
RUNNER.validate_device_plane_manager_release_descriptor(
predecessor,
schema_version=(
"nodedc.device-plane.device-manager-release.v2"
),
boundaries=(
RUNNER.expected_device_plane_manager_release_v2_boundaries()
),
)
def test_public_route_artifact_is_last_and_proxy_only(self): def test_public_route_artifact_is_last_and_proxy_only(self):
manifest, entries, _names, result = self.assert_deterministic_artifact( manifest, entries, _names, result = self.assert_deterministic_artifact(
"build-platform-device-manager-route-artifact.mjs", "build-platform-device-manager-route-artifact.mjs",
@@ -239,7 +312,7 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
): ):
RUNNER.prepare_component_runtime( RUNNER.prepare_component_runtime(
"device-plane", "device-plane",
RUNNER.DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES, RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES,
) )
self.assertEqual( self.assertEqual(
[call.args[0] for call in ensure.call_args_list], [call.args[0] for call in ensure.call_args_list],
@@ -254,7 +327,7 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
ensure_edge_identity.assert_called_once_with() ensure_edge_identity.assert_called_once_with()
def test_apply_gate_checks_exact_services_core_contract_and_runtime_boundary(self): def test_apply_gate_checks_exact_services_core_contract_and_runtime_boundary(self):
entries = RUNNER.DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES entries = RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES
services = ("device-control-core", "device-manager") services = ("device-control-core", "device-manager")
with ( with (
mock.patch.object( mock.patch.object(
@@ -341,7 +414,7 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
), ),
mock.patch.object( mock.patch.object(
RUNNER, RUNNER,
"validate_device_plane_manager_control_plane_payload", "validate_device_plane_manager_release_payload",
return_value=descriptor, return_value=descriptor,
) as payload, ) as payload,
mock.patch.object( mock.patch.object(
@@ -441,7 +514,7 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
artifact_bytes = b"arbitrary-reviewed-device-manager-release" artifact_bytes = b"arbitrary-reviewed-device-manager-release"
artifact.write_bytes(artifact_bytes) artifact.write_bytes(artifact_bytes)
artifact_sha = hashlib.sha256(artifact_bytes).hexdigest() artifact_sha = hashlib.sha256(artifact_bytes).hexdigest()
predecessor_descriptor = device_manager_release_descriptor( predecessor_descriptor = device_manager_release_v1_descriptor(
release_id=predecessor_patch, release_id=predecessor_patch,
) )
candidate_descriptor = device_manager_release_descriptor( candidate_descriptor = device_manager_release_descriptor(
@@ -483,7 +556,7 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
), ),
mock.patch.object( mock.patch.object(
RUNNER, RUNNER,
"validate_device_plane_manager_control_plane_payload", "validate_device_plane_manager_release_payload",
side_effect=( side_effect=(
candidate_descriptor, candidate_descriptor,
predecessor_descriptor, predecessor_descriptor,