feat(deploy): add Core channel forward upgrade

This commit is contained in:
Codex
2026-08-12 01:43:09 +03:00
parent c2d06bddc7
commit d8c0e9f3b1
4 changed files with 396 additions and 36 deletions
@@ -0,0 +1,28 @@
{
"schemaVersion": "nodedc.device-plane.device-edge-core-channel-upgrade.v1",
"transitionId": "__PATCH_ID__",
"action": "upgrade",
"bootstrapPredecessor": {
"patchId": "device-edge-core-channel-bootstrap-20260812-018",
"artifactSha256": "5598b7388b491fe524ab46038ce476482a93a6cf07d8ca5e00206c69ded02931"
},
"service": "device-control-core",
"composeActivation": "preserve-dedicated-additive-override",
"identity": "reuse-existing-runner-managed-host-local-private-key-public-certificate-export",
"identityRecovery": "forbidden-valid-existing-identity-required",
"tlsPurpose": "clientAuth",
"direction": "core-initiated",
"endpointPolicy": "public-ipv4-standard-https-tcp-443-only",
"publicIngress": "none-on-synology",
"edgeRegistrations": "preserved-requires-explicit-443-reconciliation",
"commandTransport": "disabled",
"gelios": "untouched",
"preservedServices": [
"device-manager",
"device-gateway",
"device-postgres",
"device-backhaul-target"
],
"healthGate": "bounded-container-grace+core-edge-contract",
"rollback": "restore-bootstrap-018-source-and-preapply-core-runtime"
}
@@ -15,7 +15,10 @@ if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) {
throw new Error("usage: build-device-edge-core-channel-bootstrap-artifact.mjs [patch-id]"); throw new Error("usage: build-device-edge-core-channel-bootstrap-artifact.mjs [patch-id]");
} }
const descriptorPath = "deployment/device-edge-core-channel-bootstrap-v1.json"; const upgrade = patchId.startsWith("device-edge-core-channel-upgrade-");
const descriptorPath = upgrade
? "deployment/device-edge-core-channel-upgrade-v1.json"
: "deployment/device-edge-core-channel-bootstrap-v1.json";
const entries = [ const entries = [
".dockerignore", ".dockerignore",
"package.json", "package.json",
@@ -97,13 +100,26 @@ try {
} }
const descriptor = JSON.parse(await readFile(join(payload, descriptorPath), "utf8")); const descriptor = JSON.parse(await readFile(join(payload, descriptorPath), "utf8"));
const descriptorContractMatches = upgrade
? (
descriptor.schemaVersion === "nodedc.device-plane.device-edge-core-channel-upgrade.v1"
&& descriptor.action === "upgrade"
&& descriptor.composeActivation === "preserve-dedicated-additive-override"
&& descriptor.identityRecovery === "forbidden-valid-existing-identity-required"
&& descriptor.endpointPolicy === "public-ipv4-standard-https-tcp-443-only"
&& descriptor.bootstrapPredecessor?.patchId === "device-edge-core-channel-bootstrap-20260812-018"
&& descriptor.bootstrapPredecessor?.artifactSha256 === "5598b7388b491fe524ab46038ce476482a93a6cf07d8ca5e00206c69ded02931"
)
: (
descriptor.schemaVersion === "nodedc.device-plane.device-edge-core-channel-bootstrap.v1"
&& descriptor.action === "activate"
&& descriptor.composeActivation === "dedicated-additive-override"
&& descriptor.identityRecovery === "exact-invalid-unexported-failed-predecessor-only"
);
if ( if (
descriptor.schemaVersion !== "nodedc.device-plane.device-edge-core-channel-bootstrap.v1" !descriptorContractMatches
|| descriptor.transitionId !== patchId || descriptor.transitionId !== patchId
|| descriptor.action !== "activate"
|| descriptor.service !== "device-control-core" || descriptor.service !== "device-control-core"
|| descriptor.composeActivation !== "dedicated-additive-override"
|| descriptor.identityRecovery !== "exact-invalid-unexported-failed-predecessor-only"
|| descriptor.tlsPurpose !== "clientAuth" || descriptor.tlsPurpose !== "clientAuth"
|| descriptor.direction !== "core-initiated" || descriptor.direction !== "core-initiated"
|| descriptor.publicIngress !== "none-on-synology" || descriptor.publicIngress !== "none-on-synology"
+275 -6
View File
@@ -250,6 +250,9 @@ DEVICE_PLANE_MANAGER_COMPOSE_REL = "docker-compose.device-manager.yml"
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL = ( DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL = (
"deployment/device-edge-core-channel-bootstrap-v1.json" "deployment/device-edge-core-channel-bootstrap-v1.json"
) )
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL = (
"deployment/device-edge-core-channel-upgrade-v1.json"
)
DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL = ( DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL = (
"docker-compose.device-edge-core-channel.yml" "docker-compose.device-edge-core-channel.yml"
) )
@@ -266,6 +269,22 @@ DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_ENTRIES = (
"services/device-control-core", "services/device-control-core",
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL, DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL,
) )
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_ENTRIES = (
".dockerignore",
"package.json",
"package-lock.json",
DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL,
"packages/device-protocol-contract",
"packages/device-edge-channel-contract",
"services/device-control-core",
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL,
)
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_PATCH_ID = (
"device-edge-core-channel-bootstrap-20260812-018"
)
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_ARTIFACT_SHA256 = (
"5598b7388b491fe524ab46038ce476482a93a6cf07d8ca5e00206c69ded02931"
)
DEVICE_PLANE_EDGE_CORE_CHANNEL_MANAGER_PREDECESSOR_PATCH_ID = ( DEVICE_PLANE_EDGE_CORE_CHANNEL_MANAGER_PREDECESSOR_PATCH_ID = (
"device-manager-release-20260811-010" "device-manager-release-20260811-010"
) )
@@ -3795,6 +3814,7 @@ def allowed_payload_path(component, rel):
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL, DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
DEVICE_PLANE_MANAGER_RELEASE_V2_REL, DEVICE_PLANE_MANAGER_RELEASE_V2_REL,
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL, DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL,
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_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",
@@ -8851,6 +8871,15 @@ def load_artifact(artifact, work_dir):
manifest["component"], manifest["component"],
entries, entries,
): ):
if is_device_plane_edge_core_channel_upgrade_slice(
manifest["component"],
entries,
):
validate_device_plane_edge_core_channel_upgrade_payload(
payload_dir,
expected_transition_id=manifest["id"],
)
else:
validate_device_plane_edge_core_channel_bootstrap_payload( validate_device_plane_edge_core_channel_bootstrap_payload(
payload_dir, payload_dir,
expected_transition_id=manifest["id"], expected_transition_id=manifest["id"],
@@ -9343,8 +9372,18 @@ def is_device_plane_edge_core_channel_bootstrap_slice(component, entries):
return ( return (
component == "device-plane" component == "device-plane"
and entries is not None and entries is not None
and tuple(entries) and tuple(entries) in (
== DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_ENTRIES DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_ENTRIES,
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_ENTRIES,
)
)
def is_device_plane_edge_core_channel_upgrade_slice(component, entries):
return (
component == "device-plane"
and entries is not None
and tuple(entries) == DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_ENTRIES
) )
@@ -9492,6 +9531,52 @@ def expected_device_plane_edge_core_channel_bootstrap_descriptor(
} }
def expected_device_plane_edge_core_channel_upgrade_descriptor(
transition_id,
):
return {
"schemaVersion": (
"nodedc.device-plane.device-edge-core-channel-upgrade.v1"
),
"transitionId": transition_id,
"action": "upgrade",
"bootstrapPredecessor": {
"patchId": (
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_PATCH_ID
),
"artifactSha256": (
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_ARTIFACT_SHA256
),
},
"service": "device-control-core",
"composeActivation": "preserve-dedicated-additive-override",
"identity": (
"reuse-existing-runner-managed-host-local-private-key-"
"public-certificate-export"
),
"identityRecovery": "forbidden-valid-existing-identity-required",
"tlsPurpose": "clientAuth",
"direction": "core-initiated",
"endpointPolicy": "public-ipv4-standard-https-tcp-443-only",
"publicIngress": "none-on-synology",
"edgeRegistrations": (
"preserved-requires-explicit-443-reconciliation"
),
"commandTransport": "disabled",
"gelios": "untouched",
"preservedServices": [
"device-manager",
"device-gateway",
"device-postgres",
"device-backhaul-target",
],
"healthGate": "bounded-container-grace+core-edge-contract",
"rollback": (
"restore-bootstrap-018-source-and-preapply-core-runtime"
),
}
def expected_device_plane_manager_failed_control_plane_descriptor(): def expected_device_plane_manager_failed_control_plane_descriptor():
return { return {
"schemaVersion": ( "schemaVersion": (
@@ -9819,6 +9904,39 @@ def validate_device_plane_edge_core_channel_bootstrap_payload(
) )
): ):
die("Device Edge Core channel bootstrap descriptor mismatch") die("Device Edge Core channel bootstrap descriptor mismatch")
validate_device_plane_edge_core_channel_compose(payload_dir)
return descriptor
def validate_device_plane_edge_core_channel_upgrade_payload(
payload_dir,
*,
expected_transition_id=None,
):
descriptor = read_strict_json(
payload_dir / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL,
"Device Edge Core channel upgrade descriptor",
max_bytes=16 * 1024,
)
transition_id = descriptor.get("transitionId")
if (
not isinstance(transition_id, str)
or not re.fullmatch(r"[A-Za-z0-9._-]{1,96}", transition_id)
or (
expected_transition_id is not None
and transition_id != expected_transition_id
)
or descriptor
!= expected_device_plane_edge_core_channel_upgrade_descriptor(
transition_id
)
):
die("Device Edge Core channel upgrade descriptor mismatch")
validate_device_plane_edge_core_channel_compose(payload_dir)
return descriptor
def validate_device_plane_edge_core_channel_compose(payload_dir):
compose = payload_dir / DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL compose = payload_dir / DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL
if sha256_file(compose) != DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_SHA256: if sha256_file(compose) != DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_SHA256:
die("Device Edge Core channel Compose mismatch") die("Device Edge Core channel Compose mismatch")
@@ -9849,11 +9967,12 @@ def validate_device_plane_edge_core_channel_bootstrap_payload(
"ports:", "ports:",
"PRIVATE KEY", "PRIVATE KEY",
"0.0.0.0:8443", "0.0.0.0:8443",
"0.0.0.0:443",
"9921:9921", "9921:9921",
) )
if any(value in compose_text for value in forbidden): if any(value in compose_text for value in forbidden):
die("Device Edge Core channel Compose boundary violation") die("Device Edge Core channel Compose boundary violation")
return descriptor return compose
def installed_device_plane_manager_compose_sha256(): def installed_device_plane_manager_compose_sha256():
@@ -11546,6 +11665,12 @@ def inspect_device_edge_channel_core_identity_state():
def validate_device_plane_edge_core_channel_bootstrap_predecessor( def validate_device_plane_edge_core_channel_bootstrap_predecessor(
payload_dir, payload_dir,
): ):
if (
payload_dir / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL
).exists():
return validate_device_plane_edge_core_channel_upgrade_predecessor(
payload_dir
)
descriptor = validate_device_plane_edge_core_channel_bootstrap_payload( descriptor = validate_device_plane_edge_core_channel_bootstrap_payload(
payload_dir payload_dir
) )
@@ -11657,6 +11782,88 @@ def validate_device_plane_edge_core_channel_bootstrap_predecessor(
} }
def validate_device_plane_edge_core_channel_upgrade_predecessor(
payload_dir,
):
descriptor = validate_device_plane_edge_core_channel_upgrade_payload(
payload_dir
)
installed_upgrade = (
DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL
)
if installed_upgrade.exists() or installed_upgrade.is_symlink():
die("Device Edge Core channel upgrade is already installed")
predecessor = descriptor["bootstrapPredecessor"]
artifact_name = f"nodedc-device-plane-{predecessor['patchId']}.tgz"
artifact = APPLIED_DIR / artifact_name
if (
not artifact.is_file()
or artifact.is_symlink()
or sha256_file(artifact) != predecessor["artifactSha256"]
):
die("Device Edge Core channel bootstrap predecessor mismatch")
records = [
row for row in load_state(STATE_FILE)
if row.get("id") == predecessor["patchId"]
and row.get("sha256") == predecessor["artifactSha256"]
]
if (
len(records) != 1
or records[0].get("status") != "ok"
or records[0].get("component") != "device-plane"
or records[0].get("artifact") != artifact_name
):
die("Device Edge Core channel bootstrap journal mismatch")
with tempfile.TemporaryDirectory(
prefix="device-edge-core-channel-upgrade-predecessor-",
dir=TMP_DIR,
) as directory:
manifest, entries, predecessor_payload = load_artifact(
artifact,
Path(directory),
)
if (
manifest.get("id") != predecessor["patchId"]
or manifest.get("component") != "device-plane"
or manifest.get("type") != "app-overlay"
or tuple(entries)
!= DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_ENTRIES
):
die("Device Edge Core channel bootstrap artifact type mismatch")
validate_device_plane_edge_core_channel_bootstrap_payload(
predecessor_payload,
expected_transition_id=predecessor["patchId"],
)
expected_source = collect_exact_files(
predecessor_payload,
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_ENTRIES,
"Device Edge Core channel bootstrap predecessor source",
)
actual_source = collect_exact_files(
DEVICE_PLANE_ROOT,
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_ENTRIES,
"installed Device Edge Core channel bootstrap source",
)
if actual_source != expected_source:
die("installed Device Edge Core channel bootstrap source drift detected")
identity_state = inspect_device_edge_channel_core_identity_state()
if identity_state != "valid-reuse-at-apply":
die("Device Edge Core channel upgrade requires the valid active identity")
for service in (
"device-control-core",
"device-manager",
"device-gateway",
"device-postgres",
):
healthcheck_compose_service("device-plane", service)
return {
"mode": "edge-core-channel-standard-https-443-upgrade",
"descriptor": descriptor,
"identityState": identity_state,
"bootstrapArtifact": artifact,
}
def validate_device_plane_manager_v2_reconciliation_backup(): def validate_device_plane_manager_v2_reconciliation_backup():
backup_dir = ( backup_dir = (
BACKUPS_DIR / DEVICE_PLANE_MANAGER_V2_RECONCILIATION_BACKUP_ID BACKUPS_DIR / DEVICE_PLANE_MANAGER_V2_RECONCILIATION_BACKUP_ID
@@ -15459,14 +15666,26 @@ def component_compose_files(
edge_descriptor = ( edge_descriptor = (
DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL
) )
edge_upgrade_descriptor = (
DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL
)
edge_override = ( edge_override = (
DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL
) )
edge_descriptor_exists = ( edge_descriptor_exists = (
edge_descriptor.exists() or edge_descriptor.is_symlink() edge_descriptor.exists() or edge_descriptor.is_symlink()
) )
edge_upgrade_descriptor_exists = (
edge_upgrade_descriptor.exists()
or edge_upgrade_descriptor.is_symlink()
)
edge_override_exists = edge_override.exists() or edge_override.is_symlink() edge_override_exists = edge_override.exists() or edge_override.is_symlink()
if edge_descriptor_exists != edge_override_exists: if (
edge_override_exists
!= (edge_descriptor_exists or edge_upgrade_descriptor_exists)
or edge_upgrade_descriptor_exists
and not edge_descriptor_exists
):
die("installed Device Edge Core channel source is incomplete") die("installed Device Edge Core channel source is incomplete")
if edge_descriptor_exists: if edge_descriptor_exists:
if ( if (
@@ -15494,6 +15713,29 @@ def component_compose_files(
) )
): ):
die("installed Device Edge Core channel descriptor drift detected") die("installed Device Edge Core channel descriptor drift detected")
if edge_upgrade_descriptor_exists:
if (
transition_id
!= DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_PATCH_ID
or edge_upgrade_descriptor.is_symlink()
or not edge_upgrade_descriptor.is_file()
):
die("installed Device Edge Core channel upgrade predecessor drift detected")
upgrade = read_strict_json(
edge_upgrade_descriptor,
"installed Device Edge Core channel upgrade descriptor",
max_bytes=16 * 1024,
)
upgrade_id = upgrade.get("transitionId")
if (
not isinstance(upgrade_id, str)
or not re.fullmatch(r"[A-Za-z0-9._-]{1,96}", upgrade_id)
or upgrade
!= expected_device_plane_edge_core_channel_upgrade_descriptor(
upgrade_id
)
):
die("installed Device Edge Core channel upgrade descriptor drift detected")
files = (*files, edge_override) files = (*files, edge_override)
overlay = DEVICE_PLANE_ROOT / DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL overlay = DEVICE_PLANE_ROOT / DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL
if overlay.exists() or overlay.is_symlink(): if overlay.exists() or overlay.is_symlink():
@@ -19471,6 +19713,21 @@ def plan_artifact(artifact):
"device_plane_transition=" "device_plane_transition="
f"{device_plane_edge_core_channel_preflight['mode']}" f"{device_plane_edge_core_channel_preflight['mode']}"
) )
upgrade = (
device_plane_edge_core_channel_preflight["mode"]
== "edge-core-channel-standard-https-443-upgrade"
)
if upgrade:
print(
"device_plane_predecessor_patch="
f"{DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_PATCH_ID}"
)
print(
"device_plane_predecessor_artifact_sha256="
f"{DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_ARTIFACT_SHA256}"
)
print("device_edge_channel_endpoint_policy=public-ipv4-standard-https-tcp-443-only")
else:
print( print(
"device_plane_predecessor_patch=" "device_plane_predecessor_patch="
f"{DEVICE_PLANE_EDGE_CORE_CHANNEL_MANAGER_PREDECESSOR_PATCH_ID}" f"{DEVICE_PLANE_EDGE_CORE_CHANNEL_MANAGER_PREDECESSOR_PATCH_ID}"
@@ -19504,6 +19761,10 @@ def plan_artifact(artifact):
print("device_edge_channel=enabled:core-initiated:pinned-mtls:registered-edges-only") print("device_edge_channel=enabled:core-initiated:pinned-mtls:registered-edges-only")
print("device_edge_channel_egress=dedicated-core-only-bridge:no-host-ingress") print("device_edge_channel_egress=dedicated-core-only-bridge:no-host-ingress")
print("device_edge_channel_identity=host-local-private-key:public-certificate-export-only") print("device_edge_channel_identity=host-local-private-key:public-certificate-export-only")
if upgrade:
print("device_edge_channel_identity_recovery=forbidden:reuse-valid-only")
print("device_edge_channel_registrations=preserved:explicit-443-reconciliation-required")
else:
print("device_edge_channel_invalid_failed_016=recover-exact-unexported-only") print("device_edge_channel_invalid_failed_016=recover-exact-unexported-only")
print("device_edge_channel_registrations=preserved") print("device_edge_channel_registrations=preserved")
print("device_edge_channel_commands=disabled") print("device_edge_channel_commands=disabled")
@@ -20207,7 +20468,10 @@ def rollback_device_plane_apply(
service, service,
) )
validate_device_manager_control_plane_runtime( validate_device_manager_control_plane_runtime(
require_edge_channel=False require_edge_channel=is_device_plane_edge_core_channel_upgrade_slice(
"device-plane",
entries,
)
) )
elif is_device_plane_manager_control_plane_slice("device-plane", entries): elif is_device_plane_manager_control_plane_slice("device-plane", entries):
for service in baseline_services: for service in baseline_services:
@@ -21024,7 +21288,12 @@ def prepare_component_runtime(component, entries=None):
"device plane management to Core", "device plane management to Core",
) )
ensure_device_edge_channel_core_identity( ensure_device_edge_channel_core_identity(
allow_invalid_unexported_recovery=True allow_invalid_unexported_recovery=(
not is_device_plane_edge_core_channel_upgrade_slice(
component,
entries,
)
)
) )
if is_device_plane_backhaul_vps_enrollment_slice( if is_device_plane_backhaul_vps_enrollment_slice(
component, component,
@@ -289,6 +289,35 @@ 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"]["commandTransport"], "disabled") self.assertEqual(checks[0]["expected_json"]["commandTransport"], "disabled")
def test_edge_core_channel_upgrade_is_core_only_and_pins_bootstrap_018(self):
manifest, entries, names, result = self.assert_deterministic_artifact(
"build-device-edge-core-channel-bootstrap-artifact.mjs",
"device-edge-core-channel-upgrade-unit-001",
RUNNER.DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_ENTRIES,
)
self.assertEqual(manifest["component"], "device-plane")
self.assertEqual(
RUNNER.component_services("device-plane", entries),
("device-control-core",),
)
self.assertEqual(result["services"], ["device-control-core"])
self.assertIn(
"payload/deployment/device-edge-core-channel-upgrade-v1.json",
names,
)
descriptor = RUNNER.expected_device_plane_edge_core_channel_upgrade_descriptor(
"device-edge-core-channel-upgrade-unit-001"
)
self.assertEqual(descriptor["action"], "upgrade")
self.assertEqual(
descriptor["bootstrapPredecessor"]["patchId"],
"device-edge-core-channel-bootstrap-20260812-018",
)
self.assertEqual(
descriptor["endpointPolicy"],
"public-ipv4-standard-https-tcp-443-only",
)
def test_release_v2_keeps_release_v1_predecessor_contract_immutable(self): def test_release_v2_keeps_release_v1_predecessor_contract_immutable(self):
predecessor = device_manager_release_v1_descriptor( predecessor = device_manager_release_v1_descriptor(
release_id="device-manager-release-20260811-010", release_id="device-manager-release-20260811-010",
@@ -492,6 +521,24 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
allow_invalid_unexported_recovery=True allow_invalid_unexported_recovery=True
) )
with (
mock.patch.object(
RUNNER,
"ensure_platform_runtime_secret",
),
mock.patch.object(
RUNNER,
"ensure_device_edge_channel_core_identity",
) as ensure_edge_identity,
):
RUNNER.prepare_component_runtime(
"device-plane",
RUNNER.DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_ENTRIES,
)
ensure_edge_identity.assert_called_once_with(
allow_invalid_unexported_recovery=False
)
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_RELEASE_V1_SUCCESSOR_ENTRIES entries = RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V1_SUCCESSOR_ENTRIES
services = ("device-control-core", "device-manager") services = ("device-control-core", "device-manager")