From 0438fbd80b534d654409d41f95d90c1c148cf1a5 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 22 Aug 2026 15:33:06 +0300 Subject: [PATCH] feat(deploy): register canonical manager v8 --- infra/deploy-runner/nodedc-deploy | 161 +++++++++++++++++- .../test_device_plane_registry.py | 47 +++++ 2 files changed, 205 insertions(+), 3 deletions(-) diff --git a/infra/deploy-runner/nodedc-deploy b/infra/deploy-runner/nodedc-deploy index b7e15e5..9c162ef 100755 --- a/infra/deploy-runner/nodedc-deploy +++ b/infra/deploy-runner/nodedc-deploy @@ -275,6 +275,9 @@ DEVICE_PLANE_MANAGER_RELEASE_V6_REL = ( DEVICE_PLANE_MANAGER_RELEASE_V7_REL = ( "deployment/device-manager-release-v7.json" ) +DEVICE_PLANE_MANAGER_RELEASE_V8_REL = ( + "deployment/device-manager-release-v8.json" +) DEVICE_PLANE_MANAGER_COMPOSE_REL = "docker-compose.device-manager.yml" DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL = ( "deployment/device-edge-core-channel-bootstrap-v1.json" @@ -412,6 +415,18 @@ DEVICE_PLANE_MANAGER_RELEASE_V7_CONTROL_CORE_PREDECESSOR_PATCH_ID = ( DEVICE_PLANE_MANAGER_RELEASE_V7_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256 = ( "8708cc4b59fa0cd5e9c6e6a7b2654ba01ea60271549167aca2631f94000d3da3" ) +DEVICE_PLANE_MANAGER_RELEASE_V8_PREDECESSOR_PATCH_ID = ( + "device-manager-release-v6-20260822-035" +) +DEVICE_PLANE_MANAGER_RELEASE_V8_PREDECESSOR_ARTIFACT_SHA256 = ( + "193faabe930e2b3f212f8eb45288e39f850ec714528b28881095be654baf9a80" +) +DEVICE_PLANE_MANAGER_RELEASE_V8_CONTROL_CORE_PREDECESSOR_PATCH_ID = ( + "device-control-core-release-v2-20260822-038" +) +DEVICE_PLANE_MANAGER_RELEASE_V8_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256 = ( + "e2d062b82b022dba662522b5d6e192026ac964d78950d903295ca3cbbc95ab28" +) DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_PATCH_ID = ( "device-edge-core-channel-bootstrap-20260812-018" ) @@ -482,6 +497,9 @@ DEVICE_PLANE_MANAGER_RELEASE_V6_COMPOSE_SHA256 = ( DEVICE_PLANE_MANAGER_RELEASE_V7_COMPOSE_SHA256 = ( DEVICE_PLANE_MANAGER_RELEASE_V6_COMPOSE_SHA256 ) +DEVICE_PLANE_MANAGER_RELEASE_V8_COMPOSE_SHA256 = ( + DEVICE_PLANE_MANAGER_RELEASE_V6_COMPOSE_SHA256 +) DEVICE_PLANE_MANAGER_FAVICON_SHA256 = { "favicon.ico": ( "f8933114a85646335ea5c94944f56d3cd8c48a6032016719f7905ff244ec0aa2" @@ -603,6 +621,11 @@ DEVICE_PLANE_MANAGER_RELEASE_V7_ENTRIES = ( "services/device-manager", DEVICE_PLANE_MANAGER_RELEASE_V7_REL, ) +DEVICE_PLANE_MANAGER_RELEASE_V8_ENTRIES = ( + DEVICE_PLANE_MANAGER_COMPOSE_REL, + "services/device-manager", + DEVICE_PLANE_MANAGER_RELEASE_V8_REL, +) DEVICE_PLANE_MANAGER_RECONCILIATION_REL = ( "deployment/device-manager-control-plane-reconciliation-v1.json" ) @@ -4645,6 +4668,7 @@ def allowed_payload_path(component, rel): DEVICE_PLANE_MANAGER_RELEASE_V5_REL, DEVICE_PLANE_MANAGER_RELEASE_V6_REL, DEVICE_PLANE_MANAGER_RELEASE_V7_REL, + DEVICE_PLANE_MANAGER_RELEASE_V8_REL, DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL, @@ -11586,6 +11610,7 @@ def is_device_plane_manager_control_plane_slice(component, entries): DEVICE_PLANE_MANAGER_RELEASE_V5_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V6_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V7_ENTRIES, + DEVICE_PLANE_MANAGER_RELEASE_V8_ENTRIES, ) ) @@ -11638,12 +11663,21 @@ def is_device_plane_manager_release_v7_slice(component, entries): ) +def is_device_plane_manager_release_v8_slice(component, entries): + return ( + component == "device-plane" + and entries is not None + and tuple(entries) == DEVICE_PLANE_MANAGER_RELEASE_V8_ENTRIES + ) + + def is_device_plane_manager_persistent_release_slice(component, entries): return ( is_device_plane_manager_release_v4_slice(component, entries) or is_device_plane_manager_release_v5_slice(component, entries) or is_device_plane_manager_release_v6_slice(component, entries) or is_device_plane_manager_release_v7_slice(component, entries) + or is_device_plane_manager_release_v8_slice(component, entries) ) @@ -11922,6 +11956,42 @@ def expected_device_plane_manager_release_v7_boundaries(): } +def expected_device_plane_manager_release_v8_boundaries(): + return { + **expected_device_plane_manager_release_v6_boundaries(), + "predecessor": { + "kind": "release", + "patchId": DEVICE_PLANE_MANAGER_RELEASE_V8_PREDECESSOR_PATCH_ID, + "artifactSha256": ( + DEVICE_PLANE_MANAGER_RELEASE_V8_PREDECESSOR_ARTIFACT_SHA256 + ), + }, + "controlCorePredecessor": { + "patchId": ( + DEVICE_PLANE_MANAGER_RELEASE_V8_CONTROL_CORE_PREDECESSOR_PATCH_ID + ), + "artifactSha256": ( + DEVICE_PLANE_MANAGER_RELEASE_V8_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256 + ), + }, + "commandFormLayout": "aligned-control-row-v1", + "secondaryEmptyTypography": "help-text-sm-v1", + "infrastructureHostProjection": "ontology-backed-host-runtime-v1", + "ontologyFoundation": ( + "ontology-core-device-foundation-20260822-001" + ), + "ontologyCatalogHash": "229c61c02a790906", + "assetBinding": "temporal-device-asset-binding-v1", + "infrastructureRuntime": ( + "host-endpoint-deployment-service-instance-v1" + ), + "healthEvidence": "ttl-observation-missing-not-unhealthy-v1", + "interactiveShell": ( + "disabled-pending-managed-session-boundary" + ), + } + + 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. @@ -12441,6 +12511,7 @@ def validate_device_plane_manager_release_payload_contract( "nodedc.device-plane.device-manager-release.v5", "nodedc.device-plane.device-manager-release.v6", "nodedc.device-plane.device-manager-release.v7", + "nodedc.device-plane.device-manager-release.v8", ): required_compose.extend(( "NODEDC_DEVICE_MANAGER_PRESENTATION_PATH: " @@ -12613,6 +12684,24 @@ def validate_device_plane_manager_release_v7_payload( return descriptor +def validate_device_plane_manager_release_v8_payload( + payload_dir, + *, + expected_release_id=None, +): + descriptor = validate_device_plane_manager_release_payload_contract( + payload_dir, + descriptor_rel=DEVICE_PLANE_MANAGER_RELEASE_V8_REL, + schema_version="nodedc.device-plane.device-manager-release.v8", + boundaries=expected_device_plane_manager_release_v8_boundaries(), + compose_sha256=DEVICE_PLANE_MANAGER_RELEASE_V8_COMPOSE_SHA256, + edge_channel=False, + expected_release_id=expected_release_id, + ) + validate_device_plane_manager_v6_favicon_bundle(payload_dir) + return descriptor + + def validate_device_plane_manager_release_payload( payload_dir, *, @@ -12625,12 +12714,18 @@ def validate_device_plane_manager_release_payload( v5 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V5_REL v6 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V6_REL v7 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V7_REL + v8 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V8_REL present = [ - path for path in (v1, v2, v3, v4, v5, v6, v7) + path for path in (v1, v2, v3, v4, v5, v6, v7, v8) if path.exists() or path.is_symlink() ] if len(present) != 1: die("Device Manager release descriptor cardinality mismatch") + if present[0] == v8: + return validate_device_plane_manager_release_v8_payload( + payload_dir, + expected_release_id=expected_release_id, + ) if present[0] == v7: return validate_device_plane_manager_release_v7_payload( payload_dir, @@ -12932,6 +13027,7 @@ def installed_device_plane_manager_compose_sha256(): v5 = root / DEVICE_PLANE_MANAGER_RELEASE_V5_REL v6 = root / DEVICE_PLANE_MANAGER_RELEASE_V6_REL v7 = root / DEVICE_PLANE_MANAGER_RELEASE_V7_REL + v8 = root / DEVICE_PLANE_MANAGER_RELEASE_V8_REL # A successful v2 overlay intentionally leaves the immutable v1 release # descriptor as predecessor evidence. Prefer the highest installed @@ -12939,6 +13035,19 @@ def installed_device_plane_manager_compose_sha256(): # the generation-specific Compose digest. During a failed v2 apply, # rollback removes the candidate-only v2 descriptor before rebuilding the # restored v1 runtime, so the same lookup follows the restored source. + if v8.exists() or v8.is_symlink(): + descriptor = read_strict_json( + v8, + "installed Device Manager release v8 descriptor", + max_bytes=16 * 1024, + ) + validate_device_plane_manager_release_descriptor( + descriptor, + schema_version="nodedc.device-plane.device-manager-release.v8", + boundaries=expected_device_plane_manager_release_v8_boundaries(), + ) + return DEVICE_PLANE_MANAGER_RELEASE_V8_COMPOSE_SHA256 + if v7.exists() or v7.is_symlink(): descriptor = read_strict_json( v7, @@ -14511,6 +14620,7 @@ def validate_device_plane_manager_v3_active_baseline(descriptor): "nodedc.device-plane.device-manager-release.v5", "nodedc.device-plane.device-manager-release.v6", "nodedc.device-plane.device-manager-release.v7", + "nodedc.device-plane.device-manager-release.v8", ): return None @@ -14661,6 +14771,7 @@ def device_plane_manager_preserved_runtime_health_services(descriptor): "nodedc.device-plane.device-manager-release.v5", "nodedc.device-plane.device-manager-release.v6", "nodedc.device-plane.device-manager-release.v7", + "nodedc.device-plane.device-manager-release.v8", ): return ( "device-control-core", @@ -14796,6 +14907,7 @@ def validate_device_plane_manager_activation_predecessor( DEVICE_PLANE_MANAGER_RELEASE_V5_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V6_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V7_ENTRIES, + DEVICE_PLANE_MANAGER_RELEASE_V8_ENTRIES, ) ): die("Device Manager release predecessor type mismatch") @@ -14818,6 +14930,8 @@ def validate_device_plane_manager_activation_predecessor( DEVICE_PLANE_MANAGER_RELEASE_V6_REL, DEVICE_PLANE_MANAGER_RELEASE_V7_ENTRIES: DEVICE_PLANE_MANAGER_RELEASE_V7_REL, + DEVICE_PLANE_MANAGER_RELEASE_V8_ENTRIES: + DEVICE_PLANE_MANAGER_RELEASE_V8_REL, }.get( tuple(predecessor_entries), DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL, @@ -17197,6 +17311,7 @@ def validate_device_manager_control_plane_runtime( require_edge_channel = edge_descriptor.is_file() and not edge_descriptor.is_symlink() if require_persistent_data is None: data_descriptors = ( + DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_RELEASE_V8_REL, DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_RELEASE_V7_REL, DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_RELEASE_V6_REL, DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_RELEASE_V5_REL, @@ -29850,6 +29965,7 @@ def plan_artifact(artifact): "nodedc.device-plane.device-manager-release.v5", "nodedc.device-plane.device-manager-release.v6", "nodedc.device-plane.device-manager-release.v7", + "nodedc.device-plane.device-manager-release.v8", ) else "build+recreate:device-control-core,device-manager" ) @@ -29867,6 +29983,7 @@ def plan_artifact(artifact): "nodedc.device-plane.device-manager-release.v5", "nodedc.device-plane.device-manager-release.v6", "nodedc.device-plane.device-manager-release.v7", + "nodedc.device-plane.device-manager-release.v8", ) else "preserved:device-gateway,device-postgres," "device-backhaul-target" @@ -29880,6 +29997,7 @@ def plan_artifact(artifact): "nodedc.device-plane.device-manager-release.v5", "nodedc.device-plane.device-manager-release.v6", "nodedc.device-plane.device-manager-release.v7", + "nodedc.device-plane.device-manager-release.v8", ) print( "device_manager_health_gate=" @@ -29903,6 +30021,7 @@ def plan_artifact(artifact): "nodedc.device-plane.device-manager-release.v5", "nodedc.device-plane.device-manager-release.v6", "nodedc.device-plane.device-manager-release.v7", + "nodedc.device-plane.device-manager-release.v8", ): print( "device_manager_overview_layout=" @@ -29911,13 +30030,18 @@ def plan_artifact(artifact): if manager_schema in ( "nodedc.device-plane.device-manager-release.v6", "nodedc.device-plane.device-manager-release.v7", + "nodedc.device-plane.device-manager-release.v8", ): print("device_manager_favicon_set=nodedc-adaptive-v1") - if manager_schema == ( - "nodedc.device-plane.device-manager-release.v7" + if manager_schema in ( + "nodedc.device-plane.device-manager-release.v7", + "nodedc.device-plane.device-manager-release.v8", ): print("device_manager_command_form=aligned-control-row-v1") print("device_manager_secondary_empty_text=help-text-sm-v1") + if manager_schema == ( + "nodedc.device-plane.device-manager-release.v7" + ): print( "device_manager_infrastructure_hosts=" "edge-registration-live-channel-v1" @@ -29926,6 +30050,37 @@ def plan_artifact(artifact): "device_manager_host_ontology=" "candidate-not-canonical" ) + if manager_schema == ( + "nodedc.device-plane.device-manager-release.v8" + ): + print( + "device_manager_infrastructure_hosts=" + "ontology-backed-host-runtime-v1" + ) + print( + "device_manager_ontology_foundation=" + "ontology-core-device-foundation-20260822-001" + ) + print( + "device_manager_ontology_catalog_hash=" + "229c61c02a790906" + ) + print( + "device_manager_asset_binding=" + "temporal-device-asset-binding-v1" + ) + print( + "device_manager_infrastructure_runtime=" + "host-endpoint-deployment-service-instance-v1" + ) + print( + "device_manager_health_evidence=" + "ttl-observation-missing-not-unhealthy-v1" + ) + print( + "device_manager_interactive_shell=" + "disabled-pending-managed-session-boundary" + ) if device_plane_manager_activation_preflight["descriptor"].get( "commandTransport" ) == "typed-service-ping-v1": diff --git a/infra/deploy-runner/test_device_plane_registry.py b/infra/deploy-runner/test_device_plane_registry.py index 315cf12..b8ba4fc 100644 --- a/infra/deploy-runner/test_device_plane_registry.py +++ b/infra/deploy-runner/test_device_plane_registry.py @@ -29,6 +29,53 @@ RUNNER = load_runner() class DevicePlaneRegistryTest(unittest.TestCase): + def test_manager_v8_pins_production_v6_and_canonical_ontology_runtime(self): + self.assertEqual( + RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V8_PREDECESSOR_PATCH_ID, + "device-manager-release-v6-20260822-035", + ) + self.assertEqual( + RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V8_PREDECESSOR_ARTIFACT_SHA256, + "193faabe930e2b3f212f8eb45288e39f850ec714528b28881095be654baf9a80", + ) + self.assertEqual( + RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V8_CONTROL_CORE_PREDECESSOR_PATCH_ID, + "device-control-core-release-v2-20260822-038", + ) + self.assertEqual( + RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V8_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256, + "e2d062b82b022dba662522b5d6e192026ac964d78950d903295ca3cbbc95ab28", + ) + self.assertEqual( + RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V8_COMPOSE_SHA256, + RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V6_COMPOSE_SHA256, + ) + boundaries = RUNNER.expected_device_plane_manager_release_v8_boundaries() + self.assertEqual( + boundaries["infrastructureHostProjection"], + "ontology-backed-host-runtime-v1", + ) + self.assertEqual( + boundaries["ontologyFoundation"], + "ontology-core-device-foundation-20260822-001", + ) + self.assertEqual(boundaries["ontologyCatalogHash"], "229c61c02a790906") + self.assertEqual( + boundaries["assetBinding"], + "temporal-device-asset-binding-v1", + ) + self.assertEqual( + boundaries["healthEvidence"], + "ttl-observation-missing-not-unhealthy-v1", + ) + self.assertEqual( + RUNNER.component_services( + "device-plane", + RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V8_ENTRIES, + ), + ("device-manager",), + ) + def test_manager_v7_pins_v6_and_live_edge_host_projection(self): self.assertEqual( RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V7_PREDECESSOR_PATCH_ID,