diff --git a/test/e2e/fixtures/phases/onboarding.ts b/test/e2e/fixtures/phases/onboarding.ts index fc685f0ff1b..f50bd9165d5 100644 --- a/test/e2e/fixtures/phases/onboarding.ts +++ b/test/e2e/fixtures/phases/onboarding.ts @@ -69,6 +69,7 @@ export interface OnboardingCleanup { } export interface OnboardingOptions { + dcodeBaseImageReference?: string; sandboxName?: string; timeoutMs?: number; } @@ -255,7 +256,11 @@ export class OnboardingPhaseFixture { ); } const sandboxName = sandboxNameFromOptions(environment.onboarding, options); - const baseImageReference = requireDcodeBaseImageReference(); + const baseImageReference = requireDcodeBaseImageReference( + options.dcodeBaseImageReference === undefined + ? process.env + : { [DCODE_BASE_IMAGE_ENV]: options.dcodeBaseImageReference }, + ); const apiKey = this.secrets.required("NVIDIA_INFERENCE_API_KEY"); this.registerSandboxCleanup(sandboxName); const result = await this.host.nemoclaw([...ONBOARD_ARGS, "--observability"], { diff --git a/test/e2e/live/cloud-experimental-checks.ts b/test/e2e/live/cloud-experimental-checks.ts index d411aa1128d..1ae59fdc20b 100644 --- a/test/e2e/live/cloud-experimental-checks.ts +++ b/test/e2e/live/cloud-experimental-checks.ts @@ -66,10 +66,14 @@ export function buildCloudExperimentalCommandEnv( sandboxName: string, apiKey: string, base: NodeJS.ProcessEnv = process.env, - options: { forwardDcodeBaseImage?: boolean } = {}, + options: { dcodeBaseImageReference?: string; forwardDcodeBaseImage?: boolean } = {}, ): NodeJS.ProcessEnv { const dcodeBaseImage = options.forwardDcodeBaseImage - ? requireDcodeBaseImageReference(base) + ? requireDcodeBaseImageReference( + options.dcodeBaseImageReference === undefined + ? base + : { [DCODE_BASE_IMAGE_ENV]: options.dcodeBaseImageReference }, + ) : undefined; return { ...buildAvailabilityProbeEnv(base), @@ -135,7 +139,9 @@ export async function runE2eCloudExperimentalChecks( targetId: string, sandboxName: string, checkScripts: readonly string[], - context: Pick, + context: Pick & { + dcodeBaseImageReference?: string; + }, ): Promise { const apiKey = context.secrets.optional("NVIDIA_INFERENCE_API_KEY") ?? ""; await context.artifacts.writeJson( @@ -150,6 +156,7 @@ export async function runE2eCloudExperimentalChecks( artifactName: `cloud-experimental-${path.basename(scriptPath, ".sh")}`, cwd: REPO_ROOT, env: buildCloudExperimentalCommandEnv(sandboxName, apiKey, process.env, { + dcodeBaseImageReference: context.dcodeBaseImageReference, forwardDcodeBaseImage: scriptPath === DEEPAGENTS_FRESH_REONBOARD_CHECK, }), redactionValues: [apiKey], diff --git a/test/e2e/live/dcode-base-image-runtime-evidence.ts b/test/e2e/live/dcode-base-image-runtime-evidence.ts index b6093e08ddc..9161e0f9a9e 100644 --- a/test/e2e/live/dcode-base-image-runtime-evidence.ts +++ b/test/e2e/live/dcode-base-image-runtime-evidence.ts @@ -6,9 +6,8 @@ import fs from "node:fs"; import { readSandboxBaseImageResolutionMetadata } from "../../../src/lib/sandbox-base-image/label-codec.ts"; import type { SandboxBaseImageResolutionMetadata } from "../../../src/lib/sandbox-base-image/types.ts"; import { - DCODE_BASE_IMAGE_ONBOARD_PLATFORM, + DCODE_BASE_IMAGE_TARGET_PLATFORM, type DcodeBaseImageContract, - type DcodePlatform, parseDcodeBaseImageContract, } from "../../../tools/e2e/dcode-base-image-contract.mts"; import { requireDcodeBaseImageReference } from "../fixtures/dcode-base-image.ts"; @@ -23,7 +22,7 @@ export interface DcodeBaseImageRuntimeEvidence { digest: string; image: string; imageId: string; - platform: DcodePlatform; + platform: typeof DCODE_BASE_IMAGE_TARGET_PLATFORM; reference: string; sandboxImage: string; source: "override"; @@ -79,23 +78,27 @@ export function parseDcodeBaseImagePublicationEvidence( const contract = parseDcodeBaseImageContract(evidence.base); if ( requireDcodeBaseImageReference(environment) !== - contract.platformReferences[DCODE_BASE_IMAGE_ONBOARD_PLATFORM] + contract.platformReferences[DCODE_BASE_IMAGE_TARGET_PLATFORM] ) { throw new Error( - `Deep Agents Code onboarding reference does not match the published ${DCODE_BASE_IMAGE_ONBOARD_PLATFORM} base contract`, + `Deep Agents Code onboarding reference does not match the published ${DCODE_BASE_IMAGE_TARGET_PLATFORM} base contract`, ); } return contract; } +export function dcodeBaseImageReferenceForContract(contract: DcodeBaseImageContract): string { + return contract.platformReferences[DCODE_BASE_IMAGE_TARGET_PLATFORM]; +} + export function loadDcodeBaseImagePublicationEvidence( targetId: string, evidencePath: string, environment: NodeJS.ProcessEnv = process.env, ): DcodeBaseImageContract | undefined { if (targetId !== DCODE_BASE_IMAGE_TARGET_ID) return undefined; - requireDcodeBaseImageReference(environment); if (!fs.existsSync(evidencePath)) { + requireDcodeBaseImageReference(environment); if (environment.GITHUB_ACTIONS === "true") { throw new Error("Deep Agents Code GitHub Actions run is missing published base evidence"); } @@ -107,14 +110,6 @@ export function loadDcodeBaseImagePublicationEvidence( ); } -function platformFor(metadata: SandboxBaseImageResolutionMetadata): DcodePlatform { - const platform = `${metadata.os}/${metadata.architecture}`; - if (platform !== "linux/amd64" && platform !== "linux/arm64") { - throw new Error(`Deep Agents Code base resolution used unsupported platform '${platform}'`); - } - return platform; -} - export function verifyDcodeBaseImageRuntimeEvidence( contract: DcodeBaseImageContract, sandboxImage: string, @@ -126,9 +121,13 @@ export function verifyDcodeBaseImageRuntimeEvidence( if (!metadata) { throw new Error("Deep Agents Code sandbox image is missing base resolution metadata"); } - const platform = platformFor(metadata); - const expectedDigest = contract.platformDigests[platform]; - const expectedReference = contract.platformReferences[platform]; + if (`${metadata.os}/${metadata.architecture}` !== DCODE_BASE_IMAGE_TARGET_PLATFORM) { + throw new Error( + `Deep Agents Code sandbox image did not use the published ${DCODE_BASE_IMAGE_TARGET_PLATFORM} base digest`, + ); + } + const expectedDigest = contract.platformDigests[DCODE_BASE_IMAGE_TARGET_PLATFORM]; + const expectedReference = dcodeBaseImageReferenceForContract(contract); if ( metadata.schema !== 1 || metadata.imageName !== contract.image || @@ -139,7 +138,7 @@ export function verifyDcodeBaseImageRuntimeEvidence( metadata.ref !== `${metadata.imageName}@${metadata.digest}` ) { throw new Error( - `Deep Agents Code sandbox image did not use the published ${platform} base digest`, + `Deep Agents Code sandbox image did not use the published ${DCODE_BASE_IMAGE_TARGET_PLATFORM} base digest`, ); } return { @@ -147,7 +146,7 @@ export function verifyDcodeBaseImageRuntimeEvidence( digest: metadata.digest, image: metadata.imageName, imageId: metadata.imageId, - platform, + platform: DCODE_BASE_IMAGE_TARGET_PLATFORM, reference: metadata.ref, sandboxImage, source: metadata.source, diff --git a/test/e2e/live/registry-targets.test.ts b/test/e2e/live/registry-targets.test.ts index 8c073a3b71d..8d3a5c6d16d 100644 --- a/test/e2e/live/registry-targets.test.ts +++ b/test/e2e/live/registry-targets.test.ts @@ -18,6 +18,7 @@ import { cloudExperimentalChecksForOnboarding } from "./cloud-experimental-check import { runE2eCloudExperimentalChecks } from "./cloud-experimental-checks.ts"; import { captureDcodeBaseImageRuntimeEvidence, + dcodeBaseImageReferenceForContract, loadDcodeBaseImagePublicationEvidence, } from "./dcode-base-image-runtime-evidence.ts"; import { buildLiveTargetRunPlan } from "./run-plan.ts"; @@ -101,6 +102,9 @@ for (const [targetIndex, target] of listTargets().entries()) { target.id, artifacts.pathFor("dcode-base-image.json"), ); + const dcodeBaseImageReference = dcodeBaseContract + ? dcodeBaseImageReferenceForContract(dcodeBaseContract) + : undefined; requireRegistryTargetSecrets(target.id, target.requiredSecrets ?? [], secrets); expect( @@ -141,6 +145,7 @@ for (const [targetIndex, target] of listTargets().entries()) { progress.phase("onboard the registry-selected sandbox"); const instance = await onboard.from(ready, { sandboxName: `e2e-reg-${targetIndex.toString(36)}`, + dcodeBaseImageReference, }); // Lifecycle phase runs between onboard and state-validation. @@ -174,11 +179,15 @@ for (const [targetIndex, target] of listTargets().entries()) { expect(checkScripts).toEqual( cloudExperimentalChecksForOnboarding(target.environment.onboarding), ); - expect(checkScripts.every((scriptPath) => - Object.is(fs.existsSync(path.join(REPO_ROOT, scriptPath)), true))).toBe(true); + expect( + checkScripts.every((scriptPath) => + Object.is(fs.existsSync(path.join(REPO_ROOT, scriptPath)), true), + ), + ).toBe(true); expect(fs.existsSync(E2E_CLOUD_EXPERIMENTAL_CHECKS_DIR)).toBe(true); await runE2eCloudExperimentalChecks(target.id, instance.sandboxName, checkScripts, { artifacts, + dcodeBaseImageReference, host, secrets, }); diff --git a/test/e2e/support/dcode-base-image-contract.test.ts b/test/e2e/support/dcode-base-image-contract.test.ts index 67c1cbf9ead..170a577e4d7 100644 --- a/test/e2e/support/dcode-base-image-contract.test.ts +++ b/test/e2e/support/dcode-base-image-contract.test.ts @@ -8,7 +8,7 @@ import { join } from "node:path"; import { describe, expect, it, vi } from "vitest"; import { - DCODE_BASE_IMAGE_ONBOARD_PLATFORM, + DCODE_BASE_IMAGE_TARGET_PLATFORM, main, validateDcodeBaseImageContract, validateDcodeBaseImageImports, @@ -19,10 +19,11 @@ const RUN_ATTEMPT = 2; const HEAD_SHA = "a".repeat(40); const IMAGE = "ghcr.io/nvidia/nemoclaw/langchain-deepagents-code-sandbox-base"; const DIGEST = `sha256:${"b".repeat(64)}`; +const AMD64_DIGEST = `sha256:${"c".repeat(64)}`; +const ARM64_DIGEST = `sha256:${"d".repeat(64)}`; +const AMD64_REFERENCE = `${IMAGE}@${AMD64_DIGEST}`; function contract(overrides: Record = {}): Record { - const amd64 = `sha256:${"c".repeat(64)}`; - const arm64 = `sha256:${"d".repeat(64)}`; return { contractVersion: 1, agent: "langchain-deepagents-code", @@ -30,10 +31,10 @@ function contract(overrides: Record = {}): Record { "run", "--rm", "--platform", - DCODE_BASE_IMAGE_ONBOARD_PLATFORM, + DCODE_BASE_IMAGE_TARGET_PLATFORM, "--network", "none", "--cap-drop", @@ -128,4 +129,5 @@ describe("Deep Agents Code E2E base contract", () => { /did not prove both required imports/u, ); }); + }); diff --git a/test/e2e/support/dcode-base-image-runtime-evidence.test.ts b/test/e2e/support/dcode-base-image-runtime-evidence.test.ts index f39db60ee21..1abcf451787 100644 --- a/test/e2e/support/dcode-base-image-runtime-evidence.test.ts +++ b/test/e2e/support/dcode-base-image-runtime-evidence.test.ts @@ -7,6 +7,7 @@ import type { SandboxBaseImageResolutionMetadata } from "../../../src/lib/sandbo import { DCODE_BASE_IMAGE, DCODE_BASE_IMAGE_ENV } from "../fixtures/dcode-base-image.ts"; import { DCODE_BASE_IMAGE_TARGET_ID, + dcodeBaseImageReferenceForContract, loadDcodeBaseImagePublicationEvidence, parseDcodeBaseImagePublicationEvidence, verifyDcodeBaseImageRuntimeEvidence, @@ -74,6 +75,19 @@ function resolutionMetadata( } describe("Deep Agents Code published base runtime evidence", () => { + it("selects the linux/amd64 platform reference when trusted manual PR E2E supplies it", () => { + const environment = publicationEnvironment({ + GITHUB_ACTIONS: "true", + GITHUB_EVENT_NAME: "workflow_dispatch", + GITHUB_SHA: "f".repeat(40), + NEMOCLAW_E2E_EXPECTED_SHA: CANDIDATE_REVISION, + }); + const contract = parseDcodeBaseImagePublicationEvidence(publicationEvidence(), environment); + + expect(dcodeBaseImageReferenceForContract(contract)).toBe(AMD64_REFERENCE); + expect(environment[DCODE_BASE_IMAGE_ENV]).toBe(AMD64_REFERENCE); + }); + it("records the completed sandbox image only when its platform digest matches publication", () => { const contract = parseDcodeBaseImagePublicationEvidence( publicationEvidence(), @@ -218,10 +232,19 @@ describe("Deep Agents Code published base runtime evidence", () => { /did not use the published linux\/amd64 base digest/, ], [ - "the opposite platform digest", + "the opposite platform digest for amd64", resolutionMetadata({ digest: ARM64_DIGEST, ref: ARM64_REFERENCE }), /did not use the published linux\/amd64 base digest/, ], + [ + "self-consistent opposite-platform metadata", + resolutionMetadata({ + architecture: "arm64", + digest: ARM64_DIGEST, + ref: ARM64_REFERENCE, + }), + /did not use the published linux\/amd64 base digest/, + ], [ "a different image repository", resolutionMetadata({ imageName: "ghcr.io/example/base" }), @@ -240,7 +263,7 @@ describe("Deep Agents Code published base runtime evidence", () => { [ "an unsupported platform", resolutionMetadata({ architecture: "ppc64le" }), - /used unsupported platform/, + /did not use the published linux\/amd64 base digest/, ], ])("rejects %s", (_label, metadata, expectedError) => { const contract = parseDcodeBaseImagePublicationEvidence( diff --git a/test/e2e/support/e2e-phase-onboarding.test.ts b/test/e2e/support/e2e-phase-onboarding.test.ts index db13e9076e1..b43b04abc90 100644 --- a/test/e2e/support/e2e-phase-onboarding.test.ts +++ b/test/e2e/support/e2e-phase-onboarding.test.ts @@ -30,6 +30,7 @@ interface CleanupCall { } const DCODE_BASE_IMAGE_REF = `${DCODE_BASE_IMAGE}@sha256:${"a".repeat(64)}`; +const DCODE_BASE_IMAGE_INDEX_REF = `${DCODE_BASE_IMAGE}@sha256:${"b".repeat(64)}`; async function withProcessEnvironment( values: Record, @@ -236,6 +237,39 @@ describe("onboarding phase fixture", () => { }); }); + it("uses the contract-selected Deep Agents Code base image reference instead of the ambient publication index", async () => { + const runner = new FakeRunner(); + runner.enqueue(shellResult(0, "onboarded\n")); + const secrets = new FakeSecrets({ NVIDIA_INFERENCE_API_KEY: "secret-token" }); + const onboard = new OnboardingPhaseFixture(new HostCliClient(runner), secrets); + + await withProcessEnvironment({ [DCODE_BASE_IMAGE_ENV]: DCODE_BASE_IMAGE_INDEX_REF }, () => + onboard.from(ready({ onboarding: "cloud-langchain-deepagents-code" }), { + dcodeBaseImageReference: DCODE_BASE_IMAGE_REF, + sandboxName: "e2e-dcode-cloud", + }), + ); + + expect(runner.calls[0]?.options?.env?.[DCODE_BASE_IMAGE_ENV]).toBe(DCODE_BASE_IMAGE_REF); + }); + + it("rejects an invalid explicit Deep Agents Code base image reference before onboarding side effects", async () => { + const runner = new FakeRunner(); + const cleanup = new FakeCleanup(); + const secrets = new FakeSecrets({ NVIDIA_INFERENCE_API_KEY: "secret-token" }); + const onboard = new OnboardingPhaseFixture(new HostCliClient(runner), secrets, cleanup); + + await expect( + onboard.from(ready({ onboarding: "cloud-langchain-deepagents-code" }), { + dcodeBaseImageReference: `${DCODE_BASE_IMAGE}:latest`, + sandboxName: "e2e-dcode-cloud", + }), + ).rejects.toThrow(/requires .* to be the immutable official/); + expect(secrets.requiredCalls).toEqual([]); + expect(cleanup.calls).toEqual([]); + expect(runner.calls).toEqual([]); + }); + it.each([ ["a missing reference", undefined], ["an empty reference", " "], diff --git a/test/e2e/support/platform-parity-cloud-experimental.test.ts b/test/e2e/support/platform-parity-cloud-experimental.test.ts index f3d813a0e56..836d425b92d 100644 --- a/test/e2e/support/platform-parity-cloud-experimental.test.ts +++ b/test/e2e/support/platform-parity-cloud-experimental.test.ts @@ -769,4 +769,24 @@ assert_status_mode disabled expect(env[DCODE_BASE_IMAGE_ENV]).toBe(baseImageReference); }); + + it("forwards the contract-selected Deep Agents Code base image reference instead of the ambient publication index", () => { + const indexReference = `${DCODE_BASE_IMAGE}@sha256:${"a".repeat(64)}`; + const platformReference = `${DCODE_BASE_IMAGE}@sha256:${"b".repeat(64)}`; + const env = buildCloudExperimentalCommandEnv( + "deepagents-sandbox", + "secret-key", + { + HOME: "/home/runner", + PATH: "/usr/bin", + [DCODE_BASE_IMAGE_ENV]: indexReference, + }, + { + dcodeBaseImageReference: platformReference, + forwardDcodeBaseImage: true, + }, + ); + + expect(env[DCODE_BASE_IMAGE_ENV]).toBe(platformReference); + }); }); diff --git a/tools/e2e/dcode-base-image-contract.mts b/tools/e2e/dcode-base-image-contract.mts index 5ad6f72e6b0..6c12dddcbc9 100644 --- a/tools/e2e/dcode-base-image-contract.mts +++ b/tools/e2e/dcode-base-image-contract.mts @@ -9,7 +9,7 @@ import { fileURLToPath } from "node:url"; const AGENT = "langchain-deepagents-code"; const IMAGE = "ghcr.io/nvidia/nemoclaw/langchain-deepagents-code-sandbox-base"; const PLATFORMS = ["linux/amd64", "linux/arm64"] as const; -export const DCODE_BASE_IMAGE_ONBOARD_PLATFORM = "linux/amd64" as const; +export const DCODE_BASE_IMAGE_TARGET_PLATFORM = "linux/amd64" as const; const DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/u; const SHA_PATTERN = /^[0-9a-f]{40}$/u; const IMPORT_MARKER = "nemoclaw-dcode-base-imports-ok"; @@ -142,7 +142,7 @@ export function validateDcodeBaseImageImports( "run", "--rm", "--platform", - "linux/amd64", + DCODE_BASE_IMAGE_TARGET_PLATFORM, "--network", "none", "--cap-drop", @@ -188,7 +188,7 @@ export function main( headSha: env.PUBLICATION_HEAD_SHA ?? "", }, ); - const baseReference = contract.platformReferences[DCODE_BASE_IMAGE_ONBOARD_PLATFORM]; + const baseReference = contract.platformReferences[DCODE_BASE_IMAGE_TARGET_PLATFORM]; validateDcodeBaseImageImports(baseReference, runDocker); appendFileSync( outputPath,