From e233ba97e1a9e70e3c7f6a0ad752b8364c85571f Mon Sep 17 00:00:00 2001 From: San Dang Date: Mon, 17 Aug 2026 17:29:08 +0700 Subject: [PATCH 01/10] fix(onboard): preserve N1x preview intent on rebuild Signed-off-by: San Dang --- docs/inference/set-up-vllm.mdx | 5 ++ .../actions/sandbox/rebuild-gpu-opt-out.ts | 2 + .../rebuild-preflight-target-phase.test.ts | 65 +++++++++++++++++++ .../sandbox/rebuild-preflight-target-phase.ts | 20 ++++++ .../sandbox/rebuild-target-runtime.test.ts | 42 +++++++++++- src/lib/onboard.ts | 1 + .../onboard/authoritative-rebuild-target.ts | 6 +- .../onboard/fatal-runtime-preflight.test.ts | 8 +++ src/lib/onboard/fatal-runtime-preflight.ts | 11 +++- src/lib/onboard/types.ts | 2 + 10 files changed, 158 insertions(+), 4 deletions(-) diff --git a/docs/inference/set-up-vllm.mdx b/docs/inference/set-up-vllm.mdx index 71cac0768a6..ca5cb1e2bf5 100644 --- a/docs/inference/set-up-vllm.mdx +++ b/docs/inference/set-up-vllm.mdx @@ -371,6 +371,11 @@ Accepting the preview prompt sets explicit managed-vLLM intent for the narrow pe Declining the prompt or setting only `NEMOCLAW_NO_EXPRESS=1` stops the installer before onboarding; neither path opens interactive provider selection. Set `NEMOCLAW_PROVIDER=install-vllm` when you need to bypass the prompt with the same explicit preview intent. +After successful preview onboarding, `rebuild` reuses the recorded `vllm-local` provider and managed-vLLM receipt as explicit preview intent. +You do not need to set `NEMOCLAW_PROVIDER` again for that rebuild. +If the provider or receipt is missing, malformed, or does not describe managed vLLM, readiness preflight stops before NemoClaw deletes the existing sandbox. +This reuse waives only the pending physical-validation finding and does not change the Deferred status. + Physical CUDA and CDI checks passed on one N1x host. The full NemoClaw Express E2E test is still pending, so the N1x platform status remains Deferred. diff --git a/src/lib/actions/sandbox/rebuild-gpu-opt-out.ts b/src/lib/actions/sandbox/rebuild-gpu-opt-out.ts index d770d0a6ab3..b75d7407373 100644 --- a/src/lib/actions/sandbox/rebuild-gpu-opt-out.ts +++ b/src/lib/actions/sandbox/rebuild-gpu-opt-out.ts @@ -127,6 +127,8 @@ export type RebuildRecreateOnboardOpts = { rebuildRegistryInferenceRoute?: RebuildRouteHandoff; rebuildProviderReconfigure?: RebuildProviderReconfigureHandoff; providerRecoveryReceipt?: ProviderRecoveryReceipt; + /** Recorded managed-vLLM intent admitted only by the N1x readiness exception. */ + allowDeferredN1xManagedVllm?: true; /** Target-scoped authority admitted by the authoritative rebuild preflight. */ rebuildGatewayAuthority?: CheckpointGatewayAuthority; preparedImageRebuild?: PreparedImageRebuildHandoff; diff --git a/src/lib/actions/sandbox/rebuild-preflight-target-phase.test.ts b/src/lib/actions/sandbox/rebuild-preflight-target-phase.test.ts index 86b7a30144c..839d2979f83 100644 --- a/src/lib/actions/sandbox/rebuild-preflight-target-phase.test.ts +++ b/src/lib/actions/sandbox/rebuild-preflight-target-phase.test.ts @@ -3,6 +3,10 @@ import { afterEach, describe, expect, it, vi } from "vitest"; +import { + serializedHostLocalInferenceReceipt, + serializedLlamaCppHostLocalInferenceReceipt, +} from "../../../../test/helpers/host-local-inference-receipt"; import type { ProviderRecoveryReceipt, RegistryInferenceRoute, @@ -12,6 +16,7 @@ import { pinRebuildTargetGatewayForReadiness, runRebuildGatewayRecoveryAfterReadiness, stageRebuildBaseImageResolutionHandoff, + stageRecordedManagedVllmIntent, stageRegistryProviderRecoveryReceipt, } from "./rebuild-preflight-target-phase"; @@ -121,6 +126,66 @@ describe("stageRegistryProviderRecoveryReceipt", () => { }); }); +describe("stageRecordedManagedVllmIntent", () => { + it("carries a canonical recorded managed-vLLM selection into both rebuild readiness gates (#9292)", () => { + const recreateOptions: { allowDeferredN1xManagedVllm?: true } = {}; + + stageRecordedManagedVllmIntent( + recreateOptions, + { + provider: "vllm-local", + hostLocalInferenceReceipt: serializedHostLocalInferenceReceipt(), + }, + "vllm-local", + ); + + expect(recreateOptions.allowDeferredN1xManagedVllm).toBe(true); + }); + + it.each([ + { + caseName: "missing receipt", + sandboxEntry: { provider: "vllm-local" }, + provider: "vllm-local", + }, + { + caseName: "malformed receipt", + sandboxEntry: { provider: "vllm-local", hostLocalInferenceReceipt: "not-json" }, + provider: "vllm-local", + }, + { + caseName: "different host-local service", + sandboxEntry: { + provider: "vllm-local", + hostLocalInferenceReceipt: serializedLlamaCppHostLocalInferenceReceipt(), + }, + provider: "vllm-local", + }, + { + caseName: "different rebuild route", + sandboxEntry: { + provider: "vllm-local", + hostLocalInferenceReceipt: serializedHostLocalInferenceReceipt(), + }, + provider: "compatible-endpoint", + }, + { + caseName: "different recorded route", + sandboxEntry: { + provider: "compatible-endpoint", + hostLocalInferenceReceipt: serializedHostLocalInferenceReceipt(), + }, + provider: "vllm-local", + }, + ])("does not infer managed-vLLM intent from a $caseName (#9292)", ({ sandboxEntry, provider }) => { + const recreateOptions: { allowDeferredN1xManagedVllm?: true } = {}; + + stageRecordedManagedVllmIntent(recreateOptions, sandboxEntry, provider); + + expect(recreateOptions).not.toHaveProperty("allowDeferredN1xManagedVllm"); + }); +}); + describe("stageRebuildBaseImageResolutionHandoff", () => { it("binds outer resolver provenance to its immutable local handoff (#7144)", () => { const imageId = `sha256:${"a".repeat(64)}`; diff --git a/src/lib/actions/sandbox/rebuild-preflight-target-phase.ts b/src/lib/actions/sandbox/rebuild-preflight-target-phase.ts index 9ccdf93d231..c6ad45401e6 100644 --- a/src/lib/actions/sandbox/rebuild-preflight-target-phase.ts +++ b/src/lib/actions/sandbox/rebuild-preflight-target-phase.ts @@ -6,6 +6,7 @@ import { CLI_NAME } from "../../cli/branding"; import type { SandboxMessagingPlan } from "../../messaging"; import { isSandboxBaseImageRefreshRequested } from "../../onboard/base-image-resolution-flow"; import type { DcodeAutoApprovalMode } from "../../onboard/dcode-auto-approval"; +import { parseHostLocalInferenceReceipt } from "../../onboard/runtime-provider/host-local-inference"; import { createRebuildProviderReconfigureHandoff, mintProviderRecoveryReceipt, @@ -83,6 +84,24 @@ export function stageRegistryProviderRecoveryReceipt( ); } +/** Reuse only the canonical managed-vLLM selection recorded by onboarding. */ +export function stageRecordedManagedVllmIntent( + recreateOptions: Pick, + sandboxEntry: Pick, + provider: string, +): void { + if (provider !== "vllm-local" || sandboxEntry.provider !== "vllm-local") return; + const serialized = sandboxEntry.hostLocalInferenceReceipt; + if (!serialized) return; + try { + if (parseHostLocalInferenceReceipt(serialized).service === "vllm") { + recreateOptions.allowDeferredN1xManagedVllm = true; + } + } catch { + // Malformed durable state must not grant the narrow N1x readiness exception. + } +} + export interface RebuildPreparedTarget { targetConfig: RebuildTargetConfig; recreateOptions: RebuildRecreateOnboardOpts; @@ -233,6 +252,7 @@ export async function prepareRebuildTargetPreflights(args: { recreateOptions.observabilityEnabled = requestedObservabilityEnabled ?? recreateOptions.observabilityEnabled; recreateOptions.observabilityRequestedExplicitly = requestedObservabilityEnabled !== undefined; + stageRecordedManagedVllmIntent(recreateOptions, sandboxEntry, resumeConfig.provider); if ( !stageRebuildHermesDashboardConfig( rebuildAgent, diff --git a/src/lib/actions/sandbox/rebuild-target-runtime.test.ts b/src/lib/actions/sandbox/rebuild-target-runtime.test.ts index 928f0abcec2..e03cc004781 100644 --- a/src/lib/actions/sandbox/rebuild-target-runtime.test.ts +++ b/src/lib/actions/sandbox/rebuild-target-runtime.test.ts @@ -9,6 +9,7 @@ const mocks = vi.hoisted(() => ({ ensureValidatedWebSearchCredential: vi.fn(), isDockerDesktopWslRuntime: vi.fn(), isLinuxDockerDriverGatewayEnabled: vi.fn(), + preflightAuthoritativeRebuildTarget: vi.fn(), preflightRebuildCredentials: vi.fn(), readGatewayProviderMetadata: vi.fn(), runOpenshell: vi.fn(), @@ -30,6 +31,7 @@ vi.mock("../../onboard/gateway-provider-metadata", async (importOriginal) => { vi.mock("./rebuild-onboard-dependencies", () => ({ rebuildOnboardDependencies: { ensureValidatedWebSearchCredential: mocks.ensureValidatedWebSearchCredential, + preflightAuthoritativeRebuildTarget: mocks.preflightAuthoritativeRebuildTarget, }, })); @@ -52,8 +54,12 @@ vi.mock("./rebuild-credential-preflight", () => ({ import * as rebuildImagePreflight from "./rebuild-custom-image-preflight"; import type { RebuildSandboxEntry } from "./rebuild-flow-helpers"; import type { RebuildRecreateOnboardOpts } from "./rebuild-gpu-opt-out"; +import type { RebuildResumeConfig } from "./rebuild-resume-config"; import type { RebuildTargetConfig } from "./rebuild-target-config"; -import { preflightRebuildTargetRuntime } from "./rebuild-target-runtime"; +import { + preflightAuthoritativeOnboardRuntime, + preflightRebuildTargetRuntime, +} from "./rebuild-target-runtime"; const platformDescriptor = Object.getOwnPropertyDescriptor(process, "platform")!; const TARGET = { @@ -189,6 +195,40 @@ describe("preflightRebuildTargetRuntime GPU route", () => { }); }); +describe("authoritative rebuild readiness", () => { + it("passes recorded managed-vLLM intent to the pre-delete readiness gate (#9292)", async () => { + const authority = { checkpoint: "gateway-authority" }; + mocks.preflightAuthoritativeRebuildTarget.mockResolvedValue(authority); + const recreateOptions = { + ...RECREATE_OPTIONS, + allowDeferredN1xManagedVllm: true, + } as RebuildRecreateOnboardOpts; + const bail = vi.fn((message: string): never => { + throw new Error(message); + }); + + await expect( + preflightAuthoritativeOnboardRuntime( + "alpha", + { provider: "vllm-local", model: "test-model" } as RebuildResumeConfig, + recreateOptions, + bail, + ), + ).resolves.toBe(true); + + expect(mocks.preflightAuthoritativeRebuildTarget).toHaveBeenCalledWith( + expect.objectContaining({ + allowDeferredN1xManagedVllm: true, + provider: "vllm-local", + model: "test-model", + sandboxName: "alpha", + }), + ); + expect(recreateOptions.rebuildGatewayAuthority).toBe(authority); + expect(bail).not.toHaveBeenCalled(); + }); +}); + describe("preflightRebuildTargetRuntime web search credential", () => { const WEB_SEARCH_TARGET = { ...TARGET, diff --git a/src/lib/onboard.ts b/src/lib/onboard.ts index 136b7401381..bedcaea818a 100644 --- a/src/lib/onboard.ts +++ b/src/lib/onboard.ts @@ -3016,6 +3016,7 @@ async function preflightAuthoritativeRebuildTarget( sandboxGpu: opts.sandboxGpu, sandboxGpuDevice: opts.sandboxGpuDevice, noGpu: opts.noGpu, + allowDeferredN1xManagedVllm: opts.allowDeferredN1xManagedVllm, }, (code) => fail(`onboard runtime preflight exited with code ${String(code)}`), ), diff --git a/src/lib/onboard/authoritative-rebuild-target.ts b/src/lib/onboard/authoritative-rebuild-target.ts index ad527349f99..bdd4be02f2d 100644 --- a/src/lib/onboard/authoritative-rebuild-target.ts +++ b/src/lib/onboard/authoritative-rebuild-target.ts @@ -21,7 +21,11 @@ export type AuthoritativeGatewayOptions = Pick< export type AuthoritativeRebuildPreflightOptions = Pick< OnboardOptions, - "sandboxGpu" | "sandboxGpuDevice" | "noGpu" | "controlUiPort" + | "sandboxGpu" + | "sandboxGpuDevice" + | "noGpu" + | "controlUiPort" + | "allowDeferredN1xManagedVllm" > & { authoritativeResumeConfig: true; /** Internal prepared-backup recovery defers route repair to authoritative onboard. */ diff --git a/src/lib/onboard/fatal-runtime-preflight.test.ts b/src/lib/onboard/fatal-runtime-preflight.test.ts index 8b9620c2cb0..64085e535d1 100644 --- a/src/lib/onboard/fatal-runtime-preflight.test.ts +++ b/src/lib/onboard/fatal-runtime-preflight.test.ts @@ -137,6 +137,14 @@ describe("report-backed runtime readiness (#7411)", () => { }), ).toThrow("exit"); + expect( + assertOnboardSystemReadiness(readiness, hostWithRuntime("docker"), { + explicitlyOptedOutGpuPassthrough: false, + allowDeferredN1xManagedVllm: true, + exitProcess: exit as never, + }), + ).toBe(readiness); + vi.stubEnv("NEMOCLAW_PROVIDER", "install-vllm"); expect( assertOnboardSystemReadiness(readiness, hostWithRuntime("docker"), { diff --git a/src/lib/onboard/fatal-runtime-preflight.ts b/src/lib/onboard/fatal-runtime-preflight.ts index eef29cd7ecb..e8568fbb6cd 100644 --- a/src/lib/onboard/fatal-runtime-preflight.ts +++ b/src/lib/onboard/fatal-runtime-preflight.ts @@ -44,7 +44,7 @@ import { MANAGED_VLLM_PROVIDER_KEY } from "./vllm-menu"; export type FatalRuntimePreflightOptions = Pick< OnboardOptions, - "sandboxGpu" | "sandboxGpuDevice" | "gpu" | "noGpu" + "sandboxGpu" | "sandboxGpuDevice" | "gpu" | "noGpu" | "allowDeferredN1xManagedVllm" > & { optedOutGpuPassthrough?: boolean; }; @@ -106,6 +106,7 @@ export interface OnboardHostReadinessOptions { resuming?: boolean; allowStorageRemediation?: boolean; allowPortableHostPreparation?: boolean; + allowDeferredN1xManagedVllm?: boolean; exitProcess?: (code: number) => never; observedAt?: string; now?: () => Date; @@ -153,7 +154,9 @@ export function assertOnboardSystemReadiness( isPortableExperimentalProfile() || !isLinuxDockerDriverGatewayEnabled(), allowStorageRemediation: options.allowStorageRemediation === true, allowPortableHostPreparation: options.allowPortableHostPreparation, - allowDeferredN1xManagedVllm: process.env.NEMOCLAW_PROVIDER === MANAGED_VLLM_PROVIDER_KEY, + allowDeferredN1xManagedVllm: + options.allowDeferredN1xManagedVllm === true || + process.env.NEMOCLAW_PROVIDER === MANAGED_VLLM_PROVIDER_KEY, }); if (admission.admitted) return readinessReport; const jetsonRuntimeMissing = admission.findingIds.includes("host.gpu.nvidia_runtime_missing"); @@ -262,6 +265,7 @@ function refreshOnboardHostReadiness( wslDockerDesktopGpuProofPassed: runtimeGpu?.wslDockerDesktopGpuProofPassed, resuming: context.resuming, allowStorageRemediation, + allowDeferredN1xManagedVllm: options.allowDeferredN1xManagedVllm, exitProcess: context.exitProcess, observedAt, now, @@ -375,6 +379,7 @@ export async function runReadinessGatedRuntimePreflight( refreshedResult.sandboxGpuConfig.mode === "0" || options.optedOutGpuPassthrough === true, resuming: context.resuming, allowStorageRemediation: managedGatewayReadiness, + allowDeferredN1xManagedVllm: options.allowDeferredN1xManagedVllm, exitProcess, }); // The only GPU detection path that may pull or start a container is delayed @@ -422,6 +427,7 @@ export async function runReadinessGatedRuntimePreflight( refreshedResult.sandboxGpuConfig.mode === "0" || options.optedOutGpuPassthrough === true, resuming: context.resuming, allowStorageRemediation: managedGatewayReadiness, + allowDeferredN1xManagedVllm: options.allowDeferredN1xManagedVllm, exitProcess, }); const gatedResult = { @@ -456,6 +462,7 @@ export function runFatalOnboardRuntimePreflight( explicitlyOptedOutGpuPassthrough, resuming: context.resuming, allowStorageRemediation: context.allowStorageRemediation, + allowDeferredN1xManagedVllm: options.allowDeferredN1xManagedVllm, exitProcess, observedAt, now, diff --git a/src/lib/onboard/types.ts b/src/lib/onboard/types.ts index c6254404187..3783929be02 100644 --- a/src/lib/onboard/types.ts +++ b/src/lib/onboard/types.ts @@ -117,6 +117,8 @@ export type OnboardOptions = { rebuildProviderReconfigure?: import("./rebuild-route-handoff").RebuildProviderReconfigureHandoff; /** Internal one-shot authority to recover the recorded provider during a locked rebuild resume. */ providerRecoveryReceipt?: import("./rebuild-route-handoff").ProviderRecoveryReceipt; + /** Internal rebuild handoff for a recorded managed-vLLM N1x preview selection. */ + allowDeferredN1xManagedVllm?: true; /** Internal one-shot handoff for the exact image context validated before rebuild deletion. */ preparedImageRebuild?: import("./prepared-dcode-rebuild").PreparedImageRebuildHandoff; /** Internal immutable managed-image/profile handoff validated before rebuild deletion. */ From 5cb5206ff82367d21a3ce48a3fe6f7a1b2443849 Mon Sep 17 00:00:00 2001 From: San Dang Date: Mon, 17 Aug 2026 18:14:41 +0700 Subject: [PATCH 02/10] fix(onboard): admit recorded N1x Express rebuilds Signed-off-by: San Dang --- docs/inference/set-up-vllm.mdx | 17 ++- .../rebuild-preflight-target-phase.test.ts | 133 ++++++++++++++---- .../sandbox/rebuild-preflight-target-phase.ts | 52 ++++++- src/lib/onboard.ts | 7 +- .../authoritative-rebuild-target.test.ts | 27 ++++ .../onboard/authoritative-rebuild-target.ts | 21 ++- 6 files changed, 210 insertions(+), 47 deletions(-) diff --git a/docs/inference/set-up-vllm.mdx b/docs/inference/set-up-vllm.mdx index ca5cb1e2bf5..2d6e013f4fa 100644 --- a/docs/inference/set-up-vllm.mdx +++ b/docs/inference/set-up-vllm.mdx @@ -371,9 +371,20 @@ Accepting the preview prompt sets explicit managed-vLLM intent for the narrow pe Declining the prompt or setting only `NEMOCLAW_NO_EXPRESS=1` stops the installer before onboarding; neither path opens interactive provider selection. Set `NEMOCLAW_PROVIDER=install-vllm` when you need to bypass the prompt with the same explicit preview intent. -After successful preview onboarding, `rebuild` reuses the recorded `vllm-local` provider and managed-vLLM receipt as explicit preview intent. -You do not need to set `NEMOCLAW_PROVIDER` again for that rebuild. -If the provider or receipt is missing, malformed, or does not describe managed vLLM, readiness preflight stops before NemoClaw deletes the existing sandbox. +On a host that still meets the N1x identity checks, `rebuild` reuses the recorded route as explicit preview intent only when all of these conditions hold: + +- The sandbox registry records provider `vllm-local`. +- The registry records model `nvidia/Qwen3.6-35B-A3B-NVFP4`. +- The registry records upstream endpoint `http://host.openshell.internal:8000/v1`. +- The registry records `endpointSource` as `onboard` and `openshellDriver` as `docker`. +- The replacement keeps the recorded provider and model, then re-derives the canonical local endpoint. +- `hostLocalInferenceReceipt` is absent or `null`, or contains a canonical vLLM receipt for `host.openshell.internal:8000` and the same model. + +NemoClaw v0.0.109 can omit `hostLocalInferenceReceipt` or record it as `null`. +Matching sandboxes from that release remain eligible only when every condition above passes. +You do not need to set `NEMOCLAW_PROVIDER` again for a matching rebuild. +Any unmet condition stops readiness preflight before NemoClaw deletes the existing sandbox. +A malformed, non-vLLM, or conflicting receipt also stops readiness preflight before deletion. This reuse waives only the pending physical-validation finding and does not change the Deferred status. diff --git a/src/lib/actions/sandbox/rebuild-preflight-target-phase.test.ts b/src/lib/actions/sandbox/rebuild-preflight-target-phase.test.ts index 839d2979f83..9797ee00ab9 100644 --- a/src/lib/actions/sandbox/rebuild-preflight-target-phase.test.ts +++ b/src/lib/actions/sandbox/rebuild-preflight-target-phase.test.ts @@ -4,6 +4,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { + hostLocalInferenceReceipt, serializedHostLocalInferenceReceipt, serializedLlamaCppHostLocalInferenceReceipt, } from "../../../../test/helpers/host-local-inference-receipt"; @@ -11,6 +12,7 @@ import type { ProviderRecoveryReceipt, RegistryInferenceRoute, } from "../../onboard/rebuild-route-handoff"; +import { serializeHostLocalInferenceReceipt } from "../../onboard/runtime-provider/host-local-inference"; import type { SandboxBaseImageResolutionMetadata } from "../../sandbox-base-image"; import { pinRebuildTargetGatewayForReadiness, @@ -127,16 +129,57 @@ describe("stageRegistryProviderRecoveryReceipt", () => { }); describe("stageRecordedManagedVllmIntent", () => { - it("carries a canonical recorded managed-vLLM selection into both rebuild readiness gates (#9292)", () => { + const n1xExpressEntry = { + provider: "vllm-local", + model: "nvidia/Qwen3.6-35B-A3B-NVFP4", + endpointUrl: "http://host.openshell.internal:8000/v1", + endpointSource: "onboard" as const, + openshellDriver: "docker", + }; + const n1xExpressSelection = { + provider: n1xExpressEntry.provider, + model: n1xExpressEntry.model, + pinEndpoint: true, + endpointUrl: null, + }; + + it.each([ + { receiptState: "absent", sandboxEntry: n1xExpressEntry }, + { + receiptState: "null", + sandboxEntry: { ...n1xExpressEntry, hostLocalInferenceReceipt: null }, + }, + ])( + "carries the v0.0.109 N1x Express selection with a $receiptState receipt into rebuild readiness (#9292)", + ({ sandboxEntry }) => { + const recreateOptions: { allowDeferredN1xManagedVllm?: true } = {}; + + stageRecordedManagedVllmIntent(recreateOptions, sandboxEntry, n1xExpressSelection); + + expect(recreateOptions.allowDeferredN1xManagedVllm).toBe(true); + }, + ); + + it("also accepts a canonical vLLM receipt on the exact N1x Express selection (#9292)", () => { const recreateOptions: { allowDeferredN1xManagedVllm?: true } = {}; + const genericReceipt = hostLocalInferenceReceipt("docker"); + const n1xReceipt = serializeHostLocalInferenceReceipt({ + ...genericReceipt, + endpoint: { + ...genericReceipt.endpoint, + host: "host.openshell.internal", + }, + inference: { + protocol: "openai-chat-completions", + model: n1xExpressEntry.model, + toolCallingRequired: true, + }, + }); stageRecordedManagedVllmIntent( recreateOptions, - { - provider: "vllm-local", - hostLocalInferenceReceipt: serializedHostLocalInferenceReceipt(), - }, - "vllm-local", + { ...n1xExpressEntry, hostLocalInferenceReceipt: n1xReceipt }, + n1xExpressSelection, ); expect(recreateOptions.allowDeferredN1xManagedVllm).toBe(true); @@ -144,46 +187,84 @@ describe("stageRecordedManagedVllmIntent", () => { it.each([ { - caseName: "missing receipt", - sandboxEntry: { provider: "vllm-local" }, - provider: "vllm-local", + caseName: "different recorded provider", + sandboxEntry: { ...n1xExpressEntry, provider: "compatible-endpoint" }, + rebuildSelection: n1xExpressSelection, + }, + { + caseName: "different recorded model", + sandboxEntry: { ...n1xExpressEntry, model: "meta-llama/Llama-3.1-8B-Instruct" }, + rebuildSelection: n1xExpressSelection, + }, + { + caseName: "different recorded endpoint", + sandboxEntry: { ...n1xExpressEntry, endpointUrl: "http://host.openshell.internal:8001/v1" }, + rebuildSelection: n1xExpressSelection, + }, + { + caseName: "different endpoint source", + sandboxEntry: { ...n1xExpressEntry, endpointSource: "inference-set" as const }, + rebuildSelection: n1xExpressSelection, + }, + { + caseName: "different OpenShell driver", + sandboxEntry: { ...n1xExpressEntry, openshellDriver: "podman" }, + rebuildSelection: n1xExpressSelection, }, { caseName: "malformed receipt", - sandboxEntry: { provider: "vllm-local", hostLocalInferenceReceipt: "not-json" }, - provider: "vllm-local", + sandboxEntry: { ...n1xExpressEntry, hostLocalInferenceReceipt: "not-json" }, + rebuildSelection: n1xExpressSelection, }, { caseName: "different host-local service", sandboxEntry: { - provider: "vllm-local", + ...n1xExpressEntry, hostLocalInferenceReceipt: serializedLlamaCppHostLocalInferenceReceipt(), }, - provider: "vllm-local", + rebuildSelection: n1xExpressSelection, }, { - caseName: "different rebuild route", + caseName: "conflicting vLLM receipt", sandboxEntry: { - provider: "vllm-local", + ...n1xExpressEntry, hostLocalInferenceReceipt: serializedHostLocalInferenceReceipt(), }, - provider: "compatible-endpoint", + rebuildSelection: n1xExpressSelection, }, { - caseName: "different recorded route", - sandboxEntry: { - provider: "compatible-endpoint", - hostLocalInferenceReceipt: serializedHostLocalInferenceReceipt(), + caseName: "different rebuild provider", + sandboxEntry: n1xExpressEntry, + rebuildSelection: { ...n1xExpressSelection, provider: "compatible-endpoint" }, + }, + { + caseName: "different rebuild model", + sandboxEntry: n1xExpressEntry, + rebuildSelection: { ...n1xExpressSelection, model: "meta-llama/Llama-3.1-8B-Instruct" }, + }, + { + caseName: "unresolved rebuild endpoint", + sandboxEntry: n1xExpressEntry, + rebuildSelection: { ...n1xExpressSelection, pinEndpoint: false }, + }, + { + caseName: "noncanonical rebuild endpoint", + sandboxEntry: n1xExpressEntry, + rebuildSelection: { + ...n1xExpressSelection, + endpointUrl: "http://host.openshell.internal:8001/v1", }, - provider: "vllm-local", }, - ])("does not infer managed-vLLM intent from a $caseName (#9292)", ({ sandboxEntry, provider }) => { - const recreateOptions: { allowDeferredN1xManagedVllm?: true } = {}; + ])( + "does not infer N1x Express intent from a $caseName (#9292)", + ({ sandboxEntry, rebuildSelection }) => { + const recreateOptions: { allowDeferredN1xManagedVllm?: true } = {}; - stageRecordedManagedVllmIntent(recreateOptions, sandboxEntry, provider); + stageRecordedManagedVllmIntent(recreateOptions, sandboxEntry, rebuildSelection); - expect(recreateOptions).not.toHaveProperty("allowDeferredN1xManagedVllm"); - }); + expect(recreateOptions).not.toHaveProperty("allowDeferredN1xManagedVllm"); + }, + ); }); describe("stageRebuildBaseImageResolutionHandoff", () => { diff --git a/src/lib/actions/sandbox/rebuild-preflight-target-phase.ts b/src/lib/actions/sandbox/rebuild-preflight-target-phase.ts index c6ad45401e6..56cf72cd060 100644 --- a/src/lib/actions/sandbox/rebuild-preflight-target-phase.ts +++ b/src/lib/actions/sandbox/rebuild-preflight-target-phase.ts @@ -84,17 +84,55 @@ export function stageRegistryProviderRecoveryReceipt( ); } -/** Reuse only the canonical managed-vLLM selection recorded by onboarding. */ +const N1X_EXPRESS_PROVIDER = "vllm-local"; +const N1X_EXPRESS_MODEL = "nvidia/Qwen3.6-35B-A3B-NVFP4"; +const N1X_EXPRESS_ENDPOINT_URL = "http://host.openshell.internal:8000/v1"; + +/** Reuse only the exact N1x Express selection recorded by onboarding. */ export function stageRecordedManagedVllmIntent( recreateOptions: Pick, - sandboxEntry: Pick, - provider: string, + sandboxEntry: Pick< + RebuildSandboxEntry, + | "provider" + | "model" + | "endpointUrl" + | "endpointSource" + | "openshellDriver" + | "hostLocalInferenceReceipt" + >, + rebuildSelection: { + provider: string; + model: string; + pinEndpoint: boolean; + endpointUrl: string | null; + }, ): void { - if (provider !== "vllm-local" || sandboxEntry.provider !== "vllm-local") return; + if ( + sandboxEntry.provider !== N1X_EXPRESS_PROVIDER || + sandboxEntry.model !== N1X_EXPRESS_MODEL || + sandboxEntry.endpointUrl !== N1X_EXPRESS_ENDPOINT_URL || + sandboxEntry.endpointSource !== "onboard" || + sandboxEntry.openshellDriver !== "docker" || + rebuildSelection.provider !== sandboxEntry.provider || + rebuildSelection.model !== sandboxEntry.model || + rebuildSelection.pinEndpoint !== true || + rebuildSelection.endpointUrl !== null + ) { + return; + } const serialized = sandboxEntry.hostLocalInferenceReceipt; - if (!serialized) return; + if (serialized === undefined || serialized === null) { + recreateOptions.allowDeferredN1xManagedVllm = true; + return; + } try { - if (parseHostLocalInferenceReceipt(serialized).service === "vllm") { + const receipt = parseHostLocalInferenceReceipt(serialized); + if ( + receipt.service === "vllm" && + receipt.endpoint.host === "host.openshell.internal" && + receipt.endpoint.port === 8000 && + receipt.inference?.model === N1X_EXPRESS_MODEL + ) { recreateOptions.allowDeferredN1xManagedVllm = true; } } catch { @@ -252,7 +290,7 @@ export async function prepareRebuildTargetPreflights(args: { recreateOptions.observabilityEnabled = requestedObservabilityEnabled ?? recreateOptions.observabilityEnabled; recreateOptions.observabilityRequestedExplicitly = requestedObservabilityEnabled !== undefined; - stageRecordedManagedVllmIntent(recreateOptions, sandboxEntry, resumeConfig.provider); + stageRecordedManagedVllmIntent(recreateOptions, sandboxEntry, resumeConfig); if ( !stageRebuildHermesDashboardConfig( rebuildAgent, diff --git a/src/lib/onboard.ts b/src/lib/onboard.ts index bedcaea818a..460e3cfc3fc 100644 --- a/src/lib/onboard.ts +++ b/src/lib/onboard.ts @@ -3012,12 +3012,7 @@ async function preflightAuthoritativeRebuildTarget( bindGatewayAuthority: () => bindGatewayOwner(getGatewayOwner()), runFatalRuntimePreflight: async () => onboardPreflightGatewayAuthority.runRuntimePreflight( - { - sandboxGpu: opts.sandboxGpu, - sandboxGpuDevice: opts.sandboxGpuDevice, - noGpu: opts.noGpu, - allowDeferredN1xManagedVllm: opts.allowDeferredN1xManagedVllm, - }, + authoritativeRebuildTarget.authoritativeRebuildRuntimePreflightOptions(opts), (code) => fail(`onboard runtime preflight exited with code ${String(code)}`), ), ensureOpenshell: () => diff --git a/src/lib/onboard/authoritative-rebuild-target.test.ts b/src/lib/onboard/authoritative-rebuild-target.test.ts index 52893b9fc96..850aa22df7b 100644 --- a/src/lib/onboard/authoritative-rebuild-target.test.ts +++ b/src/lib/onboard/authoritative-rebuild-target.test.ts @@ -4,7 +4,9 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { + authoritativeRebuildRuntimePreflightOptions, type AuthoritativeRebuildTargetDeps, + type AuthoritativeRebuildPreflightOptions, preflightAuthoritativeRebuildTarget, rebuildProviderFlowOptions, resolveAuthoritativeOnboardGatewayBinding, @@ -23,6 +25,31 @@ const target = { }; const originalGateway = process.env.OPENSHELL_GATEWAY; +describe("authoritative rebuild runtime preflight options", () => { + it("carries only target GPU state and recorded N1x preview intent (#9292)", () => { + const options = { + authoritativeResumeConfig: true, + sandboxName: "alpha", + provider: "vllm-local", + model: "nvidia/Qwen3.6-35B-A3B-NVFP4", + targetGatewayName: "nemoclaw-12345", + targetGatewayPort: 12345, + controlUiPort: null, + sandboxGpu: "enable", + sandboxGpuDevice: "nvidia.com/gpu=all", + noGpu: false, + allowDeferredN1xManagedVllm: true, + } satisfies AuthoritativeRebuildPreflightOptions; + + expect(authoritativeRebuildRuntimePreflightOptions(options)).toEqual({ + sandboxGpu: "enable", + sandboxGpuDevice: "nvidia.com/gpu=all", + noGpu: false, + allowDeferredN1xManagedVllm: true, + }); + }); +}); + function deps(overrides: Partial = {}) { return { resolveBaselinePolicy: vi.fn(() => ({})), diff --git a/src/lib/onboard/authoritative-rebuild-target.ts b/src/lib/onboard/authoritative-rebuild-target.ts index bdd4be02f2d..7528634eca0 100644 --- a/src/lib/onboard/authoritative-rebuild-target.ts +++ b/src/lib/onboard/authoritative-rebuild-target.ts @@ -21,11 +21,7 @@ export type AuthoritativeGatewayOptions = Pick< export type AuthoritativeRebuildPreflightOptions = Pick< OnboardOptions, - | "sandboxGpu" - | "sandboxGpuDevice" - | "noGpu" - | "controlUiPort" - | "allowDeferredN1xManagedVllm" + "sandboxGpu" | "sandboxGpuDevice" | "noGpu" | "controlUiPort" | "allowDeferredN1xManagedVllm" > & { authoritativeResumeConfig: true; /** Internal prepared-backup recovery defers route repair to authoritative onboard. */ @@ -37,6 +33,21 @@ export type AuthoritativeRebuildPreflightOptions = Pick< targetGatewayPort: number; }; +/** Project only the target-bound GPU and N1x intent into runtime preflight. */ +export function authoritativeRebuildRuntimePreflightOptions( + opts: AuthoritativeRebuildPreflightOptions, +): Pick< + OnboardOptions, + "sandboxGpu" | "sandboxGpuDevice" | "noGpu" | "allowDeferredN1xManagedVllm" +> { + return { + sandboxGpu: opts.sandboxGpu, + sandboxGpuDevice: opts.sandboxGpuDevice, + noGpu: opts.noGpu, + allowDeferredN1xManagedVllm: opts.allowDeferredN1xManagedVllm, + }; +} + export function resolveAuthoritativeOnboardGatewayBinding( opts: AuthoritativeGatewayOptions, ): AuthoritativeOnboardGatewayBinding | null { From 4dfcc00761ac2196bc199cfce061bf17edd3779f Mon Sep 17 00:00:00 2001 From: San Dang Date: Mon, 17 Aug 2026 18:21:19 +0700 Subject: [PATCH 03/10] fix(onboard): isolate N1x rebuild intent Signed-off-by: San Dang --- src/lib/onboard/authoritative-rebuild-target.test.ts | 8 ++++++++ src/lib/onboard/authoritative-rebuild-target.ts | 9 ++++----- src/lib/onboard/fatal-runtime-preflight.test.ts | 10 +++++++++- src/lib/onboard/fatal-runtime-preflight.ts | 6 ++++-- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/lib/onboard/authoritative-rebuild-target.test.ts b/src/lib/onboard/authoritative-rebuild-target.test.ts index 850aa22df7b..07a3b83bf33 100644 --- a/src/lib/onboard/authoritative-rebuild-target.test.ts +++ b/src/lib/onboard/authoritative-rebuild-target.test.ts @@ -47,6 +47,14 @@ describe("authoritative rebuild runtime preflight options", () => { noGpu: false, allowDeferredN1xManagedVllm: true, }); + + const { allowDeferredN1xManagedVllm: _recordedIntent, ...withoutRecordedIntent } = options; + expect(authoritativeRebuildRuntimePreflightOptions(withoutRecordedIntent)).toEqual({ + sandboxGpu: "enable", + sandboxGpuDevice: "nvidia.com/gpu=all", + noGpu: false, + allowDeferredN1xManagedVllm: false, + }); }); }); diff --git a/src/lib/onboard/authoritative-rebuild-target.ts b/src/lib/onboard/authoritative-rebuild-target.ts index 7528634eca0..796545bc9f6 100644 --- a/src/lib/onboard/authoritative-rebuild-target.ts +++ b/src/lib/onboard/authoritative-rebuild-target.ts @@ -36,15 +36,14 @@ export type AuthoritativeRebuildPreflightOptions = Pick< /** Project only the target-bound GPU and N1x intent into runtime preflight. */ export function authoritativeRebuildRuntimePreflightOptions( opts: AuthoritativeRebuildPreflightOptions, -): Pick< - OnboardOptions, - "sandboxGpu" | "sandboxGpuDevice" | "noGpu" | "allowDeferredN1xManagedVllm" -> { +): Pick & { + allowDeferredN1xManagedVllm: boolean; +} { return { sandboxGpu: opts.sandboxGpu, sandboxGpuDevice: opts.sandboxGpuDevice, noGpu: opts.noGpu, - allowDeferredN1xManagedVllm: opts.allowDeferredN1xManagedVllm, + allowDeferredN1xManagedVllm: opts.allowDeferredN1xManagedVllm === true, }; } diff --git a/src/lib/onboard/fatal-runtime-preflight.test.ts b/src/lib/onboard/fatal-runtime-preflight.test.ts index 64085e535d1..04cc1dac2e8 100644 --- a/src/lib/onboard/fatal-runtime-preflight.test.ts +++ b/src/lib/onboard/fatal-runtime-preflight.test.ts @@ -91,7 +91,7 @@ afterEach(() => { }); describe("report-backed runtime readiness (#7411)", () => { - it("requires explicit managed-vLLM intent for the Deferred N1x readiness exception (#8574)", () => { + it("requires explicit managed-vLLM intent and lets rebuild reject ambient intent (#9292)", () => { const readiness: SystemReadinessReport = { schemaVersion: "1.1.0", status: "incompatible", @@ -152,6 +152,14 @@ describe("report-backed runtime readiness (#7411)", () => { exitProcess: exit as never, }), ).toBe(readiness); + + expect(() => + assertOnboardSystemReadiness(readiness, hostWithRuntime("docker"), { + explicitlyOptedOutGpuPassthrough: false, + allowDeferredN1xManagedVllm: false, + exitProcess: exit as never, + }), + ).toThrow("exit"); }); it("rejects ambiguous gateway ownership before the caller can run effects", () => { diff --git a/src/lib/onboard/fatal-runtime-preflight.ts b/src/lib/onboard/fatal-runtime-preflight.ts index e8568fbb6cd..237229ce26e 100644 --- a/src/lib/onboard/fatal-runtime-preflight.ts +++ b/src/lib/onboard/fatal-runtime-preflight.ts @@ -44,8 +44,10 @@ import { MANAGED_VLLM_PROVIDER_KEY } from "./vllm-menu"; export type FatalRuntimePreflightOptions = Pick< OnboardOptions, - "sandboxGpu" | "sandboxGpuDevice" | "gpu" | "noGpu" | "allowDeferredN1xManagedVllm" + "sandboxGpu" | "sandboxGpuDevice" | "gpu" | "noGpu" > & { + /** Explicit false prevents ambient provider intent from crossing a rebuild boundary. */ + allowDeferredN1xManagedVllm?: boolean; optedOutGpuPassthrough?: boolean; }; @@ -155,7 +157,7 @@ export function assertOnboardSystemReadiness( allowStorageRemediation: options.allowStorageRemediation === true, allowPortableHostPreparation: options.allowPortableHostPreparation, allowDeferredN1xManagedVllm: - options.allowDeferredN1xManagedVllm === true || + options.allowDeferredN1xManagedVllm ?? process.env.NEMOCLAW_PROVIDER === MANAGED_VLLM_PROVIDER_KEY, }); if (admission.admitted) return readinessReport; From 6020b463488de90833313ae7f50a0eda5ca37b24 Mon Sep 17 00:00:00 2001 From: San Dang Date: Mon, 17 Aug 2026 18:49:30 +0700 Subject: [PATCH 04/10] fix(onboard): carry N1x intent through cached resume Signed-off-by: San Dang --- src/lib/onboard.ts | 1 + .../onboard/machine/handlers/preflight.test.ts | 18 ++++++++++++++++++ src/lib/onboard/machine/handlers/preflight.ts | 5 +++++ src/lib/onboard/machine/initial-flow-phases.ts | 2 ++ 4 files changed, 26 insertions(+) diff --git a/src/lib/onboard.ts b/src/lib/onboard.ts index 460e3cfc3fc..1cae38016e3 100644 --- a/src/lib/onboard.ts +++ b/src/lib/onboard.ts @@ -3293,6 +3293,7 @@ async function runOnboard(opts: OnboardOptions = {}): Promise { sandboxGpuDevice: opts.sandboxGpuDevice ?? null, gpuRequested: opts.gpu === true, noGpu: opts.noGpu === true, + allowDeferredN1xManagedVllm: opts.allowDeferredN1xManagedVllm, env: process.env, recordedGpuPassthroughBeforePreflight, commitSelectedAgentTransition: selectedAgentTransition.commit, diff --git a/src/lib/onboard/machine/handlers/preflight.test.ts b/src/lib/onboard/machine/handlers/preflight.test.ts index 2d7f487cf5a..8b61d994dc9 100644 --- a/src/lib/onboard/machine/handlers/preflight.test.ts +++ b/src/lib/onboard/machine/handlers/preflight.test.ts @@ -195,6 +195,24 @@ describe("handlePreflightState", () => { expect(result.resumePreflight).toBe(true); }); + it("carries verified N1x intent through cached resume readiness (#9292)", async () => { + const session = createSession(); + session.steps.preflight.status = "complete"; + const assertOnboardHostReadiness = vi.fn(); + const harness = createDeps({ assertOnboardHostReadiness }); + + await handlePreflightState({ + ...baseOptions(harness.deps, session), + resume: true, + allowDeferredN1xManagedVllm: true, + }); + + expect(assertOnboardHostReadiness).toHaveBeenCalledTimes(2); + for (const [, , options] of assertOnboardHostReadiness.mock.calls) { + expect(options).toEqual(expect.objectContaining({ allowDeferredN1xManagedVllm: true })); + } + }); + it("rejects changed gateway ownership before cached resume probe effects (#7411)", async () => { const session = createSession(); session.steps.preflight.status = "complete"; diff --git a/src/lib/onboard/machine/handlers/preflight.ts b/src/lib/onboard/machine/handlers/preflight.ts index 72c031c9c09..eb3c859e2b4 100644 --- a/src/lib/onboard/machine/handlers/preflight.ts +++ b/src/lib/onboard/machine/handlers/preflight.ts @@ -34,6 +34,7 @@ export interface PreflightStateOptions< sandboxGpuDevice?: string | null; gpuRequested: boolean; noGpu: boolean; + allowDeferredN1xManagedVllm?: boolean; env: NodeJS.ProcessEnv; deps: { getSandbox(name: string): SandboxEntry | null; @@ -55,6 +56,7 @@ export interface PreflightStateOptions< observedAt?: string; now?: () => Date; wslDockerDesktopGpuProofPassed?: boolean; + allowDeferredN1xManagedVllm?: boolean; resuming: true; }, ): void; @@ -129,6 +131,7 @@ export async function handlePreflightState< sandboxGpuDevice, gpuRequested, noGpu, + allowDeferredN1xManagedVllm, env, deps, }: PreflightStateOptions): Promise< @@ -175,6 +178,7 @@ export async function handlePreflightState< explicitlyOptedOutGpuPassthrough: resumeSandboxGpuConfig.mode === "0", observedAt: hostObservedAt, now, + allowDeferredN1xManagedVllm, resuming: true, }); // A full detector can run the bounded ARM64 WSL Docker GPU proof. Keep it @@ -197,6 +201,7 @@ export async function handlePreflightState< observedAt: hostObservedAt, now, ...(wslDockerDesktopGpuProofPassed === undefined ? {} : { wslDockerDesktopGpuProofPassed }), + allowDeferredN1xManagedVllm, resuming: true, }); } diff --git a/src/lib/onboard/machine/initial-flow-phases.ts b/src/lib/onboard/machine/initial-flow-phases.ts index c53061ef1f6..3fef64a6338 100644 --- a/src/lib/onboard/machine/initial-flow-phases.ts +++ b/src/lib/onboard/machine/initial-flow-phases.ts @@ -53,6 +53,7 @@ export interface InitialOnboardFlowPhaseOptions< sandboxGpuDevice?: string | null; gpuRequested: boolean; noGpu: boolean; + allowDeferredN1xManagedVllm?: boolean; env: NodeJS.ProcessEnv; platform?: NodeJS.Platform; recordedGpuPassthroughBeforePreflight: boolean; @@ -144,6 +145,7 @@ export function createInitialOnboardFlowPhases< sandboxGpuDevice: options.sandboxGpuDevice ?? null, gpuRequested: options.gpuRequested, noGpu: options.noGpu, + allowDeferredN1xManagedVllm: options.allowDeferredN1xManagedVllm, env: options.env, deps: { ...options.preflightDeps, From a40edf8773bf40fe048028cedc0f6c884626562f Mon Sep 17 00:00:00 2001 From: San Dang Date: Mon, 17 Aug 2026 18:56:34 +0700 Subject: [PATCH 05/10] test(onboard): cover N1x rebuild orchestration Signed-off-by: San Dang --- ...flight-target-phase-orchestration.test.ts} | 91 ++++++++++++++++++- 1 file changed, 88 insertions(+), 3 deletions(-) rename src/lib/actions/sandbox/{rebuild-managed-workload-context-window.test.ts => rebuild-preflight-target-phase-orchestration.test.ts} (60%) diff --git a/src/lib/actions/sandbox/rebuild-managed-workload-context-window.test.ts b/src/lib/actions/sandbox/rebuild-preflight-target-phase-orchestration.test.ts similarity index 60% rename from src/lib/actions/sandbox/rebuild-managed-workload-context-window.test.ts rename to src/lib/actions/sandbox/rebuild-preflight-target-phase-orchestration.test.ts index 20d454d82ed..d1bca1ab740 100644 --- a/src/lib/actions/sandbox/rebuild-managed-workload-context-window.test.ts +++ b/src/lib/actions/sandbox/rebuild-preflight-target-phase-orchestration.test.ts @@ -1,10 +1,11 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { describe, expect, it, vi } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; const mocks = vi.hoisted(() => ({ bail: vi.fn(), + preflightAuthoritativeOnboardRuntime: vi.fn(async (..._args: unknown[]) => false), prepareManagedWorkloadRebuildHandoff: vi.fn(), prepareSandboxWorkloadSourceFromRebuildHandoff: vi.fn(), prepareRebuildTargetConfig: vi.fn(), @@ -33,7 +34,7 @@ vi.mock("../../onboard/workload/runtime", () => ({ vi.mock("./rebuild-target-preflight", async (importOriginal) => ({ ...(await importOriginal()), hydrateMessagingConfigForRebuild: vi.fn(), - preflightAuthoritativeOnboardRuntime: vi.fn(async () => false), + preflightAuthoritativeOnboardRuntime: mocks.preflightAuthoritativeOnboardRuntime, prepareRebuildRecreateOptions: mocks.prepareRebuildRecreateOptions, prepareRebuildTargetConfig: mocks.prepareRebuildTargetConfig, stageRebuildHermesDashboardConfig: vi.fn(() => true), @@ -50,7 +51,13 @@ vi.mock("./rebuild-messaging-conflict-preflight", () => ({ import { managedRebuildProfileDependencies } from "./agents/managed-workload-rebuild-profile"; import { prepareRebuildTargetPreflights } from "./rebuild-preflight-target-phase"; -describe("managed workload rebuild context-window preflight", () => { +describe("prepareRebuildTargetPreflights", () => { + beforeEach(() => { + vi.clearAllMocks(); + mocks.prepareManagedWorkloadRebuildHandoff.mockResolvedValue(null); + mocks.preflightAuthoritativeOnboardRuntime.mockResolvedValue(false); + }); + it("resolves the Ollama context window through target preparation", async () => { const catalogHandoff = { agent: "openclaw", @@ -123,4 +130,82 @@ describe("managed workload rebuild context-window preflight", () => { ).resolves.toBeNull(); expect(mocks.resolveContextWindowForModel).toHaveBeenCalledWith("ollama-local", "qwen3.5:9b"); }); + + it.each([ + { caseName: "exact legacy route", endpointSource: "onboard", expectedIntent: true }, + { + caseName: "mismatched endpoint source", + endpointSource: "inference-set", + expectedIntent: undefined, + }, + ])( + "passes managed-vLLM intent from the $caseName into authoritative readiness (#9292)", + async ({ endpointSource, expectedIntent }) => { + const resumeConfig = { + provider: "vllm-local", + model: "nvidia/Qwen3.6-35B-A3B-NVFP4", + preferredInferenceApi: "openai-completions", + pinEndpoint: true, + endpointUrl: null, + compatibleEndpointReasoning: null, + compatibleEndpointReasoningEffort: null, + registryInferenceRoute: null, + }; + const recreateOptions = { + controlUiPort: 18_789, + targetGatewayName: "nemoclaw", + toolDisclosure: "progressive", + dcodeAutoApprovalMode: "disabled", + observabilityEnabled: false, + }; + mocks.prepareRebuildTargetConfig.mockReturnValue({ + agentDefinition: {}, + resumeConfig, + durableConfig: { + toolDisclosure: "progressive", + dcodeAutoApprovalMode: "disabled", + webSearchConfig: null, + }, + credentialEnv: null, + fromDockerfile: false, + hermesToolGateways: [], + }); + mocks.prepareRebuildRecreateOptions.mockReturnValue(recreateOptions); + + await expect( + prepareRebuildTargetPreflights({ + sandboxName: "my-assistant", + sandboxEntry: { + name: "my-assistant", + agent: "openclaw", + gatewayName: "nemoclaw", + openshellDriver: "docker", + provider: resumeConfig.provider, + model: resumeConfig.model, + endpointUrl: "http://host.openshell.internal:8000/v1", + endpointSource, + } as never, + rebuildAgent: "openclaw", + autoYes: true, + log: vi.fn(), + bail: mocks.bail as never, + }), + ).resolves.toBeNull(); + expect(mocks.preflightAuthoritativeOnboardRuntime).toHaveBeenCalledWith( + "my-assistant", + resumeConfig, + expect.any(Object), + mocks.bail, + {}, + ); + const readinessOptions = mocks.preflightAuthoritativeOnboardRuntime.mock.calls[0]?.[2]; + if (expectedIntent === true) { + expect(readinessOptions).toEqual( + expect.objectContaining({ allowDeferredN1xManagedVllm: true }), + ); + } else { + expect(readinessOptions).not.toHaveProperty("allowDeferredN1xManagedVllm"); + } + }, + ); }); From 100c26c64f0632664c528b52173e1aba8d731de5 Mon Sep 17 00:00:00 2001 From: San Dang Date: Mon, 17 Aug 2026 19:05:48 +0700 Subject: [PATCH 06/10] test(onboard): keep N1x orchestration linear Signed-off-by: San Dang --- ...eflight-target-phase-orchestration.test.ts | 141 ++++++++---------- 1 file changed, 64 insertions(+), 77 deletions(-) diff --git a/src/lib/actions/sandbox/rebuild-preflight-target-phase-orchestration.test.ts b/src/lib/actions/sandbox/rebuild-preflight-target-phase-orchestration.test.ts index d1bca1ab740..ab4fcdeb885 100644 --- a/src/lib/actions/sandbox/rebuild-preflight-target-phase-orchestration.test.ts +++ b/src/lib/actions/sandbox/rebuild-preflight-target-phase-orchestration.test.ts @@ -58,6 +58,57 @@ describe("prepareRebuildTargetPreflights", () => { mocks.preflightAuthoritativeOnboardRuntime.mockResolvedValue(false); }); + async function prepareN1xTarget(endpointSource: "onboard" | "inference-set") { + const resumeConfig = { + provider: "vllm-local", + model: "nvidia/Qwen3.6-35B-A3B-NVFP4", + preferredInferenceApi: "openai-completions", + pinEndpoint: true, + endpointUrl: null, + compatibleEndpointReasoning: null, + compatibleEndpointReasoningEffort: null, + registryInferenceRoute: null, + }; + mocks.prepareRebuildTargetConfig.mockReturnValue({ + agentDefinition: {}, + resumeConfig, + durableConfig: { + toolDisclosure: "progressive", + dcodeAutoApprovalMode: "disabled", + webSearchConfig: null, + }, + credentialEnv: null, + fromDockerfile: false, + hermesToolGateways: [], + }); + mocks.prepareRebuildRecreateOptions.mockReturnValue({ + controlUiPort: 18_789, + targetGatewayName: "nemoclaw", + toolDisclosure: "progressive", + dcodeAutoApprovalMode: "disabled", + observabilityEnabled: false, + }); + + await prepareRebuildTargetPreflights({ + sandboxName: "my-assistant", + sandboxEntry: { + name: "my-assistant", + agent: "openclaw", + gatewayName: "nemoclaw", + openshellDriver: "docker", + provider: resumeConfig.provider, + model: resumeConfig.model, + endpointUrl: "http://host.openshell.internal:8000/v1", + endpointSource, + } as never, + rebuildAgent: "openclaw", + autoYes: true, + log: vi.fn(), + bail: mocks.bail as never, + }); + return mocks.preflightAuthoritativeOnboardRuntime.mock.calls[0]?.[2]; + } + it("resolves the Ollama context window through target preparation", async () => { const catalogHandoff = { agent: "openclaw", @@ -131,81 +182,17 @@ describe("prepareRebuildTargetPreflights", () => { expect(mocks.resolveContextWindowForModel).toHaveBeenCalledWith("ollama-local", "qwen3.5:9b"); }); - it.each([ - { caseName: "exact legacy route", endpointSource: "onboard", expectedIntent: true }, - { - caseName: "mismatched endpoint source", - endpointSource: "inference-set", - expectedIntent: undefined, - }, - ])( - "passes managed-vLLM intent from the $caseName into authoritative readiness (#9292)", - async ({ endpointSource, expectedIntent }) => { - const resumeConfig = { - provider: "vllm-local", - model: "nvidia/Qwen3.6-35B-A3B-NVFP4", - preferredInferenceApi: "openai-completions", - pinEndpoint: true, - endpointUrl: null, - compatibleEndpointReasoning: null, - compatibleEndpointReasoningEffort: null, - registryInferenceRoute: null, - }; - const recreateOptions = { - controlUiPort: 18_789, - targetGatewayName: "nemoclaw", - toolDisclosure: "progressive", - dcodeAutoApprovalMode: "disabled", - observabilityEnabled: false, - }; - mocks.prepareRebuildTargetConfig.mockReturnValue({ - agentDefinition: {}, - resumeConfig, - durableConfig: { - toolDisclosure: "progressive", - dcodeAutoApprovalMode: "disabled", - webSearchConfig: null, - }, - credentialEnv: null, - fromDockerfile: false, - hermesToolGateways: [], - }); - mocks.prepareRebuildRecreateOptions.mockReturnValue(recreateOptions); - - await expect( - prepareRebuildTargetPreflights({ - sandboxName: "my-assistant", - sandboxEntry: { - name: "my-assistant", - agent: "openclaw", - gatewayName: "nemoclaw", - openshellDriver: "docker", - provider: resumeConfig.provider, - model: resumeConfig.model, - endpointUrl: "http://host.openshell.internal:8000/v1", - endpointSource, - } as never, - rebuildAgent: "openclaw", - autoYes: true, - log: vi.fn(), - bail: mocks.bail as never, - }), - ).resolves.toBeNull(); - expect(mocks.preflightAuthoritativeOnboardRuntime).toHaveBeenCalledWith( - "my-assistant", - resumeConfig, - expect.any(Object), - mocks.bail, - {}, - ); - const readinessOptions = mocks.preflightAuthoritativeOnboardRuntime.mock.calls[0]?.[2]; - if (expectedIntent === true) { - expect(readinessOptions).toEqual( - expect.objectContaining({ allowDeferredN1xManagedVllm: true }), - ); - } else { - expect(readinessOptions).not.toHaveProperty("allowDeferredN1xManagedVllm"); - } - }, - ); + it("passes exact legacy N1x intent into authoritative readiness (#9292)", async () => { + const readinessOptions = await prepareN1xTarget("onboard"); + + expect(readinessOptions).toEqual( + expect.objectContaining({ allowDeferredN1xManagedVllm: true }), + ); + }); + + it("withholds N1x intent for a mismatched endpoint source (#9292)", async () => { + const readinessOptions = await prepareN1xTarget("inference-set"); + + expect(readinessOptions).not.toHaveProperty("allowDeferredN1xManagedVllm"); + }); }); From 71992f60e4ca1682fb05526cd539e27431a8583a Mon Sep 17 00:00:00 2001 From: San Dang Date: Mon, 17 Aug 2026 19:12:04 +0700 Subject: [PATCH 07/10] test(onboard): keep cached resume assertions linear Signed-off-by: San Dang --- .../onboard/machine/handlers/preflight.test.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/lib/onboard/machine/handlers/preflight.test.ts b/src/lib/onboard/machine/handlers/preflight.test.ts index 8b61d994dc9..fec340b4401 100644 --- a/src/lib/onboard/machine/handlers/preflight.test.ts +++ b/src/lib/onboard/machine/handlers/preflight.test.ts @@ -208,9 +208,18 @@ describe("handlePreflightState", () => { }); expect(assertOnboardHostReadiness).toHaveBeenCalledTimes(2); - for (const [, , options] of assertOnboardHostReadiness.mock.calls) { - expect(options).toEqual(expect.objectContaining({ allowDeferredN1xManagedVllm: true })); - } + expect(assertOnboardHostReadiness).toHaveBeenNthCalledWith( + 1, + expect.anything(), + expect.anything(), + expect.objectContaining({ allowDeferredN1xManagedVllm: true }), + ); + expect(assertOnboardHostReadiness).toHaveBeenNthCalledWith( + 2, + expect.anything(), + expect.anything(), + expect.objectContaining({ allowDeferredN1xManagedVllm: true }), + ); }); it("rejects changed gateway ownership before cached resume probe effects (#7411)", async () => { From 323753b987c8aff44a4ae0d9914491b9fef51174 Mon Sep 17 00:00:00 2001 From: San Dang Date: Mon, 17 Aug 2026 22:27:56 +0700 Subject: [PATCH 08/10] fix(onboard): revert incorrect N1x rebuild changes Signed-off-by: San Dang --- docs/inference/set-up-vllm.mdx | 16 -- .../actions/sandbox/rebuild-gpu-opt-out.ts | 2 - ...d-managed-workload-context-window.test.ts} | 78 +--------- .../rebuild-preflight-target-phase.test.ts | 146 ------------------ .../sandbox/rebuild-preflight-target-phase.ts | 58 ------- .../sandbox/rebuild-target-runtime.test.ts | 42 +---- src/lib/onboard.ts | 7 +- .../authoritative-rebuild-target.test.ts | 35 ----- .../onboard/authoritative-rebuild-target.ts | 16 +- .../onboard/fatal-runtime-preflight.test.ts | 18 +-- src/lib/onboard/fatal-runtime-preflight.ts | 11 +- .../machine/handlers/preflight.test.ts | 27 ---- src/lib/onboard/machine/handlers/preflight.ts | 5 - .../onboard/machine/initial-flow-phases.ts | 2 - src/lib/onboard/types.ts | 2 - 15 files changed, 12 insertions(+), 453 deletions(-) rename src/lib/actions/sandbox/{rebuild-preflight-target-phase-orchestration.test.ts => rebuild-managed-workload-context-window.test.ts} (64%) diff --git a/docs/inference/set-up-vllm.mdx b/docs/inference/set-up-vllm.mdx index 2d6e013f4fa..71cac0768a6 100644 --- a/docs/inference/set-up-vllm.mdx +++ b/docs/inference/set-up-vllm.mdx @@ -371,22 +371,6 @@ Accepting the preview prompt sets explicit managed-vLLM intent for the narrow pe Declining the prompt or setting only `NEMOCLAW_NO_EXPRESS=1` stops the installer before onboarding; neither path opens interactive provider selection. Set `NEMOCLAW_PROVIDER=install-vllm` when you need to bypass the prompt with the same explicit preview intent. -On a host that still meets the N1x identity checks, `rebuild` reuses the recorded route as explicit preview intent only when all of these conditions hold: - -- The sandbox registry records provider `vllm-local`. -- The registry records model `nvidia/Qwen3.6-35B-A3B-NVFP4`. -- The registry records upstream endpoint `http://host.openshell.internal:8000/v1`. -- The registry records `endpointSource` as `onboard` and `openshellDriver` as `docker`. -- The replacement keeps the recorded provider and model, then re-derives the canonical local endpoint. -- `hostLocalInferenceReceipt` is absent or `null`, or contains a canonical vLLM receipt for `host.openshell.internal:8000` and the same model. - -NemoClaw v0.0.109 can omit `hostLocalInferenceReceipt` or record it as `null`. -Matching sandboxes from that release remain eligible only when every condition above passes. -You do not need to set `NEMOCLAW_PROVIDER` again for a matching rebuild. -Any unmet condition stops readiness preflight before NemoClaw deletes the existing sandbox. -A malformed, non-vLLM, or conflicting receipt also stops readiness preflight before deletion. -This reuse waives only the pending physical-validation finding and does not change the Deferred status. - Physical CUDA and CDI checks passed on one N1x host. The full NemoClaw Express E2E test is still pending, so the N1x platform status remains Deferred. diff --git a/src/lib/actions/sandbox/rebuild-gpu-opt-out.ts b/src/lib/actions/sandbox/rebuild-gpu-opt-out.ts index b75d7407373..d770d0a6ab3 100644 --- a/src/lib/actions/sandbox/rebuild-gpu-opt-out.ts +++ b/src/lib/actions/sandbox/rebuild-gpu-opt-out.ts @@ -127,8 +127,6 @@ export type RebuildRecreateOnboardOpts = { rebuildRegistryInferenceRoute?: RebuildRouteHandoff; rebuildProviderReconfigure?: RebuildProviderReconfigureHandoff; providerRecoveryReceipt?: ProviderRecoveryReceipt; - /** Recorded managed-vLLM intent admitted only by the N1x readiness exception. */ - allowDeferredN1xManagedVllm?: true; /** Target-scoped authority admitted by the authoritative rebuild preflight. */ rebuildGatewayAuthority?: CheckpointGatewayAuthority; preparedImageRebuild?: PreparedImageRebuildHandoff; diff --git a/src/lib/actions/sandbox/rebuild-preflight-target-phase-orchestration.test.ts b/src/lib/actions/sandbox/rebuild-managed-workload-context-window.test.ts similarity index 64% rename from src/lib/actions/sandbox/rebuild-preflight-target-phase-orchestration.test.ts rename to src/lib/actions/sandbox/rebuild-managed-workload-context-window.test.ts index ab4fcdeb885..20d454d82ed 100644 --- a/src/lib/actions/sandbox/rebuild-preflight-target-phase-orchestration.test.ts +++ b/src/lib/actions/sandbox/rebuild-managed-workload-context-window.test.ts @@ -1,11 +1,10 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { describe, expect, it, vi } from "vitest"; const mocks = vi.hoisted(() => ({ bail: vi.fn(), - preflightAuthoritativeOnboardRuntime: vi.fn(async (..._args: unknown[]) => false), prepareManagedWorkloadRebuildHandoff: vi.fn(), prepareSandboxWorkloadSourceFromRebuildHandoff: vi.fn(), prepareRebuildTargetConfig: vi.fn(), @@ -34,7 +33,7 @@ vi.mock("../../onboard/workload/runtime", () => ({ vi.mock("./rebuild-target-preflight", async (importOriginal) => ({ ...(await importOriginal()), hydrateMessagingConfigForRebuild: vi.fn(), - preflightAuthoritativeOnboardRuntime: mocks.preflightAuthoritativeOnboardRuntime, + preflightAuthoritativeOnboardRuntime: vi.fn(async () => false), prepareRebuildRecreateOptions: mocks.prepareRebuildRecreateOptions, prepareRebuildTargetConfig: mocks.prepareRebuildTargetConfig, stageRebuildHermesDashboardConfig: vi.fn(() => true), @@ -51,64 +50,7 @@ vi.mock("./rebuild-messaging-conflict-preflight", () => ({ import { managedRebuildProfileDependencies } from "./agents/managed-workload-rebuild-profile"; import { prepareRebuildTargetPreflights } from "./rebuild-preflight-target-phase"; -describe("prepareRebuildTargetPreflights", () => { - beforeEach(() => { - vi.clearAllMocks(); - mocks.prepareManagedWorkloadRebuildHandoff.mockResolvedValue(null); - mocks.preflightAuthoritativeOnboardRuntime.mockResolvedValue(false); - }); - - async function prepareN1xTarget(endpointSource: "onboard" | "inference-set") { - const resumeConfig = { - provider: "vllm-local", - model: "nvidia/Qwen3.6-35B-A3B-NVFP4", - preferredInferenceApi: "openai-completions", - pinEndpoint: true, - endpointUrl: null, - compatibleEndpointReasoning: null, - compatibleEndpointReasoningEffort: null, - registryInferenceRoute: null, - }; - mocks.prepareRebuildTargetConfig.mockReturnValue({ - agentDefinition: {}, - resumeConfig, - durableConfig: { - toolDisclosure: "progressive", - dcodeAutoApprovalMode: "disabled", - webSearchConfig: null, - }, - credentialEnv: null, - fromDockerfile: false, - hermesToolGateways: [], - }); - mocks.prepareRebuildRecreateOptions.mockReturnValue({ - controlUiPort: 18_789, - targetGatewayName: "nemoclaw", - toolDisclosure: "progressive", - dcodeAutoApprovalMode: "disabled", - observabilityEnabled: false, - }); - - await prepareRebuildTargetPreflights({ - sandboxName: "my-assistant", - sandboxEntry: { - name: "my-assistant", - agent: "openclaw", - gatewayName: "nemoclaw", - openshellDriver: "docker", - provider: resumeConfig.provider, - model: resumeConfig.model, - endpointUrl: "http://host.openshell.internal:8000/v1", - endpointSource, - } as never, - rebuildAgent: "openclaw", - autoYes: true, - log: vi.fn(), - bail: mocks.bail as never, - }); - return mocks.preflightAuthoritativeOnboardRuntime.mock.calls[0]?.[2]; - } - +describe("managed workload rebuild context-window preflight", () => { it("resolves the Ollama context window through target preparation", async () => { const catalogHandoff = { agent: "openclaw", @@ -181,18 +123,4 @@ describe("prepareRebuildTargetPreflights", () => { ).resolves.toBeNull(); expect(mocks.resolveContextWindowForModel).toHaveBeenCalledWith("ollama-local", "qwen3.5:9b"); }); - - it("passes exact legacy N1x intent into authoritative readiness (#9292)", async () => { - const readinessOptions = await prepareN1xTarget("onboard"); - - expect(readinessOptions).toEqual( - expect.objectContaining({ allowDeferredN1xManagedVllm: true }), - ); - }); - - it("withholds N1x intent for a mismatched endpoint source (#9292)", async () => { - const readinessOptions = await prepareN1xTarget("inference-set"); - - expect(readinessOptions).not.toHaveProperty("allowDeferredN1xManagedVllm"); - }); }); diff --git a/src/lib/actions/sandbox/rebuild-preflight-target-phase.test.ts b/src/lib/actions/sandbox/rebuild-preflight-target-phase.test.ts index 9797ee00ab9..86b7a30144c 100644 --- a/src/lib/actions/sandbox/rebuild-preflight-target-phase.test.ts +++ b/src/lib/actions/sandbox/rebuild-preflight-target-phase.test.ts @@ -3,22 +3,15 @@ import { afterEach, describe, expect, it, vi } from "vitest"; -import { - hostLocalInferenceReceipt, - serializedHostLocalInferenceReceipt, - serializedLlamaCppHostLocalInferenceReceipt, -} from "../../../../test/helpers/host-local-inference-receipt"; import type { ProviderRecoveryReceipt, RegistryInferenceRoute, } from "../../onboard/rebuild-route-handoff"; -import { serializeHostLocalInferenceReceipt } from "../../onboard/runtime-provider/host-local-inference"; import type { SandboxBaseImageResolutionMetadata } from "../../sandbox-base-image"; import { pinRebuildTargetGatewayForReadiness, runRebuildGatewayRecoveryAfterReadiness, stageRebuildBaseImageResolutionHandoff, - stageRecordedManagedVllmIntent, stageRegistryProviderRecoveryReceipt, } from "./rebuild-preflight-target-phase"; @@ -128,145 +121,6 @@ describe("stageRegistryProviderRecoveryReceipt", () => { }); }); -describe("stageRecordedManagedVllmIntent", () => { - const n1xExpressEntry = { - provider: "vllm-local", - model: "nvidia/Qwen3.6-35B-A3B-NVFP4", - endpointUrl: "http://host.openshell.internal:8000/v1", - endpointSource: "onboard" as const, - openshellDriver: "docker", - }; - const n1xExpressSelection = { - provider: n1xExpressEntry.provider, - model: n1xExpressEntry.model, - pinEndpoint: true, - endpointUrl: null, - }; - - it.each([ - { receiptState: "absent", sandboxEntry: n1xExpressEntry }, - { - receiptState: "null", - sandboxEntry: { ...n1xExpressEntry, hostLocalInferenceReceipt: null }, - }, - ])( - "carries the v0.0.109 N1x Express selection with a $receiptState receipt into rebuild readiness (#9292)", - ({ sandboxEntry }) => { - const recreateOptions: { allowDeferredN1xManagedVllm?: true } = {}; - - stageRecordedManagedVllmIntent(recreateOptions, sandboxEntry, n1xExpressSelection); - - expect(recreateOptions.allowDeferredN1xManagedVllm).toBe(true); - }, - ); - - it("also accepts a canonical vLLM receipt on the exact N1x Express selection (#9292)", () => { - const recreateOptions: { allowDeferredN1xManagedVllm?: true } = {}; - const genericReceipt = hostLocalInferenceReceipt("docker"); - const n1xReceipt = serializeHostLocalInferenceReceipt({ - ...genericReceipt, - endpoint: { - ...genericReceipt.endpoint, - host: "host.openshell.internal", - }, - inference: { - protocol: "openai-chat-completions", - model: n1xExpressEntry.model, - toolCallingRequired: true, - }, - }); - - stageRecordedManagedVllmIntent( - recreateOptions, - { ...n1xExpressEntry, hostLocalInferenceReceipt: n1xReceipt }, - n1xExpressSelection, - ); - - expect(recreateOptions.allowDeferredN1xManagedVllm).toBe(true); - }); - - it.each([ - { - caseName: "different recorded provider", - sandboxEntry: { ...n1xExpressEntry, provider: "compatible-endpoint" }, - rebuildSelection: n1xExpressSelection, - }, - { - caseName: "different recorded model", - sandboxEntry: { ...n1xExpressEntry, model: "meta-llama/Llama-3.1-8B-Instruct" }, - rebuildSelection: n1xExpressSelection, - }, - { - caseName: "different recorded endpoint", - sandboxEntry: { ...n1xExpressEntry, endpointUrl: "http://host.openshell.internal:8001/v1" }, - rebuildSelection: n1xExpressSelection, - }, - { - caseName: "different endpoint source", - sandboxEntry: { ...n1xExpressEntry, endpointSource: "inference-set" as const }, - rebuildSelection: n1xExpressSelection, - }, - { - caseName: "different OpenShell driver", - sandboxEntry: { ...n1xExpressEntry, openshellDriver: "podman" }, - rebuildSelection: n1xExpressSelection, - }, - { - caseName: "malformed receipt", - sandboxEntry: { ...n1xExpressEntry, hostLocalInferenceReceipt: "not-json" }, - rebuildSelection: n1xExpressSelection, - }, - { - caseName: "different host-local service", - sandboxEntry: { - ...n1xExpressEntry, - hostLocalInferenceReceipt: serializedLlamaCppHostLocalInferenceReceipt(), - }, - rebuildSelection: n1xExpressSelection, - }, - { - caseName: "conflicting vLLM receipt", - sandboxEntry: { - ...n1xExpressEntry, - hostLocalInferenceReceipt: serializedHostLocalInferenceReceipt(), - }, - rebuildSelection: n1xExpressSelection, - }, - { - caseName: "different rebuild provider", - sandboxEntry: n1xExpressEntry, - rebuildSelection: { ...n1xExpressSelection, provider: "compatible-endpoint" }, - }, - { - caseName: "different rebuild model", - sandboxEntry: n1xExpressEntry, - rebuildSelection: { ...n1xExpressSelection, model: "meta-llama/Llama-3.1-8B-Instruct" }, - }, - { - caseName: "unresolved rebuild endpoint", - sandboxEntry: n1xExpressEntry, - rebuildSelection: { ...n1xExpressSelection, pinEndpoint: false }, - }, - { - caseName: "noncanonical rebuild endpoint", - sandboxEntry: n1xExpressEntry, - rebuildSelection: { - ...n1xExpressSelection, - endpointUrl: "http://host.openshell.internal:8001/v1", - }, - }, - ])( - "does not infer N1x Express intent from a $caseName (#9292)", - ({ sandboxEntry, rebuildSelection }) => { - const recreateOptions: { allowDeferredN1xManagedVllm?: true } = {}; - - stageRecordedManagedVllmIntent(recreateOptions, sandboxEntry, rebuildSelection); - - expect(recreateOptions).not.toHaveProperty("allowDeferredN1xManagedVllm"); - }, - ); -}); - describe("stageRebuildBaseImageResolutionHandoff", () => { it("binds outer resolver provenance to its immutable local handoff (#7144)", () => { const imageId = `sha256:${"a".repeat(64)}`; diff --git a/src/lib/actions/sandbox/rebuild-preflight-target-phase.ts b/src/lib/actions/sandbox/rebuild-preflight-target-phase.ts index 56cf72cd060..9ccdf93d231 100644 --- a/src/lib/actions/sandbox/rebuild-preflight-target-phase.ts +++ b/src/lib/actions/sandbox/rebuild-preflight-target-phase.ts @@ -6,7 +6,6 @@ import { CLI_NAME } from "../../cli/branding"; import type { SandboxMessagingPlan } from "../../messaging"; import { isSandboxBaseImageRefreshRequested } from "../../onboard/base-image-resolution-flow"; import type { DcodeAutoApprovalMode } from "../../onboard/dcode-auto-approval"; -import { parseHostLocalInferenceReceipt } from "../../onboard/runtime-provider/host-local-inference"; import { createRebuildProviderReconfigureHandoff, mintProviderRecoveryReceipt, @@ -84,62 +83,6 @@ export function stageRegistryProviderRecoveryReceipt( ); } -const N1X_EXPRESS_PROVIDER = "vllm-local"; -const N1X_EXPRESS_MODEL = "nvidia/Qwen3.6-35B-A3B-NVFP4"; -const N1X_EXPRESS_ENDPOINT_URL = "http://host.openshell.internal:8000/v1"; - -/** Reuse only the exact N1x Express selection recorded by onboarding. */ -export function stageRecordedManagedVllmIntent( - recreateOptions: Pick, - sandboxEntry: Pick< - RebuildSandboxEntry, - | "provider" - | "model" - | "endpointUrl" - | "endpointSource" - | "openshellDriver" - | "hostLocalInferenceReceipt" - >, - rebuildSelection: { - provider: string; - model: string; - pinEndpoint: boolean; - endpointUrl: string | null; - }, -): void { - if ( - sandboxEntry.provider !== N1X_EXPRESS_PROVIDER || - sandboxEntry.model !== N1X_EXPRESS_MODEL || - sandboxEntry.endpointUrl !== N1X_EXPRESS_ENDPOINT_URL || - sandboxEntry.endpointSource !== "onboard" || - sandboxEntry.openshellDriver !== "docker" || - rebuildSelection.provider !== sandboxEntry.provider || - rebuildSelection.model !== sandboxEntry.model || - rebuildSelection.pinEndpoint !== true || - rebuildSelection.endpointUrl !== null - ) { - return; - } - const serialized = sandboxEntry.hostLocalInferenceReceipt; - if (serialized === undefined || serialized === null) { - recreateOptions.allowDeferredN1xManagedVllm = true; - return; - } - try { - const receipt = parseHostLocalInferenceReceipt(serialized); - if ( - receipt.service === "vllm" && - receipt.endpoint.host === "host.openshell.internal" && - receipt.endpoint.port === 8000 && - receipt.inference?.model === N1X_EXPRESS_MODEL - ) { - recreateOptions.allowDeferredN1xManagedVllm = true; - } - } catch { - // Malformed durable state must not grant the narrow N1x readiness exception. - } -} - export interface RebuildPreparedTarget { targetConfig: RebuildTargetConfig; recreateOptions: RebuildRecreateOnboardOpts; @@ -290,7 +233,6 @@ export async function prepareRebuildTargetPreflights(args: { recreateOptions.observabilityEnabled = requestedObservabilityEnabled ?? recreateOptions.observabilityEnabled; recreateOptions.observabilityRequestedExplicitly = requestedObservabilityEnabled !== undefined; - stageRecordedManagedVllmIntent(recreateOptions, sandboxEntry, resumeConfig); if ( !stageRebuildHermesDashboardConfig( rebuildAgent, diff --git a/src/lib/actions/sandbox/rebuild-target-runtime.test.ts b/src/lib/actions/sandbox/rebuild-target-runtime.test.ts index e03cc004781..928f0abcec2 100644 --- a/src/lib/actions/sandbox/rebuild-target-runtime.test.ts +++ b/src/lib/actions/sandbox/rebuild-target-runtime.test.ts @@ -9,7 +9,6 @@ const mocks = vi.hoisted(() => ({ ensureValidatedWebSearchCredential: vi.fn(), isDockerDesktopWslRuntime: vi.fn(), isLinuxDockerDriverGatewayEnabled: vi.fn(), - preflightAuthoritativeRebuildTarget: vi.fn(), preflightRebuildCredentials: vi.fn(), readGatewayProviderMetadata: vi.fn(), runOpenshell: vi.fn(), @@ -31,7 +30,6 @@ vi.mock("../../onboard/gateway-provider-metadata", async (importOriginal) => { vi.mock("./rebuild-onboard-dependencies", () => ({ rebuildOnboardDependencies: { ensureValidatedWebSearchCredential: mocks.ensureValidatedWebSearchCredential, - preflightAuthoritativeRebuildTarget: mocks.preflightAuthoritativeRebuildTarget, }, })); @@ -54,12 +52,8 @@ vi.mock("./rebuild-credential-preflight", () => ({ import * as rebuildImagePreflight from "./rebuild-custom-image-preflight"; import type { RebuildSandboxEntry } from "./rebuild-flow-helpers"; import type { RebuildRecreateOnboardOpts } from "./rebuild-gpu-opt-out"; -import type { RebuildResumeConfig } from "./rebuild-resume-config"; import type { RebuildTargetConfig } from "./rebuild-target-config"; -import { - preflightAuthoritativeOnboardRuntime, - preflightRebuildTargetRuntime, -} from "./rebuild-target-runtime"; +import { preflightRebuildTargetRuntime } from "./rebuild-target-runtime"; const platformDescriptor = Object.getOwnPropertyDescriptor(process, "platform")!; const TARGET = { @@ -195,40 +189,6 @@ describe("preflightRebuildTargetRuntime GPU route", () => { }); }); -describe("authoritative rebuild readiness", () => { - it("passes recorded managed-vLLM intent to the pre-delete readiness gate (#9292)", async () => { - const authority = { checkpoint: "gateway-authority" }; - mocks.preflightAuthoritativeRebuildTarget.mockResolvedValue(authority); - const recreateOptions = { - ...RECREATE_OPTIONS, - allowDeferredN1xManagedVllm: true, - } as RebuildRecreateOnboardOpts; - const bail = vi.fn((message: string): never => { - throw new Error(message); - }); - - await expect( - preflightAuthoritativeOnboardRuntime( - "alpha", - { provider: "vllm-local", model: "test-model" } as RebuildResumeConfig, - recreateOptions, - bail, - ), - ).resolves.toBe(true); - - expect(mocks.preflightAuthoritativeRebuildTarget).toHaveBeenCalledWith( - expect.objectContaining({ - allowDeferredN1xManagedVllm: true, - provider: "vllm-local", - model: "test-model", - sandboxName: "alpha", - }), - ); - expect(recreateOptions.rebuildGatewayAuthority).toBe(authority); - expect(bail).not.toHaveBeenCalled(); - }); -}); - describe("preflightRebuildTargetRuntime web search credential", () => { const WEB_SEARCH_TARGET = { ...TARGET, diff --git a/src/lib/onboard.ts b/src/lib/onboard.ts index 1cae38016e3..136b7401381 100644 --- a/src/lib/onboard.ts +++ b/src/lib/onboard.ts @@ -3012,7 +3012,11 @@ async function preflightAuthoritativeRebuildTarget( bindGatewayAuthority: () => bindGatewayOwner(getGatewayOwner()), runFatalRuntimePreflight: async () => onboardPreflightGatewayAuthority.runRuntimePreflight( - authoritativeRebuildTarget.authoritativeRebuildRuntimePreflightOptions(opts), + { + sandboxGpu: opts.sandboxGpu, + sandboxGpuDevice: opts.sandboxGpuDevice, + noGpu: opts.noGpu, + }, (code) => fail(`onboard runtime preflight exited with code ${String(code)}`), ), ensureOpenshell: () => @@ -3293,7 +3297,6 @@ async function runOnboard(opts: OnboardOptions = {}): Promise { sandboxGpuDevice: opts.sandboxGpuDevice ?? null, gpuRequested: opts.gpu === true, noGpu: opts.noGpu === true, - allowDeferredN1xManagedVllm: opts.allowDeferredN1xManagedVllm, env: process.env, recordedGpuPassthroughBeforePreflight, commitSelectedAgentTransition: selectedAgentTransition.commit, diff --git a/src/lib/onboard/authoritative-rebuild-target.test.ts b/src/lib/onboard/authoritative-rebuild-target.test.ts index 07a3b83bf33..52893b9fc96 100644 --- a/src/lib/onboard/authoritative-rebuild-target.test.ts +++ b/src/lib/onboard/authoritative-rebuild-target.test.ts @@ -4,9 +4,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { - authoritativeRebuildRuntimePreflightOptions, type AuthoritativeRebuildTargetDeps, - type AuthoritativeRebuildPreflightOptions, preflightAuthoritativeRebuildTarget, rebuildProviderFlowOptions, resolveAuthoritativeOnboardGatewayBinding, @@ -25,39 +23,6 @@ const target = { }; const originalGateway = process.env.OPENSHELL_GATEWAY; -describe("authoritative rebuild runtime preflight options", () => { - it("carries only target GPU state and recorded N1x preview intent (#9292)", () => { - const options = { - authoritativeResumeConfig: true, - sandboxName: "alpha", - provider: "vllm-local", - model: "nvidia/Qwen3.6-35B-A3B-NVFP4", - targetGatewayName: "nemoclaw-12345", - targetGatewayPort: 12345, - controlUiPort: null, - sandboxGpu: "enable", - sandboxGpuDevice: "nvidia.com/gpu=all", - noGpu: false, - allowDeferredN1xManagedVllm: true, - } satisfies AuthoritativeRebuildPreflightOptions; - - expect(authoritativeRebuildRuntimePreflightOptions(options)).toEqual({ - sandboxGpu: "enable", - sandboxGpuDevice: "nvidia.com/gpu=all", - noGpu: false, - allowDeferredN1xManagedVllm: true, - }); - - const { allowDeferredN1xManagedVllm: _recordedIntent, ...withoutRecordedIntent } = options; - expect(authoritativeRebuildRuntimePreflightOptions(withoutRecordedIntent)).toEqual({ - sandboxGpu: "enable", - sandboxGpuDevice: "nvidia.com/gpu=all", - noGpu: false, - allowDeferredN1xManagedVllm: false, - }); - }); -}); - function deps(overrides: Partial = {}) { return { resolveBaselinePolicy: vi.fn(() => ({})), diff --git a/src/lib/onboard/authoritative-rebuild-target.ts b/src/lib/onboard/authoritative-rebuild-target.ts index 796545bc9f6..ad527349f99 100644 --- a/src/lib/onboard/authoritative-rebuild-target.ts +++ b/src/lib/onboard/authoritative-rebuild-target.ts @@ -21,7 +21,7 @@ export type AuthoritativeGatewayOptions = Pick< export type AuthoritativeRebuildPreflightOptions = Pick< OnboardOptions, - "sandboxGpu" | "sandboxGpuDevice" | "noGpu" | "controlUiPort" | "allowDeferredN1xManagedVllm" + "sandboxGpu" | "sandboxGpuDevice" | "noGpu" | "controlUiPort" > & { authoritativeResumeConfig: true; /** Internal prepared-backup recovery defers route repair to authoritative onboard. */ @@ -33,20 +33,6 @@ export type AuthoritativeRebuildPreflightOptions = Pick< targetGatewayPort: number; }; -/** Project only the target-bound GPU and N1x intent into runtime preflight. */ -export function authoritativeRebuildRuntimePreflightOptions( - opts: AuthoritativeRebuildPreflightOptions, -): Pick & { - allowDeferredN1xManagedVllm: boolean; -} { - return { - sandboxGpu: opts.sandboxGpu, - sandboxGpuDevice: opts.sandboxGpuDevice, - noGpu: opts.noGpu, - allowDeferredN1xManagedVllm: opts.allowDeferredN1xManagedVllm === true, - }; -} - export function resolveAuthoritativeOnboardGatewayBinding( opts: AuthoritativeGatewayOptions, ): AuthoritativeOnboardGatewayBinding | null { diff --git a/src/lib/onboard/fatal-runtime-preflight.test.ts b/src/lib/onboard/fatal-runtime-preflight.test.ts index 04cc1dac2e8..8b9620c2cb0 100644 --- a/src/lib/onboard/fatal-runtime-preflight.test.ts +++ b/src/lib/onboard/fatal-runtime-preflight.test.ts @@ -91,7 +91,7 @@ afterEach(() => { }); describe("report-backed runtime readiness (#7411)", () => { - it("requires explicit managed-vLLM intent and lets rebuild reject ambient intent (#9292)", () => { + it("requires explicit managed-vLLM intent for the Deferred N1x readiness exception (#8574)", () => { const readiness: SystemReadinessReport = { schemaVersion: "1.1.0", status: "incompatible", @@ -137,14 +137,6 @@ describe("report-backed runtime readiness (#7411)", () => { }), ).toThrow("exit"); - expect( - assertOnboardSystemReadiness(readiness, hostWithRuntime("docker"), { - explicitlyOptedOutGpuPassthrough: false, - allowDeferredN1xManagedVllm: true, - exitProcess: exit as never, - }), - ).toBe(readiness); - vi.stubEnv("NEMOCLAW_PROVIDER", "install-vllm"); expect( assertOnboardSystemReadiness(readiness, hostWithRuntime("docker"), { @@ -152,14 +144,6 @@ describe("report-backed runtime readiness (#7411)", () => { exitProcess: exit as never, }), ).toBe(readiness); - - expect(() => - assertOnboardSystemReadiness(readiness, hostWithRuntime("docker"), { - explicitlyOptedOutGpuPassthrough: false, - allowDeferredN1xManagedVllm: false, - exitProcess: exit as never, - }), - ).toThrow("exit"); }); it("rejects ambiguous gateway ownership before the caller can run effects", () => { diff --git a/src/lib/onboard/fatal-runtime-preflight.ts b/src/lib/onboard/fatal-runtime-preflight.ts index 237229ce26e..eef29cd7ecb 100644 --- a/src/lib/onboard/fatal-runtime-preflight.ts +++ b/src/lib/onboard/fatal-runtime-preflight.ts @@ -46,8 +46,6 @@ export type FatalRuntimePreflightOptions = Pick< OnboardOptions, "sandboxGpu" | "sandboxGpuDevice" | "gpu" | "noGpu" > & { - /** Explicit false prevents ambient provider intent from crossing a rebuild boundary. */ - allowDeferredN1xManagedVllm?: boolean; optedOutGpuPassthrough?: boolean; }; @@ -108,7 +106,6 @@ export interface OnboardHostReadinessOptions { resuming?: boolean; allowStorageRemediation?: boolean; allowPortableHostPreparation?: boolean; - allowDeferredN1xManagedVllm?: boolean; exitProcess?: (code: number) => never; observedAt?: string; now?: () => Date; @@ -156,9 +153,7 @@ export function assertOnboardSystemReadiness( isPortableExperimentalProfile() || !isLinuxDockerDriverGatewayEnabled(), allowStorageRemediation: options.allowStorageRemediation === true, allowPortableHostPreparation: options.allowPortableHostPreparation, - allowDeferredN1xManagedVllm: - options.allowDeferredN1xManagedVllm ?? - process.env.NEMOCLAW_PROVIDER === MANAGED_VLLM_PROVIDER_KEY, + allowDeferredN1xManagedVllm: process.env.NEMOCLAW_PROVIDER === MANAGED_VLLM_PROVIDER_KEY, }); if (admission.admitted) return readinessReport; const jetsonRuntimeMissing = admission.findingIds.includes("host.gpu.nvidia_runtime_missing"); @@ -267,7 +262,6 @@ function refreshOnboardHostReadiness( wslDockerDesktopGpuProofPassed: runtimeGpu?.wslDockerDesktopGpuProofPassed, resuming: context.resuming, allowStorageRemediation, - allowDeferredN1xManagedVllm: options.allowDeferredN1xManagedVllm, exitProcess: context.exitProcess, observedAt, now, @@ -381,7 +375,6 @@ export async function runReadinessGatedRuntimePreflight( refreshedResult.sandboxGpuConfig.mode === "0" || options.optedOutGpuPassthrough === true, resuming: context.resuming, allowStorageRemediation: managedGatewayReadiness, - allowDeferredN1xManagedVllm: options.allowDeferredN1xManagedVllm, exitProcess, }); // The only GPU detection path that may pull or start a container is delayed @@ -429,7 +422,6 @@ export async function runReadinessGatedRuntimePreflight( refreshedResult.sandboxGpuConfig.mode === "0" || options.optedOutGpuPassthrough === true, resuming: context.resuming, allowStorageRemediation: managedGatewayReadiness, - allowDeferredN1xManagedVllm: options.allowDeferredN1xManagedVllm, exitProcess, }); const gatedResult = { @@ -464,7 +456,6 @@ export function runFatalOnboardRuntimePreflight( explicitlyOptedOutGpuPassthrough, resuming: context.resuming, allowStorageRemediation: context.allowStorageRemediation, - allowDeferredN1xManagedVllm: options.allowDeferredN1xManagedVllm, exitProcess, observedAt, now, diff --git a/src/lib/onboard/machine/handlers/preflight.test.ts b/src/lib/onboard/machine/handlers/preflight.test.ts index fec340b4401..2d7f487cf5a 100644 --- a/src/lib/onboard/machine/handlers/preflight.test.ts +++ b/src/lib/onboard/machine/handlers/preflight.test.ts @@ -195,33 +195,6 @@ describe("handlePreflightState", () => { expect(result.resumePreflight).toBe(true); }); - it("carries verified N1x intent through cached resume readiness (#9292)", async () => { - const session = createSession(); - session.steps.preflight.status = "complete"; - const assertOnboardHostReadiness = vi.fn(); - const harness = createDeps({ assertOnboardHostReadiness }); - - await handlePreflightState({ - ...baseOptions(harness.deps, session), - resume: true, - allowDeferredN1xManagedVllm: true, - }); - - expect(assertOnboardHostReadiness).toHaveBeenCalledTimes(2); - expect(assertOnboardHostReadiness).toHaveBeenNthCalledWith( - 1, - expect.anything(), - expect.anything(), - expect.objectContaining({ allowDeferredN1xManagedVllm: true }), - ); - expect(assertOnboardHostReadiness).toHaveBeenNthCalledWith( - 2, - expect.anything(), - expect.anything(), - expect.objectContaining({ allowDeferredN1xManagedVllm: true }), - ); - }); - it("rejects changed gateway ownership before cached resume probe effects (#7411)", async () => { const session = createSession(); session.steps.preflight.status = "complete"; diff --git a/src/lib/onboard/machine/handlers/preflight.ts b/src/lib/onboard/machine/handlers/preflight.ts index eb3c859e2b4..72c031c9c09 100644 --- a/src/lib/onboard/machine/handlers/preflight.ts +++ b/src/lib/onboard/machine/handlers/preflight.ts @@ -34,7 +34,6 @@ export interface PreflightStateOptions< sandboxGpuDevice?: string | null; gpuRequested: boolean; noGpu: boolean; - allowDeferredN1xManagedVllm?: boolean; env: NodeJS.ProcessEnv; deps: { getSandbox(name: string): SandboxEntry | null; @@ -56,7 +55,6 @@ export interface PreflightStateOptions< observedAt?: string; now?: () => Date; wslDockerDesktopGpuProofPassed?: boolean; - allowDeferredN1xManagedVllm?: boolean; resuming: true; }, ): void; @@ -131,7 +129,6 @@ export async function handlePreflightState< sandboxGpuDevice, gpuRequested, noGpu, - allowDeferredN1xManagedVllm, env, deps, }: PreflightStateOptions): Promise< @@ -178,7 +175,6 @@ export async function handlePreflightState< explicitlyOptedOutGpuPassthrough: resumeSandboxGpuConfig.mode === "0", observedAt: hostObservedAt, now, - allowDeferredN1xManagedVllm, resuming: true, }); // A full detector can run the bounded ARM64 WSL Docker GPU proof. Keep it @@ -201,7 +197,6 @@ export async function handlePreflightState< observedAt: hostObservedAt, now, ...(wslDockerDesktopGpuProofPassed === undefined ? {} : { wslDockerDesktopGpuProofPassed }), - allowDeferredN1xManagedVllm, resuming: true, }); } diff --git a/src/lib/onboard/machine/initial-flow-phases.ts b/src/lib/onboard/machine/initial-flow-phases.ts index 3fef64a6338..c53061ef1f6 100644 --- a/src/lib/onboard/machine/initial-flow-phases.ts +++ b/src/lib/onboard/machine/initial-flow-phases.ts @@ -53,7 +53,6 @@ export interface InitialOnboardFlowPhaseOptions< sandboxGpuDevice?: string | null; gpuRequested: boolean; noGpu: boolean; - allowDeferredN1xManagedVllm?: boolean; env: NodeJS.ProcessEnv; platform?: NodeJS.Platform; recordedGpuPassthroughBeforePreflight: boolean; @@ -145,7 +144,6 @@ export function createInitialOnboardFlowPhases< sandboxGpuDevice: options.sandboxGpuDevice ?? null, gpuRequested: options.gpuRequested, noGpu: options.noGpu, - allowDeferredN1xManagedVllm: options.allowDeferredN1xManagedVllm, env: options.env, deps: { ...options.preflightDeps, diff --git a/src/lib/onboard/types.ts b/src/lib/onboard/types.ts index 3783929be02..c6254404187 100644 --- a/src/lib/onboard/types.ts +++ b/src/lib/onboard/types.ts @@ -117,8 +117,6 @@ export type OnboardOptions = { rebuildProviderReconfigure?: import("./rebuild-route-handoff").RebuildProviderReconfigureHandoff; /** Internal one-shot authority to recover the recorded provider during a locked rebuild resume. */ providerRecoveryReceipt?: import("./rebuild-route-handoff").ProviderRecoveryReceipt; - /** Internal rebuild handoff for a recorded managed-vLLM N1x preview selection. */ - allowDeferredN1xManagedVllm?: true; /** Internal one-shot handoff for the exact image context validated before rebuild deletion. */ preparedImageRebuild?: import("./prepared-dcode-rebuild").PreparedImageRebuildHandoff; /** Internal immutable managed-image/profile handoff validated before rebuild deletion. */ From 6b26b724f9e056cad1081b22a6faf32b130f6e57 Mon Sep 17 00:00:00 2001 From: San Dang Date: Mon, 17 Aug 2026 22:39:57 +0700 Subject: [PATCH 09/10] fix(onboard): preserve derived N1x rebuild intent Signed-off-by: San Dang --- docs/inference/set-up-vllm.mdx | 16 ++ .../actions/sandbox/rebuild-gpu-opt-out.ts | 2 + ...flight-target-phase-orchestration.test.ts} | 78 ++++++++- .../rebuild-preflight-target-phase.test.ts | 153 ++++++++++++++++++ .../sandbox/rebuild-preflight-target-phase.ts | 60 +++++++ .../sandbox/rebuild-target-runtime.test.ts | 42 ++++- src/lib/onboard.ts | 7 +- .../authoritative-rebuild-target.test.ts | 35 ++++ .../onboard/authoritative-rebuild-target.ts | 16 +- .../onboard/fatal-runtime-preflight.test.ts | 18 ++- src/lib/onboard/fatal-runtime-preflight.ts | 11 +- .../machine/handlers/preflight.test.ts | 27 ++++ src/lib/onboard/machine/handlers/preflight.ts | 5 + .../onboard/machine/initial-flow-phases.ts | 2 + src/lib/onboard/types.ts | 2 + 15 files changed, 462 insertions(+), 12 deletions(-) rename src/lib/actions/sandbox/{rebuild-managed-workload-context-window.test.ts => rebuild-preflight-target-phase-orchestration.test.ts} (64%) diff --git a/docs/inference/set-up-vllm.mdx b/docs/inference/set-up-vllm.mdx index 71cac0768a6..c7d80896bae 100644 --- a/docs/inference/set-up-vllm.mdx +++ b/docs/inference/set-up-vllm.mdx @@ -371,6 +371,22 @@ Accepting the preview prompt sets explicit managed-vLLM intent for the narrow pe Declining the prompt or setting only `NEMOCLAW_NO_EXPRESS=1` stops the installer before onboarding; neither path opens interactive provider selection. Set `NEMOCLAW_PROVIDER=install-vllm` when you need to bypass the prompt with the same explicit preview intent. +On a host that still meets the N1x identity checks, `rebuild` reuses the recorded route as explicit preview intent only when all of these conditions hold: + +- The sandbox registry records provider `vllm-local`. +- The registry records model `nvidia/Qwen3.6-35B-A3B-NVFP4`. +- If the registry records `endpointUrl` as `null`, NemoClaw derives `http://host.openshell.internal:8000/v1`; otherwise, `endpointUrl` must equal that URL. +- The registry records `endpointSource` as `onboard` and `openshellDriver` as `docker`. +- The replacement keeps the recorded provider and model, then re-derives the canonical local endpoint. +- `hostLocalInferenceReceipt` is absent or `null`, or contains a canonical vLLM receipt for `host.openshell.internal:8000` and the same model. + +NemoClaw v0.0.109 can omit `hostLocalInferenceReceipt` or record it as `null`. +Matching sandboxes from that release remain eligible only when every condition above passes. +You do not need to set `NEMOCLAW_PROVIDER` again for a matching rebuild. +Any unmet condition stops readiness preflight before NemoClaw deletes the existing sandbox. +A malformed, non-vLLM, or conflicting receipt also stops readiness preflight before deletion. +This reuse waives only the pending physical-validation finding and does not change the Deferred status. + Physical CUDA and CDI checks passed on one N1x host. The full NemoClaw Express E2E test is still pending, so the N1x platform status remains Deferred. diff --git a/src/lib/actions/sandbox/rebuild-gpu-opt-out.ts b/src/lib/actions/sandbox/rebuild-gpu-opt-out.ts index d770d0a6ab3..b75d7407373 100644 --- a/src/lib/actions/sandbox/rebuild-gpu-opt-out.ts +++ b/src/lib/actions/sandbox/rebuild-gpu-opt-out.ts @@ -127,6 +127,8 @@ export type RebuildRecreateOnboardOpts = { rebuildRegistryInferenceRoute?: RebuildRouteHandoff; rebuildProviderReconfigure?: RebuildProviderReconfigureHandoff; providerRecoveryReceipt?: ProviderRecoveryReceipt; + /** Recorded managed-vLLM intent admitted only by the N1x readiness exception. */ + allowDeferredN1xManagedVllm?: true; /** Target-scoped authority admitted by the authoritative rebuild preflight. */ rebuildGatewayAuthority?: CheckpointGatewayAuthority; preparedImageRebuild?: PreparedImageRebuildHandoff; diff --git a/src/lib/actions/sandbox/rebuild-managed-workload-context-window.test.ts b/src/lib/actions/sandbox/rebuild-preflight-target-phase-orchestration.test.ts similarity index 64% rename from src/lib/actions/sandbox/rebuild-managed-workload-context-window.test.ts rename to src/lib/actions/sandbox/rebuild-preflight-target-phase-orchestration.test.ts index 20d454d82ed..ab4fcdeb885 100644 --- a/src/lib/actions/sandbox/rebuild-managed-workload-context-window.test.ts +++ b/src/lib/actions/sandbox/rebuild-preflight-target-phase-orchestration.test.ts @@ -1,10 +1,11 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { describe, expect, it, vi } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; const mocks = vi.hoisted(() => ({ bail: vi.fn(), + preflightAuthoritativeOnboardRuntime: vi.fn(async (..._args: unknown[]) => false), prepareManagedWorkloadRebuildHandoff: vi.fn(), prepareSandboxWorkloadSourceFromRebuildHandoff: vi.fn(), prepareRebuildTargetConfig: vi.fn(), @@ -33,7 +34,7 @@ vi.mock("../../onboard/workload/runtime", () => ({ vi.mock("./rebuild-target-preflight", async (importOriginal) => ({ ...(await importOriginal()), hydrateMessagingConfigForRebuild: vi.fn(), - preflightAuthoritativeOnboardRuntime: vi.fn(async () => false), + preflightAuthoritativeOnboardRuntime: mocks.preflightAuthoritativeOnboardRuntime, prepareRebuildRecreateOptions: mocks.prepareRebuildRecreateOptions, prepareRebuildTargetConfig: mocks.prepareRebuildTargetConfig, stageRebuildHermesDashboardConfig: vi.fn(() => true), @@ -50,7 +51,64 @@ vi.mock("./rebuild-messaging-conflict-preflight", () => ({ import { managedRebuildProfileDependencies } from "./agents/managed-workload-rebuild-profile"; import { prepareRebuildTargetPreflights } from "./rebuild-preflight-target-phase"; -describe("managed workload rebuild context-window preflight", () => { +describe("prepareRebuildTargetPreflights", () => { + beforeEach(() => { + vi.clearAllMocks(); + mocks.prepareManagedWorkloadRebuildHandoff.mockResolvedValue(null); + mocks.preflightAuthoritativeOnboardRuntime.mockResolvedValue(false); + }); + + async function prepareN1xTarget(endpointSource: "onboard" | "inference-set") { + const resumeConfig = { + provider: "vllm-local", + model: "nvidia/Qwen3.6-35B-A3B-NVFP4", + preferredInferenceApi: "openai-completions", + pinEndpoint: true, + endpointUrl: null, + compatibleEndpointReasoning: null, + compatibleEndpointReasoningEffort: null, + registryInferenceRoute: null, + }; + mocks.prepareRebuildTargetConfig.mockReturnValue({ + agentDefinition: {}, + resumeConfig, + durableConfig: { + toolDisclosure: "progressive", + dcodeAutoApprovalMode: "disabled", + webSearchConfig: null, + }, + credentialEnv: null, + fromDockerfile: false, + hermesToolGateways: [], + }); + mocks.prepareRebuildRecreateOptions.mockReturnValue({ + controlUiPort: 18_789, + targetGatewayName: "nemoclaw", + toolDisclosure: "progressive", + dcodeAutoApprovalMode: "disabled", + observabilityEnabled: false, + }); + + await prepareRebuildTargetPreflights({ + sandboxName: "my-assistant", + sandboxEntry: { + name: "my-assistant", + agent: "openclaw", + gatewayName: "nemoclaw", + openshellDriver: "docker", + provider: resumeConfig.provider, + model: resumeConfig.model, + endpointUrl: "http://host.openshell.internal:8000/v1", + endpointSource, + } as never, + rebuildAgent: "openclaw", + autoYes: true, + log: vi.fn(), + bail: mocks.bail as never, + }); + return mocks.preflightAuthoritativeOnboardRuntime.mock.calls[0]?.[2]; + } + it("resolves the Ollama context window through target preparation", async () => { const catalogHandoff = { agent: "openclaw", @@ -123,4 +181,18 @@ describe("managed workload rebuild context-window preflight", () => { ).resolves.toBeNull(); expect(mocks.resolveContextWindowForModel).toHaveBeenCalledWith("ollama-local", "qwen3.5:9b"); }); + + it("passes exact legacy N1x intent into authoritative readiness (#9292)", async () => { + const readinessOptions = await prepareN1xTarget("onboard"); + + expect(readinessOptions).toEqual( + expect.objectContaining({ allowDeferredN1xManagedVllm: true }), + ); + }); + + it("withholds N1x intent for a mismatched endpoint source (#9292)", async () => { + const readinessOptions = await prepareN1xTarget("inference-set"); + + expect(readinessOptions).not.toHaveProperty("allowDeferredN1xManagedVllm"); + }); }); diff --git a/src/lib/actions/sandbox/rebuild-preflight-target-phase.test.ts b/src/lib/actions/sandbox/rebuild-preflight-target-phase.test.ts index 86b7a30144c..32f4aaa25a9 100644 --- a/src/lib/actions/sandbox/rebuild-preflight-target-phase.test.ts +++ b/src/lib/actions/sandbox/rebuild-preflight-target-phase.test.ts @@ -3,15 +3,22 @@ import { afterEach, describe, expect, it, vi } from "vitest"; +import { + hostLocalInferenceReceipt, + serializedHostLocalInferenceReceipt, + serializedLlamaCppHostLocalInferenceReceipt, +} from "../../../../test/helpers/host-local-inference-receipt"; import type { ProviderRecoveryReceipt, RegistryInferenceRoute, } from "../../onboard/rebuild-route-handoff"; +import { serializeHostLocalInferenceReceipt } from "../../onboard/runtime-provider/host-local-inference"; import type { SandboxBaseImageResolutionMetadata } from "../../sandbox-base-image"; import { pinRebuildTargetGatewayForReadiness, runRebuildGatewayRecoveryAfterReadiness, stageRebuildBaseImageResolutionHandoff, + stageRecordedManagedVllmIntent, stageRegistryProviderRecoveryReceipt, } from "./rebuild-preflight-target-phase"; @@ -121,6 +128,152 @@ describe("stageRegistryProviderRecoveryReceipt", () => { }); }); +describe("stageRecordedManagedVllmIntent", () => { + const n1xExpressEntry = { + provider: "vllm-local", + model: "nvidia/Qwen3.6-35B-A3B-NVFP4", + endpointUrl: null, + endpointSource: "onboard" as const, + openshellDriver: "docker", + }; + const n1xExpressSelection = { + provider: n1xExpressEntry.provider, + model: n1xExpressEntry.model, + pinEndpoint: true, + endpointUrl: null, + }; + + it.each([ + { state: "derived endpoint and absent receipt", sandboxEntry: n1xExpressEntry }, + { + state: "derived endpoint and null receipt", + sandboxEntry: { ...n1xExpressEntry, hostLocalInferenceReceipt: null }, + }, + { + state: "recorded canonical endpoint", + sandboxEntry: { + ...n1xExpressEntry, + endpointUrl: "http://host.openshell.internal:8000/v1", + }, + }, + ])( + "carries the v0.0.109 N1x Express selection with a $state into rebuild readiness (#9292)", + ({ sandboxEntry }) => { + const recreateOptions: { allowDeferredN1xManagedVllm?: true } = {}; + + stageRecordedManagedVllmIntent(recreateOptions, sandboxEntry, n1xExpressSelection); + + expect(recreateOptions.allowDeferredN1xManagedVllm).toBe(true); + }, + ); + + it("also accepts a canonical vLLM receipt on the exact N1x Express selection (#9292)", () => { + const recreateOptions: { allowDeferredN1xManagedVllm?: true } = {}; + const genericReceipt = hostLocalInferenceReceipt("docker"); + const n1xReceipt = serializeHostLocalInferenceReceipt({ + ...genericReceipt, + endpoint: { + ...genericReceipt.endpoint, + host: "host.openshell.internal", + }, + inference: { + protocol: "openai-chat-completions", + model: n1xExpressEntry.model, + toolCallingRequired: true, + }, + }); + + stageRecordedManagedVllmIntent( + recreateOptions, + { ...n1xExpressEntry, hostLocalInferenceReceipt: n1xReceipt }, + n1xExpressSelection, + ); + + expect(recreateOptions.allowDeferredN1xManagedVllm).toBe(true); + }); + + it.each([ + { + caseName: "different recorded provider", + sandboxEntry: { ...n1xExpressEntry, provider: "compatible-endpoint" }, + rebuildSelection: n1xExpressSelection, + }, + { + caseName: "different recorded model", + sandboxEntry: { ...n1xExpressEntry, model: "meta-llama/Llama-3.1-8B-Instruct" }, + rebuildSelection: n1xExpressSelection, + }, + { + caseName: "different recorded endpoint", + sandboxEntry: { ...n1xExpressEntry, endpointUrl: "http://host.openshell.internal:8001/v1" }, + rebuildSelection: n1xExpressSelection, + }, + { + caseName: "different endpoint source", + sandboxEntry: { ...n1xExpressEntry, endpointSource: "inference-set" as const }, + rebuildSelection: n1xExpressSelection, + }, + { + caseName: "different OpenShell driver", + sandboxEntry: { ...n1xExpressEntry, openshellDriver: "podman" }, + rebuildSelection: n1xExpressSelection, + }, + { + caseName: "malformed receipt", + sandboxEntry: { ...n1xExpressEntry, hostLocalInferenceReceipt: "not-json" }, + rebuildSelection: n1xExpressSelection, + }, + { + caseName: "different host-local service", + sandboxEntry: { + ...n1xExpressEntry, + hostLocalInferenceReceipt: serializedLlamaCppHostLocalInferenceReceipt(), + }, + rebuildSelection: n1xExpressSelection, + }, + { + caseName: "conflicting vLLM receipt", + sandboxEntry: { + ...n1xExpressEntry, + hostLocalInferenceReceipt: serializedHostLocalInferenceReceipt(), + }, + rebuildSelection: n1xExpressSelection, + }, + { + caseName: "different rebuild provider", + sandboxEntry: n1xExpressEntry, + rebuildSelection: { ...n1xExpressSelection, provider: "compatible-endpoint" }, + }, + { + caseName: "different rebuild model", + sandboxEntry: n1xExpressEntry, + rebuildSelection: { ...n1xExpressSelection, model: "meta-llama/Llama-3.1-8B-Instruct" }, + }, + { + caseName: "unresolved rebuild endpoint", + sandboxEntry: n1xExpressEntry, + rebuildSelection: { ...n1xExpressSelection, pinEndpoint: false }, + }, + { + caseName: "noncanonical rebuild endpoint", + sandboxEntry: n1xExpressEntry, + rebuildSelection: { + ...n1xExpressSelection, + endpointUrl: "http://host.openshell.internal:8001/v1", + }, + }, + ])( + "does not infer N1x Express intent from a $caseName (#9292)", + ({ sandboxEntry, rebuildSelection }) => { + const recreateOptions: { allowDeferredN1xManagedVllm?: true } = {}; + + stageRecordedManagedVllmIntent(recreateOptions, sandboxEntry, rebuildSelection); + + expect(recreateOptions).not.toHaveProperty("allowDeferredN1xManagedVllm"); + }, + ); +}); + describe("stageRebuildBaseImageResolutionHandoff", () => { it("binds outer resolver provenance to its immutable local handoff (#7144)", () => { const imageId = `sha256:${"a".repeat(64)}`; diff --git a/src/lib/actions/sandbox/rebuild-preflight-target-phase.ts b/src/lib/actions/sandbox/rebuild-preflight-target-phase.ts index 9ccdf93d231..c64852ae3f9 100644 --- a/src/lib/actions/sandbox/rebuild-preflight-target-phase.ts +++ b/src/lib/actions/sandbox/rebuild-preflight-target-phase.ts @@ -6,6 +6,7 @@ import { CLI_NAME } from "../../cli/branding"; import type { SandboxMessagingPlan } from "../../messaging"; import { isSandboxBaseImageRefreshRequested } from "../../onboard/base-image-resolution-flow"; import type { DcodeAutoApprovalMode } from "../../onboard/dcode-auto-approval"; +import { parseHostLocalInferenceReceipt } from "../../onboard/runtime-provider/host-local-inference"; import { createRebuildProviderReconfigureHandoff, mintProviderRecoveryReceipt, @@ -83,6 +84,64 @@ export function stageRegistryProviderRecoveryReceipt( ); } +const N1X_EXPRESS_PROVIDER = "vllm-local"; +const N1X_EXPRESS_MODEL = "nvidia/Qwen3.6-35B-A3B-NVFP4"; +const N1X_EXPRESS_ENDPOINT_URL = "http://host.openshell.internal:8000/v1"; + +/** Reuse only the exact N1x Express selection recorded by onboarding. */ +export function stageRecordedManagedVllmIntent( + recreateOptions: Pick, + sandboxEntry: Pick< + RebuildSandboxEntry, + | "provider" + | "model" + | "endpointUrl" + | "endpointSource" + | "openshellDriver" + | "hostLocalInferenceReceipt" + >, + rebuildSelection: { + provider: string; + model: string; + pinEndpoint: boolean; + endpointUrl: string | null; + }, +): void { + const recordedEndpointUsesCanonicalLocalRoute = + sandboxEntry.endpointUrl === null || sandboxEntry.endpointUrl === N1X_EXPRESS_ENDPOINT_URL; + if ( + sandboxEntry.provider !== N1X_EXPRESS_PROVIDER || + sandboxEntry.model !== N1X_EXPRESS_MODEL || + !recordedEndpointUsesCanonicalLocalRoute || + sandboxEntry.endpointSource !== "onboard" || + sandboxEntry.openshellDriver !== "docker" || + rebuildSelection.provider !== sandboxEntry.provider || + rebuildSelection.model !== sandboxEntry.model || + rebuildSelection.pinEndpoint !== true || + rebuildSelection.endpointUrl !== null + ) { + return; + } + const serialized = sandboxEntry.hostLocalInferenceReceipt; + if (serialized === undefined || serialized === null) { + recreateOptions.allowDeferredN1xManagedVllm = true; + return; + } + try { + const receipt = parseHostLocalInferenceReceipt(serialized); + if ( + receipt.service === "vllm" && + receipt.endpoint.host === "host.openshell.internal" && + receipt.endpoint.port === 8000 && + receipt.inference?.model === N1X_EXPRESS_MODEL + ) { + recreateOptions.allowDeferredN1xManagedVllm = true; + } + } catch { + // Malformed durable state must not grant the narrow N1x readiness exception. + } +} + export interface RebuildPreparedTarget { targetConfig: RebuildTargetConfig; recreateOptions: RebuildRecreateOnboardOpts; @@ -233,6 +292,7 @@ export async function prepareRebuildTargetPreflights(args: { recreateOptions.observabilityEnabled = requestedObservabilityEnabled ?? recreateOptions.observabilityEnabled; recreateOptions.observabilityRequestedExplicitly = requestedObservabilityEnabled !== undefined; + stageRecordedManagedVllmIntent(recreateOptions, sandboxEntry, resumeConfig); if ( !stageRebuildHermesDashboardConfig( rebuildAgent, diff --git a/src/lib/actions/sandbox/rebuild-target-runtime.test.ts b/src/lib/actions/sandbox/rebuild-target-runtime.test.ts index 928f0abcec2..e03cc004781 100644 --- a/src/lib/actions/sandbox/rebuild-target-runtime.test.ts +++ b/src/lib/actions/sandbox/rebuild-target-runtime.test.ts @@ -9,6 +9,7 @@ const mocks = vi.hoisted(() => ({ ensureValidatedWebSearchCredential: vi.fn(), isDockerDesktopWslRuntime: vi.fn(), isLinuxDockerDriverGatewayEnabled: vi.fn(), + preflightAuthoritativeRebuildTarget: vi.fn(), preflightRebuildCredentials: vi.fn(), readGatewayProviderMetadata: vi.fn(), runOpenshell: vi.fn(), @@ -30,6 +31,7 @@ vi.mock("../../onboard/gateway-provider-metadata", async (importOriginal) => { vi.mock("./rebuild-onboard-dependencies", () => ({ rebuildOnboardDependencies: { ensureValidatedWebSearchCredential: mocks.ensureValidatedWebSearchCredential, + preflightAuthoritativeRebuildTarget: mocks.preflightAuthoritativeRebuildTarget, }, })); @@ -52,8 +54,12 @@ vi.mock("./rebuild-credential-preflight", () => ({ import * as rebuildImagePreflight from "./rebuild-custom-image-preflight"; import type { RebuildSandboxEntry } from "./rebuild-flow-helpers"; import type { RebuildRecreateOnboardOpts } from "./rebuild-gpu-opt-out"; +import type { RebuildResumeConfig } from "./rebuild-resume-config"; import type { RebuildTargetConfig } from "./rebuild-target-config"; -import { preflightRebuildTargetRuntime } from "./rebuild-target-runtime"; +import { + preflightAuthoritativeOnboardRuntime, + preflightRebuildTargetRuntime, +} from "./rebuild-target-runtime"; const platformDescriptor = Object.getOwnPropertyDescriptor(process, "platform")!; const TARGET = { @@ -189,6 +195,40 @@ describe("preflightRebuildTargetRuntime GPU route", () => { }); }); +describe("authoritative rebuild readiness", () => { + it("passes recorded managed-vLLM intent to the pre-delete readiness gate (#9292)", async () => { + const authority = { checkpoint: "gateway-authority" }; + mocks.preflightAuthoritativeRebuildTarget.mockResolvedValue(authority); + const recreateOptions = { + ...RECREATE_OPTIONS, + allowDeferredN1xManagedVllm: true, + } as RebuildRecreateOnboardOpts; + const bail = vi.fn((message: string): never => { + throw new Error(message); + }); + + await expect( + preflightAuthoritativeOnboardRuntime( + "alpha", + { provider: "vllm-local", model: "test-model" } as RebuildResumeConfig, + recreateOptions, + bail, + ), + ).resolves.toBe(true); + + expect(mocks.preflightAuthoritativeRebuildTarget).toHaveBeenCalledWith( + expect.objectContaining({ + allowDeferredN1xManagedVllm: true, + provider: "vllm-local", + model: "test-model", + sandboxName: "alpha", + }), + ); + expect(recreateOptions.rebuildGatewayAuthority).toBe(authority); + expect(bail).not.toHaveBeenCalled(); + }); +}); + describe("preflightRebuildTargetRuntime web search credential", () => { const WEB_SEARCH_TARGET = { ...TARGET, diff --git a/src/lib/onboard.ts b/src/lib/onboard.ts index 136b7401381..1cae38016e3 100644 --- a/src/lib/onboard.ts +++ b/src/lib/onboard.ts @@ -3012,11 +3012,7 @@ async function preflightAuthoritativeRebuildTarget( bindGatewayAuthority: () => bindGatewayOwner(getGatewayOwner()), runFatalRuntimePreflight: async () => onboardPreflightGatewayAuthority.runRuntimePreflight( - { - sandboxGpu: opts.sandboxGpu, - sandboxGpuDevice: opts.sandboxGpuDevice, - noGpu: opts.noGpu, - }, + authoritativeRebuildTarget.authoritativeRebuildRuntimePreflightOptions(opts), (code) => fail(`onboard runtime preflight exited with code ${String(code)}`), ), ensureOpenshell: () => @@ -3297,6 +3293,7 @@ async function runOnboard(opts: OnboardOptions = {}): Promise { sandboxGpuDevice: opts.sandboxGpuDevice ?? null, gpuRequested: opts.gpu === true, noGpu: opts.noGpu === true, + allowDeferredN1xManagedVllm: opts.allowDeferredN1xManagedVllm, env: process.env, recordedGpuPassthroughBeforePreflight, commitSelectedAgentTransition: selectedAgentTransition.commit, diff --git a/src/lib/onboard/authoritative-rebuild-target.test.ts b/src/lib/onboard/authoritative-rebuild-target.test.ts index 52893b9fc96..07a3b83bf33 100644 --- a/src/lib/onboard/authoritative-rebuild-target.test.ts +++ b/src/lib/onboard/authoritative-rebuild-target.test.ts @@ -4,7 +4,9 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { + authoritativeRebuildRuntimePreflightOptions, type AuthoritativeRebuildTargetDeps, + type AuthoritativeRebuildPreflightOptions, preflightAuthoritativeRebuildTarget, rebuildProviderFlowOptions, resolveAuthoritativeOnboardGatewayBinding, @@ -23,6 +25,39 @@ const target = { }; const originalGateway = process.env.OPENSHELL_GATEWAY; +describe("authoritative rebuild runtime preflight options", () => { + it("carries only target GPU state and recorded N1x preview intent (#9292)", () => { + const options = { + authoritativeResumeConfig: true, + sandboxName: "alpha", + provider: "vllm-local", + model: "nvidia/Qwen3.6-35B-A3B-NVFP4", + targetGatewayName: "nemoclaw-12345", + targetGatewayPort: 12345, + controlUiPort: null, + sandboxGpu: "enable", + sandboxGpuDevice: "nvidia.com/gpu=all", + noGpu: false, + allowDeferredN1xManagedVllm: true, + } satisfies AuthoritativeRebuildPreflightOptions; + + expect(authoritativeRebuildRuntimePreflightOptions(options)).toEqual({ + sandboxGpu: "enable", + sandboxGpuDevice: "nvidia.com/gpu=all", + noGpu: false, + allowDeferredN1xManagedVllm: true, + }); + + const { allowDeferredN1xManagedVllm: _recordedIntent, ...withoutRecordedIntent } = options; + expect(authoritativeRebuildRuntimePreflightOptions(withoutRecordedIntent)).toEqual({ + sandboxGpu: "enable", + sandboxGpuDevice: "nvidia.com/gpu=all", + noGpu: false, + allowDeferredN1xManagedVllm: false, + }); + }); +}); + function deps(overrides: Partial = {}) { return { resolveBaselinePolicy: vi.fn(() => ({})), diff --git a/src/lib/onboard/authoritative-rebuild-target.ts b/src/lib/onboard/authoritative-rebuild-target.ts index ad527349f99..796545bc9f6 100644 --- a/src/lib/onboard/authoritative-rebuild-target.ts +++ b/src/lib/onboard/authoritative-rebuild-target.ts @@ -21,7 +21,7 @@ export type AuthoritativeGatewayOptions = Pick< export type AuthoritativeRebuildPreflightOptions = Pick< OnboardOptions, - "sandboxGpu" | "sandboxGpuDevice" | "noGpu" | "controlUiPort" + "sandboxGpu" | "sandboxGpuDevice" | "noGpu" | "controlUiPort" | "allowDeferredN1xManagedVllm" > & { authoritativeResumeConfig: true; /** Internal prepared-backup recovery defers route repair to authoritative onboard. */ @@ -33,6 +33,20 @@ export type AuthoritativeRebuildPreflightOptions = Pick< targetGatewayPort: number; }; +/** Project only the target-bound GPU and N1x intent into runtime preflight. */ +export function authoritativeRebuildRuntimePreflightOptions( + opts: AuthoritativeRebuildPreflightOptions, +): Pick & { + allowDeferredN1xManagedVllm: boolean; +} { + return { + sandboxGpu: opts.sandboxGpu, + sandboxGpuDevice: opts.sandboxGpuDevice, + noGpu: opts.noGpu, + allowDeferredN1xManagedVllm: opts.allowDeferredN1xManagedVllm === true, + }; +} + export function resolveAuthoritativeOnboardGatewayBinding( opts: AuthoritativeGatewayOptions, ): AuthoritativeOnboardGatewayBinding | null { diff --git a/src/lib/onboard/fatal-runtime-preflight.test.ts b/src/lib/onboard/fatal-runtime-preflight.test.ts index 8b9620c2cb0..04cc1dac2e8 100644 --- a/src/lib/onboard/fatal-runtime-preflight.test.ts +++ b/src/lib/onboard/fatal-runtime-preflight.test.ts @@ -91,7 +91,7 @@ afterEach(() => { }); describe("report-backed runtime readiness (#7411)", () => { - it("requires explicit managed-vLLM intent for the Deferred N1x readiness exception (#8574)", () => { + it("requires explicit managed-vLLM intent and lets rebuild reject ambient intent (#9292)", () => { const readiness: SystemReadinessReport = { schemaVersion: "1.1.0", status: "incompatible", @@ -137,6 +137,14 @@ describe("report-backed runtime readiness (#7411)", () => { }), ).toThrow("exit"); + expect( + assertOnboardSystemReadiness(readiness, hostWithRuntime("docker"), { + explicitlyOptedOutGpuPassthrough: false, + allowDeferredN1xManagedVllm: true, + exitProcess: exit as never, + }), + ).toBe(readiness); + vi.stubEnv("NEMOCLAW_PROVIDER", "install-vllm"); expect( assertOnboardSystemReadiness(readiness, hostWithRuntime("docker"), { @@ -144,6 +152,14 @@ describe("report-backed runtime readiness (#7411)", () => { exitProcess: exit as never, }), ).toBe(readiness); + + expect(() => + assertOnboardSystemReadiness(readiness, hostWithRuntime("docker"), { + explicitlyOptedOutGpuPassthrough: false, + allowDeferredN1xManagedVllm: false, + exitProcess: exit as never, + }), + ).toThrow("exit"); }); it("rejects ambiguous gateway ownership before the caller can run effects", () => { diff --git a/src/lib/onboard/fatal-runtime-preflight.ts b/src/lib/onboard/fatal-runtime-preflight.ts index eef29cd7ecb..237229ce26e 100644 --- a/src/lib/onboard/fatal-runtime-preflight.ts +++ b/src/lib/onboard/fatal-runtime-preflight.ts @@ -46,6 +46,8 @@ export type FatalRuntimePreflightOptions = Pick< OnboardOptions, "sandboxGpu" | "sandboxGpuDevice" | "gpu" | "noGpu" > & { + /** Explicit false prevents ambient provider intent from crossing a rebuild boundary. */ + allowDeferredN1xManagedVllm?: boolean; optedOutGpuPassthrough?: boolean; }; @@ -106,6 +108,7 @@ export interface OnboardHostReadinessOptions { resuming?: boolean; allowStorageRemediation?: boolean; allowPortableHostPreparation?: boolean; + allowDeferredN1xManagedVllm?: boolean; exitProcess?: (code: number) => never; observedAt?: string; now?: () => Date; @@ -153,7 +156,9 @@ export function assertOnboardSystemReadiness( isPortableExperimentalProfile() || !isLinuxDockerDriverGatewayEnabled(), allowStorageRemediation: options.allowStorageRemediation === true, allowPortableHostPreparation: options.allowPortableHostPreparation, - allowDeferredN1xManagedVllm: process.env.NEMOCLAW_PROVIDER === MANAGED_VLLM_PROVIDER_KEY, + allowDeferredN1xManagedVllm: + options.allowDeferredN1xManagedVllm ?? + process.env.NEMOCLAW_PROVIDER === MANAGED_VLLM_PROVIDER_KEY, }); if (admission.admitted) return readinessReport; const jetsonRuntimeMissing = admission.findingIds.includes("host.gpu.nvidia_runtime_missing"); @@ -262,6 +267,7 @@ function refreshOnboardHostReadiness( wslDockerDesktopGpuProofPassed: runtimeGpu?.wslDockerDesktopGpuProofPassed, resuming: context.resuming, allowStorageRemediation, + allowDeferredN1xManagedVllm: options.allowDeferredN1xManagedVllm, exitProcess: context.exitProcess, observedAt, now, @@ -375,6 +381,7 @@ export async function runReadinessGatedRuntimePreflight( refreshedResult.sandboxGpuConfig.mode === "0" || options.optedOutGpuPassthrough === true, resuming: context.resuming, allowStorageRemediation: managedGatewayReadiness, + allowDeferredN1xManagedVllm: options.allowDeferredN1xManagedVllm, exitProcess, }); // The only GPU detection path that may pull or start a container is delayed @@ -422,6 +429,7 @@ export async function runReadinessGatedRuntimePreflight( refreshedResult.sandboxGpuConfig.mode === "0" || options.optedOutGpuPassthrough === true, resuming: context.resuming, allowStorageRemediation: managedGatewayReadiness, + allowDeferredN1xManagedVllm: options.allowDeferredN1xManagedVllm, exitProcess, }); const gatedResult = { @@ -456,6 +464,7 @@ export function runFatalOnboardRuntimePreflight( explicitlyOptedOutGpuPassthrough, resuming: context.resuming, allowStorageRemediation: context.allowStorageRemediation, + allowDeferredN1xManagedVllm: options.allowDeferredN1xManagedVllm, exitProcess, observedAt, now, diff --git a/src/lib/onboard/machine/handlers/preflight.test.ts b/src/lib/onboard/machine/handlers/preflight.test.ts index 2d7f487cf5a..fec340b4401 100644 --- a/src/lib/onboard/machine/handlers/preflight.test.ts +++ b/src/lib/onboard/machine/handlers/preflight.test.ts @@ -195,6 +195,33 @@ describe("handlePreflightState", () => { expect(result.resumePreflight).toBe(true); }); + it("carries verified N1x intent through cached resume readiness (#9292)", async () => { + const session = createSession(); + session.steps.preflight.status = "complete"; + const assertOnboardHostReadiness = vi.fn(); + const harness = createDeps({ assertOnboardHostReadiness }); + + await handlePreflightState({ + ...baseOptions(harness.deps, session), + resume: true, + allowDeferredN1xManagedVllm: true, + }); + + expect(assertOnboardHostReadiness).toHaveBeenCalledTimes(2); + expect(assertOnboardHostReadiness).toHaveBeenNthCalledWith( + 1, + expect.anything(), + expect.anything(), + expect.objectContaining({ allowDeferredN1xManagedVllm: true }), + ); + expect(assertOnboardHostReadiness).toHaveBeenNthCalledWith( + 2, + expect.anything(), + expect.anything(), + expect.objectContaining({ allowDeferredN1xManagedVllm: true }), + ); + }); + it("rejects changed gateway ownership before cached resume probe effects (#7411)", async () => { const session = createSession(); session.steps.preflight.status = "complete"; diff --git a/src/lib/onboard/machine/handlers/preflight.ts b/src/lib/onboard/machine/handlers/preflight.ts index 72c031c9c09..eb3c859e2b4 100644 --- a/src/lib/onboard/machine/handlers/preflight.ts +++ b/src/lib/onboard/machine/handlers/preflight.ts @@ -34,6 +34,7 @@ export interface PreflightStateOptions< sandboxGpuDevice?: string | null; gpuRequested: boolean; noGpu: boolean; + allowDeferredN1xManagedVllm?: boolean; env: NodeJS.ProcessEnv; deps: { getSandbox(name: string): SandboxEntry | null; @@ -55,6 +56,7 @@ export interface PreflightStateOptions< observedAt?: string; now?: () => Date; wslDockerDesktopGpuProofPassed?: boolean; + allowDeferredN1xManagedVllm?: boolean; resuming: true; }, ): void; @@ -129,6 +131,7 @@ export async function handlePreflightState< sandboxGpuDevice, gpuRequested, noGpu, + allowDeferredN1xManagedVllm, env, deps, }: PreflightStateOptions): Promise< @@ -175,6 +178,7 @@ export async function handlePreflightState< explicitlyOptedOutGpuPassthrough: resumeSandboxGpuConfig.mode === "0", observedAt: hostObservedAt, now, + allowDeferredN1xManagedVllm, resuming: true, }); // A full detector can run the bounded ARM64 WSL Docker GPU proof. Keep it @@ -197,6 +201,7 @@ export async function handlePreflightState< observedAt: hostObservedAt, now, ...(wslDockerDesktopGpuProofPassed === undefined ? {} : { wslDockerDesktopGpuProofPassed }), + allowDeferredN1xManagedVllm, resuming: true, }); } diff --git a/src/lib/onboard/machine/initial-flow-phases.ts b/src/lib/onboard/machine/initial-flow-phases.ts index c53061ef1f6..3fef64a6338 100644 --- a/src/lib/onboard/machine/initial-flow-phases.ts +++ b/src/lib/onboard/machine/initial-flow-phases.ts @@ -53,6 +53,7 @@ export interface InitialOnboardFlowPhaseOptions< sandboxGpuDevice?: string | null; gpuRequested: boolean; noGpu: boolean; + allowDeferredN1xManagedVllm?: boolean; env: NodeJS.ProcessEnv; platform?: NodeJS.Platform; recordedGpuPassthroughBeforePreflight: boolean; @@ -144,6 +145,7 @@ export function createInitialOnboardFlowPhases< sandboxGpuDevice: options.sandboxGpuDevice ?? null, gpuRequested: options.gpuRequested, noGpu: options.noGpu, + allowDeferredN1xManagedVllm: options.allowDeferredN1xManagedVllm, env: options.env, deps: { ...options.preflightDeps, diff --git a/src/lib/onboard/types.ts b/src/lib/onboard/types.ts index c6254404187..3783929be02 100644 --- a/src/lib/onboard/types.ts +++ b/src/lib/onboard/types.ts @@ -117,6 +117,8 @@ export type OnboardOptions = { rebuildProviderReconfigure?: import("./rebuild-route-handoff").RebuildProviderReconfigureHandoff; /** Internal one-shot authority to recover the recorded provider during a locked rebuild resume. */ providerRecoveryReceipt?: import("./rebuild-route-handoff").ProviderRecoveryReceipt; + /** Internal rebuild handoff for a recorded managed-vLLM N1x preview selection. */ + allowDeferredN1xManagedVllm?: true; /** Internal one-shot handoff for the exact image context validated before rebuild deletion. */ preparedImageRebuild?: import("./prepared-dcode-rebuild").PreparedImageRebuildHandoff; /** Internal immutable managed-image/profile handoff validated before rebuild deletion. */ From a3237565f3b7860d4718dc6bb892da2a3e04c594 Mon Sep 17 00:00:00 2001 From: San Dang Date: Mon, 17 Aug 2026 23:55:54 +0700 Subject: [PATCH 10/10] refactor(onboard): isolate N1x rebuild eligibility Signed-off-by: San Dang --- .../rebuild-preflight-target-phase.test.ts | 153 ------------------ .../sandbox/rebuild-preflight-target-phase.ts | 60 +------ .../sandbox/rebuild-target-preflight.ts | 1 + .../actions/sandbox/rebuild-target-staging.ts | 32 ++++ .../sandbox/n1x-managed-vllm-rebuild.test.ts | 151 +++++++++++++++++ .../sandbox/n1x-managed-vllm-rebuild.ts | 66 ++++++++ 6 files changed, 251 insertions(+), 212 deletions(-) create mode 100644 src/lib/domain/sandbox/n1x-managed-vllm-rebuild.test.ts create mode 100644 src/lib/domain/sandbox/n1x-managed-vllm-rebuild.ts diff --git a/src/lib/actions/sandbox/rebuild-preflight-target-phase.test.ts b/src/lib/actions/sandbox/rebuild-preflight-target-phase.test.ts index 32f4aaa25a9..86b7a30144c 100644 --- a/src/lib/actions/sandbox/rebuild-preflight-target-phase.test.ts +++ b/src/lib/actions/sandbox/rebuild-preflight-target-phase.test.ts @@ -3,22 +3,15 @@ import { afterEach, describe, expect, it, vi } from "vitest"; -import { - hostLocalInferenceReceipt, - serializedHostLocalInferenceReceipt, - serializedLlamaCppHostLocalInferenceReceipt, -} from "../../../../test/helpers/host-local-inference-receipt"; import type { ProviderRecoveryReceipt, RegistryInferenceRoute, } from "../../onboard/rebuild-route-handoff"; -import { serializeHostLocalInferenceReceipt } from "../../onboard/runtime-provider/host-local-inference"; import type { SandboxBaseImageResolutionMetadata } from "../../sandbox-base-image"; import { pinRebuildTargetGatewayForReadiness, runRebuildGatewayRecoveryAfterReadiness, stageRebuildBaseImageResolutionHandoff, - stageRecordedManagedVllmIntent, stageRegistryProviderRecoveryReceipt, } from "./rebuild-preflight-target-phase"; @@ -128,152 +121,6 @@ describe("stageRegistryProviderRecoveryReceipt", () => { }); }); -describe("stageRecordedManagedVllmIntent", () => { - const n1xExpressEntry = { - provider: "vllm-local", - model: "nvidia/Qwen3.6-35B-A3B-NVFP4", - endpointUrl: null, - endpointSource: "onboard" as const, - openshellDriver: "docker", - }; - const n1xExpressSelection = { - provider: n1xExpressEntry.provider, - model: n1xExpressEntry.model, - pinEndpoint: true, - endpointUrl: null, - }; - - it.each([ - { state: "derived endpoint and absent receipt", sandboxEntry: n1xExpressEntry }, - { - state: "derived endpoint and null receipt", - sandboxEntry: { ...n1xExpressEntry, hostLocalInferenceReceipt: null }, - }, - { - state: "recorded canonical endpoint", - sandboxEntry: { - ...n1xExpressEntry, - endpointUrl: "http://host.openshell.internal:8000/v1", - }, - }, - ])( - "carries the v0.0.109 N1x Express selection with a $state into rebuild readiness (#9292)", - ({ sandboxEntry }) => { - const recreateOptions: { allowDeferredN1xManagedVllm?: true } = {}; - - stageRecordedManagedVllmIntent(recreateOptions, sandboxEntry, n1xExpressSelection); - - expect(recreateOptions.allowDeferredN1xManagedVllm).toBe(true); - }, - ); - - it("also accepts a canonical vLLM receipt on the exact N1x Express selection (#9292)", () => { - const recreateOptions: { allowDeferredN1xManagedVllm?: true } = {}; - const genericReceipt = hostLocalInferenceReceipt("docker"); - const n1xReceipt = serializeHostLocalInferenceReceipt({ - ...genericReceipt, - endpoint: { - ...genericReceipt.endpoint, - host: "host.openshell.internal", - }, - inference: { - protocol: "openai-chat-completions", - model: n1xExpressEntry.model, - toolCallingRequired: true, - }, - }); - - stageRecordedManagedVllmIntent( - recreateOptions, - { ...n1xExpressEntry, hostLocalInferenceReceipt: n1xReceipt }, - n1xExpressSelection, - ); - - expect(recreateOptions.allowDeferredN1xManagedVllm).toBe(true); - }); - - it.each([ - { - caseName: "different recorded provider", - sandboxEntry: { ...n1xExpressEntry, provider: "compatible-endpoint" }, - rebuildSelection: n1xExpressSelection, - }, - { - caseName: "different recorded model", - sandboxEntry: { ...n1xExpressEntry, model: "meta-llama/Llama-3.1-8B-Instruct" }, - rebuildSelection: n1xExpressSelection, - }, - { - caseName: "different recorded endpoint", - sandboxEntry: { ...n1xExpressEntry, endpointUrl: "http://host.openshell.internal:8001/v1" }, - rebuildSelection: n1xExpressSelection, - }, - { - caseName: "different endpoint source", - sandboxEntry: { ...n1xExpressEntry, endpointSource: "inference-set" as const }, - rebuildSelection: n1xExpressSelection, - }, - { - caseName: "different OpenShell driver", - sandboxEntry: { ...n1xExpressEntry, openshellDriver: "podman" }, - rebuildSelection: n1xExpressSelection, - }, - { - caseName: "malformed receipt", - sandboxEntry: { ...n1xExpressEntry, hostLocalInferenceReceipt: "not-json" }, - rebuildSelection: n1xExpressSelection, - }, - { - caseName: "different host-local service", - sandboxEntry: { - ...n1xExpressEntry, - hostLocalInferenceReceipt: serializedLlamaCppHostLocalInferenceReceipt(), - }, - rebuildSelection: n1xExpressSelection, - }, - { - caseName: "conflicting vLLM receipt", - sandboxEntry: { - ...n1xExpressEntry, - hostLocalInferenceReceipt: serializedHostLocalInferenceReceipt(), - }, - rebuildSelection: n1xExpressSelection, - }, - { - caseName: "different rebuild provider", - sandboxEntry: n1xExpressEntry, - rebuildSelection: { ...n1xExpressSelection, provider: "compatible-endpoint" }, - }, - { - caseName: "different rebuild model", - sandboxEntry: n1xExpressEntry, - rebuildSelection: { ...n1xExpressSelection, model: "meta-llama/Llama-3.1-8B-Instruct" }, - }, - { - caseName: "unresolved rebuild endpoint", - sandboxEntry: n1xExpressEntry, - rebuildSelection: { ...n1xExpressSelection, pinEndpoint: false }, - }, - { - caseName: "noncanonical rebuild endpoint", - sandboxEntry: n1xExpressEntry, - rebuildSelection: { - ...n1xExpressSelection, - endpointUrl: "http://host.openshell.internal:8001/v1", - }, - }, - ])( - "does not infer N1x Express intent from a $caseName (#9292)", - ({ sandboxEntry, rebuildSelection }) => { - const recreateOptions: { allowDeferredN1xManagedVllm?: true } = {}; - - stageRecordedManagedVllmIntent(recreateOptions, sandboxEntry, rebuildSelection); - - expect(recreateOptions).not.toHaveProperty("allowDeferredN1xManagedVllm"); - }, - ); -}); - describe("stageRebuildBaseImageResolutionHandoff", () => { it("binds outer resolver provenance to its immutable local handoff (#7144)", () => { const imageId = `sha256:${"a".repeat(64)}`; diff --git a/src/lib/actions/sandbox/rebuild-preflight-target-phase.ts b/src/lib/actions/sandbox/rebuild-preflight-target-phase.ts index c64852ae3f9..0c415187ce8 100644 --- a/src/lib/actions/sandbox/rebuild-preflight-target-phase.ts +++ b/src/lib/actions/sandbox/rebuild-preflight-target-phase.ts @@ -6,7 +6,6 @@ import { CLI_NAME } from "../../cli/branding"; import type { SandboxMessagingPlan } from "../../messaging"; import { isSandboxBaseImageRefreshRequested } from "../../onboard/base-image-resolution-flow"; import type { DcodeAutoApprovalMode } from "../../onboard/dcode-auto-approval"; -import { parseHostLocalInferenceReceipt } from "../../onboard/runtime-provider/host-local-inference"; import { createRebuildProviderReconfigureHandoff, mintProviderRecoveryReceipt, @@ -57,6 +56,7 @@ import { prepareRebuildTargetConfig, type RebuildTargetConfig, stageRebuildHermesDashboardConfig, + stageRecordedManagedVllmIntent, } from "./rebuild-target-preflight"; /** Upper bound on how long a minted provider-recovery receipt stays valid. */ @@ -84,64 +84,6 @@ export function stageRegistryProviderRecoveryReceipt( ); } -const N1X_EXPRESS_PROVIDER = "vllm-local"; -const N1X_EXPRESS_MODEL = "nvidia/Qwen3.6-35B-A3B-NVFP4"; -const N1X_EXPRESS_ENDPOINT_URL = "http://host.openshell.internal:8000/v1"; - -/** Reuse only the exact N1x Express selection recorded by onboarding. */ -export function stageRecordedManagedVllmIntent( - recreateOptions: Pick, - sandboxEntry: Pick< - RebuildSandboxEntry, - | "provider" - | "model" - | "endpointUrl" - | "endpointSource" - | "openshellDriver" - | "hostLocalInferenceReceipt" - >, - rebuildSelection: { - provider: string; - model: string; - pinEndpoint: boolean; - endpointUrl: string | null; - }, -): void { - const recordedEndpointUsesCanonicalLocalRoute = - sandboxEntry.endpointUrl === null || sandboxEntry.endpointUrl === N1X_EXPRESS_ENDPOINT_URL; - if ( - sandboxEntry.provider !== N1X_EXPRESS_PROVIDER || - sandboxEntry.model !== N1X_EXPRESS_MODEL || - !recordedEndpointUsesCanonicalLocalRoute || - sandboxEntry.endpointSource !== "onboard" || - sandboxEntry.openshellDriver !== "docker" || - rebuildSelection.provider !== sandboxEntry.provider || - rebuildSelection.model !== sandboxEntry.model || - rebuildSelection.pinEndpoint !== true || - rebuildSelection.endpointUrl !== null - ) { - return; - } - const serialized = sandboxEntry.hostLocalInferenceReceipt; - if (serialized === undefined || serialized === null) { - recreateOptions.allowDeferredN1xManagedVllm = true; - return; - } - try { - const receipt = parseHostLocalInferenceReceipt(serialized); - if ( - receipt.service === "vllm" && - receipt.endpoint.host === "host.openshell.internal" && - receipt.endpoint.port === 8000 && - receipt.inference?.model === N1X_EXPRESS_MODEL - ) { - recreateOptions.allowDeferredN1xManagedVllm = true; - } - } catch { - // Malformed durable state must not grant the narrow N1x readiness exception. - } -} - export interface RebuildPreparedTarget { targetConfig: RebuildTargetConfig; recreateOptions: RebuildRecreateOnboardOpts; diff --git a/src/lib/actions/sandbox/rebuild-target-preflight.ts b/src/lib/actions/sandbox/rebuild-target-preflight.ts index f46289ef311..c0368f626bd 100644 --- a/src/lib/actions/sandbox/rebuild-target-preflight.ts +++ b/src/lib/actions/sandbox/rebuild-target-preflight.ts @@ -19,4 +19,5 @@ export { hydrateMessagingConfigForRebuild, prepareRebuildRecreateOptions, stageRebuildHermesDashboardConfig, + stageRecordedManagedVllmIntent, } from "./rebuild-target-staging"; diff --git a/src/lib/actions/sandbox/rebuild-target-staging.ts b/src/lib/actions/sandbox/rebuild-target-staging.ts index 9c8b7d70a47..e703e3ec364 100644 --- a/src/lib/actions/sandbox/rebuild-target-staging.ts +++ b/src/lib/actions/sandbox/rebuild-target-staging.ts @@ -2,11 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 import { hydrateMessagingChannelConfig } from "../../messaging-channel-config"; +import { isRecordedN1xManagedVllmRebuildEligible } from "../../domain/sandbox/n1x-managed-vllm-rebuild"; import { getStoredMessagingChannelConfig } from "../../onboard/messaging-config"; import { createRebuildRouteHandoff, type RegistryInferenceRoute, } from "../../onboard/rebuild-route-handoff"; +import { parseHostLocalInferenceReceipt } from "../../onboard/runtime-provider/host-local-inference"; import type { SandboxBaseImageResolutionMetadata } from "../../sandbox-base-image"; import * as onboardSession from "../../state/onboard-session"; import type { RebuildBail } from "./rebuild-credential-preflight"; @@ -84,6 +86,36 @@ export function stageRebuildHermesDashboardConfig( return true; } +/** Stage the recorded N1x decision without moving action state into the domain layer. */ +export function stageRecordedManagedVllmIntent( + recreateOptions: Pick, + sandboxEntry: Pick< + RebuildSandboxEntry, + | "provider" + | "model" + | "endpointUrl" + | "endpointSource" + | "openshellDriver" + | "hostLocalInferenceReceipt" + >, + rebuildSelection: { + provider: string; + model: string; + pinEndpoint: boolean; + endpointUrl: string | null; + }, +): void { + if ( + isRecordedN1xManagedVllmRebuildEligible( + sandboxEntry, + rebuildSelection, + parseHostLocalInferenceReceipt, + ) + ) { + recreateOptions.allowDeferredN1xManagedVllm = true; + } +} + export function hydrateMessagingConfigForRebuild( sandboxName: string, log: (msg: string) => void, diff --git a/src/lib/domain/sandbox/n1x-managed-vllm-rebuild.test.ts b/src/lib/domain/sandbox/n1x-managed-vllm-rebuild.test.ts new file mode 100644 index 00000000000..c28e1c7de63 --- /dev/null +++ b/src/lib/domain/sandbox/n1x-managed-vllm-rebuild.test.ts @@ -0,0 +1,151 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { describe, expect, it } from "vitest"; + +import { + hostLocalInferenceReceipt, + serializedHostLocalInferenceReceipt, + serializedLlamaCppHostLocalInferenceReceipt, +} from "../../../../test/helpers/host-local-inference-receipt"; +import { + parseHostLocalInferenceReceipt, + serializeHostLocalInferenceReceipt, +} from "../../onboard/runtime-provider/host-local-inference"; +import { isRecordedN1xManagedVllmRebuildEligible } from "./n1x-managed-vllm-rebuild"; + +describe("recorded N1x managed-vLLM rebuild eligibility", () => { + const n1xExpressEntry = { + provider: "vllm-local", + model: "nvidia/Qwen3.6-35B-A3B-NVFP4", + endpointUrl: null, + endpointSource: "onboard", + openshellDriver: "docker", + }; + const n1xExpressSelection = { + provider: n1xExpressEntry.provider, + model: n1xExpressEntry.model, + pinEndpoint: true, + endpointUrl: null, + }; + const eligible = ( + sandboxEntry: Parameters[0], + rebuildSelection: Parameters< + typeof isRecordedN1xManagedVllmRebuildEligible + >[1] = n1xExpressSelection, + ) => + isRecordedN1xManagedVllmRebuildEligible( + sandboxEntry, + rebuildSelection, + parseHostLocalInferenceReceipt, + ); + + it.each([ + { state: "derived endpoint and absent receipt", sandboxEntry: n1xExpressEntry }, + { + state: "derived endpoint and null receipt", + sandboxEntry: { ...n1xExpressEntry, hostLocalInferenceReceipt: null }, + }, + { + state: "recorded canonical endpoint", + sandboxEntry: { + ...n1xExpressEntry, + endpointUrl: "http://host.openshell.internal:8000/v1", + }, + }, + ])("accepts the v0.0.109 N1x Express selection with a $state (#9292)", ({ sandboxEntry }) => { + expect(eligible(sandboxEntry)).toBe(true); + }); + + it("accepts a canonical vLLM receipt on the exact N1x Express selection (#9292)", () => { + const genericReceipt = hostLocalInferenceReceipt("docker"); + const n1xReceipt = serializeHostLocalInferenceReceipt({ + ...genericReceipt, + endpoint: { + ...genericReceipt.endpoint, + host: "host.openshell.internal", + }, + inference: { + protocol: "openai-chat-completions", + model: n1xExpressEntry.model, + toolCallingRequired: true, + }, + }); + + expect(eligible({ ...n1xExpressEntry, hostLocalInferenceReceipt: n1xReceipt })).toBe(true); + }); + + it.each([ + { + caseName: "different recorded provider", + sandboxEntry: { ...n1xExpressEntry, provider: "compatible-endpoint" }, + rebuildSelection: n1xExpressSelection, + }, + { + caseName: "different recorded model", + sandboxEntry: { ...n1xExpressEntry, model: "meta-llama/Llama-3.1-8B-Instruct" }, + rebuildSelection: n1xExpressSelection, + }, + { + caseName: "different recorded endpoint", + sandboxEntry: { ...n1xExpressEntry, endpointUrl: "http://host.openshell.internal:8001/v1" }, + rebuildSelection: n1xExpressSelection, + }, + { + caseName: "different endpoint source", + sandboxEntry: { ...n1xExpressEntry, endpointSource: "inference-set" }, + rebuildSelection: n1xExpressSelection, + }, + { + caseName: "different OpenShell driver", + sandboxEntry: { ...n1xExpressEntry, openshellDriver: "podman" }, + rebuildSelection: n1xExpressSelection, + }, + { + caseName: "malformed receipt", + sandboxEntry: { ...n1xExpressEntry, hostLocalInferenceReceipt: "not-json" }, + rebuildSelection: n1xExpressSelection, + }, + { + caseName: "different host-local service", + sandboxEntry: { + ...n1xExpressEntry, + hostLocalInferenceReceipt: serializedLlamaCppHostLocalInferenceReceipt(), + }, + rebuildSelection: n1xExpressSelection, + }, + { + caseName: "conflicting vLLM receipt", + sandboxEntry: { + ...n1xExpressEntry, + hostLocalInferenceReceipt: serializedHostLocalInferenceReceipt(), + }, + rebuildSelection: n1xExpressSelection, + }, + { + caseName: "different rebuild provider", + sandboxEntry: n1xExpressEntry, + rebuildSelection: { ...n1xExpressSelection, provider: "compatible-endpoint" }, + }, + { + caseName: "different rebuild model", + sandboxEntry: n1xExpressEntry, + rebuildSelection: { ...n1xExpressSelection, model: "meta-llama/Llama-3.1-8B-Instruct" }, + }, + { + caseName: "unresolved rebuild endpoint", + sandboxEntry: n1xExpressEntry, + rebuildSelection: { ...n1xExpressSelection, pinEndpoint: false }, + }, + { + caseName: "noncanonical rebuild endpoint", + sandboxEntry: n1xExpressEntry, + rebuildSelection: { + ...n1xExpressSelection, + endpointUrl: "http://host.openshell.internal:8001/v1", + }, + }, + ])("rejects a $caseName (#9292)", ({ sandboxEntry, rebuildSelection }) => { + expect(eligible(sandboxEntry, rebuildSelection)).toBe(false); + }); +}); diff --git a/src/lib/domain/sandbox/n1x-managed-vllm-rebuild.ts b/src/lib/domain/sandbox/n1x-managed-vllm-rebuild.ts new file mode 100644 index 00000000000..7509c79c88d --- /dev/null +++ b/src/lib/domain/sandbox/n1x-managed-vllm-rebuild.ts @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const N1X_EXPRESS_PROVIDER = "vllm-local"; +const N1X_EXPRESS_MODEL = "nvidia/Qwen3.6-35B-A3B-NVFP4"; +const N1X_EXPRESS_ENDPOINT_URL = "http://host.openshell.internal:8000/v1"; + +export interface RecordedN1xManagedVllmRoute { + provider?: string | null; + model?: string | null; + endpointUrl?: string | null; + endpointSource?: string | null; + openshellDriver?: string | null; + hostLocalInferenceReceipt?: string | null; +} + +export interface N1xManagedVllmRebuildSelection { + provider: string; + model: string; + pinEndpoint: boolean; + endpointUrl: string | null; +} + +export interface N1xManagedVllmReceipt { + service: string; + endpoint: { host: string; port: number }; + inference?: { model: string }; +} + +export type ParseN1xManagedVllmReceipt = (serialized: string) => N1xManagedVllmReceipt; + +/** Decide whether a recorded route proves the exact Deferred N1x managed-vLLM selection. */ +export function isRecordedN1xManagedVllmRebuildEligible( + sandboxEntry: RecordedN1xManagedVllmRoute, + rebuildSelection: N1xManagedVllmRebuildSelection, + parseReceipt: ParseN1xManagedVllmReceipt, +): boolean { + const recordedEndpointUsesCanonicalLocalRoute = + sandboxEntry.endpointUrl === null || sandboxEntry.endpointUrl === N1X_EXPRESS_ENDPOINT_URL; + if ( + sandboxEntry.provider !== N1X_EXPRESS_PROVIDER || + sandboxEntry.model !== N1X_EXPRESS_MODEL || + !recordedEndpointUsesCanonicalLocalRoute || + sandboxEntry.endpointSource !== "onboard" || + sandboxEntry.openshellDriver !== "docker" || + rebuildSelection.provider !== sandboxEntry.provider || + rebuildSelection.model !== sandboxEntry.model || + rebuildSelection.pinEndpoint !== true || + rebuildSelection.endpointUrl !== null + ) { + return false; + } + const serialized = sandboxEntry.hostLocalInferenceReceipt; + if (serialized === undefined || serialized === null) return true; + try { + const receipt = parseReceipt(serialized); + return ( + receipt.service === "vllm" && + receipt.endpoint.host === "host.openshell.internal" && + receipt.endpoint.port === 8000 && + receipt.inference?.model === N1X_EXPRESS_MODEL + ); + } catch { + return false; + } +}