Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions test/e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -638,10 +638,20 @@ Changes to `src/lib/actions/sandbox/status-snapshot.ts` select the exact
delivery-recovery changes bound to the reboot simulation that independently
probes the restored gateway and host forwarding.
Every internal revision with selected jobs or targets automatically dispatches
its deterministic plan after eligible PR CI passes. This behavior includes all
internal E2E control-plane changes. Internal dispatch does not use
`approve-e2e` or a maintainer role check. If no job or target is selected, the
required check passes without an E2E run.
its deterministic plan after eligible PR CI passes. Automatic PR E2E planning
and PR Review Advisor E2E recommendations temporarily omit only
`jetson-nvmap-gpu`. This behavior includes all internal E2E control-plane
changes. Internal dispatch does not use
`approve-e2e` or a maintainer role check. If no job or target remains after the
Jetson omission, the required check passes without an E2E run.

The `jetson-nvmap-gpu` job remains available through an explicit manual `E2E
main` dispatch with `jobs=jetson-nvmap-gpu`. Before setting
`allow_jetson_runner_queue=true`, a repository administrator must confirm that
the Jetson runner is online in the authoritative repository runner inventory.
Restore Jetson automatic planning and PR Review Advisor recommendations only
after the Colossus-backed Jetson runner path is available and its online state
can be confirmed before queueing.
When selected E2E fails, a later internal PR commit starts a new dispatch after
eligible PR CI passes for that commit. The dispatch runs the complete
deterministic plan for the later commit, not only the selection that failed.
Expand Down
76 changes: 76 additions & 0 deletions test/pr-e2e-gate-jetson-exclusion.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { expect, it } from "vitest";
import {
normalizeE2eCoverageResult,
normalizeE2eTargetAdvisorResult,
trustedE2eRecommendationInventory,
} from "../tools/advisors/e2e-recommendations.mts";
import { buildRiskPlan, riskPlanRequiredJobIds } from "../tools/advisors/risk-plan.mts";
import {
focusedPrGateE2eJobsForChangedFiles,
validateRiskPlan,
} from "../tools/e2e/pr-e2e-gate.mts";
import { focusedE2eJobsForChangedFiles } from "../tools/e2e/workflow-boundary.mts";

it("omits the guarded Jetson job from automatic PR E2E plans (#7610)", () => {
const changedFiles = ["test/e2e/live/jetson-nvmap-gpu.test.ts"];

expect(focusedE2eJobsForChangedFiles(changedFiles)).toEqual([
{ id: "jetson-nvmap-gpu", matchedFiles: changedFiles },
]);
const plan = buildRiskPlan({
headSha: "a".repeat(40),
changedFiles,
focusedE2eJobs: focusedPrGateE2eJobsForChangedFiles(changedFiles),
});

expect(focusedPrGateE2eJobsForChangedFiles(changedFiles)).toEqual([]);
expect(validateRiskPlan(plan, new Set(riskPlanRequiredJobIds(plan)))).toEqual(plan);
expect(riskPlanRequiredJobIds(plan)).not.toContain("jetson-nvmap-gpu");
expect(riskPlanRequiredJobIds(plan)).toEqual(
expect.arrayContaining(["cloud-inference", "cloud-onboard", "security-posture"]),
);
});

it("omits the guarded Jetson job from advisor E2E recommendations (#7610)", () => {
const changedFiles = ["test/e2e/live/jetson-nvmap-gpu.test.ts"];
const metadata = { baseRef: "origin/main", headRef: "HEAD", changedFiles };
const riskPlan = buildRiskPlan({
headSha: "a".repeat(40),
changedFiles,
focusedE2eJobs: focusedPrGateE2eJobsForChangedFiles(changedFiles),
});
const modelGuidance = {
requiredTests: [{ id: "jetson-nvmap-gpu", reason: "Model-selected coverage." }],
optionalTests: [{ id: "jetson-nvmap-gpu", reason: "Model-selected coverage." }],
required: [
{
id: "jetson-nvmap-gpu",
workflow: "e2e.yaml",
selectorType: "job",
reason: "Model-selected job.",
},
],
optional: [],
confidence: "high",
};

const coverage = normalizeE2eCoverageResult(modelGuidance, metadata, riskPlan);
const targets = normalizeE2eTargetAdvisorResult(modelGuidance, metadata, { riskPlan });
const dynamicallyDiscovered = normalizeE2eTargetAdvisorResult(modelGuidance, metadata, {
riskPlan,
changedFileSources: {
[changedFiles[0]]: "// @module-tag e2e/credential-free\n",
},
});

expect(trustedE2eRecommendationInventory().allowedJobIds).not.toContain("jetson-nvmap-gpu");
expect(coverage.requiredTests.map((item) => item.id)).not.toContain("jetson-nvmap-gpu");
expect(coverage.optionalTests.map((item) => item.id)).not.toContain("jetson-nvmap-gpu");
expect(targets.required.map((item) => item.id)).not.toContain("jetson-nvmap-gpu");
expect(targets.optional.map((item) => item.id)).not.toContain("jetson-nvmap-gpu");
expect(dynamicallyDiscovered.changedCredentialFreeTests).toEqual([]);
expect(dynamicallyDiscovered.required.map((item) => item.id)).not.toContain("jetson-nvmap-gpu");
});
23 changes: 13 additions & 10 deletions tools/advisors/e2e-recommendations.mts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { liveTargetSupport } from "../../test/e2e/registry/runtime-support.ts";
import { moduleTagDeclarations } from "../e2e/module-tags.mts";
import { containsCommandShapedE2eText } from "./e2e-text.mts";
import { enumValue, recordItems, stringOrUndefined } from "./json.mts";
import { buildRiskPlan, type RiskPlan } from "./risk-plan.mts";
import { buildRiskPlan, isPrE2ePlanningJob, type RiskPlan } from "./risk-plan.mts";

const E2E_WORKFLOW = "e2e.yaml";
const E2E_WORKFLOW_PATH = `.github/workflows/${E2E_WORKFLOW}`;
Expand Down Expand Up @@ -191,12 +191,13 @@ export function normalizeE2eCoverageResult(
}

function deterministicCoverageTests(changedFiles: string[], riskPlan: RiskPlan): E2eCoverageTest[] {
const tests: E2eCoverageTest[] = [...riskPlan.requiredJobs, ...riskPlan.requiredTargets].map(
(selection) => ({
id: selection.id,
reason: selection.reasons.join(" "),
}),
);
const tests: E2eCoverageTest[] = [
...riskPlan.requiredJobs.filter((job) => isPrE2ePlanningJob(job.id)),
...riskPlan.requiredTargets,
].map((selection) => ({
id: selection.id,
reason: selection.reasons.join(" "),
}));
if (requiresCloudOnboardE2e(changedFiles) && !tests.some((test) => test.id === "cloud-onboard")) {
tests.push({
id: "cloud-onboard",
Expand Down Expand Up @@ -352,7 +353,9 @@ function buildE2eTargetNormalizationContext(
const trustedWorkflowText = readTrustedE2eWorkflowText();
const trustedCredentialFreeTests = discoverTrustedCredentialFreeTests();
const allowedJobIds = new Set(
extractAllowedE2eJobIds(trustedWorkflowText, trustedCredentialFreeTests),
extractAllowedE2eJobIds(trustedWorkflowText, trustedCredentialFreeTests).filter(
isPrE2ePlanningJob,
),
);
// The analyzed workflow is untrusted input. It may explain why a changed test is
// unwired, but it must never introduce a selector that CI could later dispatch.
Expand Down Expand Up @@ -380,7 +383,7 @@ function buildE2eTargetNormalizationContext(
for (const [file, project] of changedCredentialFreeProjects) {
const source = changedSource(file, changedFileSources);
const row = source ? credentialFreeTestRow(file, source) : undefined;
if (!row || !project) continue;
if (!row || !project || !isPrE2ePlanningJob(row.id)) continue;
addMapValue(liveTestToJobs, row.file, row.id);
allowedJobIds.add(row.id);
changedCredentialFreeTests.push(row);
Expand Down Expand Up @@ -454,7 +457,7 @@ function trustedAllowedJobIds(): string[] {
return extractAllowedE2eJobIds(
readTrustedE2eWorkflowText(),
discoverTrustedCredentialFreeTests(),
);
).filter(isPrE2ePlanningJob);
}

function extractAllowedE2eJobIds(
Expand Down
7 changes: 7 additions & 0 deletions tools/advisors/risk-plan.mts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const PR_E2E_TYPED_TARGET_IDS = [
] as const;

const PR_E2E_TYPED_TARGET_ID_SET = new Set<string>(PR_E2E_TYPED_TARGET_IDS);
const PR_E2E_PLANNING_OMITTED_JOB_IDS = new Set(["jetson-nvmap-gpu"]);
const DEEPAGENTS_HEADLESS_INFERENCE_CHECK =
"test/e2e/e2e-cloud-experimental/checks/07-deepagents-code-headless-inference.sh";
const DEEPAGENTS_CODE_RUNTIME_ROOT = "agents/langchain-deepagents-code/";
Expand Down Expand Up @@ -153,6 +154,12 @@ export function isPrE2eTypedTargetId(value: string): boolean {
return PR_E2E_TYPED_TARGET_ID_SET.has(value);
}

export function isPrE2ePlanningJob(value: string): boolean {
// Automatic PR planning cannot confirm an online self-hosted Jetson runner.
// Remove this exclusion after the Colossus-backed runner path can make that confirmation.
return !PR_E2E_PLANNING_OMITTED_JOB_IDS.has(value);
}

export function focusedPrE2eTargetsForChangedFiles(
changedFiles: readonly string[],
): TrustedFocusedE2eTarget[] {
Expand Down
16 changes: 13 additions & 3 deletions tools/e2e/pr-e2e-gate.mts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { githubApi, githubApiWithResponse, githubRestPaginated } from "../adviso
import { parseArgs } from "../advisors/io.mts";
import {
buildRiskPlan,
isPrE2ePlanningJob,
isPrE2eTypedTargetId,
RISK_PLAN_VERSION,
type RiskPlan,
Expand Down Expand Up @@ -690,7 +691,7 @@ export function validateRiskPlan(value: unknown, allowedJobs: ReadonlySet<string
const rebuilt = buildRiskPlan({
headSha: value.headSha,
changedFiles: value.changedFiles as string[],
focusedE2eJobs: focusedE2eJobsForChangedFiles(value.changedFiles as string[]),
focusedE2eJobs: focusedPrGateE2eJobsForChangedFiles(value.changedFiles as string[]),
});
if (JSON.stringify(value) !== JSON.stringify(rebuilt)) {
throw new Error("risk plan does not match its hash and inputs");
Expand All @@ -717,6 +718,15 @@ export function validateRiskPlan(value: unknown, allowedJobs: ReadonlySet<string
return rebuilt;
}

export function focusedPrGateE2eJobsForChangedFiles(
changedFiles: readonly string[],
inventory = readFreeStandingJobsInventory(),
): ReturnType<typeof focusedE2eJobsForChangedFiles> {
return focusedE2eJobsForChangedFiles(changedFiles, inventory).filter((selection) =>
isPrE2ePlanningJob(selection.id),
);
}

function riskPlanSelectionIds(plan: RiskPlan): string[] {
return [...riskPlanRequiredJobIds(plan), ...riskPlanRequiredTargetIds(plan)];
}
Expand Down Expand Up @@ -3135,7 +3145,7 @@ export async function startPrGate(
buildRiskPlan({
headSha: command.headSha,
changedFiles,
focusedE2eJobs: focusedE2eJobsForChangedFiles(changedFiles, inventory),
focusedE2eJobs: focusedPrGateE2eJobsForChangedFiles(changedFiles, inventory),
}),
allowedJobs,
);
Expand Down Expand Up @@ -3257,7 +3267,7 @@ async function startAuthorizedPrGate(command: AuthorizedE2ECommand): Promise<voi
buildRiskPlan({
headSha: command.headSha,
changedFiles,
focusedE2eJobs: focusedE2eJobsForChangedFiles(changedFiles, inventory),
focusedE2eJobs: focusedPrGateE2eJobsForChangedFiles(changedFiles, inventory),
}),
new Set(inventory.allowedJobs),
);
Expand Down
6 changes: 4 additions & 2 deletions tools/pr-review-advisor/analyze.mts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
stringOrDefault,
stringOrUndefined,
} from "../advisors/json.mts";
import { buildRiskPlan, type RiskPlan } from "../advisors/risk-plan.mts";
import { buildRiskPlan, isPrE2ePlanningJob, type RiskPlan } from "../advisors/risk-plan.mts";
import {
type AdvisorCompletedTurn,
type AdvisorContextToolResult,
Expand Down Expand Up @@ -1043,7 +1043,9 @@ async function collectDeterministicContext(options: {
const riskPlan = buildRiskPlan({
headSha: options.headSha,
changedFiles: options.changedFiles,
focusedE2eJobs: focusedE2eJobsForChangedFiles(options.changedFiles),
focusedE2eJobs: focusedE2eJobsForChangedFiles(options.changedFiles).filter((selection) =>
isPrE2ePlanningJob(selection.id),
),
});
const riskyAreas = [
...detectRiskyAreas(options.changedFiles),
Expand Down
Loading