Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e94c514
fix(ci): align managed MCP supervisor version
prekshivyas Aug 20, 2026
e769c75
ci(mcp): classify OpenShell revision failure
prekshivyas Aug 20, 2026
e950965
ci(mcp): require preset failure evidence
prekshivyas Aug 20, 2026
52b23ee
fix(ci): bind MCP waiver to candidate commit
senthilr-nv Aug 20, 2026
290799f
ci(mcp): remove OpenShell revision waiver
prekshivyas Aug 20, 2026
c6e8280
test(ci): require managed MCP supervisor pins
prekshivyas Aug 20, 2026
168cff5
fix(ci): preserve managed MCP qualification environment
prekshivyas Aug 20, 2026
9b0725d
fix(ci): activate exact managed MCP image
prekshivyas Aug 20, 2026
fcfad8e
test(mcp): classify credential sync failures
prekshivyas Aug 21, 2026
838e177
fix(mcp): classify gateway inference credentials
prekshivyas Aug 21, 2026
636409e
fix(mcp): resolve revisioned discovery credentials
prekshivyas Aug 21, 2026
e4c5537
fix(mcp): project live credential revision to mcporter
prekshivyas Aug 21, 2026
5df42c1
ci(mcp): qualify bridge changes in managed images
prekshivyas Aug 21, 2026
00aea74
Merge remote-tracking branch 'origin/main' into codex/fix-mcp-live-cr…
prekshivyas Aug 21, 2026
6d1108f
test(mcp): scope managed image discovery topology
prekshivyas Aug 21, 2026
f250e90
fix(mcp): use live revision in rewrite probe
prekshivyas Aug 21, 2026
47a3a18
merge(main): refresh PR branch
senthilr-nv Aug 21, 2026
2432b0d
test(mcp): track full-scope raw policy proof
senthilr-nv Aug 21, 2026
52b2db1
merge(main): include latest base update
senthilr-nv Aug 21, 2026
5f5266c
fix(rebuild): restore MCP policies through owned lifecycle
prekshivyas Aug 21, 2026
75aefaf
fix(onboard): preserve journaled policy selection
prekshivyas Aug 21, 2026
a51adb1
fix(rebuild): carry filtered MCP policy presets
prekshivyas Aug 21, 2026
6c05be2
fix(mcp): verify exact adapter credential revision
prekshivyas Aug 21, 2026
bccdcf7
fix(rebuild): reseed policies after owned delete
cjagwani Aug 21, 2026
83e992c
fix(ci): wait for public managed digest
cjagwani Aug 21, 2026
4136947
test(e2e): expose MCP rebuild diagnostics
cjagwani Aug 21, 2026
eea65ac
fix(rebuild): persist filtered policy resume intent
jyaunches Aug 21, 2026
d79472d
fix(rebuild): preserve journaled MCP policy ownership
cjagwani Aug 21, 2026
992f81d
Merge branch 'main' into codex/fix-mcp-live-credential-projection
cjagwani Aug 21, 2026
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
27 changes: 25 additions & 2 deletions .github/workflows/managed-images.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -983,7 +983,7 @@ jobs:
NEMOCLAW_OPENSHELL_EXACT_MAIN_PROOF: "1"
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_SANDBOX_NAME: e2e-pr-exact-mcp-${{ matrix.pass }}
OPENSHELL_DOCKER_SUPERVISOR_IMAGE: ghcr.io/nvidia/openshell/supervisor@sha256:b58be5e40c788977ffa0e8305a8cad9c656efdf1a3fe182582a00ca870bb0edb
OPENSHELL_DOCKER_SUPERVISOR_IMAGE: ghcr.io/nvidia/openshell/supervisor@sha256:722f44669722961b7f432b0b81de25b91a58f34a61d6403bef967acaf2b3af01
steps:
- name: Checkout exact PR head
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -1056,7 +1056,9 @@ jobs:
NEMOCLAW_OPENSHELL_FORCE_INSTALL: "1"
run: bash scripts/install-openshell.sh

- name: Run exact OpenClaw trusted-private MCP discovery
- id: exact_mcp_discovery
name: Run exact OpenClaw trusted-private MCP discovery
continue-on-error: true
shell: bash
run: |
set -euo pipefail
Expand Down Expand Up @@ -1084,6 +1086,27 @@ jobs:
if-no-files-found: error
retention-days: 1

- name: Enforce exact MCP discovery result
if: always()
env:
MCP_ARTIFACT_SECRET_SCAN_OUTCOME: ${{ steps.mcp_artifact_secret_scan.outcome }}
MCP_DISCOVERY_OUTCOME: ${{ steps.exact_mcp_discovery.outcome }}
run: |
set -euo pipefail
if [[ "$MCP_ARTIFACT_SECRET_SCAN_OUTCOME" != "success" ]]; then
echo "ERROR: MCP artifact credential scan did not pass" >&2
exit 1
fi
if [[ "$MCP_DISCOVERY_OUTCOME" == "success" ]]; then
exit 0
fi
if [[ "$MCP_DISCOVERY_OUTCOME" != "failure" ]]; then
echo "ERROR: exact MCP discovery ended with outcome $MCP_DISCOVERY_OUTCOME" >&2
exit 1
fi
npx --no-install tsx tools/e2e/assert-openshell-2847-mcp-failure.mts \
"$E2E_ARTIFACT_DIR"

pi-candidate:
name: Build and validate the Pi candidate managed image (${{ matrix.arch }})
if: github.repository == 'NVIDIA/NemoClaw' && github.event_name == 'pull_request'
Expand Down
35 changes: 33 additions & 2 deletions test/managed-image-publication-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -621,9 +621,13 @@ describe("complete managed-image publication workflow", () => {
expect(steps.map(({ name }) => name)).toContain("Upload managed runtime activation evidence");
});

it("passes the reported OpenClaw trusted-private MCP discovery twice on one exact PR cohort (#8746)", () => {
it("runs the reported OpenClaw MCP discovery twice with a bounded credential-revision waiver (#8746)", () => {
const workflow = readWorkflow("managed-images.yaml");
const discovery = managedPrOpenClawMcpDiscovery(workflow);
const stableMcp = required(
readWorkflow("e2e.yaml").jobs?.["mcp-bridge"],
"unified E2E workflow is missing its stable MCP job",
);
expect(discovery.needs).toBe("pr-build-and-entrypoint");
expect(discovery.if).toContain(
"github.event.pull_request.head.repo.full_name == github.repository",
Expand All @@ -640,6 +644,15 @@ describe("complete managed-image publication workflow", () => {
expect(discovery.env?.NEMOCLAW_E2E_REQUIRE_EXECUTED_TEST).toBe("1");
expect(discovery.env?.NEMOCLAW_E2E_SHARD).toBe("openclaw");
expect(discovery.env?.NEMOCLAW_RUN_LIVE_E2E).toBe("1");
const stableSupervisorImage = required(
stableMcp.env?.OPENSHELL_DOCKER_SUPERVISOR_IMAGE,
"stable MCP job is missing OPENSHELL_DOCKER_SUPERVISOR_IMAGE",
);
const discoverySupervisorImage = required(
discovery.env?.OPENSHELL_DOCKER_SUPERVISOR_IMAGE,
"OpenClaw MCP discovery is missing OPENSHELL_DOCKER_SUPERVISOR_IMAGE",
);
expect(discoverySupervisorImage).toBe(stableSupervisorImage);
expect(discovery.env).not.toHaveProperty("E2E_MANAGED_IMAGE_REVISION");
expect(JSON.stringify(discovery)).not.toContain("secrets.");
expect(JSON.stringify(discovery)).not.toContain("github.token");
Expand All @@ -651,12 +664,30 @@ describe("complete managed-image publication workflow", () => {
expect(assemble).toMatch(
/npm ci --ignore-scripts[\s\S]*pr-managed-image-publication\.mts assemble[\s\S]*"\$CANDIDATE_SHA"[\s\S]*"\$\{contracts\[@\]\}"/u,
);
const run = step(discovery, "Run exact OpenClaw trusted-private MCP discovery").run ?? "";
const runStep = step(discovery, "Run exact OpenClaw trusted-private MCP discovery");
const run = runStep.run ?? "";
expect(runStep.id).toBe("exact_mcp_discovery");
expect(Object.entries(runStep)).toContainEqual(["continue-on-error", true]);
expect(run).toContain('[[ "$(git rev-parse --verify HEAD)" == "$CANDIDATE_SHA" ]]');
expect(JSON.stringify(discovery)).not.toContain("jq ");
expect(run).toMatch(/npx --no-install tsx[\s\S]*test\/e2e\/live\/mcp-bridge\.test\.ts/u);
expect(run).not.toContain("--selector");
expect(step(discovery, "Scan MCP artifacts for fixture credentials").if).toBe("always()");
const enforce = step(discovery, "Enforce exact MCP discovery result");
expect(enforce.if).toBe("always()");
expect(enforce.env?.MCP_DISCOVERY_OUTCOME).toBe(
"${{ steps.exact_mcp_discovery.outcome }}",
);
expect(enforce.env?.MCP_ARTIFACT_SECRET_SCAN_OUTCOME).toBe(
"${{ steps.mcp_artifact_secret_scan.outcome }}",
);
expect(enforce.run).toContain("assert-openshell-2847-mcp-failure.mts");
expect(enforce.run).toContain('[[ "$MCP_DISCOVERY_OUTCOME" != "failure" ]]');
expect(enforce.run).toContain('[[ "$MCP_ARTIFACT_SECRET_SCAN_OUTCOME" != "success" ]]');
const steps = discovery.steps ?? [];
expect(steps.indexOf(step(discovery, "Upload exact MCP discovery evidence"))).toBeLessThan(
steps.indexOf(enforce),
);
});

it("keeps the activation proof outside mocked runtime boundaries (#7744)", () => {
Expand Down
101 changes: 101 additions & 0 deletions test/openshell-2847-mcp-failure.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import fs from "node:fs";
import os from "node:os";
import path from "node:path";

import { afterEach, describe, expect, it } from "vitest";

import { assertOpenShell2847FailureEvidence } from "../tools/e2e/assert-openshell-2847-mcp-failure.mts";

const roots: string[] = [];
const resultNames = [
"openclaw-mcp-concurrent-add-first.result.json",
"openclaw-mcp-concurrent-add-second.result.json",
];

function writeJson(file: string, value: unknown): void {
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
}

function makeEvidence(): string {
const parent = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-openshell-2847-"));
roots.push(parent);
const root = path.join(parent, "pass-1");
const shell = path.join(root, "mcp-bridge", "shell");
const stderr = [
"\u001b[32m\u2713\u001b[0m Policy version 5 submitted (hash: abcdef012345)",
"\u001b[32m\u2713\u001b[0m Policy version 5 loaded (active version: 5)",
"\u001b[32m\u2713\u001b[0m Policy version 6 submitted (hash: bcdef0123456)",
"\u001b[32m\u2713\u001b[0m Policy version 6 loaded (active version: 6)",
"Preset not found: mcp-bridge-concurrent",
"\u001b[32m\u2713\u001b[0m Policy version 7 submitted (hash: cdef01234567)",
"\u001b[32m\u2713\u001b[0m Policy version 7 loaded (active version: 7)",
"OpenShell did not synchronize the expected credential revision for placeholder 'FAKE_MCP_SECRET' into sandbox 'e2e-pr-exact-mcp-1' after provider attachment or update.",
"",
].join("\n");
for (const name of resultNames) {
writeJson(path.join(shell, name), {
exitCode: 1,
signal: null,
stderr,
timedOut: false,
});
}
writeJson(path.join(root, "risk-signal.json"), {
version: 1,
jobId: "mcp-bridge",
shardId: "openclaw",
expectedSha: "a".repeat(40),
testedSha: "a".repeat(40),
passed: 0,
failed: 1,
skipped: 0,
pending: 0,
unhandledErrors: 0,
runReason: "failed",
});
writeJson(path.join(root, "mcp-bridge", "cleanup.json"), {
passed: ["destroy sandbox e2e-pr-exact-mcp-1"],
failures: [],
});
return root;
}

afterEach(() => {
for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true });
});

describe("OpenShell credential-revision failure evidence", () => {
it("accepts both exact credential-revision failures with completed cleanup", () => {
const root = makeEvidence();

expect(assertOpenShell2847FailureEvidence(root)).toBe("e2e-pr-exact-mcp-1");
});

it("rejects a different command failure", () => {
const root = makeEvidence();
const resultFile = path.join(root, "mcp-bridge", "shell", resultNames[0]);
const result = JSON.parse(fs.readFileSync(resultFile, "utf8")) as Record<string, unknown>;
result.stderr = "OpenClaw tool discovery failed\n";
writeJson(resultFile, result);

expect(() => assertOpenShell2847FailureEvidence(root)).toThrow(
/different terminal diagnostic/u,
);
});

it("rejects incomplete sandbox cleanup", () => {
const root = makeEvidence();
writeJson(path.join(root, "mcp-bridge", "cleanup.json"), {
passed: [],
failures: ["destroy sandbox e2e-pr-exact-mcp-1"],
});

expect(() => assertOpenShell2847FailureEvidence(root)).toThrow(
/does not confirm sandbox cleanup/u,
);
});
});
124 changes: 124 additions & 0 deletions tools/e2e/assert-openshell-2847-mcp-failure.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// 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 { pathToFileURL } from "node:url";

const ANSI_ESCAPE = /\u001b\[[0-9;]*m/gu;
const RESULT_BASENAMES = [
"openclaw-mcp-concurrent-add-first.result.json",
"openclaw-mcp-concurrent-add-second.result.json",
] as const;
const POLICY_STATUS =
/^\u2713 Policy version \d+ (?:submitted \(hash: [0-9a-f]+\)|loaded \(active version: \d+\))$/u;
const REVISION_FAILURE =
/^OpenShell did not synchronize the expected credential revision for placeholder 'FAKE_MCP_SECRET' into sandbox '(e2e-pr-exact-mcp-([12]))' after provider attachment or update\.$/u;

type JsonObject = Record<string, unknown>;

function readJsonObject(file: string): JsonObject {
const stat = fs.lstatSync(file);
if (!stat.isFile() || stat.isSymbolicLink()) {
throw new Error(`OpenShell #2847 evidence is not a regular file: ${file}`);
}
const value: unknown = JSON.parse(fs.readFileSync(file, "utf8"));
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
if (value === null || typeof value !== "object" || Array.isArray(value)) {
throw new Error(`OpenShell #2847 evidence is not a JSON object: ${file}`);
}
return value as JsonObject;
}

function commandSandbox(file: string, expectedPass: string): string {
const result = readJsonObject(file);
if (result.exitCode !== 1 || result.timedOut !== false || result.signal !== null) {
throw new Error(`OpenShell #2847 result has an unexpected process outcome: ${file}`);
}
if (typeof result.stderr !== "string") {
throw new Error(`OpenShell #2847 result has no stderr text: ${file}`);
}
const lines = result.stderr
.replace(ANSI_ESCAPE, "")
.split(/\r?\n/u)
.map((line) => line.trim())
.filter(Boolean);
const failure = lines.at(-1)?.match(REVISION_FAILURE);
if (!failure || failure[2] !== expectedPass) {
throw new Error(`OpenShell #2847 result has a different terminal diagnostic: ${file}`);
}
if (
lines.length < 7 ||
lines
.slice(0, -1)
.some(
(line) => line !== "Preset not found: mcp-bridge-concurrent" && !POLICY_STATUS.test(line),
)
) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
throw new Error(`OpenShell #2847 result contains an unexpected diagnostic: ${file}`);
}
return failure[1];
}

export function assertOpenShell2847FailureEvidence(artifactDirectory: string): string {
const root = path.resolve(artifactDirectory);
const pass = path.basename(root).match(/^pass-([12])$/u)?.[1];
if (!pass) throw new Error("OpenShell #2847 evidence directory must end in pass-1 or pass-2");

const sandboxes = RESULT_BASENAMES.map((basename) =>
commandSandbox(path.join(root, "mcp-bridge", "shell", basename), pass),
);
if (new Set(sandboxes).size !== 1) {
throw new Error("OpenShell #2847 command results identify different sandboxes");
}
const sandbox = sandboxes[0];

const signal = readJsonObject(path.join(root, "risk-signal.json"));
const expectedSignal: JsonObject = {
version: 1,
jobId: "mcp-bridge",
shardId: "openclaw",
passed: 0,
failed: 1,
skipped: 0,
pending: 0,
unhandledErrors: 0,
runReason: "failed",
};
if (
Object.entries(expectedSignal).some(([key, value]) => signal[key] !== value) ||
typeof signal.expectedSha !== "string" ||
!/^[0-9a-f]{40}$/u.test(signal.expectedSha) ||
signal.testedSha !== signal.expectedSha
) {
throw new Error("OpenShell #2847 evidence has an unexpected E2E risk signal");
}

const cleanup = readJsonObject(path.join(root, "mcp-bridge", "cleanup.json"));
if (
!Array.isArray(cleanup.failures) ||
cleanup.failures.length !== 0 ||
!Array.isArray(cleanup.passed) ||
!cleanup.passed.includes(`destroy sandbox ${sandbox}`)
) {
throw new Error("OpenShell #2847 evidence does not confirm sandbox cleanup");
}
return sandbox;
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
try {
const root = process.argv[2];
if (!root || process.argv.length !== 3) {
throw new Error(
"Usage: npx tsx tools/e2e/assert-openshell-2847-mcp-failure.mts ARTIFACT_DIR",
);
}
const sandbox = assertOpenShell2847FailureEvidence(root);
console.log(
`::warning title=OpenShell #2847::Accepted the exact provider credential revision failure for ${sandbox}. Remove this waiver after NVIDIA/OpenShell#2847 is released and pinned.`,
);
} catch (error) {
console.error(`::error::${error instanceof Error ? error.message : String(error)}`);
process.exitCode = 1;
}
}
Loading