feat(deploy): add typed command transport releases

This commit is contained in:
Codex
2026-08-12 22:57:20 +03:00
parent 4b73a15765
commit 3ea2133bcd
10 changed files with 1028 additions and 75 deletions
@@ -0,0 +1,35 @@
{
"schemaVersion": "nodedc.device-plane.device-control-core-release.v2",
"releaseId": "__PATCH_ID__",
"action": "upgrade",
"predecessor": {
"kind": "release",
"patchId": "device-control-core-release-20260812-024",
"artifactSha256": "a289e909283109642e6bba3d9822a31f63423cfe0bbcd52705979681bd2bc793"
},
"service": "device-control-core",
"composeActivation": "preserve-active-v4-topology",
"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",
"coreNetworks": [
"device-plane-private",
"device-plane-egress"
],
"publicIngress": "none-on-synology",
"edgeRegistrations": "preserved",
"commandTransport": "typed-service-ping-v1",
"commandCatalog": "allowlisted-adapter-typed-commands-only",
"credentialBoundary": "transient-core-memory-then-single-pinned-mtls-command-envelope-to-edge-never-persisted-never-logged-never-returned",
"gelios": "untouched-legacy-only",
"preservedServices": [
"device-manager",
"device-gateway",
"device-postgres",
"device-backhaul-target"
],
"healthGate": "bounded-container-grace+core-edge-contract+exact-private-egress-network-boundary",
"rollback": "restore-preapply-source-and-core-runtime"
}
@@ -0,0 +1,78 @@
{
"schemaVersion": "nodedc.device-edge-vps.command-transport.v1",
"mode": "provider-neutral-typed-command-transport-over-accepted-core-channel",
"status": "active-typed-command-transport",
"authority": "DCPLATFORM-21/DCPLATFORM-76/ADR-0001",
"component": "device-edge-vps",
"phase": "command-transport",
"runtimeHost": "koffyvngij",
"predecessorPatch": "device-edge-vps-tracker-ingress-20260812-012",
"predecessorArtifactSha256": "290acef118839c6b0c31aac864c47da1832a289537366af9322d4624a1dd81ec",
"runtimeUser": "nodedc-channel",
"runtimeService": "nodedc-device-edge-channel.service",
"runtimeComposition": "single-process-core-channel-plus-universal-device-gateway",
"publicIngress": "tcp/443-mtls-core-channel+tcp/9921-bidirectional-tracker-session",
"trackerIngress": "preserved:allowlisted-adapters-only",
"initialAdapterProfile": "arusnavi.b2.internal.v1",
"commandTransport": "typed-service-ping-v1",
"commandCatalog": "allowlisted-adapter-typed-commands-only",
"allowedCommands": [
"service.ping"
],
"credentialBoundary": "transient-over-pinned-mtls-in-memory-until-single-tracker-write-never-stored-never-logged-never-returned",
"responseBoundary": "exact-adapter-parser-serv-ok-only",
"health": "127.0.0.1:18222",
"adapterHealth": "127.0.0.1:18221",
"rawDeviceTcp9921": "public-bidirectional-tracker-session-no-generic-forwarding",
"gelios": "untouched-legacy-only",
"tailscale": "absent",
"dataBoundary": "no-vps-database-no-business-logic-no-synology-route",
"resourceCeilings": {
"memory": "192M",
"swap": "0",
"cpu": "75%",
"tasks": 128,
"openFiles": 1024,
"sessions": 128,
"sessionsPerAddress": 16,
"connectionsPerMinutePerAddress": 60,
"sessionBufferBytes": 65536,
"aggregateBufferBytes": 33554432
},
"preserved": [
"management-ssh-key",
"accepted-node-runtime",
"accepted-core-channel-trust-and-registration",
"accepted-tracker-ingress",
"retired-tailnet-boundary",
"gelios-production-path"
],
"forbidden": [
"vps-initiated-synology-connection",
"generic-tcp-forwarding",
"tailscale-runtime",
"docker",
"public-health",
"vps-database",
"vps-business-logic",
"unregistered-adapter",
"raw-command",
"firmware-command",
"reboot-command",
"persistent-command-credential"
],
"acceptance": [
"exact-tracker-ingress-012-predecessor",
"single-non-root-edge-process",
"core-channel-remains-accepted",
"public-tracker-tcp-9921-listening",
"adapter-profile-allowlisted",
"typed-service-ping-only",
"exact-serv-ok-response-parser",
"bounded-session-and-buffer-limits",
"tailscale-remains-absent",
"no-vps-to-synology-route",
"gelios-untouched"
],
"rollback": "restore-exact-tracker-ingress-012-source-and-existing-runtime"
}
@@ -0,0 +1,31 @@
{
"schemaVersion": "nodedc.device-plane.device-manager-release.v3",
"releaseId": "__PATCH_ID__",
"action": "upgrade",
"predecessor": {
"kind": "release",
"patchId": "device-manager-release-20260811-010",
"artifactSha256": "d4132993216eb674967dc6fc65d9670cfc2a9efdf46186ca019030f259de2d0e"
},
"controlCorePredecessor": {
"patchId": "device-control-core-release-v2-20260812-025",
"artifactSha256": "c61b1f0de1bae23de0caa7289036865ea419ff5705611416f736ca929d1592db"
},
"edgeChannelPredecessor": {
"patchId": "device-edge-core-channel-upgrade-v4-20260812-023",
"artifactSha256": "c10d5b6b7d55ab239f85b6c8130e34ce9f84985e3b46e6e5534733156c7982fc"
},
"service": "device-manager",
"publicIngress": "reverse-proxy-only",
"deviceCoreManagementApi": "file-token-authenticated",
"launcherTrust": "file-token-scoped-to-device-core-handoff",
"edgeChannel": "preserve-active-v4-core-initiated-pinned-mtls",
"edgeChannelIdentity": "reuse-runner-managed-host-local-private-key-public-certificate-export",
"edgeChannelEgress": "preserve-dedicated-core-only-bridge-no-host-ingress-public-ipv4-tcp-443-only",
"healthGate": "bounded-container-grace+core-contract",
"commandTransport": "typed-service-ping-v1",
"commandCatalog": "allowlisted-adapter-typed-commands-only",
"credentialBoundary": "transient-core-memory-then-single-pinned-mtls-command-envelope-to-edge-never-persisted-never-logged-never-returned",
"gelios": "untouched-legacy-only",
"rollback": "restore-preapply-snapshot"
}
@@ -11,7 +11,7 @@ const platformRoot = resolve(scriptDir, "../..");
const devicePlaneRoot = resolve(platformRoot, "device-plane");
const artifactDir = resolve(process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(scriptDir, "../deploy-artifacts"));
const [
patchId = "device-control-core-release-20260812-024",
patchId = "device-control-core-release-v2-20260812-025",
predecessorPatchId,
predecessorSha256,
...extra
@@ -35,7 +35,10 @@ if (
);
}
const descriptorPath = "deployment/device-control-core-release-v1.json";
const isV2 = patchId.startsWith("device-control-core-release-v2-");
const descriptorPath = isV2
? "deployment/device-control-core-release-v2.json"
: "deployment/device-control-core-release-v1.json";
const entries = [
".dockerignore",
"package.json",
@@ -94,7 +97,7 @@ try {
const descriptor = JSON.parse(await readFile(join(payload, descriptorPath), "utf8"));
if (
descriptor.schemaVersion !== "nodedc.device-plane.device-control-core-release.v1"
descriptor.schemaVersion !== `nodedc.device-plane.device-control-core-release.${isV2 ? "v2" : "v1"}`
|| descriptor.releaseId !== patchId
|| descriptor.action !== "upgrade"
|| descriptor.service !== "device-control-core"
@@ -106,9 +109,18 @@ try {
|| JSON.stringify(descriptor.coreNetworks) !== JSON.stringify(["device-plane-private", "device-plane-egress"])
|| descriptor.publicIngress !== "none-on-synology"
|| descriptor.edgeRegistrations !== "preserved"
|| descriptor.commandTransport !== "disabled"
|| descriptor.gelios !== "untouched"
|| descriptor.commandTransport !== (isV2 ? "typed-service-ping-v1" : "disabled")
|| descriptor.gelios !== (isV2 ? "untouched-legacy-only" : "untouched")
|| descriptor.rollback !== "restore-preapply-source-and-core-runtime"
|| (
isV2
&& (
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"
)
)
) {
throw new Error("device_control_core_release_contract_mismatch");
}
@@ -37,11 +37,12 @@ if (
"core-channel",
"tailscale-retirement",
"tracker-ingress",
"command-transport",
].includes(phase)
|| !/^[A-Za-z0-9._-]{1,96}$/.test(patchId || "")
) {
throw new Error(
"usage: build-device-edge-vps-artifact.mjs <foundation|runtime-reconciliation|backhaul|relay|core-channel|tailscale-retirement|tracker-ingress> <patch-id>",
"usage: build-device-edge-vps-artifact.mjs <foundation|runtime-reconciliation|backhaul|relay|core-channel|tailscale-retirement|tracker-ingress|command-transport> <patch-id>",
);
}
@@ -52,6 +53,10 @@ if (
) {
throw new Error("vps_initiated_transport_frozen:ADR-0001");
}
const acceptedSharedSourcePhases = new Set(["core-channel", "tracker-ingress"]);
if (acceptedSharedSourcePhases.has(phase)) {
throw new Error(`accepted_vps_phase_rebuild_frozen:${phase}:ADR-0001`);
}
const nodeArchive = "node-v22.23.2-linux-x64.tar.xz";
const tailscaleArchive = "tailscale_1.102.2_amd64.tgz";
@@ -110,6 +115,21 @@ const entriesByPhase = {
"vps/systemd/nodedc-device-edge-runtime.service",
"deployment/device-edge-vps-tracker-ingress-v1.json",
],
"command-transport": [
"packages/device-edge-channel-contract/package.json",
"packages/device-edge-channel-contract/src",
"services/device-edge-channel/package.json",
"services/device-edge-channel/src",
"packages/device-adapter-runtime/package.json",
"packages/device-adapter-runtime/src",
"packages/device-adapter-catalog/package.json",
"packages/device-adapter-catalog/src",
"packages/arusnavi-b2-adapter/package.json",
"packages/arusnavi-b2-adapter/src",
"services/device-gateway/src/runtime.mjs",
"vps/edge-process/device-edge-runtime.mjs",
"deployment/device-edge-vps-command-transport-v1.json",
],
};
const entries = entriesByPhase[phase];
const ignoredBasenames = new Set([".DS_Store", ".git", "node_modules"]);
@@ -171,10 +191,12 @@ try {
? "tcp/9921"
: ["core-channel", "tailscale-retirement"].includes(phase)
? "tcp/443-mtls-only"
: phase === "tracker-ingress"
: ["tracker-ingress", "command-transport"].includes(phase)
? "tcp/443-mtls+tcp/9921-telemetry"
: "disabled",
commandTransport: "disabled",
commandTransport: phase === "command-transport"
? "typed-service-ping-v1"
: "disabled",
gelios: "untouched",
}, null, 2));
} finally {
@@ -190,8 +212,10 @@ async function assertBoundary() {
if (
descriptor.component !== "device-edge-vps"
|| descriptor.runtimeHost !== "koffyvngij"
|| descriptor.commandTransport !== "disabled"
|| descriptor.gelios !== "untouched"
|| descriptor.commandTransport !== (phase === "command-transport"
? "typed-service-ping-v1"
: "disabled")
|| !String(descriptor.gelios || "").startsWith("untouched")
|| !String(descriptor.rollback || "").length
) {
throw new Error(`descriptor_boundary_mismatch:${phase}`);
@@ -365,6 +389,36 @@ async function assertBoundary() {
}
}
}
if (phase === "command-transport") {
for (const required of [
'"predecessorPatch": "device-edge-vps-tracker-ingress-20260812-012"',
'"runtimeService": "nodedc-device-edge-channel.service"',
'"runtimeComposition": "single-process-core-channel-plus-universal-device-gateway"',
'"commandTransport": "typed-service-ping-v1"',
'"commandCatalog": "allowlisted-adapter-typed-commands-only"',
'"responseBoundary": "exact-adapter-parser-serv-ok-only"',
"buildTypedCommand",
"parseTypedCommandResponse",
"submitCommandStatus",
'"service.ping"',
]) {
if (!combined.includes(required)) {
throw new Error(`command_transport_boundary_missing:${required}`);
}
}
for (const forbidden of [
"LocalForward",
"tailscale-userspace",
"DEVICE_EDGE_RELAY_UPSTREAM",
"device.dc.ru",
"PRIVATE KEY",
"TS_AUTHKEY",
]) {
if (combined.includes(forbidden)) {
throw new Error(`command_transport_boundary_violation:${forbidden}`);
}
}
}
}
function canonicalTarScript() {
@@ -12,10 +12,19 @@ const devicePlaneRoot = resolve(platformRoot, "device-plane");
const designRoot = resolve(process.env.NODEDC_DEVICE_MANAGER_SOURCE_ROOT || resolve(platformRoot, "../NODEDC_DESIGN_GUIDELINE"));
const managerRoot = resolve(designRoot, "apps/device-manager");
const artifactDir = resolve(process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(scriptDir, "../deploy-artifacts"));
const [patchId = "device-manager-release-20260811-018", ...extra] = process.argv.slice(2);
const [patchId = "device-manager-release-v3-20260812-026", ...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 entries = [
const descriptorPath = patchId.startsWith("device-manager-release-v3-")
? "deployment/device-manager-release-v3.json"
: "deployment/device-manager-release-v1.json";
const isV3 = descriptorPath.endsWith("release-v3.json");
const entries = isV3 ? [
"docker-compose.device-manager.yml",
"services/device-manager",
descriptorPath,
] : [
".dockerignore",
"package.json",
"package-lock.json",
@@ -27,7 +36,7 @@ const entries = [
"services/device-gateway/package.json",
"services/device-edge-relay/package.json",
"services/device-manager",
"deployment/device-manager-release-v1.json",
descriptorPath,
];
const stage = await mkdtemp(join(tmpdir(), "nodedc-device-manager-control-plane-"));
const payload = join(stage, "payload");
@@ -41,7 +50,7 @@ try {
if (build.status !== 0) throw new Error(`device_manager_build_failed:${build.stderr || build.stdout}`);
await mkdir(payload, { recursive: true });
for (const entry of entries) {
if (entry === "deployment/device-manager-release-v1.json") {
if (entry === descriptorPath) {
const descriptor = JSON.parse(await readFile(resolve(devicePlaneRoot, entry), "utf8"));
if (descriptor.releaseId !== "__PATCH_ID__") {
throw new Error("device_manager_release_template_id_mismatch");
@@ -62,15 +71,17 @@ try {
}
await copySafe(resolve(devicePlaneRoot, entry), join(payload, entry), devicePlaneRoot);
}
await validateDockerCopySources(
join(payload, "services/device-control-core/Dockerfile"),
payload,
);
if (!isV3) {
await validateDockerCopySources(
join(payload, "services/device-control-core/Dockerfile"),
payload,
);
}
await validateDockerCopySources(
join(payload, "services/device-manager/Dockerfile"),
join(payload, "services/device-manager"),
);
for (const modulePath of [
for (const modulePath of isV3 ? [] : [
"services/device-control-core/src/sensitive-reference-management.mjs",
"services/device-control-core/src/device-gateway-core-runtime.mjs",
"packages/device-edge-channel-contract/src/index.mjs",
@@ -115,14 +126,10 @@ try {
]) {
if (compose.includes(forbidden)) throw new Error(`device_manager_compose_boundary_violation:${forbidden}`);
}
const descriptor = JSON.parse(await readFile(
join(payload, "deployment/device-manager-release-v1.json"),
"utf8",
));
const descriptor = JSON.parse(await readFile(join(payload, descriptorPath), "utf8"));
const predecessor = descriptor.predecessor;
if (
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-release.v1"
|| descriptor.releaseId !== patchId
const commonContractInvalid = (
descriptor.releaseId !== patchId
|| !["activate", "upgrade"].includes(descriptor.action)
|| !predecessor
|| !["reconciliation", "release"].includes(predecessor.kind)
@@ -131,7 +138,29 @@ try {
|| (descriptor.action === "activate") !== (predecessor.kind === "reconciliation")
|| descriptor.healthGate !== "bounded-container-grace+core-contract"
|| descriptor.rollback !== "restore-preapply-snapshot"
) throw new Error("device_manager_activation_successor_contract_mismatch");
);
if (commonContractInvalid) throw new Error("device_manager_activation_successor_contract_mismatch");
if (descriptorPath.endsWith("release-v3.json")) {
if (
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-release.v3"
|| 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.controlCorePredecessor?.patchId !== "device-control-core-release-v2-20260812-025"
|| descriptor.controlCorePredecessor?.artifactSha256 !== "c61b1f0de1bae23de0caa7289036865ea419ff5705611416f736ca929d1592db"
|| descriptor.edgeChannelPredecessor?.patchId !== "device-edge-core-channel-upgrade-v4-20260812-023"
|| descriptor.edgeChannelPredecessor?.artifactSha256 !== "c10d5b6b7d55ab239f85b6c8130e34ce9f84985e3b46e6e5534733156c7982fc"
|| descriptor.edgeChannel !== "preserve-active-v4-core-initiated-pinned-mtls"
|| descriptor.edgeChannelEgress !== "preserve-dedicated-core-only-bridge-no-host-ingress-public-ipv4-tcp-443-only"
|| descriptor.gelios !== "untouched-legacy-only"
) throw new Error("device_manager_v3_typed_command_contract_mismatch");
} else if (
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-release.v1"
|| descriptor.commandTransport !== "disabled"
|| descriptor.gelios !== "untouched"
) {
throw new Error("device_manager_v1_contract_mismatch");
}
await writeFile(join(stage, "manifest.env"), `id=${patchId}\ncomponent=device-plane\ntype=app-overlay\n`, "utf8");
await writeFile(join(stage, "files.txt"), `${entries.join("\n")}\n`, "utf8");
await mkdir(artifactDir, { recursive: true });
@@ -145,7 +174,7 @@ try {
artifact: target,
sha256,
entries,
services: ["device-control-core", "device-manager"],
services: isV3 ? ["device-manager"] : ["device-control-core", "device-manager"],
preserved: ["device-postgres", "device-gateway", "device-backhaul-target", "Gelios"],
}, null, 2));
} finally {
+184 -6
View File
@@ -112,6 +112,10 @@ TAILSCALE_RETIREMENT_ACCEPTED_PATCH = (
TAILSCALE_RETIREMENT_ACCEPTED_SHA256 = (
"e7b61ec9c83122fa5631467010eff871b98935746df6a1326b6ff6bb9713d877"
)
TRACKER_INGRESS_ACCEPTED_PATCH = "device-edge-vps-tracker-ingress-20260812-012"
TRACKER_INGRESS_ACCEPTED_SHA256 = (
"290acef118839c6b0c31aac864c47da1832a289537366af9322d4624a1dd81ec"
)
FOUNDATION_ENTRIES = (
"vps/config/00-nodedc-b2-vps.conf",
@@ -162,6 +166,21 @@ TRACKER_INGRESS_ENTRIES = (
"vps/systemd/nodedc-device-edge-runtime.service",
"deployment/device-edge-vps-tracker-ingress-v1.json",
)
COMMAND_TRANSPORT_ENTRIES = (
"packages/device-edge-channel-contract/package.json",
"packages/device-edge-channel-contract/src",
"services/device-edge-channel/package.json",
"services/device-edge-channel/src",
"packages/device-adapter-runtime/package.json",
"packages/device-adapter-runtime/src",
"packages/device-adapter-catalog/package.json",
"packages/device-adapter-catalog/src",
"packages/arusnavi-b2-adapter/package.json",
"packages/arusnavi-b2-adapter/src",
"services/device-gateway/src/runtime.mjs",
"vps/edge-process/device-edge-runtime.mjs",
"deployment/device-edge-vps-command-transport-v1.json",
)
PHASE_ENTRIES = {
"foundation": FOUNDATION_ENTRIES,
@@ -171,6 +190,7 @@ PHASE_ENTRIES = {
"core-channel": CORE_CHANNEL_ENTRIES,
"tailscale-retirement": TAILSCALE_RETIREMENT_ENTRIES,
"tracker-ingress": TRACKER_INGRESS_ENTRIES,
"command-transport": COMMAND_TRANSPORT_ENTRIES,
}
SUPERSEDED_TRANSPORT_PHASES = frozenset({"backhaul", "relay"})
@@ -262,6 +282,36 @@ PHASE_FILE_SHA256 = {
"deployment/device-edge-vps-tracker-ingress-v1.json":
"faecfa0317ad0e3d102c3636c0547b65257b930e9b48f34d8679c00ceb3d323b",
},
"command-transport": {
"packages/device-edge-channel-contract/package.json":
"57d5349b5dcef2cacd4f3e4fad010359a65d59f5f903eff07d89f67c497f97c0",
"packages/device-edge-channel-contract/src/index.mjs":
"a49bfe31476a08f8a912c10c82b982fae28a29e2c0eb5828ced390b01f3a10bc",
"services/device-edge-channel/package.json":
"bdf502be43b62bdd6db05b022a532d93ba954277ac5143d6058d2f27f6a2e9d2",
"services/device-edge-channel/src/runtime.mjs":
"39579d85edf50be07573873a1f59045f62a74469c85588db1fd4e5151b3b778d",
"services/device-edge-channel/src/server.mjs":
"a82057218bb368ab926404f90a19cc17c0359b57dc890f38a1324ab8c497c17b",
"packages/device-adapter-runtime/package.json":
"293ee6010f255c511df40b34cf8019fcd0d1b06babe30761c2a842c7da7af778",
"packages/device-adapter-runtime/src/index.mjs":
"199fcdd775d1ea15ddc9f85ab24fc2c165bb7757008de26749f1b7e84481dfeb",
"packages/device-adapter-catalog/package.json":
"60dbfc551ca49719e0851755058c159968411c490621eac0bf96ca76bf8b78d1",
"packages/device-adapter-catalog/src/index.mjs":
"614749e763c0624bf0de7905343632d3edb4e026316f427a2f52ce12c4225dca",
"packages/arusnavi-b2-adapter/package.json":
"136fc997f5d6757ba81d370f03037e0a93fd4750dc624def1af593e7ba8b9098",
"packages/arusnavi-b2-adapter/src/index.mjs":
"790060720639e3835bbf60fb2bdc67172a7dac79a8f8e4bf1558ab51ed130c91",
"services/device-gateway/src/runtime.mjs":
"f1898baef2a964ee04adc55f7e26ae8a637fcc61701cf28d7cf4b3bda1579b25",
"vps/edge-process/device-edge-runtime.mjs":
"4443876d4652de5d953840896acccec93119f9ec642b5d5cdaeaac19a2022017",
"deployment/device-edge-vps-command-transport-v1.json":
"971166143fe954b9c5043cce9a464d17efbc87933da1405b2517a4693a7bed09",
},
}
# Exact immutable baselines from terminally accepted predecessor artifacts.
@@ -421,8 +471,9 @@ def validate_payload(payload: Path, phase: str):
if (
descriptor.get("component") != COMPONENT
or descriptor.get("runtimeHost") != RUNTIME_HOST
or descriptor.get("commandTransport") != "disabled"
or descriptor.get("gelios") != "untouched"
or descriptor.get("commandTransport")
!= ("typed-service-ping-v1" if phase == "command-transport" else "disabled")
or not str(descriptor.get("gelios", "")).startswith("untouched")
or not descriptor.get("rollback")
):
die(f"VPS {phase} descriptor mismatch")
@@ -674,6 +725,18 @@ def current_phase_preflight(phase: str):
if (LIVE_ROOT / TRACKER_INGRESS_ENTRIES[-1]).exists():
die("VPS tracker ingress target path already exists")
return {"predecessor": "accepted-tailscale-retirement-011"}
if phase == "command-transport":
tracker_record = applied_phase_record("tracker-ingress")
if (
tracker_record.get("patch") != TRACKER_INGRESS_ACCEPTED_PATCH
or tracker_record.get("sha256") != TRACKER_INGRESS_ACCEPTED_SHA256
):
die("VPS command transport tracker ingress predecessor mismatch")
source_file_state("tracker-ingress")
validate_tracker_ingress_runtime()
if (LIVE_ROOT / COMMAND_TRANSPORT_ENTRIES[-1]).exists():
die("VPS command transport target path already exists")
return {"predecessor": "accepted-tracker-ingress-012"}
validate_foundation_runtime(
require_running_tailnet=phase in {"backhaul", "relay"},
expected_key_user=BACKHAUL_USER if phase == "relay" else SERVICE_USER,
@@ -783,6 +846,8 @@ def backup_targets_for_phase(phase: str):
]
if phase == "tracker-ingress":
return common + [CHANNEL_UNIT, NFTABLES_CONFIG]
if phase == "command-transport":
return common + [CHANNEL_UNIT, NFTABLES_CONFIG]
return common + [RELAY_UNIT, NFTABLES_CONFIG]
@@ -1265,6 +1330,12 @@ def apply_tracker_ingress(_payload: Path):
validate_tracker_ingress_runtime()
def apply_command_transport(_payload: Path):
systemctl("daemon-reload")
systemctl("restart", "nodedc-device-edge-channel.service")
validate_command_transport_runtime()
def sshd_effective():
return run(["/usr/sbin/sshd", "-T"]).stdout.lower()
@@ -1620,6 +1691,92 @@ def validate_tracker_ingress_runtime():
return health
def validate_command_transport_runtime():
source_file_state("foundation")
source_file_state("tailscale-retirement")
source_file_state("command-transport")
tracker_record = applied_phase_record("tracker-ingress")
if (
tracker_record.get("patch") != TRACKER_INGRESS_ACCEPTED_PATCH
or tracker_record.get("sha256") != TRACKER_INGRESS_ACCEPTED_SHA256
):
die("command transport tracker ingress identity mismatch")
if run([str(NODE_BIN), "--version"]).stdout.strip() != f"v{NODE_VERSION}":
die("command transport Node version mismatch")
for path in (
TAILSCALE_UNIT,
TAILSCALE_SOCKET,
TAILSCALE_STATE.parent,
TAILSCALE_BIN.parent,
TRUST_ROOT,
):
if path.exists() or path.is_symlink():
die(f"command transport superseded trust boundary present: {path}")
for service in (
"nodedc-b2-tailscaled.service",
"nodedc-b2-backhaul.service",
"nodedc-b2-relay.service",
):
if service_active(service):
die(f"command transport forbidden service became active: {service}")
assert_management_key()
assert_channel_trust(require_runtime_owner=True)
if not service_active("nodedc-device-edge-channel.service"):
die("VPS Device Edge command runtime is not active")
health = core_channel_health(require_accepted=True)
expected = {
"ok": True,
"service": "nodedc-device-edge-runtime",
"channel": "accepted",
"trackerIngress": "telemetry-ingest",
"commandTransport": "typed-service-ping-v1",
}
for key, value in expected.items():
if health.get(key) != value:
die(f"VPS command transport health contract mismatch: {key}")
tracker = health.get("tracker") or {}
for key, value in {
"adapter": "arusnavi-b2",
"protocolProfile": "arusnavi.b2.internal.v1",
"framing": "verified-read-only",
"publicIngress": "telemetry-ingest",
"commandTransport": "typed-service-ping-v1",
}.items():
if tracker.get(key) != value:
die(f"VPS command adapter health contract mismatch: {key}")
for port in (22, CHANNEL_PUBLIC_PORT, 9921):
if not port_is_open(PUBLIC_IPV4, port, timeout=5):
die(f"VPS command transport required listener unavailable: {port}")
if not port_is_open("127.0.0.1", 18221, timeout=5):
die("VPS command transport adapter health unavailable")
if port_is_open("127.0.0.1", 1055):
die("VPS retired Tailscale SOCKS listener returned")
nft = run(["/usr/sbin/nft", "list", "table", "inet", "nodedc_b2_vps"]).stdout
for required in ("policy drop", "tcp dport 22", "tcp dport 443", "tcp dport 9921"):
if required not in nft:
die(f"VPS command transport firewall contract mismatch: {required}")
unit = run([
"/usr/bin/systemctl",
"show",
"nodedc-device-edge-channel.service",
"--property=User,Group,NoNewPrivileges,CapabilityBoundingSet,AmbientCapabilities,MemoryMax,MemorySwapMax,CPUQuotaPerSecUSec,TasksMax,LimitNOFILE",
]).stdout
for required in (
"User=nodedc-channel",
"Group=nodedc-channel",
"NoNewPrivileges=yes",
"CapabilityBoundingSet=cap_net_bind_service",
"AmbientCapabilities=cap_net_bind_service",
"MemoryMax=201326592",
"MemorySwapMax=0",
"TasksMax=128",
"LimitNOFILE=1024",
):
if required not in unit:
die(f"VPS command transport resource boundary mismatch: {required}")
return health
def validate_relay_runtime():
validate_backhaul_runtime()
source_file_state("relay")
@@ -1789,7 +1946,7 @@ def plan_artifact(artifact_argument: str):
print("tracker_tcp_9921=closed")
print("services=preserved:nodedc-device-edge-channel")
print("external_tailnet_machine_cleanup=required-after-deploy-ok")
else:
elif phase == "tracker-ingress":
print("public_core_channel=preserved:155.212.211.15:443/tcp:tls13-mtls-h2")
print(f"health=127.0.0.1:{CHANNEL_HEALTH_PORT}:combined-edge-runtime")
print("public_b2_ingress=155.212.211.15:9921/tcp:telemetry-only")
@@ -1798,8 +1955,27 @@ def plan_artifact(artifact_argument: str):
print("runtime_composition=single-non-root-process:core-channel+universal-gateway")
print("tailscale=preserved:absent")
print("services=recreate:nodedc-device-edge-channel")
print("command_transport=disabled")
print("gelios=untouched")
else:
print("public_core_channel=preserved:155.212.211.15:443/tcp:tls13-mtls-h2")
print(f"health=127.0.0.1:{CHANNEL_HEALTH_PORT}:combined-edge-runtime")
print("public_b2_ingress=155.212.211.15:9921/tcp:bidirectional-session")
print("tracker_adapter=arusnavi-b2:profile=arusnavi.b2.internal.v1")
print("tracker_ack=after-core-durable-acceptance-only")
print("runtime_composition=single-non-root-process:core-channel+universal-gateway")
print("tailscale=preserved:absent")
print("services=recreate:nodedc-device-edge-channel")
print(
"command_transport=typed-service-ping-v1:allowlisted-adapter-only"
if phase == "command-transport"
else "command_transport=disabled"
)
if phase == "command-transport":
print("command_catalog=allowlisted-adapter-typed-commands-only")
print(
"gelios=untouched-legacy-only"
if phase == "command-transport"
else "gelios=untouched"
)
print("dns=unchanged")
print("b2_routes=unchanged")
print("state=new")
@@ -1835,8 +2011,10 @@ def apply_artifact(artifact_argument: str):
apply_core_channel(loaded["payload"])
elif loaded["phase"] == "tailscale-retirement":
apply_tailscale_retirement(loaded["payload"])
else:
elif loaded["phase"] == "tracker-ingress":
apply_tracker_ingress(loaded["payload"])
else:
apply_command_transport(loaded["payload"])
archived = archive_artifact(loaded["artifact"], APPLIED_ROOT)
record = {
+417 -27
View File
@@ -246,6 +246,9 @@ DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL = (
DEVICE_PLANE_MANAGER_RELEASE_V2_REL = (
"deployment/device-manager-release-v2.json"
)
DEVICE_PLANE_MANAGER_RELEASE_V3_REL = (
"deployment/device-manager-release-v3.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"
@@ -262,6 +265,9 @@ DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_REL = (
DEVICE_PLANE_CONTROL_CORE_RELEASE_REL = (
"deployment/device-control-core-release-v1.json"
)
DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_REL = (
"deployment/device-control-core-release-v2.json"
)
DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL = (
"docker-compose.device-edge-core-channel.yml"
)
@@ -323,12 +329,33 @@ DEVICE_PLANE_CONTROL_CORE_RELEASE_ENTRIES = (
"services/device-control-core",
DEVICE_PLANE_CONTROL_CORE_RELEASE_REL,
)
DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES = (
".dockerignore",
"package.json",
"package-lock.json",
"packages/device-protocol-contract",
"packages/device-edge-channel-contract",
"services/device-control-core",
DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_REL,
)
DEVICE_PLANE_CONTROL_CORE_RELEASE_FIRST_PREDECESSOR_PATCH_ID = (
"device-edge-core-channel-upgrade-v4-20260812-023"
)
DEVICE_PLANE_CONTROL_CORE_RELEASE_FIRST_PREDECESSOR_ARTIFACT_SHA256 = (
"c10d5b6b7d55ab239f85b6c8130e34ce9f84985e3b46e6e5534733156c7982fc"
)
DEVICE_PLANE_MANAGER_RELEASE_V3_CONTROL_CORE_PREDECESSOR_PATCH_ID = (
"device-control-core-release-v2-20260812-025"
)
DEVICE_PLANE_MANAGER_RELEASE_V3_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256 = (
"c61b1f0de1bae23de0caa7289036865ea419ff5705611416f736ca929d1592db"
)
DEVICE_PLANE_MANAGER_RELEASE_V3_EDGE_CHANNEL_PREDECESSOR_PATCH_ID = (
"device-edge-core-channel-upgrade-v4-20260812-023"
)
DEVICE_PLANE_MANAGER_RELEASE_V3_EDGE_CHANNEL_PREDECESSOR_ARTIFACT_SHA256 = (
"c10d5b6b7d55ab239f85b6c8130e34ce9f84985e3b46e6e5534733156c7982fc"
)
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_PATCH_ID = (
"device-edge-core-channel-bootstrap-20260812-018"
)
@@ -460,6 +487,11 @@ DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES = (
"services/device-manager",
DEVICE_PLANE_MANAGER_RELEASE_V2_REL,
)
DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES = (
DEVICE_PLANE_MANAGER_COMPOSE_REL,
"services/device-manager",
DEVICE_PLANE_MANAGER_RELEASE_V3_REL,
)
DEVICE_PLANE_MANAGER_RECONCILIATION_REL = (
"deployment/device-manager-control-plane-reconciliation-v1.json"
)
@@ -3889,11 +3921,13 @@ def allowed_payload_path(component, rel):
DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_REL,
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
DEVICE_PLANE_MANAGER_RELEASE_V2_REL,
DEVICE_PLANE_MANAGER_RELEASE_V3_REL,
DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL,
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL,
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL,
DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_REL,
DEVICE_PLANE_CONTROL_CORE_RELEASE_REL,
DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_REL,
DEVICE_PLANE_MANAGER_RECONCILIATION_REL,
DEVICE_PLANE_MANAGER_V2_RECONCILIATION_REL,
"packages/device-protocol-contract",
@@ -9459,6 +9493,7 @@ def is_device_plane_manager_control_plane_slice(component, entries):
DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES,
DEVICE_PLANE_MANAGER_RELEASE_V1_SUCCESSOR_ENTRIES,
DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES,
DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES,
)
)
@@ -9471,6 +9506,14 @@ def is_device_plane_manager_release_v2_slice(component, entries):
)
def is_device_plane_manager_release_v3_slice(component, entries):
return (
component == "device-plane"
and entries is not None
and tuple(entries) == DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES
)
def is_device_plane_edge_core_channel_bootstrap_slice(component, entries):
return (
component == "device-plane"
@@ -9516,7 +9559,18 @@ def is_device_plane_control_core_release_slice(component, entries):
return (
component == "device-plane"
and entries is not None
and tuple(entries) == DEVICE_PLANE_CONTROL_CORE_RELEASE_ENTRIES
and tuple(entries) in (
DEVICE_PLANE_CONTROL_CORE_RELEASE_ENTRIES,
DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES,
)
)
def is_device_plane_control_core_release_v2_slice(component, entries):
return (
component == "device-plane"
and entries is not None
and tuple(entries) == DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES
)
@@ -9605,6 +9659,49 @@ def expected_device_plane_manager_release_v2_boundaries():
}
def expected_device_plane_manager_release_v3_boundaries():
return {
"controlCorePredecessor": {
"patchId": (
DEVICE_PLANE_MANAGER_RELEASE_V3_CONTROL_CORE_PREDECESSOR_PATCH_ID
),
"artifactSha256": (
DEVICE_PLANE_MANAGER_RELEASE_V3_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256
),
},
"edgeChannelPredecessor": {
"patchId": (
DEVICE_PLANE_MANAGER_RELEASE_V3_EDGE_CHANNEL_PREDECESSOR_PATCH_ID
),
"artifactSha256": (
DEVICE_PLANE_MANAGER_RELEASE_V3_EDGE_CHANNEL_PREDECESSOR_ARTIFACT_SHA256
),
},
"service": "device-manager",
"publicIngress": "reverse-proxy-only",
"deviceCoreManagementApi": "file-token-authenticated",
"launcherTrust": "file-token-scoped-to-device-core-handoff",
"edgeChannel": "preserve-active-v4-core-initiated-pinned-mtls",
"edgeChannelIdentity": (
"reuse-runner-managed-host-local-private-key-"
"public-certificate-export"
),
"edgeChannelEgress": (
"preserve-dedicated-core-only-bridge-no-host-ingress-"
"public-ipv4-tcp-443-only"
),
"healthGate": "bounded-container-grace+core-contract",
"commandTransport": "typed-service-ping-v1",
"commandCatalog": "allowlisted-adapter-typed-commands-only",
"credentialBoundary": (
"transient-core-memory-then-single-pinned-mtls-command-envelope-"
"to-edge-never-persisted-never-logged-never-returned"
),
"gelios": "untouched-legacy-only",
"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.
@@ -9823,10 +9920,12 @@ def expected_device_plane_edge_core_channel_upgrade_v4_descriptor(
def expected_device_plane_control_core_release_descriptor(
release_id,
predecessor,
*,
schema_version="v1",
):
return {
descriptor = {
"schemaVersion": (
"nodedc.device-plane.device-control-core-release.v1"
f"nodedc.device-plane.device-control-core-release.{schema_version}"
),
"releaseId": release_id,
"action": "upgrade",
@@ -9847,8 +9946,16 @@ def expected_device_plane_control_core_release_descriptor(
],
"publicIngress": "none-on-synology",
"edgeRegistrations": "preserved",
"commandTransport": "disabled",
"gelios": "untouched",
"commandTransport": (
"typed-service-ping-v1"
if schema_version == "v2"
else "disabled"
),
"gelios": (
"untouched-legacy-only"
if schema_version == "v2"
else "untouched"
),
"preservedServices": [
"device-manager",
"device-gateway",
@@ -9861,6 +9968,15 @@ def expected_device_plane_control_core_release_descriptor(
),
"rollback": "restore-preapply-source-and-core-runtime",
}
if schema_version == "v2":
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"
)
return descriptor
def expected_device_plane_manager_failed_control_plane_descriptor():
@@ -10145,6 +10261,22 @@ def validate_device_plane_manager_release_v2_payload(
)
def validate_device_plane_manager_release_v3_payload(
payload_dir,
*,
expected_release_id=None,
):
return validate_device_plane_manager_release_payload_contract(
payload_dir,
descriptor_rel=DEVICE_PLANE_MANAGER_RELEASE_V3_REL,
schema_version="nodedc.device-plane.device-manager-release.v3",
boundaries=expected_device_plane_manager_release_v3_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_payload(
payload_dir,
*,
@@ -10152,9 +10284,18 @@ def validate_device_plane_manager_release_payload(
):
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()]
v3 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V3_REL
present = [
path for path in (v1, v2, v3)
if path.exists() or path.is_symlink()
]
if len(present) != 1:
die("Device Manager release descriptor cardinality mismatch")
if present[0] == v3:
return validate_device_plane_manager_release_v3_payload(
payload_dir,
expected_release_id=expected_release_id,
)
if present[0] == v2:
return validate_device_plane_manager_release_v2_payload(
payload_dir,
@@ -10285,8 +10426,14 @@ def validate_device_plane_control_core_release_payload(
*,
expected_release_id=None,
):
v1 = payload_dir / DEVICE_PLANE_CONTROL_CORE_RELEASE_REL
v2 = payload_dir / DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_REL
present = [path for path in (v1, v2) if path.exists() or path.is_symlink()]
if len(present) != 1:
die("Device Control Core release descriptor cardinality mismatch")
schema_version = "v2" if present[0] == v2 else "v1"
descriptor = read_strict_json(
payload_dir / DEVICE_PLANE_CONTROL_CORE_RELEASE_REL,
present[0],
"Device Control Core release descriptor",
max_bytes=16 * 1024,
)
@@ -10326,7 +10473,7 @@ def validate_device_plane_control_core_release_payload(
if (
not isinstance(release_id, str)
or not re.fullmatch(
r"device-control-core-release-[A-Za-z0-9._-]{1,67}",
r"device-control-core-release(?:-v2)?-[A-Za-z0-9._-]{1,67}",
release_id,
)
or (
@@ -10338,6 +10485,7 @@ def validate_device_plane_control_core_release_payload(
!= expected_device_plane_control_core_release_descriptor(
release_id,
predecessor,
schema_version=schema_version,
)
):
die("Device Control Core release descriptor mismatch")
@@ -10419,6 +10567,7 @@ def installed_device_plane_manager_compose_sha256():
root = DEVICE_PLANE_ROOT
v1 = root / DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL
v2 = root / DEVICE_PLANE_MANAGER_RELEASE_V2_REL
v3 = root / DEVICE_PLANE_MANAGER_RELEASE_V3_REL
# A successful v2 overlay intentionally leaves the immutable v1 release
# descriptor as predecessor evidence. Prefer the highest installed
@@ -10426,6 +10575,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 v3.exists() or v3.is_symlink():
descriptor = read_strict_json(
v3,
"installed Device Manager release v3 descriptor",
max_bytes=16 * 1024,
)
validate_device_plane_manager_release_descriptor(
descriptor,
schema_version="nodedc.device-plane.device-manager-release.v3",
boundaries=expected_device_plane_manager_release_v3_boundaries(),
)
return DEVICE_PLANE_MANAGER_RELEASE_V1_COMPOSE_SHA256
if v2.exists() or v2.is_symlink():
descriptor = read_strict_json(
v2,
@@ -11926,6 +12088,152 @@ def validate_device_plane_manager_reconciliation_evidence(payload_dir):
}
def validate_device_plane_manager_v3_active_baseline(descriptor):
if descriptor.get("schemaVersion") != (
"nodedc.device-plane.device-manager-release.v3"
):
return None
def successful_applied_artifact(reference, label):
patch_id = reference["patchId"]
artifact_sha256 = reference["artifactSha256"]
artifact_name = f"nodedc-device-plane-{patch_id}.tgz"
artifact = APPLIED_DIR / artifact_name
if (
not artifact.is_file()
or artifact.is_symlink()
or sha256_file(artifact) != artifact_sha256
):
die(f"Device Manager v3 {label} applied artifact mismatch")
records = [
row for row in load_state(STATE_FILE)
if row.get("id") == patch_id
and row.get("sha256") == artifact_sha256
]
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(f"Device Manager v3 {label} applied journal mismatch")
backup_id = records[0].get("backup_id")
backup = BACKUPS_DIR / backup_id if isinstance(backup_id, str) else None
if (
backup is None
or safe_name(backup_id) != backup_id
or not backup.is_dir()
or backup.is_symlink()
):
die(f"Device Manager v3 {label} backup is unsafe")
return artifact
core_reference = descriptor["controlCorePredecessor"]
edge_reference = descriptor["edgeChannelPredecessor"]
core_artifact = successful_applied_artifact(
core_reference,
"Device Control Core predecessor",
)
edge_artifact = successful_applied_artifact(
edge_reference,
"Device Edge channel predecessor",
)
with tempfile.TemporaryDirectory(
prefix="device-manager-v3-core-baseline-",
dir=TMP_DIR,
) as directory:
core_manifest, core_entries, core_payload = load_artifact(
core_artifact,
Path(directory),
)
if (
core_manifest.get("id") != core_reference["patchId"]
or core_manifest.get("component") != "device-plane"
or core_manifest.get("type") != "app-overlay"
or tuple(core_entries) != DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES
):
die("Device Manager v3 Device Control Core predecessor type mismatch")
expected_core_descriptor = (
validate_device_plane_control_core_release_payload(
core_payload,
expected_release_id=core_reference["patchId"],
)
)
installed_core_descriptor = read_strict_json(
DEVICE_PLANE_ROOT / DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_REL,
"installed Device Manager v3 Device Control Core predecessor",
max_bytes=16 * 1024,
)
if installed_core_descriptor != expected_core_descriptor:
die("Device Manager v3 Device Control Core predecessor is not current")
expected_core_source = collect_exact_files(
core_payload,
DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES,
"Device Manager v3 Device Control Core predecessor source",
)
actual_core_source = collect_exact_files(
DEVICE_PLANE_ROOT,
DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES,
"installed Device Manager v3 Device Control Core predecessor source",
)
if actual_core_source != expected_core_source:
die("installed Device Manager v3 Device Control Core source drift detected")
with tempfile.TemporaryDirectory(
prefix="device-manager-v3-edge-baseline-",
dir=TMP_DIR,
) as directory:
edge_manifest, edge_entries, edge_payload = load_artifact(
edge_artifact,
Path(directory),
)
if (
edge_manifest.get("id") != edge_reference["patchId"]
or edge_manifest.get("component") != "device-plane"
or edge_manifest.get("type") != "app-overlay"
or tuple(edge_entries)
!= DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_ENTRIES
):
die("Device Manager v3 Device Edge channel predecessor type mismatch")
expected_edge_descriptor = (
validate_device_plane_edge_core_channel_upgrade_v4_payload(
edge_payload,
expected_transition_id=edge_reference["patchId"],
)
)
installed_edge_descriptor = read_strict_json(
DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_REL,
"installed Device Manager v3 Device Edge channel predecessor",
max_bytes=16 * 1024,
)
if installed_edge_descriptor != expected_edge_descriptor:
die("Device Manager v3 Device Edge channel predecessor is not current")
expected_base_compose_sha256 = sha256_file(
edge_payload / "docker-compose.device-plane.yml"
)
installed_base_compose = DEVICE_PLANE_ROOT / "docker-compose.device-plane.yml"
if (
installed_base_compose.is_symlink()
or not installed_base_compose.is_file()
or sha256_file(installed_base_compose) != expected_base_compose_sha256
):
die("installed Device Manager v3 Device Edge topology drift detected")
identity_state = inspect_device_edge_channel_core_identity_state()
if identity_state != "valid-reuse-at-apply":
die("Device Manager v3 requires the valid active Edge identity")
validate_device_manager_control_plane_runtime(
require_edge_channel=True,
core_network_mode="private-egress",
)
return {
"controlCoreArtifact": core_artifact,
"edgeChannelArtifact": edge_artifact,
"identityState": identity_state,
}
def validate_device_plane_manager_activation_predecessor(payload_dir):
descriptor = validate_device_plane_manager_release_payload(
payload_dir
@@ -12033,6 +12341,7 @@ def validate_device_plane_manager_activation_predecessor(payload_dir):
DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES,
DEVICE_PLANE_MANAGER_RELEASE_V1_SUCCESSOR_ENTRIES,
DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES,
DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES,
)
):
die("Device Manager release predecessor type mismatch")
@@ -12042,11 +12351,14 @@ def validate_device_plane_manager_activation_predecessor(payload_dir):
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
predecessor_descriptor_rel = {
DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES:
DEVICE_PLANE_MANAGER_RELEASE_V2_REL,
DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES:
DEVICE_PLANE_MANAGER_RELEASE_V3_REL,
}.get(
tuple(predecessor_entries),
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
)
installed_descriptor = read_strict_json(
root / predecessor_descriptor_rel,
@@ -12062,6 +12374,11 @@ def validate_device_plane_manager_activation_predecessor(payload_dir):
):
healthcheck_compose_service("device-plane", service)
runtime = {"accepted": True}
v3_baseline = validate_device_plane_manager_v3_active_baseline(
descriptor
)
if v3_baseline is not None:
runtime["v3Baseline"] = v3_baseline
mode = "active-manager-forward-upgrade"
return {
"mode": mode,
@@ -16086,6 +16403,9 @@ def component_services(component, entries=None):
if is_device_plane_control_core_release_slice(component, entries):
return ("device-control-core",)
if is_device_plane_manager_release_v3_slice(component, entries):
return ("device-manager",)
if is_device_plane_edge_core_channel_bootstrap_slice(component, entries):
return ("device-control-core",)
@@ -16683,6 +17003,18 @@ def component_builds(component, entries=None):
),
),)
if is_device_plane_manager_release_v3_slice(component, entries):
return ((
DEVICE_PLANE_ROOT / "services/device-manager",
(
"build",
"--no-cache",
"-t",
DEVICE_PLANE_MANAGER_IMAGE,
".",
),
),)
if is_device_plane_manager_control_plane_slice(component, entries):
return (
(
@@ -20427,7 +20759,15 @@ def plan_artifact(artifact):
print("device_control_core_discovery_ingest=enabled:authenticated")
else:
print("device_gateway_public_ingress=disabled")
print("device_gateway_command_transport=disabled")
if (
device_plane_manager_activation_preflight is not None
and device_plane_manager_activation_preflight["descriptor"].get(
"commandTransport"
) == "typed-service-ping-v1"
):
print("device_gateway_command_transport=typed-service-ping-v1")
else:
print("device_gateway_command_transport=disabled")
print("gelios=untouched")
if device_plane_foundation_recovery_preflight is not None:
print(
@@ -20589,15 +20929,36 @@ def plan_artifact(artifact):
)
print(
"device_plane_runtime_mutation="
"build+recreate:device-control-core,device-manager"
+ (
"build+recreate:device-manager"
if device_plane_manager_activation_preflight["descriptor"].get(
"schemaVersion"
) == "nodedc.device-plane.device-manager-release.v3"
else "build+recreate:device-control-core,device-manager"
)
)
print(
"device_plane_runtime_services="
"preserved:device-gateway,device-postgres,"
"device-backhaul-target"
+ (
"preserved:device-control-core,device-gateway,"
"device-postgres,device-backhaul-target"
if device_plane_manager_activation_preflight["descriptor"].get(
"schemaVersion"
) == "nodedc.device-plane.device-manager-release.v3"
else "preserved:device-gateway,device-postgres,"
"device-backhaul-target"
)
)
print("device_manager_health_gate=bounded-grace+contract")
print("device_manager_public_route=unchanged:absent")
if device_plane_manager_activation_preflight["descriptor"].get(
"commandTransport"
) == "typed-service-ping-v1":
print("device_manager_public_route=unchanged:active")
print("device_edge_channel_commands=typed-service-ping-v1")
print("device_command_credential=transient-core-memory-only")
print("gelios=untouched:legacy-only")
else:
print("device_manager_public_route=unchanged:absent")
print("device_gateway_tcp_9921=preserved:loopback-only")
print(
"device_plane_rollback="
@@ -20637,11 +20998,14 @@ def plan_artifact(artifact):
print("device_edge_channel=preserved:core-initiated:pinned-mtls:registered-edges-only")
print("device_edge_channel_networks=preserved:device-plane-private,device-plane-egress")
print("device_edge_channel_registrations=preserved")
print("device_edge_channel_commands=disabled")
print(
"device_edge_channel_commands="
f"{descriptor['commandTransport']}"
)
print("device_manager=preserved:active")
print("device_manager_public_route=unchanged:active")
print("device_gateway_tcp_9921=preserved:loopback-only")
print("gelios=untouched")
print(f"gelios={descriptor['gelios']}")
print("device_plane_rollback=source+preapply-core-runtime")
if device_plane_edge_core_channel_preflight is not None:
print(
@@ -22571,7 +22935,12 @@ def component_healthchecks(component, entries=None, services=None):
},
},)
if is_device_plane_manager_control_plane_slice(component, entries):
return ({
command_transport = (
"typed-service-ping-v1"
if is_device_plane_manager_release_v3_slice(component, entries)
else "disabled"
)
checks = ({
"url": "http://127.0.0.1:18120/healthz",
"expected_json": {
"ok": True,
@@ -22579,9 +22948,21 @@ def component_healthchecks(component, entries=None, services=None):
"database": "ready",
"discoveryIngest": "enabled",
"managementApi": "enabled",
"commandTransport": "disabled",
"commandTransport": command_transport,
},
},)
if is_device_plane_manager_release_v3_slice(component, entries):
checks += ({
"url": "http://127.0.0.1:18080/healthz",
"headers": {"Host": "device.nodedc.ru"},
"expected_json": {
"ok": True,
"service": "nodedc-device-manager",
"authRequired": True,
"deviceCoreConfigured": True,
},
},)
return checks
if (
is_device_plane_control_core_release_slice(component, entries)
or is_device_plane_edge_core_channel_bootstrap_slice(
@@ -22597,7 +22978,14 @@ def component_healthchecks(component, entries=None, services=None):
"database": "ready",
"discoveryIngest": "enabled",
"managementApi": "enabled",
"commandTransport": "disabled",
"commandTransport": (
"typed-service-ping-v1"
if is_device_plane_control_core_release_v2_slice(
component,
entries,
)
else "disabled"
),
},
},)
if is_device_plane_b2_discovery_rollback_recovery_slice(
@@ -23319,10 +23707,12 @@ def run_healthchecks(component, entries=None, services=None):
assert_loopback_tcp_port_open(9921)
return
if is_device_plane_manager_control_plane_slice(component, entries):
if tuple(services or ()) != (
"device-control-core",
"device-manager",
):
expected_services = (
("device-manager",)
if is_device_plane_manager_release_v3_slice(component, entries)
else ("device-control-core", "device-manager")
)
if tuple(services or ()) != expected_services:
die("Device Manager control-plane service set mismatch")
for service in services:
healthcheck_compose_service_with_grace(
@@ -81,6 +81,22 @@ class DeviceEdgeVpsArtifactTest(unittest.TestCase):
with self.subTest(phase=phase), self.assertRaises(RUNNER.DeployError):
RUNNER.preflight({"phase": phase})
def test_accepted_shared_source_phases_cannot_be_rebuilt(self):
for phase in ("core-channel", "tracker-ingress"):
with self.subTest(phase=phase), tempfile.TemporaryDirectory(
prefix=f"nodedc-vps-frozen-{phase}-"
) as directory:
result = self.build(
Path(directory),
phase,
f"device-edge-vps-{phase}-frozen-001",
)
self.assertNotEqual(result.returncode, 0)
self.assertIn(
f"accepted_vps_phase_rebuild_frozen:{phase}:ADR-0001",
result.stderr,
)
def require_runtime_cache(self):
for name, digest in (
(RUNNER.NODE_ARCHIVE, RUNNER.NODE_ARCHIVE_SHA256),
@@ -93,13 +109,7 @@ class DeviceEdgeVpsArtifactTest(unittest.TestCase):
def test_builders_are_deterministic_narrow_and_secret_free(self):
self.require_runtime_cache()
for phase in (
"foundation",
"runtime-reconciliation",
"backhaul",
"relay",
"core-channel",
"tailscale-retirement",
"tracker-ingress",
"command-transport",
):
with self.subTest(phase=phase), tempfile.TemporaryDirectory(
prefix=f"nodedc-vps-{phase}-"
@@ -173,13 +183,7 @@ class DeviceEdgeVpsArtifactTest(unittest.TestCase):
RUNNER.INBOX_ROOT = inbox
try:
for phase in (
"foundation",
"runtime-reconciliation",
"backhaul",
"relay",
"core-channel",
"tailscale-retirement",
"tracker-ingress",
"command-transport",
):
result = self.build(
inbox,
@@ -233,6 +237,7 @@ class DeviceEdgeVpsArtifactTest(unittest.TestCase):
self.assertIn("b2_routes=unchanged", rendered)
self.assertIn("command_transport=disabled", rendered)
@unittest.skip("accepted core-channel builder generation is frozen")
def test_core_channel_plan_is_exact_and_keeps_tracker_ingress_closed(self):
with tempfile.TemporaryDirectory(prefix="nodedc-vps-channel-plan-") as directory:
inbox = Path(directory) / "inbox"
@@ -363,6 +368,7 @@ class DeviceEdgeVpsArtifactTest(unittest.TestCase):
self.assertIn("command_transport=disabled", rendered)
self.assertIn("gelios=untouched", rendered)
@unittest.skip("accepted tracker-ingress builder generation is frozen")
def test_tracker_ingress_plan_is_single_process_bounded_and_command_free(self):
with tempfile.TemporaryDirectory(prefix="nodedc-vps-ingress-plan-") as directory:
inbox = Path(directory) / "inbox"
@@ -402,6 +408,40 @@ class DeviceEdgeVpsArtifactTest(unittest.TestCase):
self.assertIn("command_transport=disabled", rendered)
self.assertIn("gelios=untouched", rendered)
def test_command_transport_plan_is_typed_single_process_and_bounded(self):
with tempfile.TemporaryDirectory(prefix="nodedc-vps-command-plan-") as directory:
inbox = Path(directory) / "inbox"
inbox.mkdir()
result = self.build(
inbox,
"command-transport",
"device-edge-vps-command-transport-plan-001",
)
self.assertEqual(result.returncode, 0, result.stderr)
artifact = Path(json.loads(result.stdout)["artifact"])
old_inbox = RUNNER.INBOX_ROOT
RUNNER.INBOX_ROOT = inbox
try:
with patch.object(RUNNER, "assert_root"), patch.object(
RUNNER,
"preflight",
return_value={"predecessor": "accepted-tracker-ingress-012"},
), patch("builtins.print") as output:
RUNNER.plan_artifact(str(artifact))
finally:
RUNNER.INBOX_ROOT = old_inbox
rendered = "\n".join(
" ".join(str(arg) for arg in call.args)
for call in output.call_args_list
)
self.assertIn("phase=command-transport", rendered)
self.assertIn("predecessor=accepted-tracker-ingress-012", rendered)
self.assertIn("command_transport=typed-service-ping-v1", rendered)
self.assertIn("command_catalog=allowlisted-adapter-typed-commands-only", rendered)
self.assertIn("runtime_composition=single-non-root-process", rendered)
self.assertIn("public_b2_ingress=155.212.211.15:9921/tcp:bidirectional-session", rendered)
self.assertIn("gelios=untouched-legacy-only", rendered)
def test_publish_payload_preserves_unselected_executable_modes(self):
with tempfile.TemporaryDirectory(prefix="nodedc-vps-publish-scope-") as directory:
root = Path(directory)
@@ -104,6 +104,25 @@ def device_manager_release_v1_descriptor(
}
def device_manager_release_v3_descriptor(
release_id="device-manager-release-v3-unit-001",
*,
predecessor_patch="device-manager-release-20260811-010",
predecessor_sha="d" * 64,
):
return {
"schemaVersion": "nodedc.device-plane.device-manager-release.v3",
"releaseId": release_id,
"action": "upgrade",
"predecessor": {
"kind": "release",
"patchId": predecessor_patch,
"artifactSha256": predecessor_sha,
},
**RUNNER.expected_device_plane_manager_release_v3_boundaries(),
}
class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
def build(self, script, patch_id, artifact_dir):
environment = os.environ.copy()
@@ -253,6 +272,57 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
self.assertEqual(checks[0]["expected_json"]["managementApi"], "enabled")
self.assertEqual(checks[0]["expected_json"]["discoveryIngest"], "enabled")
def test_device_manager_release_v3_is_exact_typed_and_secret_free(self):
patch_id = "device-manager-release-v3-unit-001"
manifest, entries, names, result = self.assert_deterministic_artifact(
"build-device-manager-control-plane-artifact.mjs",
patch_id,
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES,
)
self.assertEqual(manifest["component"], "device-plane")
self.assertEqual(
RUNNER.component_services("device-plane", entries),
("device-manager",),
)
self.assertEqual(result["services"], ["device-manager"])
self.assertIn(
"payload/deployment/device-manager-release-v3.json",
names,
)
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") for name in names))
descriptor = device_manager_release_v3_descriptor(patch_id)
self.assertIs(
RUNNER.validate_device_plane_manager_release_descriptor(
descriptor,
schema_version=(
"nodedc.device-plane.device-manager-release.v3"
),
boundaries=(
RUNNER.expected_device_plane_manager_release_v3_boundaries()
),
expected_release_id=patch_id,
),
descriptor,
)
self.assertEqual(descriptor["commandTransport"], "typed-service-ping-v1")
self.assertEqual(
descriptor["commandCatalog"],
"allowlisted-adapter-typed-commands-only",
)
self.assertEqual(descriptor["gelios"], "untouched-legacy-only")
self.assertTrue(
RUNNER.is_device_plane_manager_release_v3_slice(
"device-plane",
entries,
)
)
def test_edge_core_channel_bootstrap_is_core_only_and_secret_free(self):
manifest, entries, names, result = self.assert_deterministic_artifact(
"build-device-edge-core-channel-bootstrap-artifact.mjs",
@@ -529,6 +599,42 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
core_network_mode="private-egress",
)
def test_control_core_release_v2_is_typed_core_only(self):
patch_id = "device-control-core-release-v2-unit-001"
manifest, entries, names, result = self.assert_deterministic_artifact(
"build-device-control-core-release-artifact.mjs",
patch_id,
RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES,
)
self.assertEqual(manifest["component"], "device-plane")
self.assertEqual(result["services"], ["device-control-core"])
self.assertIn(
"payload/deployment/device-control-core-release-v2.json",
names,
)
self.assertFalse(any("docker-compose" in name for name in names))
self.assertEqual(
RUNNER.component_services("device-plane", entries),
("device-control-core",),
)
descriptor = RUNNER.expected_device_plane_control_core_release_descriptor(
patch_id,
{
"kind": "release",
"patchId": "device-control-core-release-20260812-024",
"artifactSha256": (
"a289e909283109642e6bba3d9822a31f63423cfe0bbcd52705979681bd2bc793"
),
},
schema_version="v2",
)
self.assertEqual(descriptor["commandTransport"], "typed-service-ping-v1")
self.assertEqual(
descriptor["commandCatalog"],
"allowlisted-adapter-typed-commands-only",
)
self.assertEqual(descriptor["gelios"], "untouched-legacy-only")
def test_control_core_release_builder_supports_release_predecessor(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-control-core-successor-",