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
2 changes: 1 addition & 1 deletion agents/hermes/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# Layers PR-specific code (plugin, config, startup script) on top of the
# pre-built Hermes base image. Mirrors the OpenClaw Dockerfile structure.

ARG BASE_IMAGE=ghcr.io/nvidia/nemoclaw/hermes-sandbox-base@sha256:fa05221f5c7bcafea7e263c84e5d06f87e37d1ccb78dc28c113f1a4066aa544c
ARG BASE_IMAGE=ghcr.io/nvidia/nemoclaw/hermes-sandbox-base@sha256:c925afe7c0742474166a4813a59f5961a93da4d130e9b35fb04088a384702142

# Group repository-owned files outside the final image so BuildKit can collapse
# related files without invalidating earlier final-image work.
Expand Down
13 changes: 9 additions & 4 deletions test/e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -538,10 +538,15 @@ through cold onboarding and a real first turn.
The repository-root `Dockerfile.base` remains in only the `platform-install`
family. It selects `cloud-onboard` and does not trigger the cold `full-e2e`
path.
The Deep Agents Code headless-inference check additionally selects the exact
`ubuntu-repo-cloud-langchain-deepagents-code` typed target. That target is
hashed into the risk plan beside the control-plane floor jobs, so the
controller dispatches both selector types in one correlated workflow run.
Non-documentation runtime changes under `agents/langchain-deepagents-code/`
and changes to the Deep Agents Code headless-inference check select the exact
`ubuntu-repo-cloud-langchain-deepagents-code` typed target. Documentation and
ordinary test changes alone do not select it. The target is hashed into the
risk plan beside any control-plane floor jobs, so the controller dispatches
both selector types in one correlated workflow run. Fork revisions whose plans
select credential-bearing jobs or targets instead require explicit protected
credentialed-E2E approval. Plans with no selected jobs or targets can complete
without an E2E run.
An internal revision whose matched control-plane files are drawn only from the
trusted controller and observer boundaries—`.github/workflows/pr-e2e-gate.yaml`,
`tools/e2e/pr-e2e-gate.mts`, and `tools/e2e/pr-e2e-required.mts`—automatically
Expand Down
7 changes: 4 additions & 3 deletions test/pr-e2e-gate-fork-approval.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const CI_RUN_ID = 99;
const CI_RUN_ATTEMPT = 3;
const GATE_RUN_ID = 77;
const APPROVAL_RUN_ID = 123;
const DCODE_PATCH = "agents/langchain-deepagents-code/patch-managed-deepagents-code.py";
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllEnvs();
Expand Down Expand Up @@ -324,7 +325,7 @@ function successfulApprovedForkRoutes(approvals: unknown, requests: RecordedGitH
}

describe("PR E2E controller fork credentialed E2E approval safety", () => {
it("requires protected approval before a risky fork can run credentialed E2E", async () => {
it("requires the selected DCode target and protected approval before a risky fork can run credentialed E2E (#7463)", async () => {
const workDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-pr-e2e-gate-fork-"));
const outputPath = path.join(workDir, "github-output");
fs.writeFileSync(outputPath, "", { mode: 0o600 });
Expand All @@ -350,7 +351,7 @@ describe("PR E2E controller fork credentialed E2E approval safety", () => {
),
githubFetchRoute(
({ url }) => url.includes("/pulls/42/files?"),
() => githubResponse([{ filename: "src/lib/onboard.ts" }]),
() => githubResponse([{ filename: DCODE_PATCH }]),
),
githubFetchRoute(
({ url, method }) => url.endsWith("/check-runs/17") && method === "PATCH",
Expand Down Expand Up @@ -388,7 +389,7 @@ describe("PR E2E controller fork credentialed E2E approval safety", () => {
expect(JSON.stringify(pending?.body)).toContain("head repository `contributor/NemoClaw`");
expect(JSON.stringify(pending?.body)).toContain(`head SHA \`${HEAD_SHA}\``);
expect(JSON.stringify(pending?.body)).toContain(`base SHA \`${BASE_SHA}\``);
expect(JSON.stringify(pending?.body)).toContain("jobs:");
expect(JSON.stringify(pending?.body)).toContain("targets:");
expect(JSON.stringify(pending?.body)).toContain("deterministic plan");
expect(fs.readFileSync(outputPath, "utf8")).toContain(
[
Expand Down
29 changes: 28 additions & 1 deletion test/pr-risk-plan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe("deterministic PR risk plan", () => {
const second = plan("src/lib/onboard.ts", "src/lib/state/registry.ts");

expect(first).toEqual(second);
expect(first.version).toBe(5);
expect(first.version).toBe(6);
expect(first.headSha).toBe(HEAD_SHA);
expect(first.planHash).toMatch(/^[a-f0-9]{64}$/u);
expect(first.changedFiles).toEqual(["src/lib/onboard.ts", "src/lib/state/registry.ts"]);
Expand Down Expand Up @@ -115,6 +115,33 @@ describe("deterministic PR risk plan", () => {
expect(requiresCredentialedE2eAuthorization(result)).toBe(true);
});

it("selects the Deep Agents Code target for its managed runtime changes (#7463)", () => {
const changedFiles = [
"agents/langchain-deepagents-code/dependency-review.md",
"agents/langchain-deepagents-code/patch-managed-deepagents-code.py",
"test/langchain-deepagents-code-managed-model-params.test.ts",
"test/langchain-deepagents-code-nemotron-profile-plugin.test.ts",
];
const result = buildRiskPlan({ headSha: HEAD_SHA, changedFiles });
const docsAndTestsOnly = plan(
"agents/langchain-deepagents-code/dependency-review.md",
"agents/langchain-deepagents-code/runtime-notes.mdx",
"agents/langchain-deepagents-code/resolver.test.ts",
"test/langchain-deepagents-code-managed-model-params.test.ts",
);

expect(riskPlanRequiredTargetIds(result)).toEqual(PR_E2E_TYPED_TARGET_IDS);
expect(result.requiredTargets).toEqual([
expect.objectContaining({
id: PR_E2E_TYPED_TARGET_IDS[0],
families: ["focused-e2e"],
matchedFiles: ["agents/langchain-deepagents-code/patch-managed-deepagents-code.py"],
}),
]);
expect(result.tier).toBe(2);
expect(riskPlanRequiredTargetIds(docsAndTestsOnly)).toEqual([]);
});

it("does not infer security or inference risk from unrelated path substrings", () => {
const result = plan("src/lib/actions/sandbox/mcp-bridge-provider.ts", "src/lib/secretary.ts");

Expand Down
20 changes: 14 additions & 6 deletions tools/advisors/risk-plan.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@

import { createHash } from "node:crypto";

export const RISK_PLAN_VERSION = 5 as const;
export const RISK_PLAN_VERSION = 6 as const;

export const PR_E2E_TYPED_TARGET_IDS = ["ubuntu-repo-cloud-langchain-deepagents-code"] as const;

const PR_E2E_TYPED_TARGET_ID_SET = new Set<string>(PR_E2E_TYPED_TARGET_IDS);
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/";

export type RiskTier = 0 | 1 | 2 | 3;
export type RiskFamilyId =
Expand Down Expand Up @@ -108,10 +109,10 @@ const RISK_RELEVANT_TEST_FILES = new Set([
"test/e2e/risk-signal-reporter.ts",
]);
const FOCUSED_E2E_SUMMARY =
"Changed workflow-wired E2E tests must execute through their trusted canonical jobs or typed targets.";
"Changed runtime surfaces and workflow-wired E2E tests must execute through their trusted canonical jobs or typed targets.";
const FOCUSED_E2E_INVARIANTS = [
"the changed test remains wired to a job or typed-target selector declared by the trusted workflow",
"the canonical execution path runs the changed test rather than treating it as advisory coverage",
"the selected job or typed target exercises the changed runtime surface or test",
"the canonical execution path runs the required coverage rather than treating it as advisory",
] as const;

export function isPrE2eTypedTargetId(value: string): boolean {
Expand All @@ -121,11 +122,18 @@ export function isPrE2eTypedTargetId(value: string): boolean {
export function focusedPrE2eTargetsForChangedFiles(
changedFiles: readonly string[],
): TrustedFocusedE2eTarget[] {
return changedFiles.includes(DEEPAGENTS_HEADLESS_INFERENCE_CHECK)
const matchedFiles = stableUnique(
changedFiles.filter(
(file) =>
file === DEEPAGENTS_HEADLESS_INFERENCE_CHECK ||
(file.startsWith(DEEPAGENTS_CODE_RUNTIME_ROOT) && isRuntimeRelevant(file)),
),
);
return matchedFiles.length > 0
? [
{
id: PR_E2E_TYPED_TARGET_IDS[0],
matchedFiles: [DEEPAGENTS_HEADLESS_INFERENCE_CHECK],
matchedFiles,
},
]
: [];
Expand Down
Loading