Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
96d71c5
test(e2e): consolidate priority 2 live seams
prekshivyas Jul 27, 2026
a773b52
test(e2e): guard retired selector execution
prekshivyas Jul 27, 2026
8dd2093
test(e2e): keep boundary fixtures linear
prekshivyas Jul 27, 2026
a176245
fix(e2e): preserve retired target selectors
prekshivyas Jul 27, 2026
e8b4123
perf(e2e): focus retained coverage
prekshivyas Jul 30, 2026
fbe230e
fix(e2e): retain retired selector compatibility
prekshivyas Jul 30, 2026
2bbf778
merge: resolve conflicts with main
prekshivyas Jul 30, 2026
d2b7c8f
merge(main): resolve PR #7665 conflicts
prekshivyas Jul 30, 2026
5e00ea7
test(e2e): align gateway release evidence
prekshivyas Jul 30, 2026
62bdeb9
test(e2e): retain batch upgrade coverage
prekshivyas Jul 30, 2026
c193e47
test(e2e): separate batch upgrade boundaries
prekshivyas Jul 30, 2026
4f64091
fix(e2e): keep gateway migration on the existing lane
apurvvkumaria Jul 30, 2026
d8cd881
fix(e2e): reject hosted inference step overrides
apurvvkumaria Jul 30, 2026
f37fb1d
fix(e2e): preserve selector transition jobs
prekshivyas Jul 30, 2026
7cc422d
test(e2e): bound batch rebuild calls
prekshivyas Jul 30, 2026
2b81d2a
merge(e2e): align with PR #7665 transition
prekshivyas Jul 30, 2026
f933abf
test(e2e): require retired selector cases
prekshivyas Jul 30, 2026
608a31f
merge(main): refresh PR #7665 gate state
prekshivyas Jul 30, 2026
c4a51ab
merge(e2e): refresh PR #7665 dependency
prekshivyas Jul 30, 2026
3747a78
fix(e2e): bridge retired rebuild selectors
apurvvkumaria Jul 30, 2026
5229525
merge(e2e): resolve PR #7931 conflicts after #7665
prekshivyas Jul 30, 2026
c579d39
merge(e2e): retire superseded selector bridge
prekshivyas Jul 30, 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
160 changes: 0 additions & 160 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3350,85 +3350,6 @@ jobs:
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh

sandbox-rebuild:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',sandbox-rebuild,') || contains(format(',{0},', inputs.targets), ',sandbox-rebuild,') }}
runs-on: ubuntu-latest
timeout-minutes: 90
env:
E2E_JOB: "1"
E2E_TARGET_ID: "sandbox-rebuild"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/sandbox-rebuild
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
OPENSHELL_GATEWAY: nemoclaw
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false

- *dockerhub-auth

- id: selector_compatibility
name: Classify sandbox-rebuild candidate selector
env:
LEGACY_TEST_FILE: test/e2e/live/sandbox-rebuild.test.ts
shell: bash
run: |
set -euo pipefail
if [[ -f "$LEGACY_TEST_FILE" ]]; then
retired=false
else
retired=true
fi
printf 'retired=%s\n' "$retired" >> "$GITHUB_OUTPUT"

- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75

- name: Install OpenShell
if: ${{ steps.selector_compatibility.outputs.retired != 'true' }}
env:
NEMOCLAW_NON_INTERACTIVE: "1"
run: |
set -euo pipefail
env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN bash scripts/install-openshell.sh

- name: Run sandbox rebuild live test
if: ${{ steps.selector_compatibility.outputs.retired != 'true' }}
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/sandbox-rebuild.test.ts

- name: Upload sandbox rebuild artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57

- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh


sandbox-rlimits-connect:
needs: generate-matrix
if: ${{ contains(format(',{0},', inputs.jobs), ',sandbox-rlimits-connect,') || contains(format(',{0},', inputs.targets), ',sandbox-rlimits-connect,') }}
Expand Down Expand Up @@ -3601,85 +3522,6 @@ jobs:
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh

upgrade-stale-sandbox:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',upgrade-stale-sandbox,') || contains(format(',{0},', inputs.targets), ',upgrade-stale-sandbox,') }}
runs-on: ubuntu-latest
timeout-minutes: 85
env:
E2E_JOB: "1"
E2E_TARGET_ID: "upgrade-stale-sandbox"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/upgrade-stale-sandbox
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-upgrade-stale"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false

- *dockerhub-auth

- id: selector_compatibility
name: Classify upgrade-stale-sandbox candidate selector
env:
LEGACY_TEST_FILE: test/e2e/live/upgrade-stale-sandbox.test.ts
shell: bash
run: |
set -euo pipefail
if [[ -f "$LEGACY_TEST_FILE" ]]; then
retired=false
else
retired=true
fi
printf 'retired=%s\n' "$retired" >> "$GITHUB_OUTPUT"

- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75

- name: Install OpenShell CLI
if: ${{ steps.selector_compatibility.outputs.retired != 'true' }}
run: |
set -euo pipefail
env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN \
bash scripts/install-openshell.sh

- name: Run upgrade stale sandbox live Vitest test
if: ${{ steps.selector_compatibility.outputs.retired != 'true' }}
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/upgrade-stale-sandbox.test.ts

- name: Upload upgrade stale sandbox artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57

- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh


double-onboard:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',double-onboard,') || contains(format(',{0},', inputs.targets), ',double-onboard,') }}
Expand Down Expand Up @@ -5960,11 +5802,9 @@ jobs:
rebuild-openclaw,
rebuild-hermes,
rebuild-hermes-stale-base,
sandbox-rebuild,
sandbox-rlimits-connect,
overlayfs-autofix,
state-backup-restore,
upgrade-stale-sandbox,
openshell-gateway-upgrade,
token-rotation,
messaging-compatible-endpoint,
Expand Down
51 changes: 38 additions & 13 deletions test/e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,31 @@ The retired `hermes-dashboard` selector remains a compatibility alias for
the manually selected `mock`, `internal-nvidia`, or `public-nvidia` inference
mode.

## Retired selector transition
## Retired selector compatibility

When a candidate no longer contains `test/e2e/live/sandbox-rebuild.test.ts` or
`test/e2e/live/upgrade-stale-sandbox.test.ts`, PR gate requests for the
corresponding job or target selector run focused replacement tests through the
compatibility controller. While the file remains, the selector runs its
dedicated live E2E test. `rebuild-openclaw` is the canonical live rebuild and
upgrade target.
PR gate requests using the retired `sandbox-rebuild` and
`upgrade-stale-sandbox` job or target selectors run focused replacement tests
through the compatibility controller. `rebuild-openclaw` is the canonical live
rebuild and upgrade target.

## Current OpenClaw plugin EXDEV lifecycle

The `openclaw-plugin-runtime-exdev` job keeps one current-version lifecycle:

1. Onboard the custom weather plugin as v1.
2. Restart the gateway and verify v1.
3. Recreate the sandbox with the plugin changed to v2.
4. Run the cross-device runtime-dependency replacement probe.

The recreation remains the replacement boundary. It verifies the v2 plugin
with runtime inspection, `tools.catalog`, and `tools.invoke`, and it preserves
the workspace marker. The job also keeps the test-only tmpfs mount, unchanged
stock policy-source bytes, and the distinct-device and source-side `EXDEV`
checks. The duplicate v3 rebuild is removed from this job. The
`rebuild-openclaw` job remains the canonical live rebuild coverage.

The runtime target for `openclaw-plugin-runtime-exdev` is 16–17 minutes.
Scheduled-run timing for the reduced lifecycle has not yet been measured.

## Larger-runner routing

Expand Down Expand Up @@ -572,9 +589,16 @@ and advisor concurrency groups include that eligibility, so an ignored
metadata-edit run cannot cancel an eligible run for the same PR. The trusted
controller reads all changed files after eligible PR CI completes and builds
the deterministic risk plan.
Runtime families and changes to workflow-wired live tests select
canonical selectors from the trusted `e2e.yaml` inventory independently of
advisor output. Ordinary internal changes execute those focused selections.
Runtime families and changes to workflow-wired live tests or their owning
helpers select canonical jobs from the trusted `e2e.yaml` inventory
independently of advisor output. A workflow-wired live test or owning helper
selects one to three focused E2E journeys. A gateway-migration live test or
owning helper selects `openshell-gateway-upgrade`.

Changes only under `test/e2e/support/` select no credentialed live E2E job.
The `e2e-support` Vitest project runs those support tests in PR CI. A new or
renamed live test that does not match the trusted workflow inventory keeps the
conservative control-plane floor until its canonical job mapping is added.
Gate initialization, CI coordination, automatic internal dispatch, and fork
maintainer approval share one non-cancelling FIFO concurrency group for the
repository, PR number, PR SHA, and base SHA. `queue: max` keeps pending jobs for
Expand All @@ -595,9 +619,10 @@ GitHub consequently returns no head-repository object.
Shared sandbox-boundary changes have a floor of `full-e2e`, `hermes-e2e`, and
`security-posture`. E2E control-plane changes select `cloud-onboard`,
`cloud-inference`, and `security-posture`. The `e2e-control-plane`
family is a conservative path boundary that includes non-documentation files
under `tools/e2e/` and `test/e2e/`, plus the E2E and PR-CI workflows, risk
policy, dependency and test configuration, and preparation and upload actions.
family remains the conservative boundary for shared E2E tools, workflow and
security files, unknown live test paths, risk policy, dependency and test
configuration, and preparation and upload actions. These cross-cutting changes
keep the broad three-job floor.
Repository-root `Dockerfile` changes additionally select `full-e2e` alongside
the platform-install `cloud-onboard` floor so OpenClaw final-image changes run
through cold onboarding and a real first turn.
Expand Down
68 changes: 68 additions & 0 deletions test/e2e/live/openclaw-plugin-runtime-exdev-lifecycle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

export const CURRENT_LIFECYCLE_PHASES = [
"confirm Docker CLI and clear the current plugin sandbox",
"clone and prepare the current plugin fixture",
"install and validate current OpenShell",
"build and onboard plugin v1",
"restart the gateway and confirm plugin v1",
"recreate the sandbox with plugin v2",
"prove cross-device runtime dependency replacement",
] as const;

export type WeatherFixtureVersion = "v1" | "v2";

type LifecycleCommand = {
command: "node";
args: string[];
};

export function currentLifecycleCommands(input: {
cliEntrypoint: string;
dockerfilePath: string;
sandboxName: string;
}): {
onboard: LifecycleCommand;
recreate: LifecycleCommand;
restart: LifecycleCommand;
} {
return {
onboard: {
command: "node",
args: [
input.cliEntrypoint,
"onboard",
"--fresh",
"--non-interactive",
"--yes-i-accept-third-party-software",
"--agent",
"openclaw",
"--from",
input.dockerfilePath,
],
},
recreate: {
command: "node",
args: [
input.cliEntrypoint,
"onboard",
"--fresh",
"--recreate-sandbox",
"--non-interactive",
"--yes",
"--yes-i-accept-third-party-software",
"--name",
input.sandboxName,
"--agent",
"openclaw",
"--from",
input.dockerfilePath,
],
},
restart: {
command: "node",
args: [input.cliEntrypoint, input.sandboxName, "gateway", "restart"],
},
};
}
Loading
Loading