diff --git a/test/e2e/support/pi-agent-qualification-events.test.ts b/test/e2e/support/pi-agent-qualification-events.test.ts index 554bd4f6956..de15d31874d 100644 --- a/test/e2e/support/pi-agent-qualification-events.test.ts +++ b/test/e2e/support/pi-agent-qualification-events.test.ts @@ -1,8 +1,17 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import fs from "node:fs"; +import path from "node:path"; + import { describe, expect, it } from "vitest"; +import { + catalogueTarget, + catalogueTargetsForChangedFiles, +} from "../../../tools/e2e/target-catalogue.mts"; +import { REPO_ROOT } from "../fixtures/paths.ts"; + import { derivePiImageSourcePaths, parsePiJsonEvents, @@ -61,6 +70,36 @@ describe("Pi qualification event oracle", () => { ); }); + it.each(["pi-agent-qualification-amd64", "pi-agent-qualification-arm64"])( + "%s keeps every real Pi image source in its PR target ownership boundary", + (targetId) => { + const dockerfiles = ["agents/pi/Dockerfile", "agents/pi/Dockerfile.base"].map((file) => + fs.readFileSync(path.join(REPO_ROOT, file), "utf8"), + ); + const imageSources = derivePiImageSourcePaths(dockerfiles); + const target = catalogueTarget(targetId); + const uncovered = imageSources.filter( + (source) => + !target.owningPaths.some((owner) => { + const normalizedOwner = owner.replace(/\/$/u, ""); + return source === normalizedOwner || source.startsWith(`${normalizedOwner}/`); + }), + ); + + expect(uncovered, `${target.id} must own every Pi Docker COPY input`).toEqual([]); + }, + ); + + it("selects both Pi qualification targets for a copied blueprint source", () => { + expect( + catalogueTargetsForChangedFiles(["nemoclaw-blueprint/scripts/nemotron-inference-fix.js"]).map( + (target) => target.id, + ), + ).toEqual( + expect.arrayContaining(["pi-agent-qualification-amd64", "pi-agent-qualification-arm64"]), + ); + }); + it("accepts one successful read and an exact final response", () => { const events = parsePiJsonEvents(eventStream()); diff --git a/tools/e2e/target-catalogue.mts b/tools/e2e/target-catalogue.mts index eccefc8d086..5b8508594cb 100644 --- a/tools/e2e/target-catalogue.mts +++ b/tools/e2e/target-catalogue.mts @@ -215,6 +215,32 @@ const nonInteractive = { NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1", } as const; +// Keep every checked-in input copied by the Pi Dockerfiles in the PR selection boundary. +// test/e2e/support/pi-agent-qualification-events.test.ts verifies this list against the +// real Dockerfiles so a new COPY instruction cannot silently reuse a stale image receipt. +const PI_IMAGE_SOURCE_OWNING_PATHS = [ + ".dockerignore", + "agents/pi/", + "nemoclaw-blueprint/", + "scripts/lib/bundled-npm-package.mts", + "scripts/lib/entrypoint-env-wrapper.sh", + "scripts/lib/patch-bundled-npm-ip-address.mts", + "scripts/lib/reviewed-npm-archive.mts", + "scripts/lib/sandbox-rlimits.sh", + "scripts/managed-bootstrap-entrypoint.c", + "scripts/managed-bootstrap-trampoline.sh", + "scripts/managed-startup-hold.sh", + "scripts/patch-bundled-npm-brace-expansion.mts", + "scripts/patch-bundled-npm-tar.mts", + "scripts/security/build-native-security-packages.sh", + "scripts/security/build-perl-security-packages.sh", + "scripts/security/patches/libssh2-1.11.1-cve-2026.patch", + "scripts/security/patches/perl-5.44.0-net-ping-capability-tests.patch", + "scripts/security/patches/python3.13-htmlparser-cve-2026-15308.patch", + "scripts/upgrade-bundled-npm.mts", + "tools/mcp-tool-discovery-runtime/reviewed-runtime-bundle/managed-startup-image-runtime.bundle", +] as const; + function commonEgressTarget(options: { displayName: string; environment?: Readonly>; @@ -1165,7 +1191,7 @@ export const E2E_TARGET_CATALOGUE: readonly E2eCatalogueTarget[] = [ runner: "ubuntu-24.04", shard: "linux-amd64", owningPaths: [ - "agents/pi/", + ...PI_IMAGE_SOURCE_OWNING_PATHS, "ci/pi-agent-qualification-v1-linux-amd64.json", "src/lib/agent/candidate-authority.ts", "src/lib/agent/candidate.ts", @@ -1199,7 +1225,7 @@ export const E2E_TARGET_CATALOGUE: readonly E2eCatalogueTarget[] = [ runner: "ubuntu-24.04-arm", shard: "linux-arm64", owningPaths: [ - "agents/pi/", + ...PI_IMAGE_SOURCE_OWNING_PATHS, "ci/pi-agent-qualification-v1-linux-arm64.json", "src/lib/agent/candidate-authority.ts", "src/lib/agent/candidate.ts",