feat(manager): expose safe VPS edge health

This commit is contained in:
DCCONSTRUCTIONS
2026-08-22 13:08:04 +03:00
parent 07ffb22d38
commit 6ed4414a97
14 changed files with 464 additions and 42 deletions
@@ -11,11 +11,13 @@ const platformRoot = resolve(scriptDir, "../..");
const devicePlaneRoot = platformRoot;
const managerRoot = resolve(platformRoot, "apps/device-manager");
const artifactDir = resolve(process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(scriptDir, "../deploy-artifacts"));
const [patchId = "device-manager-release-v6-20260822-035", ...extra] = process.argv.slice(2);
const [patchId = "device-manager-release-v7-20260822-037", ...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]");
const descriptorPath = patchId.startsWith("device-manager-release-v6-")
? "deployment/device-manager-release-v6.json"
const descriptorPath = patchId.startsWith("device-manager-release-v7-")
? "deployment/device-manager-release-v7.json"
: patchId.startsWith("device-manager-release-v6-")
? "deployment/device-manager-release-v6.json"
: patchId.startsWith("device-manager-release-v5-")
? "deployment/device-manager-release-v5.json"
: patchId.startsWith("device-manager-release-v4-")
@@ -28,7 +30,8 @@ const isV3 = descriptorPath.endsWith("release-v3.json");
const isV4 = descriptorPath.endsWith("release-v4.json");
const isV5 = descriptorPath.endsWith("release-v5.json");
const isV6 = descriptorPath.endsWith("release-v6.json");
const isPersistent = isV4 || isV5 || isV6;
const isV7 = descriptorPath.endsWith("release-v7.json");
const isPersistent = isV4 || isV5 || isV6 || isV7;
const isManagerOnly = isV3 || isPersistent;
const composeSource = resolve(devicePlaneRoot, "docker-compose.device-manager.yml");
const composeSourceSha256 = createHash("sha256").update(await readFile(composeSource)).digest("hex");
@@ -167,7 +170,38 @@ try {
: "restore-preapply-snapshot")
);
if (commonContractInvalid) throw new Error("device_manager_activation_successor_contract_mismatch");
if (descriptorPath.endsWith("release-v6.json")) {
if (descriptorPath.endsWith("release-v7.json")) {
if (
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-release.v7"
|| descriptor.predecessor?.kind !== "release"
|| descriptor.predecessor?.patchId !== "device-manager-release-v6-20260822-035"
|| descriptor.predecessor?.artifactSha256 !== "193faabe930e2b3f212f8eb45288e39f850ec714528b28881095be654baf9a80"
|| descriptor.controlCorePredecessor?.patchId !== "device-control-core-release-v2-20260822-036"
|| descriptor.controlCorePredecessor?.artifactSha256 !== "8708cc4b59fa0cd5e9c6e6a7b2654ba01ea60271549167aca2631f94000d3da3"
|| descriptor.edgeChannelPredecessor?.patchId !== "device-edge-core-channel-upgrade-v4-20260812-023"
|| descriptor.edgeChannelPredecessor?.artifactSha256 !== "c10d5b6b7d55ab239f85b6c8130e34ce9f84985e3b46e6e5534733156c7982fc"
|| descriptor.commandTransport !== "typed-service-ping-v1"
|| 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.presentationPersistence !== "runner-managed-host-data-bind"
|| descriptor.presentationDataHostPath !== "/volume1/docker/nodedc-device-plane/data/device-manager"
|| descriptor.presentationDataContainerPath !== "/var/lib/nodedc-device-manager"
|| descriptor.presentationDataOwnership !== "uid-1000-gid-1000-mode-0750"
|| descriptor.presentationDataLifecycle !== "preserve-across-manager-recreate-and-source-rollback"
|| descriptor.presentationPath !== "/var/lib/nodedc-device-manager/device-manager-presentation.json"
|| descriptor.mediaRoot !== "/var/lib/nodedc-device-manager/media"
|| descriptor.defaultAccentHex !== "#f5f5f5"
|| descriptor.overviewLayout !== "mission-core-landing-stage-v1"
|| descriptor.faviconSet !== "nodedc-adaptive-v1"
|| descriptor.commandFormLayout !== "aligned-control-row-v1"
|| descriptor.secondaryEmptyTypography !== "help-text-sm-v1"
|| descriptor.infrastructureHostProjection !== "edge-registration-live-channel-v1"
|| descriptor.ontologyStatus !== "generic-host-domain-candidate-not-canonical"
|| descriptor.rollback !== "restore-preapply-snapshot-preserve-manager-data"
|| descriptor.gelios !== "untouched-legacy-only"
) throw new Error("device_manager_v7_infrastructure_host_projection_contract_mismatch");
await validateFaviconBundle(payload, "device_manager_v7");
} else if (descriptorPath.endsWith("release-v6.json")) {
if (
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-release.v6"
|| descriptor.predecessor?.kind !== "release"
@@ -193,29 +227,7 @@ try {
|| descriptor.rollback !== "restore-preapply-snapshot-preserve-manager-data"
|| descriptor.gelios !== "untouched-legacy-only"
) throw new Error("device_manager_v6_favicon_contract_mismatch");
const faviconHashes = {
"favicon.ico": "f8933114a85646335ea5c94944f56d3cd8c48a6032016719f7905ff244ec0aa2",
"favicon/favicon.ico": "f8933114a85646335ea5c94944f56d3cd8c48a6032016719f7905ff244ec0aa2",
"favicon/icon-adaptive.svg": "481984e83997d786bb0a72ad1ee80037db13aef3a0792ab3109df95c2199b38e",
"favicon/apple-touch-icon.png": "afdccc28152a566e264e533ca218362f05d5bcec936c647f9a54f414c0bd4763",
"favicon/icon-192.png": "5b10a24feb4754f15c69761cef42f91a01f885d04095156b1a12e254875fdd4d",
"favicon/icon-512.png": "f98bac3dba59b7eefbe89f8bb8abc25567226a54ab7ed3b6b1a4caffbdd9ee15",
"favicon/manifest.webmanifest.json": "2a8ecdc6e6c64833f812ae02bbc0c7bd9b435e0cfa75cc21d6edf054d41275fc",
};
for (const [relativePath, expectedSha256] of Object.entries(faviconHashes)) {
const content = await readFile(join(payload, "services/device-manager/dist", relativePath));
const actualSha256 = createHash("sha256").update(content).digest("hex");
if (actualSha256 !== expectedSha256) throw new Error(`device_manager_v6_favicon_hash_mismatch:${relativePath}`);
}
const indexHtml = await readFile(join(payload, "services/device-manager/dist/index.html"), "utf8");
for (const requiredLink of [
'href="/favicon/icon-adaptive.svg"',
'href="/favicon/favicon.ico"',
'href="/favicon/apple-touch-icon.png"',
'href="/favicon/icon-192.png"',
'href="/favicon/icon-512.png"',
'href="/favicon/manifest.webmanifest.json"',
]) if (!indexHtml.includes(requiredLink)) throw new Error(`device_manager_v6_favicon_link_missing:${requiredLink}`);
await validateFaviconBundle(payload, "device_manager_v6");
} else if (descriptorPath.endsWith("release-v5.json")) {
if (
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-release.v5"
@@ -303,6 +315,41 @@ try {
await rm(stage, { recursive: true, force: true });
}
async function validateFaviconBundle(payloadRoot, errorPrefix) {
const faviconHashes = {
"favicon.ico": "f8933114a85646335ea5c94944f56d3cd8c48a6032016719f7905ff244ec0aa2",
"favicon/favicon.ico": "f8933114a85646335ea5c94944f56d3cd8c48a6032016719f7905ff244ec0aa2",
"favicon/icon-adaptive.svg": "481984e83997d786bb0a72ad1ee80037db13aef3a0792ab3109df95c2199b38e",
"favicon/apple-touch-icon.png": "afdccc28152a566e264e533ca218362f05d5bcec936c647f9a54f414c0bd4763",
"favicon/icon-192.png": "5b10a24feb4754f15c69761cef42f91a01f885d04095156b1a12e254875fdd4d",
"favicon/icon-512.png": "f98bac3dba59b7eefbe89f8bb8abc25567226a54ab7ed3b6b1a4caffbdd9ee15",
"favicon/manifest.webmanifest.json": "2a8ecdc6e6c64833f812ae02bbc0c7bd9b435e0cfa75cc21d6edf054d41275fc",
};
for (const [relativePath, expectedSha256] of Object.entries(faviconHashes)) {
const content = await readFile(join(payloadRoot, "services/device-manager/dist", relativePath));
const actualSha256 = createHash("sha256").update(content).digest("hex");
if (actualSha256 !== expectedSha256) {
throw new Error(`${errorPrefix}_favicon_hash_mismatch:${relativePath}`);
}
}
const indexHtml = await readFile(
join(payloadRoot, "services/device-manager/dist/index.html"),
"utf8",
);
for (const requiredLink of [
'href="/favicon/icon-adaptive.svg"',
'href="/favicon/favicon.ico"',
'href="/favicon/apple-touch-icon.png"',
'href="/favicon/icon-192.png"',
'href="/favicon/icon-512.png"',
'href="/favicon/manifest.webmanifest.json"',
]) {
if (!indexHtml.includes(requiredLink)) {
throw new Error(`${errorPrefix}_favicon_link_missing:${requiredLink}`);
}
}
}
async function copySafe(source, destination, sourceBoundary) {
const sourceStat = await lstat(source);
if (sourceStat.isSymbolicLink()) throw new Error(`source_symlink_rejected:${relative(sourceBoundary, source)}`);
@@ -570,6 +570,73 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
)
)
def test_device_manager_release_v7_adds_live_edge_host_projection(self):
patch_id = "device-manager-release-v7-unit-001"
manifest, entries, names, result = self.assert_deterministic_artifact(
"build-device-manager-control-plane-artifact.mjs",
patch_id,
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V7_ENTRIES,
)
self.assertEqual(manifest["component"], "device-plane")
self.assertEqual(result["services"], ["device-manager"])
self.assertFalse(
any(
name.startswith("payload/services/device-control-core/")
for name in names
)
)
self.assertFalse(any(name.startswith("payload/packages/") for name in names))
self.assertFalse(any(name.endswith((".test.mjs", ".map")) for name in names))
template = json.loads(
(
DEVICE_CORE_ROOT
/ "deployment/device-manager-release-v7.json"
).read_text(encoding="utf-8")
)
descriptor = {**template, "releaseId": patch_id}
self.assertIs(
RUNNER.validate_device_plane_manager_release_descriptor(
descriptor,
schema_version=(
"nodedc.device-plane.device-manager-release.v7"
),
boundaries=(
RUNNER.expected_device_plane_manager_release_v7_boundaries()
),
expected_release_id=patch_id,
),
descriptor,
)
self.assertEqual(
descriptor["predecessor"],
{
"kind": "release",
"patchId": "device-manager-release-v6-20260822-035",
"artifactSha256": (
"193faabe930e2b3f212f8eb45288e39f850ec714528b28881095be654baf9a80"
),
},
)
self.assertEqual(
descriptor["controlCorePredecessor"],
{
"patchId": "device-control-core-release-v2-20260822-036",
"artifactSha256": (
"8708cc4b59fa0cd5e9c6e6a7b2654ba01ea60271549167aca2631f94000d3da3"
),
},
)
self.assertEqual(
descriptor["infrastructureHostProjection"],
"edge-registration-live-channel-v1",
)
self.assertTrue(
RUNNER.is_device_plane_manager_release_v7_slice(
"device-plane",
entries,
)
)
def test_historical_manager_builder_fails_closed_after_v4_compose(self):
if self.historical_manager_compose_is_current():
self.skipTest("historical Manager Compose is still current")