Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5ea76ef
fix(e2e): retain immutable openshell dev artifacts
rsliter Aug 13, 2026
9958e7f
fix(e2e): preserve installer trust boundary
rsliter Aug 13, 2026
2bff3bd
fix(e2e): harden dev artifact consumption
rsliter Aug 14, 2026
6aeb27d
docs(e2e): clarify failure evidence fallback
rsliter Aug 14, 2026
9a003b7
fix(e2e): close artifact verification races
rsliter Aug 14, 2026
9ad555c
test(e2e): prove oversized archives skip extraction
cv Aug 14, 2026
57ed235
fix(e2e): trust dev artifact resolver
rsliter Aug 14, 2026
715ce51
merge(e2e): integrate archive extraction test
rsliter Aug 14, 2026
ba88829
merge(e2e): update PR branch from main
cv Aug 14, 2026
342c9a3
fix(e2e): accept reviewed artifact dependency
cv Aug 14, 2026
6b5f7ed
fix(e2e): align trusted artifact boundaries
rsliter Aug 14, 2026
9e37a19
merge(e2e): integrate artifact boundary fix
rsliter Aug 14, 2026
62a69ee
fix(e2e): reuse trusted OpenShell installer
cv Aug 14, 2026
60b10ce
Merge remote-tracking branch 'origin/codex/fix-openshell-dev-artifact…
cv Aug 14, 2026
cef89d4
Merge remote-tracking branch 'origin/main' into codex/fix-openshell-d…
cv Aug 14, 2026
ee9ea47
merge(e2e): incorporate current main fixes
cv Aug 14, 2026
85f614c
Merge remote-tracking branch 'origin/codex/fix-openshell-dev-artifact…
cv Aug 14, 2026
dfc31fb
fix(e2e): align workflow boundary guidance
cv Aug 14, 2026
d25a6d3
fix(e2e): restore protected artifact install path
rsliter Aug 14, 2026
f6d80e5
merge(e2e): reconcile workflow boundary guidance
rsliter Aug 14, 2026
f489e92
test(e2e): keep installer fixture linear
cv Aug 14, 2026
e5c26d8
docs(e2e): clarify shard verification boundary
rsliter Aug 14, 2026
476160e
merge(e2e): reconcile installer fixture cleanup
rsliter Aug 14, 2026
75de2d2
fix(e2e): keep installer trust anchor unchanged
cv Aug 14, 2026
ab2e565
Merge remote-tracking branch 'origin/codex/fix-openshell-dev-artifact…
cv Aug 14, 2026
5542ef6
Merge remote-tracking branch 'origin/main' into codex/fix-9063-final
cv Aug 14, 2026
efcfcc8
docs(e2e): distinguish retained artifact shims
cv Aug 14, 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
111 changes: 107 additions & 4 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1770,10 +1770,52 @@ jobs:
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh

mcp-bridge-dev:
openshell-dev-artifact:
needs: generate-matrix
if: ${{ contains(fromJSON(needs.generate-matrix.outputs.selected_jobs), 'mcp-bridge-dev') }}
runs-on: ubuntu-latest
permissions:
contents: read
timeout-minutes: 15
outputs:
artifact_name: ${{ steps.resolve_openshell_dev_artifact.outputs.artifact_name }}
source_commit: ${{ steps.resolve_openshell_dev_artifact.outputs.source_commit }}
manifest_sha256: ${{ steps.resolve_openshell_dev_artifact.outputs.manifest_sha256 }}
steps:
- name: Checkout trusted OpenShell dev tooling
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ github.repository }}
ref: ${{ inputs.workflow_sha || github.workflow_sha }}
path: .trusted-openshell-dev-artifact
persist-credentials: false
sparse-checkout: |
scripts/install-openshell.sh
tools/e2e/openshell-dev-artifact.mts

- name: Set up Node for OpenShell dev artifact resolution
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22

- id: resolve_openshell_dev_artifact
name: Resolve immutable OpenShell dev artifact
run: >-
node --experimental-strip-types --no-warnings
"${{ github.workspace }}/.trusted-openshell-dev-artifact/tools/e2e/openshell-dev-artifact.mts" resolve
"${{ runner.temp }}/openshell-dev-artifact"

- name: Upload OpenShell dev artifact resolution
if: ${{ always() }}
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: ${{ steps.resolve_openshell_dev_artifact.outputs.artifact_name || format('openshell-dev-infrastructure-failure-{0}-{1}', github.run_id, github.run_attempt) }}
path: ${{ runner.temp }}/openshell-dev-artifact/

mcp-bridge-dev:
needs: [generate-matrix, openshell-dev-artifact]
if: ${{ contains(fromJSON(needs.generate-matrix.outputs.selected_jobs), 'mcp-bridge-dev') }}
runs-on: ubuntu-latest
permissions:
contents: read
timeout-minutes: 90
Expand All @@ -1797,6 +1839,17 @@ jobs:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false

- name: Checkout trusted OpenShell dev tooling
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ github.repository }}
ref: ${{ inputs.workflow_sha || github.workflow_sha }}
path: .trusted-openshell-dev-artifact
persist-credentials: false
sparse-checkout: |
scripts/install-openshell.sh
tools/e2e/openshell-dev-artifact.mts

- *dockerhub-auth

- name: Prepare E2E workspace
Expand All @@ -1809,6 +1862,25 @@ jobs:
with:
provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }}

- name: Restore immutable OpenShell dev artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ needs.openshell-dev-artifact.outputs.artifact_name }}
path: ${{ runner.temp }}/openshell-dev-artifact
digest-mismatch: error

- name: Verify immutable OpenShell dev artifact
env:
OPENSHELL_DEV_ARTIFACT_DIR: ${{ runner.temp }}/openshell-dev-artifact
OPENSHELL_DEV_EXPECTED_MANIFEST_SHA256: ${{ needs.openshell-dev-artifact.outputs.manifest_sha256 }}
OPENSHELL_DEV_EXPECTED_SOURCE_COMMIT: ${{ needs.openshell-dev-artifact.outputs.source_commit }}
run: >-
node --experimental-strip-types --no-warnings
"${{ github.workspace }}/.trusted-openshell-dev-artifact/tools/e2e/openshell-dev-artifact.mts" verify
"$OPENSHELL_DEV_ARTIFACT_DIR"
"$OPENSHELL_DEV_EXPECTED_SOURCE_COMMIT"
"$OPENSHELL_DEV_EXPECTED_MANIFEST_SHA256"

- name: Install and verify cloudflared prerequisite
# Update posture: keep this dev compatibility lane on the same reviewed
# version/SHA256 pair as the stable lane; workflow-contract tests fail
Expand All @@ -1834,17 +1906,47 @@ jobs:
- name: Generate MCP test TLS
run: bash test/e2e/setup-mcp-test-tls.sh

- name: Revoke Docker auth before unverified dev tooling
- name: Revoke Docker auth before OpenShell development tooling
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh

- name: Install OpenShell CLI
- name: Install immutable OpenShell dev artifact
env:
NEMOCLAW_ACCEPT_DEV_UNVERIFIED_INSTALL: "1"
NEMOCLAW_OPENSHELL_FORCE_INSTALL: "1"
OPENSHELL_DEV_ASSET_DIR: ${{ runner.temp }}/openshell-dev-artifact/assets
run: |
set -euo pipefail
bash scripts/install-openshell.sh
shim_dir="$(mktemp -d)"
trap 'rm -rf "$shim_dir"' EXIT
cat >"$shim_dir/gh" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
if [[ "$#" -ne 10 || "$1" != "release" || "$2" != "download" || "$3" != "dev" || "$4" != "--repo" || "$5" != "NVIDIA/OpenShell" || "$6" != "--pattern" || "$8" != "--dir" || "${10}" != "--clobber" ]]; then
printf 'Unsupported gh invocation for retained OpenShell assets.\n' >&2
exit 64
fi
asset="$7"
destination="$9"
case "$asset" in
openshell-x86_64-unknown-linux-musl.tar.gz | openshell-checksums-sha256.txt | openshell-gateway-x86_64-unknown-linux-gnu.tar.gz | openshell-gateway-checksums-sha256.txt | openshell-sandbox-x86_64-unknown-linux-gnu.tar.gz | openshell-sandbox-checksums-sha256.txt) ;;
*)
printf 'Unsupported retained OpenShell asset: %s\n' "$asset" >&2
exit 64
;;
esac
source_asset="${OPENSHELL_DEV_ASSET_DIR}/${asset}"
[[ -f "$source_asset" && ! -L "$source_asset" && "$destination" = /* && -d "$destination" && ! -L "$destination" ]]
cp -- "$source_asset" "$destination/$asset"
EOF
cat >"$shim_dir/curl" <<'EOF'
#!/usr/bin/env bash
printf 'Network fallback is disabled for retained OpenShell assets.\n' >&2
exit 1
EOF
chmod 700 "$shim_dir/gh" "$shim_dir/curl"
PATH="$shim_dir:$PATH" \
bash "${{ github.workspace }}/.trusted-openshell-dev-artifact/scripts/install-openshell.sh"

- id: mcp_runtime_compatibility
name: Classify OpenShell credential-boundary compatibility
Expand Down Expand Up @@ -3543,6 +3645,7 @@ jobs:
openshell-gateway-auth-contract,
mcp-bridge,
openshell-credential-generation-window,
openshell-dev-artifact,
mcp-bridge-dev,
managed-image-multiarch-startup,
llama-cpp-dgx-spark-plan,
Expand Down
1 change: 1 addition & 0 deletions scripts/checks/vitest-project-overlap.mts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const INSTALLER_INTEGRATION_TESTS = new Set([
"test/install-forward-restore-diagnostics.test.ts",
"test/install-hermes-forward-restore.test.ts",
"test/install-managed-cli-reuse.test.ts",
"test/install-openshell-e2e-artifact.test.ts",
"test/install-openshell-version-pin.test.ts",
"test/install-openshell-version-check.test.ts",
"test/install-preflight-docker-bootstrap.test.ts",
Expand Down
21 changes: 21 additions & 0 deletions test/e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,27 @@ trust boundary and are never host-prebuilt by this fixture.
The runtime target for `openclaw-plugin-runtime-exdev` is 16–17 minutes.
Push-run timing for the reduced lifecycle has not yet been measured.

## OpenShell development artifact retention

The `openshell-dev-artifact` job resolves the public OpenShell `dev` release
once for each selected `mcp-bridge-dev` run. It records the source commit and
the GitHub asset ID, source URL, size, and SHA-256 digest for every required
Linux x64 archive and checksum file. It rejects release drift during download,
then uploads the verified bytes under a content-addressed name with the shared
14-day E2E retention policy.

The OpenClaw, Hermes, and LangChain Deep Agents Code shards restore and verify
that same artifact with the trusted workflow revision. An exact-argument and
asset-allowlisted `gh` shim presents only those retained files to the unchanged
trusted `scripts/install-openshell.sh` path. A separate `curl` shim blocks
network fallback. The installer still checks the release checksums and archive
structure before installation. A missing, replaced, or corrupt upstream asset
fails the resolver as an infrastructure failure. The job error reports the
failed identifier and source URL, and `resolution.json` records them when the
artifact directory remains writable. The three product shards do not start in
that case, so the run cannot report a product failure before reaching product
assertions.

## Larger-runner routing

The larger-runner experiment is inactive while the configuration variable
Expand Down
113 changes: 110 additions & 3 deletions test/e2e/support/mcp-workflow-boundary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,26 +264,133 @@ describe("MCP workflow artifact boundary", () => {
}
});

it("revokes Docker credentials before executing unverified dev artifacts", () => {
it("revokes Docker credentials before executing OpenShell development tooling (#9051)", () => {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-mcp-workflow-"));
const workflowPath = path.join(directory, "e2e.yaml");
try {
const workflow = YAML.parse(fs.readFileSync(".github/workflows/e2e.yaml", "utf8")) as {
jobs: Record<string, { steps: Array<Record<string, unknown>> }>;
};
workflow.jobs["mcp-bridge-dev"].steps = workflow.jobs["mcp-bridge-dev"].steps.filter(
(step) => step.name !== "Revoke Docker auth before unverified dev tooling",
(step) => step.name !== "Revoke Docker auth before OpenShell development tooling",
);
fs.writeFileSync(workflowPath, YAML.stringify(workflow));

expect(validateMcpOpenShellWorkflowBoundary(workflowPath)).toContain(
"mcp-bridge-dev must revoke Docker auth before unverified dev tooling",
"mcp-bridge-dev must revoke Docker auth before OpenShell development tooling",
);
} finally {
fs.rmSync(directory, { force: true, recursive: true });
}
});

it("rejects moving or unverified inputs for the OpenShell dev shards (#9051)", () => {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-mcp-workflow-"));
const workflowPath = path.join(directory, "e2e.yaml");
try {
const workflow = YAML.parse(fs.readFileSync(".github/workflows/e2e.yaml", "utf8")) as {
jobs: Record<
string,
{
needs?: string | string[];
steps: Array<{
env?: Record<string, unknown>;
name?: string;
run?: string;
uses?: string;
with?: Record<string, unknown>;
}>;
}
>;
};
const dev = workflow.jobs["mcp-bridge-dev"];
dev.needs = "generate-matrix";
const restore = dev.steps.find(
(step) => step.name === "Restore immutable OpenShell dev artifact",
);
const verify = dev.steps.find(
(step) => step.name === "Verify immutable OpenShell dev artifact",
);
const install = dev.steps.find(
(step) => step.name === "Install immutable OpenShell dev artifact",
);
requireFixture(restore?.with, "OpenShell dev artifact restore fixture is missing");
requireFixture(verify?.run, "OpenShell dev artifact verification fixture is missing");
requireFixture(install?.run, "OpenShell dev artifact installation fixture is missing");
restore.uses = "actions/download-artifact@main";
restore.with.name = "openshell-dev-latest";
verify.run = verify.run.replace(".trusted-openshell-dev-artifact/", "");
install.env = { NEMOCLAW_ACCEPT_DEV_UNVERIFIED_INSTALL: "1" };
install.run = "bash scripts/install-openshell.sh";
fs.writeFileSync(workflowPath, YAML.stringify(workflow));

expect(validateMcpOpenShellWorkflowBoundary(workflowPath)).toEqual(
expect.arrayContaining([
"mcp-bridge-dev must depend on its reviewed artifact producers",
"mcp-bridge-dev must use the reviewed immutable artifact downloader",
"mcp-bridge-dev must restore exactly the resolver's content-addressed artifact",
"mcp-bridge-dev must verify the immutable OpenShell artifact before installation",
"mcp-bridge-dev installer must receive only the retained OpenShell asset directory",
"mcp-bridge-dev must install retained assets through the trusted no-network release path",
]),
);
} finally {
fs.rmSync(directory, { force: true, recursive: true });
}
});

it.each([
{
name: "candidate checkout ref",
mutate: (job: { steps: Array<Record<string, unknown>> }) => {
const checkout = job.steps.find(
(step) => step.name === "Checkout trusted OpenShell dev tooling",
);
requireFixture(
checkout?.with,
"trusted OpenShell resolver checkout fixture is missing",
);
const withValues = checkout.with as Record<string, unknown>;
withValues.ref = "${{ inputs.checkout_sha || github.sha }}";
},
expected: "openshell-dev-artifact must check out only the trusted workflow revision",
},
{
name: "candidate workspace invocation",
mutate: (job: { steps: Array<Record<string, unknown>> }) => {
const resolve = job.steps.find(
(step) => step.name === "Resolve immutable OpenShell dev artifact",
);
requireFixture(
typeof resolve?.run === "string",
"trusted OpenShell resolver invocation fixture is missing",
);
resolve.run = resolve.run.replace(
".trusted-openshell-dev-artifact/tools/e2e/openshell-dev-artifact.mts",
".candidate-runtime/tools/e2e/openshell-dev-artifact.mts",
);
},
expected: "openshell-dev-artifact must run the trusted immutable resolver",
},
])("rejects a $name for OpenShell dev artifact resolution (#9051)", ({
expected,
mutate,
}) => {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-mcp-workflow-"));
const workflowPath = path.join(directory, "e2e.yaml");
try {
const workflow = YAML.parse(fs.readFileSync(".github/workflows/e2e.yaml", "utf8")) as {
jobs: Record<string, { steps: Array<Record<string, unknown>> }>;
};
mutate(workflow.jobs["openshell-dev-artifact"]);
fs.writeFileSync(workflowPath, YAML.stringify(workflow));

expect(validateMcpOpenShellWorkflowBoundary(workflowPath)).toContain(expected);
} finally {
fs.rmSync(directory, { force: true, recursive: true });
}
});

it("rejects any additional artifact upload outside the scanned directory", () => {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-mcp-workflow-"));
const workflowPath = path.join(directory, "e2e.yaml");
Expand Down
Loading
Loading