diff --git a/src/lib/onboard.ts b/src/lib/onboard.ts index 1b70da187fc..f8d97673ddb 100644 --- a/src/lib/onboard.ts +++ b/src/lib/onboard.ts @@ -122,9 +122,8 @@ const { }: typeof import("./onboard/created-sandbox-finalization") = require("./onboard/created-sandbox-finalization"); const providerKeyBridge: typeof import("./onboard/provider-key-bridge") = require("./onboard/provider-key-bridge"); const compatibleEndpointGatewayRoute: typeof import("./onboard/inference-providers/compatible-endpoint-gateway-route") = require("./onboard/inference-providers/compatible-endpoint-gateway-route"); -const { - isLinuxDockerDriverGatewayEnabled, -}: typeof import("./onboard/docker-driver-platform") = require("./onboard/docker-driver-platform"); +const dockerDriverPlatform: typeof import("./onboard/docker-driver-platform") = require("./onboard/docker-driver-platform"); +const { isLinuxDockerDriverGatewayEnabled } = dockerDriverPlatform; const { reconcileGatewayGpuReuseForGpuIntent, }: typeof import("./onboard/gateway-gpu-passthrough") = require("./onboard/gateway-gpu-passthrough"); @@ -2207,7 +2206,8 @@ async function recoverGatewayRuntime() { const { getSandboxRuntimeRegistryFields, hasSandboxGpuDrift, updateReusedSandboxMetadata } = sandboxRegistryMetadata.createSandboxRegistryMetadataHelpers({ - isLinuxDockerDriverGatewayEnabled, + getOpenShellComputeDriverName: () => + dockerDriverPlatform.resolveCurrentOpenShellComputePlan().driverName, getInstalledOpenshellVersion, runCaptureOpenshell, }); diff --git a/src/lib/onboard/compute/plan.test.ts b/src/lib/onboard/compute/plan.test.ts new file mode 100644 index 00000000000..2a669346800 --- /dev/null +++ b/src/lib/onboard/compute/plan.test.ts @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { describe, expect, it } from "vitest"; +import { isLinuxDockerDriverGatewayEnabled } from "../docker-driver-platform"; +import { resolveCurrentOpenShellComputePlan, usesManagedDockerGateway } from "./plan"; + +describe("current OpenShell compute plan", () => { + it.each([ + { + label: "Linux x64", + platform: "linux" as const, + arch: "x64" as const, + driverName: "docker", + gatewayLauncher: "nemoclaw", + }, + { + label: "Linux arm64", + platform: "linux" as const, + arch: "arm64" as const, + driverName: "docker", + gatewayLauncher: "nemoclaw", + }, + { + label: "Apple Silicon macOS", + platform: "darwin" as const, + arch: "arm64" as const, + driverName: "docker", + gatewayLauncher: "nemoclaw", + }, + { + label: "Intel macOS", + platform: "darwin" as const, + arch: "x64" as const, + driverName: "kubernetes", + gatewayLauncher: "openshell", + }, + { + label: "Windows x64", + platform: "win32" as const, + arch: "x64" as const, + driverName: "kubernetes", + gatewayLauncher: "openshell", + }, + ])("preserves the existing driver and gateway-launch behavior on $label (#7744)", ({ + platform, + arch, + driverName, + gatewayLauncher, + }) => { + expect(resolveCurrentOpenShellComputePlan(platform, arch)).toEqual({ + driverName, + gatewayLauncher, + }); + expect(isLinuxDockerDriverGatewayEnabled(platform, arch)).toBe(driverName === "docker"); + }); + + it.each([ + { driverName: "docker", gatewayLauncher: "nemoclaw", expected: true }, + { driverName: "docker", gatewayLauncher: "openshell", expected: false }, + { driverName: "podman", gatewayLauncher: "nemoclaw", expected: false }, + { driverName: "mxc", gatewayLauncher: "nemoclaw", expected: false }, + ] as const)("reports Docker lifecycle ownership as $expected for $driverName with the $gatewayLauncher launcher (#7744)", ({ + driverName, + gatewayLauncher, + expected, + }) => { + expect(usesManagedDockerGateway({ driverName, gatewayLauncher })).toBe(expected); + }); +}); diff --git a/src/lib/onboard/compute/plan.ts b/src/lib/onboard/compute/plan.ts new file mode 100644 index 00000000000..1e6079d086b --- /dev/null +++ b/src/lib/onboard/compute/plan.ts @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +export type OpenShellGatewayLauncher = "nemoclaw" | "openshell"; + +/** + * Keeps OpenShell driver identity separate from the component that launches + * its gateway. A future driver does not inherit Docker lifecycle behavior + * because NemoClaw launches its gateway. + */ +export interface OpenShellComputePlan { + readonly driverName: string; + readonly gatewayLauncher: OpenShellGatewayLauncher; +} + +/** + * Describes the behavior NemoClaw uses today. Driver selection will move behind + * this seam without changing the existing Docker and Kubernetes paths first. + */ +export function resolveCurrentOpenShellComputePlan( + platform: NodeJS.Platform = process.platform, + arch: NodeJS.Architecture = process.arch, +): OpenShellComputePlan { + const managedDockerGateway = platform === "linux" || (platform === "darwin" && arch === "arm64"); + + return { + driverName: managedDockerGateway ? "docker" : "kubernetes", + gatewayLauncher: managedDockerGateway ? "nemoclaw" : "openshell", + }; +} + +export function usesManagedDockerGateway( + plan: Pick, +): boolean { + return plan.driverName === "docker" && plan.gatewayLauncher === "nemoclaw"; +} diff --git a/src/lib/onboard/docker-driver-platform.ts b/src/lib/onboard/docker-driver-platform.ts index 2dbc453cc4b..bb0b0756408 100644 --- a/src/lib/onboard/docker-driver-platform.ts +++ b/src/lib/onboard/docker-driver-platform.ts @@ -1,9 +1,13 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { resolveCurrentOpenShellComputePlan, usesManagedDockerGateway } from "./compute/plan"; + +export { resolveCurrentOpenShellComputePlan } from "./compute/plan"; + export function isLinuxDockerDriverGatewayEnabled( platform: NodeJS.Platform = process.platform, arch: NodeJS.Architecture = process.arch, ): boolean { - return platform === "linux" || (platform === "darwin" && arch === "arm64"); + return usesManagedDockerGateway(resolveCurrentOpenShellComputePlan(platform, arch)); } diff --git a/src/lib/onboard/sandbox-registry-metadata.test.ts b/src/lib/onboard/sandbox-registry-metadata.test.ts index 055deb69d84..d6798a13fd1 100644 --- a/src/lib/onboard/sandbox-registry-metadata.test.ts +++ b/src/lib/onboard/sandbox-registry-metadata.test.ts @@ -8,34 +8,16 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import type { AgentDefinition } from "../agent/defs"; import type { SandboxGpuConfig } from "./sandbox-gpu-mode"; -const ORIGINAL_PLATFORM = Object.getOwnPropertyDescriptor(process, "platform"); - -/** - * Overrides process.platform for runtime-driver metadata tests. - */ -function setPlatform(platform: NodeJS.Platform): void { - Object.defineProperty(process, "platform", { value: platform, configurable: true }); -} - /** - * Restores the original process.platform descriptor after each platform-specific assertion. + * Loads the compiled metadata helpers with an explicit resolved compute driver. */ -function restorePlatform(): void { - if (ORIGINAL_PLATFORM) { - Object.defineProperty(process, "platform", ORIGINAL_PLATFORM); - } -} - -/** - * Loads the compiled metadata helpers after each test has configured process state. - */ -async function makeHelpers(opts: { dockerDriverEnabled: boolean }) { +async function makeHelpers(driverName: string) { // Import the compiled module: sandbox-registry-metadata.ts pulls in state/registry, // which transitively requires the JS-only `./platform` helper that vitest cannot // resolve from TS source. Same pattern as `vm-dns-monkeypatch.test.ts`. const metadata = await import("./sandbox-registry-metadata"); return metadata.createSandboxRegistryMetadataHelpers({ - isLinuxDockerDriverGatewayEnabled: () => opts.dockerDriverEnabled, + getOpenShellComputeDriverName: () => driverName, getInstalledOpenshellVersion: () => "0.0.42", runCaptureOpenshell: () => null, }); @@ -106,7 +88,7 @@ describe("sandbox registry metadata", () => { }); const helpers = metadata.createSandboxRegistryMetadataHelpers({ - isLinuxDockerDriverGatewayEnabled: () => true, + getOpenShellComputeDriverName: () => "docker", getInstalledOpenshellVersion: () => "0.0.44", runCaptureOpenshell: () => "openshell 0.0.44", }); @@ -155,7 +137,7 @@ describe("sandbox registry metadata", () => { const dashboardPorts = await import("./dashboard-port"); const gatewayRegistry = await import("../state/gateway-registry"); const helpers = metadata.createSandboxRegistryMetadataHelpers({ - isLinuxDockerDriverGatewayEnabled: () => true, + getOpenShellComputeDriverName: () => "docker", getInstalledOpenshellVersion: () => "0.0.44", runCaptureOpenshell: () => "openshell 0.0.44", }); @@ -190,32 +172,44 @@ describe("sandbox registry metadata", () => { }); describe("getSandboxRuntimeRegistryFields openshellDriver", () => { - afterEach(restorePlatform); - - it("records Docker for macOS sandboxes on the Docker-driver gateway path", async () => { - setPlatform("darwin"); - const helpers = await makeHelpers({ dockerDriverEnabled: true }); + it("records the resolved Docker compute driver (#7744)", async () => { + const helpers = await makeHelpers("docker"); const fields = helpers.getSandboxRuntimeRegistryFields(GPU_OFF); expect(fields.openshellDriver).toBe("docker"); }); - it("records Docker for Linux sandboxes on the Docker-driver gateway path", async () => { - setPlatform("linux"); - const helpers = await makeHelpers({ dockerDriverEnabled: true }); + it("records the resolved Kubernetes compute driver (#7744)", async () => { + const helpers = await makeHelpers("kubernetes"); const fields = helpers.getSandboxRuntimeRegistryFields(GPU_OFF); - expect(fields.openshellDriver).toBe("docker"); + expect(fields.openshellDriver).toBe("kubernetes"); }); - it("records Kubernetes for legacy Linux sandboxes when the Docker-driver gateway is disabled", async () => { - setPlatform("linux"); - const helpers = await makeHelpers({ dockerDriverEnabled: false }); + it.each([ + "podman", + "mxc", + ])("passes the resolved %s driver through to registry metadata (#7744)", async (driverName) => { + const helpers = await makeHelpers(driverName); const fields = helpers.getSandboxRuntimeRegistryFields(GPU_OFF); - expect(fields.openshellDriver).toBe("kubernetes"); + expect(fields.openshellDriver).toBe(driverName); + }); + + it("resolves driver identity when metadata is recorded rather than at module load (#7744)", async () => { + const metadata = await import("./sandbox-registry-metadata"); + let driverName = "docker"; + const helpers = metadata.createSandboxRegistryMetadataHelpers({ + getOpenShellComputeDriverName: () => driverName, + getInstalledOpenshellVersion: () => "0.0.42", + runCaptureOpenshell: () => null, + }); + + driverName = "kubernetes"; + + expect(helpers.getSandboxRuntimeRegistryFields(GPU_OFF).openshellDriver).toBe("kubernetes"); }); }); diff --git a/src/lib/onboard/sandbox-registry-metadata.ts b/src/lib/onboard/sandbox-registry-metadata.ts index 9abb49a5ec0..57b82000d83 100644 --- a/src/lib/onboard/sandbox-registry-metadata.ts +++ b/src/lib/onboard/sandbox-registry-metadata.ts @@ -8,7 +8,7 @@ import { getSandboxAgentRegistryFields } from "./sandbox-agent"; import type { SandboxGpuConfig } from "./sandbox-gpu-mode"; export interface SandboxRegistryMetadataDeps { - isLinuxDockerDriverGatewayEnabled(): boolean; + getOpenShellComputeDriverName(): string; getInstalledOpenshellVersion(versionOutput?: string | null): string | null; runCaptureOpenshell(args: string[], opts?: Record): string | null; } @@ -55,10 +55,6 @@ export function createSandboxRegistryMetadataHelpers( | "openshellDriver" | "openshellVersion" > { - // OpenShell's Docker-driver gateway always starts with OPENSHELL_DRIVERS=docker, - // including on macOS arm64 (#3454). Recording "vm" for darwin here makes later - // setup misclassify the sandbox and run VM-only DNS monkeypatch / warning paths - // (#3728). return { gpuEnabled: config.sandboxGpuEnabled, hostGpuDetected: config.hostGpuDetected, @@ -68,7 +64,9 @@ export function createSandboxRegistryMetadataHelpers( // Only persist a proof when this run produced one; omit on reuse/update // paths so a prior proof result is preserved rather than nulled out. ...(config.sandboxGpuProof ? { sandboxGpuProof: config.sandboxGpuProof } : {}), - openshellDriver: deps.isLinuxDockerDriverGatewayEnabled() ? "docker" : "kubernetes", + // Driver identity comes from the resolved compute plan, not the host + // gateway launcher; those layers may differ (#7744). + openshellDriver: deps.getOpenShellComputeDriverName(), openshellVersion: deps.getInstalledOpenshellVersion( deps.runCaptureOpenshell(["--version"], { ignoreError: true }), ),