fix(deploy): allow repeatable typed core releases

This commit is contained in:
Codex
2026-08-13 09:44:07 +03:00
parent 71fff82c99
commit 9c359111ea
3 changed files with 186 additions and 6 deletions
@@ -36,6 +36,10 @@ if (
}
const isV2 = patchId.startsWith("device-control-core-release-v2-");
const expectedV2Predecessor = Object.freeze({
patchId: predecessorPatchId ?? "device-control-core-release-20260812-024",
artifactSha256: predecessorSha256 ?? "a289e909283109642e6bba3d9822a31f63423cfe0bbcd52705979681bd2bc793",
});
const descriptorPath = isV2
? "deployment/device-control-core-release-v2.json"
: "deployment/device-control-core-release-v1.json";
@@ -117,8 +121,8 @@ try {
&& (
descriptor.commandCatalog !== "allowlisted-adapter-typed-commands-only"
|| descriptor.credentialBoundary !== "transient-core-memory-then-single-pinned-mtls-command-envelope-to-edge-never-persisted-never-logged-never-returned"
|| descriptor.predecessor?.patchId !== "device-control-core-release-20260812-024"
|| descriptor.predecessor?.artifactSha256 !== "a289e909283109642e6bba3d9822a31f63423cfe0bbcd52705979681bd2bc793"
|| descriptor.predecessor?.patchId !== expectedV2Predecessor.patchId
|| descriptor.predecessor?.artifactSha256 !== expectedV2Predecessor.artifactSha256
)
)
) {
+19 -4
View File
@@ -12874,7 +12874,12 @@ def validate_device_plane_control_core_release_predecessor(payload_dir):
):
die("Device Control Core release predecessor journal mismatch")
installed_release = DEVICE_PLANE_ROOT / DEVICE_PLANE_CONTROL_CORE_RELEASE_REL
installed_v1_release = (
DEVICE_PLANE_ROOT / DEVICE_PLANE_CONTROL_CORE_RELEASE_REL
)
installed_v2_release = (
DEVICE_PLANE_ROOT / DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_REL
)
with tempfile.TemporaryDirectory(
prefix="device-control-core-release-predecessor-",
dir=TMP_DIR,
@@ -12903,10 +12908,21 @@ def validate_device_plane_control_core_release_predecessor(payload_dir):
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_REL,
)
)
if installed_release.exists() or installed_release.is_symlink():
if (
installed_v1_release.exists()
or installed_v1_release.is_symlink()
or installed_v2_release.exists()
or installed_v2_release.is_symlink()
):
die("Device Control Core first release is already installed")
else:
if tuple(entries) != DEVICE_PLANE_CONTROL_CORE_RELEASE_ENTRIES:
if tuple(entries) == DEVICE_PLANE_CONTROL_CORE_RELEASE_ENTRIES:
installed_release = installed_v1_release
source_entries = DEVICE_PLANE_CONTROL_CORE_RELEASE_ENTRIES
elif tuple(entries) == DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES:
installed_release = installed_v2_release
source_entries = DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES
else:
die("Device Control Core release predecessor type mismatch")
expected_installed = validate_device_plane_control_core_release_payload(
predecessor_payload,
@@ -12919,7 +12935,6 @@ def validate_device_plane_control_core_release_predecessor(payload_dir):
)
if installed_descriptor != expected_installed:
die("Device Control Core release predecessor is not current")
source_entries = DEVICE_PLANE_CONTROL_CORE_RELEASE_ENTRIES
expected_source = collect_exact_files(
predecessor_payload,
source_entries,
@@ -691,6 +691,167 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
},
)
def test_control_core_release_v2_builder_supports_v2_release_predecessor(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-control-core-v2-successor-",
) as directory:
artifact_dir = Path(directory) / "successor"
environment = os.environ.copy()
environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir)
predecessor_id = "device-control-core-release-v2-unit-001"
predecessor_sha = "b" * 64
completed = subprocess.run(
[
"node",
str(
SCRIPT_DIR
/ "build-device-control-core-release-artifact.mjs"
),
"device-control-core-release-v2-unit-002",
predecessor_id,
predecessor_sha,
],
cwd=PLATFORM_ROOT,
env=environment,
check=True,
capture_output=True,
text=True,
)
successor = json.loads(completed.stdout)
extracted = Path(directory) / "extracted-successor"
extracted.mkdir()
_manifest, entries, payload = RUNNER.load_artifact(
Path(successor["artifact"]),
extracted,
)
self.assertEqual(
tuple(entries),
RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES,
)
descriptor = json.loads(
(
payload / RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_REL
).read_text(encoding="utf-8")
)
self.assertEqual(
descriptor["predecessor"],
{
"kind": "release",
"patchId": predecessor_id,
"artifactSha256": predecessor_sha,
},
)
def test_control_core_release_v2_accepts_current_v2_release_predecessor(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-control-core-v2-predecessor-",
) as directory:
root = Path(directory)
applied = root / "applied"
temporary = root / "tmp"
device_plane = root / "device-plane"
for path in (applied, temporary, device_plane / "deployment"):
path.mkdir(parents=True)
predecessor_id = "device-control-core-release-v2-unit-001"
predecessor_sha = "b" * 64
artifact_name = f"nodedc-device-plane-{predecessor_id}.tgz"
(applied / artifact_name).write_bytes(b"reviewed-v2-predecessor")
predecessor = {
"kind": "release",
"patchId": predecessor_id,
"artifactSha256": predecessor_sha,
}
predecessor_descriptor = (
RUNNER.expected_device_plane_control_core_release_descriptor(
predecessor_id,
{
"kind": "release",
"patchId": "device-control-core-release-20260812-024",
"artifactSha256": "a" * 64,
},
schema_version="v2",
)
)
candidate_descriptor = (
RUNNER.expected_device_plane_control_core_release_descriptor(
"device-control-core-release-v2-unit-002",
predecessor,
schema_version="v2",
)
)
installed = (
device_plane / RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_REL
)
installed.write_text(
json.dumps(predecessor_descriptor),
encoding="utf-8",
)
with (
mock.patch.object(RUNNER, "APPLIED_DIR", applied),
mock.patch.object(RUNNER, "TMP_DIR", temporary),
mock.patch.object(RUNNER, "DEVICE_PLANE_ROOT", device_plane),
mock.patch.object(
RUNNER,
"validate_device_plane_control_core_release_payload",
side_effect=(candidate_descriptor, predecessor_descriptor),
),
mock.patch.object(
RUNNER,
"load_artifact",
return_value=(
{
"id": predecessor_id,
"component": "device-plane",
"type": "app-overlay",
},
RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES,
root / "predecessor-payload",
),
),
mock.patch.object(
RUNNER,
"load_state",
return_value=[{
"id": predecessor_id,
"artifact": artifact_name,
"component": "device-plane",
"sha256": predecessor_sha,
"status": "ok",
}],
),
mock.patch.object(
RUNNER,
"sha256_file",
return_value=predecessor_sha,
),
mock.patch.object(
RUNNER,
"collect_exact_files",
return_value={"same": "source"},
),
mock.patch.object(RUNNER, "component_compose_files"),
mock.patch.object(
RUNNER,
"inspect_device_edge_channel_core_identity_state",
return_value="valid-reuse-at-apply",
),
mock.patch.object(RUNNER, "healthcheck_compose_service"),
mock.patch.object(
RUNNER,
"validate_device_manager_control_plane_runtime",
),
):
result = (
RUNNER.validate_device_plane_control_core_release_predecessor(
root / "candidate-payload"
)
)
self.assertEqual(
result["mode"],
"active-device-control-core-forward-release",
)
def test_control_core_release_rejects_direct_legacy_predecessor(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-control-core-release-invalid-",