|
1 | 1 | // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
2 | 2 | // SPDX-License-Identifier: Apache-2.0 |
3 | 3 |
|
| 4 | +import fs from "node:fs"; |
| 5 | +import os from "node:os"; |
| 6 | +import path from "node:path"; |
| 7 | + |
4 | 8 | import { afterEach, beforeEach, describe, expect, it, type MockInstance, vi } from "vitest"; |
| 9 | + |
5 | 10 | import * as dockerImage from "../../adapters/docker/image"; |
6 | 11 | import * as agentDefs from "../../agent/defs"; |
7 | 12 | import * as agentOnboard from "../../agent/onboard"; |
@@ -189,6 +194,91 @@ describe("rebuild agent base image preflight", () => { |
189 | 194 | }); |
190 | 195 | }); |
191 | 196 |
|
| 197 | + it("hands a pinned NemoCUA image alias to the inner sandbox create (#9649)", () => { |
| 198 | + const cuaOverrideEnvVar = "NEMOCLAW_CUA_SANDBOX_IMAGE_REF"; |
| 199 | + const mutableRef = "nemocua-scenario:mutable"; |
| 200 | + const pinnedRef = `nemoclaw-nemocua-sandbox-base-local:rebuild-1-${"a".repeat(16)}-image-${"b".repeat(64)}`; |
| 201 | + const agent = agentDefs.loadAgent("nemocua", { NEMOCLAW_CUA_ENABLED: "1" }); |
| 202 | + const root = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-cua-rebuild-test-")); |
| 203 | + let buildContext = root; |
| 204 | + vi.stubEnv("NEMOCLAW_CUA_ENABLED", "1"); |
| 205 | + vi.stubEnv(cuaOverrideEnvVar, mutableRef); |
| 206 | + vi.spyOn(agentDefs, "loadAgent").mockReturnValue(agent); |
| 207 | + vi.spyOn(agentOnboard, "ensureAgentBaseImage").mockReturnValue({ |
| 208 | + imageTag: mutableRef, |
| 209 | + built: false, |
| 210 | + }); |
| 211 | + const pinImage = vi |
| 212 | + .spyOn(agentOnboard, "pinAgentSandboxBaseImageRef") |
| 213 | + .mockReturnValue(pinnedRef); |
| 214 | + const dockerRmi = vi.spyOn(dockerImage, "dockerRmi").mockReturnValue({ status: 0 } as never); |
| 215 | + |
| 216 | + try { |
| 217 | + const preflight = ensureRebuildAgentBaseImage("nemocua", makeBail()); |
| 218 | + expect(preflight).toMatchObject({ |
| 219 | + ok: true, |
| 220 | + imageRef: pinnedRef, |
| 221 | + overrideEnvVar: cuaOverrideEnvVar, |
| 222 | + }); |
| 223 | + expect(pinImage).toHaveBeenCalledWith("nemocua", mutableRef, { |
| 224 | + forceLocal: true, |
| 225 | + temporary: true, |
| 226 | + }); |
| 227 | + |
| 228 | + const restore = pinRebuildAgentBaseImageForRecreate(preflight); |
| 229 | + try { |
| 230 | + const inner = agentOnboard.createAgentSandbox(agent, { rootDir: root }); |
| 231 | + buildContext = inner.buildCtx; |
| 232 | + const dockerfile = fs.readFileSync(inner.stagedDockerfile, "utf8"); |
| 233 | + expect(dockerfile).toContain(`ARG BASE_IMAGE=${pinnedRef}`); |
| 234 | + expect(dockerfile).not.toContain(mutableRef); |
| 235 | + } finally { |
| 236 | + restore(); |
| 237 | + } |
| 238 | + |
| 239 | + expect(process.env[cuaOverrideEnvVar]).toBe(mutableRef); |
| 240 | + expect(disposeRebuildAgentBaseImagePreflight(preflight)).toBe(true); |
| 241 | + expect(dockerRmi).toHaveBeenCalledWith(pinnedRef, { |
| 242 | + ignoreError: true, |
| 243 | + suppressOutput: true, |
| 244 | + }); |
| 245 | + } finally { |
| 246 | + fs.rmSync(buildContext, { recursive: true, force: true }); |
| 247 | + fs.rmSync(root, { recursive: true, force: true }); |
| 248 | + vi.unstubAllEnvs(); |
| 249 | + } |
| 250 | + }); |
| 251 | + |
| 252 | + it("passes an immutable NemoCUA digest through without a local alias (#9649)", () => { |
| 253 | + const cuaOverrideEnvVar = "NEMOCLAW_CUA_SANDBOX_IMAGE_REF"; |
| 254 | + const digestRef = `registry.example/nemocua@sha256:${"a".repeat(64)}`; |
| 255 | + const agent = agentDefs.loadAgent("nemocua", { NEMOCLAW_CUA_ENABLED: "1" }); |
| 256 | + vi.stubEnv("NEMOCLAW_CUA_ENABLED", "1"); |
| 257 | + vi.stubEnv(cuaOverrideEnvVar, digestRef); |
| 258 | + vi.spyOn(agentDefs, "loadAgent").mockReturnValue(agent); |
| 259 | + vi.spyOn(agentOnboard, "ensureAgentBaseImage").mockReturnValue({ |
| 260 | + imageTag: digestRef, |
| 261 | + built: false, |
| 262 | + }); |
| 263 | + const pinImage = vi.spyOn(agentOnboard, "pinAgentSandboxBaseImageRef"); |
| 264 | + const dockerRmi = vi.spyOn(dockerImage, "dockerRmi"); |
| 265 | + |
| 266 | + try { |
| 267 | + const preflight = ensureRebuildAgentBaseImage("nemocua", makeBail()); |
| 268 | + |
| 269 | + expect(preflight).toEqual({ |
| 270 | + ok: true, |
| 271 | + imageRef: digestRef, |
| 272 | + overrideEnvVar: cuaOverrideEnvVar, |
| 273 | + }); |
| 274 | + expect(pinImage).not.toHaveBeenCalled(); |
| 275 | + expect(disposeRebuildAgentBaseImagePreflight(preflight)).toBe(true); |
| 276 | + expect(dockerRmi).not.toHaveBeenCalled(); |
| 277 | + } finally { |
| 278 | + vi.unstubAllEnvs(); |
| 279 | + } |
| 280 | + }); |
| 281 | + |
192 | 282 | it("fails closed when an explicit local result lacks validated outer metadata", () => { |
193 | 283 | process.env[overrideEnvVar] = "nemoclaw-hermes-sandbox-base-local:caller"; |
194 | 284 | const mutableRef = "nemoclaw-hermes-sandbox-base-local:resolved"; |
|
0 commit comments