E2E main #15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| name: E2E | |
| run-name: "${{ inputs.checkout_sha != '' && format('E2E PR #{0} ({1})', inputs.pr_number, inputs.correlation_id) || inputs.correlation_id != '' && format('E2E {0} ({1})', github.ref_name, inputs.correlation_id) || format('E2E {0}', github.ref_name) }}" | |
| on: | |
| schedule: | |
| - cron: "0 0 * * *" | |
| workflow_dispatch: | |
| inputs: | |
| targets: | |
| description: "Optional comma-separated typed target ids. Empty runs all supported live targets." | |
| required: false | |
| default: "" | |
| type: string | |
| jobs: | |
| description: "Optional comma-separated E2E test IDs. Empty runs default-enabled tests only when targets is also empty; explicit-only tests openshell-gateway-auth-contract, mcp-bridge-dev, hermes-gpu-startup, jetson-nvmap-gpu, and staging-brev-launchable are skipped unless selected." | |
| required: false | |
| default: "" | |
| type: string | |
| include_staging_brev_launchable: | |
| description: "Include Exact staging Brev Launchable in a full E2E run when jobs and targets are empty." | |
| required: false | |
| default: false | |
| type: boolean | |
| inference_mode: | |
| description: "Inference adapter mode for compatible Vitest E2E jobs: mock, internal-nvidia, or public-nvidia." | |
| required: false | |
| default: "mock" | |
| type: choice | |
| options: | |
| - mock | |
| - internal-nvidia | |
| - public-nvidia | |
| allow_jetson_runner_queue: | |
| description: "Repository administrators only: before setting true for jetson-nvmap-gpu, confirm an online Jetson runner in the authoritative NVIDIA/NemoClaw Settings -> Actions -> Runners inventory; queued jobs do not honor timeout-minutes before assignment." | |
| required: false | |
| default: false | |
| type: boolean | |
| pr_number: | |
| description: Optional PR number for selective-dispatch result comments. | |
| required: false | |
| type: string | |
| default: "" | |
| post_to_slack: | |
| description: Post a selective-dispatch scorecard to the preview Slack route. | |
| required: false | |
| default: false | |
| type: boolean | |
| checkout_sha: | |
| description: PR SHA selected by the controller. | |
| required: false | |
| default: "" | |
| type: string | |
| checkout_repository: | |
| description: PR head repository selected by the controller. | |
| required: false | |
| default: "" | |
| type: string | |
| controller_check_id: | |
| description: Controller-owned check run that authorizes this exact E2E run. | |
| required: false | |
| default: "" | |
| type: string | |
| base_sha: | |
| description: PR base commit selected by the controller. | |
| required: false | |
| default: "" | |
| type: string | |
| workflow_sha: | |
| description: Trusted E2E workflow commit selected by the controller. | |
| required: false | |
| default: "" | |
| type: string | |
| plan_hash: | |
| description: SHA-256 of the selected E2E plan. | |
| required: false | |
| default: "" | |
| type: string | |
| correlation_id: | |
| description: Run correlation ID (UUIDv4). | |
| required: false | |
| default: "" | |
| type: string | |
| permissions: | |
| checks: read | |
| contents: read | |
| pull-requests: read | |
| concurrency: | |
| group: e2e-${{ github.ref }}-${{ inputs.checkout_sha != '' && format('pr-{0}', inputs.pr_number) || (inputs.include_staging_brev_launchable && inputs.jobs == '' && inputs.targets == '' && format('full-{0}', github.run_id)) || inputs.targets || 'supported' }}-${{ inputs.checkout_sha != '' && 'pr-gate' || inputs.jobs || 'all-jobs' }} | |
| cancel-in-progress: ${{ inputs.checkout_sha != '' }} | |
| env: | |
| NEMOCLAW_E2E_EXPECTED_SHA: ${{ inputs.checkout_sha }} | |
| NEMOCLAW_E2E_PLAN_HASH: ${{ inputs.plan_hash }} | |
| NEMOCLAW_E2E_CORRELATION_ID: ${{ inputs.correlation_id }} | |
| NEMOCLAW_E2E_SHARD: default | |
| jobs: | |
| base-image-publication: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 55 | |
| permissions: | |
| actions: read | |
| contents: read | |
| steps: | |
| - id: publication_mode | |
| name: Classify base-image publication requirement | |
| env: | |
| CHECKOUT_SHA: ${{ inputs.checkout_sha }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| REF: ${{ github.ref }} | |
| REPOSITORY: ${{ github.repository }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| case "${REPOSITORY}:${REF}:${EVENT_NAME}:${CHECKOUT_SHA:+controller}" in | |
| NVIDIA/NemoClaw:refs/heads/main:schedule:|NVIDIA/NemoClaw:refs/heads/main:workflow_dispatch:) | |
| required=1 | |
| ;; | |
| NVIDIA/NemoClaw:refs/heads/main:workflow_dispatch:controller) | |
| required=0 | |
| ;; | |
| *) | |
| echo "::error::base-image publication mode is not trusted" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| printf 'required=%s\n' "${required}" >> "${GITHUB_OUTPUT}" | |
| - name: Check out trusted E2E workflow | |
| if: ${{ steps.publication_mode.outputs.required == '1' }} | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: ${{ github.sha }} | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Set up Node for publication verification | |
| if: ${{ steps.publication_mode.outputs.required == '1' }} | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 22 | |
| - name: Verify applicable base-image publication | |
| if: ${{ steps.publication_mode.outputs.required == '1' }} | |
| env: | |
| EXPECTED_SHA: ${{ github.sha }} | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: node --experimental-strip-types --no-warnings tools/e2e/base-image-publication.mts --wait-seconds 3000 --poll-seconds 30 | |
| generate-matrix: | |
| needs: base-image-publication | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| outputs: | |
| matrix: ${{ steps.controller_matrix.outputs.matrix || steps.matrix.outputs.matrix }} | |
| test_matrix: ${{ steps.matrix.outputs.test_matrix }} | |
| hermes_selected: ${{ steps.matrix.outputs.hermes_selected }} | |
| explicit_only_jobs: ${{ steps.matrix.outputs.explicit_only_jobs }} | |
| runner_routing: ${{ steps.runner_routing.outputs.runner_routing }} | |
| steps: | |
| - id: controller_matrix | |
| name: Build trusted controller target matrix | |
| if: ${{ inputs.checkout_sha != '' }} | |
| env: | |
| TARGETS: ${{ inputs.targets }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| case "${TARGETS}" in | |
| "") | |
| matrix='[]' | |
| ;; | |
| ubuntu-repo-cloud-langchain-deepagents-code) | |
| matrix='[{"id":"ubuntu-repo-cloud-langchain-deepagents-code","runner":"ubuntu-latest","label":"ubuntu-repo-cloud-langchain-deepagents-code"}]' | |
| ;; | |
| ubuntu-repo-docker-post-reboot-recovery) | |
| matrix='[{"id":"ubuntu-repo-docker-post-reboot-recovery","runner":"ubuntu-latest","label":"ubuntu-repo-docker-post-reboot-recovery"}]' | |
| ;; | |
| ubuntu-repo-cloud-langchain-deepagents-code,ubuntu-repo-docker-post-reboot-recovery) | |
| matrix='[{"id":"ubuntu-repo-cloud-langchain-deepagents-code","runner":"ubuntu-latest","label":"ubuntu-repo-cloud-langchain-deepagents-code"},{"id":"ubuntu-repo-docker-post-reboot-recovery","runner":"ubuntu-latest","label":"ubuntu-repo-docker-post-reboot-recovery"}]' | |
| ;; | |
| *) | |
| echo "::error::PR E2E target is not approved by the trusted controller" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| printf 'matrix=%s\n' "${matrix}" >> "${GITHUB_OUTPUT}" | |
| - id: runner_routing | |
| name: Build trusted larger-runner routing | |
| env: | |
| CHECKOUT_SHA: ${{ inputs.checkout_sha }} | |
| LARGER_RUNNER_LABEL: ${{ vars.E2E_LARGER_RUNNER_LABEL }} | |
| REF: ${{ github.ref }} | |
| REPOSITORY: ${{ github.repository }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| larger_runner="ubuntu-latest" | |
| if [[ "${REPOSITORY}" == "NVIDIA/NemoClaw" && "${REF}" == "refs/heads/main" && -z "${CHECKOUT_SHA}" && -n "${LARGER_RUNNER_LABEL}" ]]; then | |
| if [[ ! "${LARGER_RUNNER_LABEL}" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$ ]]; then | |
| echo "::error::E2E_LARGER_RUNNER_LABEL must be a 1-64 character workflow label using letters, digits, dots, underscores, or hyphens" >&2 | |
| exit 1 | |
| fi | |
| larger_runner="${LARGER_RUNNER_LABEL}" | |
| fi | |
| runner_routing="$(jq -cn --arg standard "ubuntu-latest" --arg larger "${larger_runner}" '{"channels-stop-start-hermes":$larger,"channels-stop-start-openclaw":$standard,"common-egress-agent":$larger,"hermes-discord":$larger,"hermes-e2e":$larger,"hermes-inference-switch":$larger,"hermes-shields-config":$larger,"mcp-bridge-deepagents":$larger,"mcp-bridge-hermes":$larger,"mcp-bridge-openclaw":$standard,"rebuild-hermes":$larger,"rebuild-hermes-stale-base":$larger,"security-posture-hermes":$larger,"security-posture-openclaw":$standard}')" | |
| printf 'runner_routing=%s\n' "${runner_routing}" >> "${GITHUB_OUTPUT}" | |
| - name: Authenticate controller dispatch | |
| if: ${{ inputs.checkout_sha != '' }} | |
| env: | |
| ACTOR: ${{ github.actor }} | |
| BASE_SHA: ${{ inputs.base_sha }} | |
| CHECKOUT_SHA: ${{ inputs.checkout_sha }} | |
| CONTROLLER_CHECK_ID: ${{ inputs.controller_check_id }} | |
| CORRELATION_ID: ${{ inputs.correlation_id }} | |
| GITHUB_TOKEN: ${{ github.token }} | |
| JOBS: ${{ inputs.jobs }} | |
| PLAN_HASH: ${{ inputs.plan_hash }} | |
| PR_NUMBER: ${{ inputs.pr_number }} | |
| RUN_ATTEMPT: ${{ github.run_attempt }} | |
| RUN_ID: ${{ github.run_id }} | |
| TARGETS: ${{ inputs.targets }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| [[ "$ACTOR" == "github-actions[bot]" ]] || { echo "::error::PR E2E must be dispatched by the trusted controller"; exit 1; } | |
| [[ "$RUN_ATTEMPT" == "1" ]] || { echo "::error::controller-authorized PR E2E cannot be rerun"; exit 1; } | |
| [[ "$CONTROLLER_CHECK_ID" =~ ^[1-9][0-9]*$ ]] || { echo "::error::controller_check_id must be a positive integer"; exit 1; } | |
| expected_external_id="nemoclaw-pr-e2e:v2:${PR_NUMBER}:${CHECKOUT_SHA}:${BASE_SHA}" | |
| expected_run_url="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${RUN_ID}" | |
| jobs_summary="${JOBS//,/, }" | |
| targets_summary="${TARGETS//,/, }" | |
| expected_summary="Risk plan ${PLAN_HASH} selected jobs: ${jobs_summary:-none}; targets: ${targets_summary:-none}. Child run: ${expected_run_url}." | |
| controller_auth_max_attempts=45 | |
| controller_auth_poll_seconds=2 | |
| check_json="" | |
| for ((_attempt = 1; _attempt <= controller_auth_max_attempts; _attempt++)); do | |
| if check_json="$(curl --fail --silent --show-error --proto '=https' \ | |
| --header "Authorization: Bearer ${GITHUB_TOKEN}" \ | |
| --header "Accept: application/vnd.github+json" \ | |
| --header "Cache-Control: no-cache" \ | |
| --header "Pragma: no-cache" \ | |
| --header "X-GitHub-Api-Version: 2022-11-28" \ | |
| "https://api.github.com/repos/${GITHUB_REPOSITORY}/check-runs/${CONTROLLER_CHECK_ID}")" && | |
| [[ "$(jq -r '.output.summary // ""' <<< "$check_json")" == "$expected_summary" ]]; then | |
| break | |
| fi | |
| check_json="" | |
| if (( _attempt < controller_auth_max_attempts )); then | |
| sleep "$controller_auth_poll_seconds" | |
| fi | |
| done | |
| [[ -n "$check_json" ]] || { echo "::error::trusted controller authorization was not published for this run"; exit 1; } | |
| jq -e \ | |
| --argjson check_id "$CONTROLLER_CHECK_ID" \ | |
| --arg external_id "$expected_external_id" \ | |
| --arg head_sha "$CHECKOUT_SHA" \ | |
| --arg summary "$expected_summary" \ | |
| ' | |
| .id == $check_id and | |
| .name == "E2E / PR Gate Coordination" and | |
| .app.id == 15368 and | |
| .app.slug == "github-actions" and | |
| .head_sha == $head_sha and | |
| .external_id == $external_id and | |
| .status == "in_progress" and | |
| .conclusion == null and | |
| .output.summary == $summary | |
| ' <<< "$check_json" >/dev/null || | |
| { echo "::error::controller check does not authorize this exact E2E run"; exit 1; } | |
| - name: Authorize Launchable E2E maintainer dispatch | |
| if: ${{ github.event_name == 'workflow_dispatch' && ((inputs.jobs == 'staging-brev-launchable' && inputs.targets == '') || (inputs.include_staging_brev_launchable && inputs.jobs == '' && inputs.targets == '')) }} | |
| env: | |
| ACTOR: ${{ github.actor }} | |
| GITHUB_TOKEN: ${{ github.token }} | |
| TRIGGERING_ACTOR: ${{ github.triggering_actor }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| require_maintainer() { | |
| local maintainer="$1" | |
| if [[ ! "$maintainer" =~ ^[A-Za-z0-9-]{1,39}$ || "$maintainer" == -* || "$maintainer" == *- ]]; then | |
| echo "::error::Launchable E2E actor is invalid" >&2 | |
| exit 1 | |
| fi | |
| local permission_json | |
| permission_json="$(curl --fail --silent --show-error --proto '=https' \ | |
| --header "Authorization: Bearer ${GITHUB_TOKEN}" \ | |
| --header "Accept: application/vnd.github+json" \ | |
| --header "X-GitHub-Api-Version: 2022-11-28" \ | |
| "https://api.github.com/repos/${GITHUB_REPOSITORY}/collaborators/${maintainer}/permission")" | |
| if [[ "$(jq -r '.user.login // ""' <<< "$permission_json" | tr '[:upper:]' '[:lower:]')" != "$(tr '[:upper:]' '[:lower:]' <<< "$maintainer")" ]]; then | |
| echo "::error::Launchable E2E permission response did not match the actor" >&2 | |
| exit 1 | |
| fi | |
| case "$(jq -r '.role_name // ""' <<< "$permission_json")" in | |
| maintain | admin) ;; | |
| *) | |
| echo "::error::Launchable E2E requires a repository maintainer or administrator" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| } | |
| require_maintainer "$ACTOR" | |
| if [[ "${TRIGGERING_ACTOR,,}" != "${ACTOR,,}" ]]; then | |
| require_maintainer "$TRIGGERING_ACTOR" | |
| fi | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| repository: ${{ inputs.checkout_repository || github.repository }} | |
| ref: ${{ inputs.checkout_sha || github.sha }} | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Validate controller dispatch | |
| if: ${{ inputs.checkout_sha != '' }} | |
| env: | |
| BASE_SHA: ${{ inputs.base_sha }} | |
| CHECKOUT_REPOSITORY: ${{ inputs.checkout_repository }} | |
| CHECKOUT_SHA: ${{ inputs.checkout_sha }} | |
| EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }} | |
| GITHUB_TOKEN: ${{ github.token }} | |
| JOBS: ${{ inputs.jobs }} | |
| PLAN_HASH: ${{ inputs.plan_hash }} | |
| PR_NUMBER: ${{ inputs.pr_number }} | |
| CORRELATION_ID: ${{ inputs.correlation_id }} | |
| TARGETS: ${{ inputs.targets }} | |
| WORKFLOW_EVENT: ${{ github.event_name }} | |
| WORKFLOW_REF: ${{ github.ref }} | |
| WORKFLOW_SHA: ${{ github.workflow_sha }} | |
| run: | | |
| set -euo pipefail | |
| [[ "$WORKFLOW_EVENT" == "workflow_dispatch" && "$WORKFLOW_REF" == "refs/heads/main" ]] || { echo "::error::PR E2E runs must be dispatched from main"; exit 1; } | |
| [[ "$CHECKOUT_REPOSITORY" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || { echo "::error::checkout_repository must be an owner/repository name"; exit 1; } | |
| [[ "$CHECKOUT_SHA" =~ ^[a-f0-9]{40}$ ]] || { echo "::error::checkout_sha must be a lowercase 40-character SHA"; exit 1; } | |
| [[ "$BASE_SHA" =~ ^[a-f0-9]{40}$ ]] || { echo "::error::base_sha must be a lowercase 40-character SHA"; exit 1; } | |
| [[ "$EXPECTED_WORKFLOW_SHA" =~ ^[a-f0-9]{40}$ && "$WORKFLOW_SHA" == "$EXPECTED_WORKFLOW_SHA" ]] || { echo "::error::workflow_sha must match the trusted workflow commit"; exit 1; } | |
| [[ "$(git rev-parse --verify HEAD)" == "$CHECKOUT_SHA" ]] || { echo "::error::checked-out commit does not match checkout_sha"; exit 1; } | |
| [[ "$PLAN_HASH" =~ ^[a-f0-9]{64}$ ]] || { echo "::error::plan_hash must be a lowercase SHA-256"; exit 1; } | |
| [[ "$CORRELATION_ID" =~ ^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$ ]] || { echo "::error::correlation_id must be a lowercase UUIDv4"; exit 1; } | |
| [[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]] || { echo "::error::pr_number must be a positive integer"; exit 1; } | |
| [[ -n "$JOBS" || -n "$TARGETS" ]] || { echo "::error::PR E2E runs require controller-selected jobs or targets"; exit 1; } | |
| case "$TARGETS" in | |
| ""|ubuntu-repo-cloud-langchain-deepagents-code|ubuntu-repo-docker-post-reboot-recovery|ubuntu-repo-cloud-langchain-deepagents-code,ubuntu-repo-docker-post-reboot-recovery) ;; | |
| *) echo "::error::PR E2E target is not approved by the trusted controller"; exit 1 ;; | |
| esac | |
| pull_json="$(curl --fail --silent --show-error --proto '=https' \ | |
| --header "Authorization: Bearer ${GITHUB_TOKEN}" \ | |
| --header "Accept: application/vnd.github+json" \ | |
| --header "X-GitHub-Api-Version: 2022-11-28" \ | |
| "https://api.github.com/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}")" | |
| [[ "$(jq -r '.state' <<< "$pull_json")" == "open" ]] || { echo "::error::pull request must still be open"; exit 1; } | |
| [[ "$(jq -r '.head.repo.full_name // ""' <<< "$pull_json")" == "$CHECKOUT_REPOSITORY" ]] || { echo "::error::checkout_repository must match the PR head repository"; exit 1; } | |
| [[ "$(jq -r '.head.sha' <<< "$pull_json")" == "$CHECKOUT_SHA" ]] || { echo "::error::checkout_sha must match the PR SHA"; exit 1; } | |
| [[ "$(jq -r '.base.sha' <<< "$pull_json")" == "$BASE_SHA" ]] || { echo "::error::base_sha must match the PR base commit"; exit 1; } | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| with: | |
| build-cli: "false" | |
| - id: matrix | |
| name: Generate E2E target matrix | |
| env: | |
| CHECKOUT_SHA: ${{ inputs.checkout_sha }} | |
| CONTROLLER_MATRIX: ${{ steps.controller_matrix.outputs.matrix }} | |
| INFERENCE_MODE: ${{ inputs.inference_mode || 'mock' }} | |
| JOBS: ${{ inputs.jobs }} | |
| TARGETS: ${{ inputs.targets }} | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/workflow-plan.mts --ci-output | |
| if [ -n "${CHECKOUT_SHA}" ]; then | |
| matrix="$(sed -n 's/^matrix=//p' "${GITHUB_OUTPUT}")" | |
| expected_controller_matrix="$(jq -c 'map({id, runner}) | sort_by(.id)' <<< "${CONTROLLER_MATRIX}")" | |
| actual_controller_matrix="$(jq -c 'map({id, runner}) | sort_by(.id)' <<< "${matrix}")" | |
| if [ "${actual_controller_matrix}" != "${expected_controller_matrix}" ]; then | |
| : > "${GITHUB_OUTPUT}" | |
| echo "::error::E2E planner matrix does not match controller-selected targets" >&2 | |
| exit 1 | |
| fi | |
| fi | |
| - name: Record trusted E2E dispatch receipt | |
| if: ${{ github.event_name == 'workflow_dispatch' }} | |
| env: | |
| ALLOW_JETSON_RUNNER_QUEUE: ${{ inputs.allow_jetson_runner_queue && 'true' || 'false' }} | |
| CANDIDATE_SHA: ${{ inputs.checkout_sha || github.sha }} | |
| DISPATCH_JOBS: ${{ inputs.jobs }} | |
| DISPATCH_RECEIPT_DIR: ${{ runner.temp }}/nemoclaw-e2e-dispatch | |
| DISPATCH_TARGETS: ${{ inputs.targets }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| INCLUDE_STAGING_BREV_LAUNCHABLE: ${{ inputs.include_staging_brev_launchable && 'true' || 'false' }} | |
| RUN_ATTEMPT: ${{ github.run_attempt }} | |
| RUN_ID: ${{ github.run_id }} | |
| run: | | |
| set -euo pipefail | |
| install -d -m 0700 "$DISPATCH_RECEIPT_DIR" | |
| jq -n \ | |
| --arg candidateSha "$CANDIDATE_SHA" \ | |
| --arg eventName "$EVENT_NAME" \ | |
| --arg jobs "$DISPATCH_JOBS" \ | |
| --arg targets "$DISPATCH_TARGETS" \ | |
| --arg workflowRunId "$RUN_ID" \ | |
| --argjson allowJetsonRunnerQueue "$ALLOW_JETSON_RUNNER_QUEUE" \ | |
| --argjson includeStagingBrevLaunchable "$INCLUDE_STAGING_BREV_LAUNCHABLE" \ | |
| --argjson workflowRunAttempt "$RUN_ATTEMPT" \ | |
| '{ | |
| kind: "nemoclaw-e2e-dispatch-v1", | |
| candidateSha: $candidateSha, | |
| eventName: $eventName, | |
| workflowRunId: $workflowRunId, | |
| workflowRunAttempt: $workflowRunAttempt, | |
| jobs: $jobs, | |
| targets: $targets, | |
| allowJetsonRunnerQueue: $allowJetsonRunnerQueue, | |
| includeStagingBrevLaunchable: $includeStagingBrevLaunchable, | |
| defaultSuiteSelected: ($jobs == "" and $targets == "") | |
| }' >"$DISPATCH_RECEIPT_DIR/dispatch.json" | |
| - name: Upload trusted E2E dispatch receipt | |
| if: ${{ github.event_name == 'workflow_dispatch' }} | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: e2e-dispatch-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: ${{ runner.temp }}/nemoclaw-e2e-dispatch/dispatch.json | |
| retired-selector-compatibility: | |
| needs: generate-matrix | |
| if: ${{ inputs.checkout_sha != '' && (contains(format(',{0},', inputs.jobs), ',credential-migration,') || contains(format(',{0},', inputs.jobs), ',credential-sanitization,') || contains(format(',{0},', inputs.jobs), ',diagnostics,') || contains(format(',{0},', inputs.jobs), ',docs-validation,') || contains(format(',{0},', inputs.jobs), ',gateway-drift-preflight,') || contains(format(',{0},', inputs.jobs), ',gateway-health-honest,') || contains(format(',{0},', inputs.jobs), ',onboard-negative-paths,') || contains(format(',{0},', inputs.jobs), ',openshell-version-pin,') || contains(format(',{0},', inputs.jobs), ',sandbox-rebuild,') || contains(format(',{0},', inputs.jobs), ',ubuntu-repo-cli-smoke,') || contains(format(',{0},', inputs.jobs), ',upgrade-stale-sandbox,') || contains(format(',{0},', inputs.targets), ',sandbox-rebuild,') || contains(format(',{0},', inputs.targets), ',upgrade-stale-sandbox,')) }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| env: | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/retired-selector-compatibility | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| repository: ${{ inputs.checkout_repository || github.repository }} | |
| ref: ${{ inputs.checkout_sha || github.sha }} | |
| persist-credentials: false | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Verify retired selector replacements | |
| env: | |
| JOBS: ${{ inputs.jobs }} | |
| TARGETS: ${{ inputs.targets }} | |
| run: npx tsx tools/e2e/retired-selector-compatibility.mts | |
| - name: Upload retired selector compatibility evidence | |
| if: always() | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: e2e-retired-selector-compatibility | |
| path: e2e-artifacts/live/retired-selector-compatibility/ | |
| staging-brev-launchable: | |
| name: Exact staging Brev Launchable | |
| needs: generate-matrix | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' && ((inputs.jobs == 'staging-brev-launchable' && inputs.targets == '') || (inputs.include_staging_brev_launchable && inputs.jobs == '' && inputs.targets == '')) }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 180 | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: staging-brev-launchable-cpu | |
| queue: max | |
| cancel-in-progress: false | |
| env: | |
| CANDIDATE_SHA: ${{ inputs.checkout_sha || github.sha }} | |
| E2E_DEFAULT_ENABLED: "0" | |
| E2E_JOB: "1" | |
| INSTANCE_NAME: nclaw-e2e-${{ github.run_id }}-${{ github.run_attempt }} | |
| steps: | |
| - name: Checkout trusted Launchable lane | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: ${{ github.workflow_sha }} | |
| persist-credentials: false | |
| sparse-checkout: | | |
| tools/e2e/brev-launchable-e2e.sh | |
| sparse-checkout-cone-mode: false | |
| - id: workspace | |
| name: Prepare the trusted lane | |
| env: | |
| BREV_API_KEY: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' && secrets.BREV_API_KEY || '' }} | |
| BREV_CLI_SHA256: 5a6e70374db9be33f85f299161733b4a8409840d47638c781429b96e8d53704f | |
| BREV_CLI_VERSION: 0.6.330 | |
| BREV_ORG_ID: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' && secrets.BREV_ORG_ID || '' }} | |
| run: | | |
| set -euo pipefail | |
| work_dir="$(mktemp -d "${RUNNER_TEMP}/nemoclaw-launchable-e2e.XXXXXX")" | |
| chmod 700 "$work_dir" | |
| archive="${RUNNER_TEMP}/brev-cli.tar.gz" | |
| curl -fsSL -o "$archive" "https://github.com/brevdev/brev-cli/releases/download/v${BREV_CLI_VERSION}/brev-cli_${BREV_CLI_VERSION}_linux_amd64.tar.gz" | |
| printf '%s %s\n' "$BREV_CLI_SHA256" "$archive" | sha256sum -c - | |
| tar -xzf "$archive" -C "${RUNNER_TEMP}" brev && sudo install -m 0755 "${RUNNER_TEMP}/brev" /usr/local/bin/brev | |
| brev login --api-key "$BREV_API_KEY" --org-id "$BREV_ORG_ID" | |
| printf 'work_dir=%s\n' "$work_dir" >> "$GITHUB_OUTPUT" | |
| - name: Record E2E dispatch identity | |
| env: | |
| CANDIDATE_SHA: ${{ env.CANDIDATE_SHA }} | |
| DISPATCH_JOBS: ${{ inputs.jobs }} | |
| DISPATCH_TARGETS: ${{ inputs.targets }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| INCLUDE_STAGING_BREV_LAUNCHABLE: ${{ inputs.include_staging_brev_launchable && 'true' || 'false' }} | |
| RUN_ATTEMPT: ${{ github.run_attempt }} | |
| RUN_ID: ${{ github.run_id }} | |
| WORK_DIR: ${{ steps.workspace.outputs.work_dir }} | |
| run: | | |
| jq -n \ | |
| --arg candidateSha "$CANDIDATE_SHA" \ | |
| --arg eventName "$EVENT_NAME" \ | |
| --arg jobs "$DISPATCH_JOBS" \ | |
| --arg targets "$DISPATCH_TARGETS" \ | |
| --arg workflowRunId "$RUN_ID" \ | |
| --argjson includeStagingBrevLaunchable "$INCLUDE_STAGING_BREV_LAUNCHABLE" \ | |
| --argjson workflowRunAttempt "$RUN_ATTEMPT" \ | |
| '{ | |
| kind: "nemoclaw-e2e-dispatch-v1", | |
| candidateSha: $candidateSha, | |
| eventName: $eventName, | |
| workflowRunId: $workflowRunId, | |
| workflowRunAttempt: $workflowRunAttempt, | |
| jobs: $jobs, | |
| targets: $targets, | |
| includeStagingBrevLaunchable: $includeStagingBrevLaunchable, | |
| defaultSuiteSelected: ($jobs == "" and $targets == "") | |
| }' >"$WORK_DIR/dispatch.json" | |
| - name: Build, deploy, verify, test, and clean up | |
| env: | |
| BREV_LAUNCHABLE_ID: ${{ vars.NEMOCLAW_STAGING_LAUNCHABLE_ID }} | |
| GH_TOKEN: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' && secrets.NEMOCLAW_IMAGE_DISPATCH_TOKEN || '' }} | |
| NVIDIA_INFERENCE_API_KEY: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' && secrets.NVIDIA_INFERENCE_API_KEY || '' }} | |
| WORK_DIR: ${{ steps.workspace.outputs.work_dir }} | |
| run: tools/e2e/brev-launchable-e2e.sh | |
| - name: Upload Launchable evidence | |
| if: ${{ always() && steps.workspace.outputs.work_dir != '' }} | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: staging-brev-launchable-${{ env.CANDIDATE_SHA }}-${{ github.run_id }} | |
| path: | | |
| ${{ steps.workspace.outputs.work_dir }}/lane.log | |
| ${{ steps.workspace.outputs.work_dir }}/dispatch.json | |
| ${{ steps.workspace.outputs.work_dir }}/launchable-e2e.json | |
| ${{ steps.workspace.outputs.work_dir }}/full-e2e.log | |
| ${{ steps.workspace.outputs.work_dir }}/cleanup.json | |
| live: | |
| needs: generate-matrix | |
| if: ${{ needs.generate-matrix.outputs.matrix != '[]' }} | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 45 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJSON(needs.generate-matrix.outputs.matrix) }} | |
| env: | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1" | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| repository: ${{ inputs.checkout_repository || github.repository }} | |
| ref: ${{ inputs.checkout_sha || github.sha }} | |
| persist-credentials: false | |
| # Keep only the credential-bearing step anchored. Cleanup mappings stay | |
| # explicit because strict YAML decoders reject 100 or more aliases here. | |
| - &dockerhub-auth | |
| name: Authenticate to Docker Hub | |
| uses: NVIDIA/NemoClaw/.github/actions/docker-auth-setup@78091da47e290f49b8fe3f3e70b72362a0853928 | |
| with: | |
| auth-required: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && inputs.checkout_sha == '' && '1' || '0' }} | |
| username: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && inputs.checkout_sha == '' && secrets.DOCKERHUB_USERNAME || '' }} | |
| token: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && inputs.checkout_sha == '' && secrets.DOCKERHUB_TOKEN || '' }} | |
| - name: Configure live E2E trace directory | |
| env: | |
| TARGET_ID: ${{ matrix.id }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| printf 'NEMOCLAW_TRACE_DIR=%s\n' "${RUNNER_TEMP}/nemoclaw-e2e-traces/${TARGET_ID}" >> "${GITHUB_ENV}" | |
| # invalidState: the selected PR-modifiable TUI check needs a PTY driver, | |
| # but the fixed GitHub-hosted runner image does not provide expect. | |
| # sourceBoundary: privileged host setup runs from the first-party | |
| # host-dependency-setup action pinned to an immutable full SHA, never the | |
| # PR-controlled target ref; the check only consumes expect without privilege. | |
| # whyNotSourceFix: GitHub-hosted jobs cannot use a repository-owned host | |
| # image, and caching privileged dpkg state between clean runners is not | |
| # supported. | |
| # regressionTest: the workflow-boundary suite pins this target, condition, | |
| # ordering, action provenance, and package mapping. | |
| # removalCondition: remove the install when the hosted runner supplies | |
| # expect or the acceptance check no longer requires a PTY. | |
| - name: Install Deep Agents Code TUI host dependencies | |
| if: ${{ matrix.id == 'ubuntu-repo-cloud-langchain-deepagents-code' }} | |
| uses: NVIDIA/NemoClaw/.github/actions/host-dependency-setup@4def1501b34ce586f83b91af50a66b5d22b31d75 | |
| with: | |
| packages: expect | |
| # Configure NEMOCLAW_TRACE_DIR before workspace prep so every child | |
| # command writes raw traces under runner temp, never under upload roots. | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| # invalidState: a profile plugin installed with --no-deps can import even | |
| # when an incomplete base image omitted its required upstream packages. | |
| # sourceBoundary: this trusted workflow scopes the repo-owned stripped-base | |
| # build to the exact DCode target; the production Dockerfile must reject it | |
| # at the isolated import gate before its later dependency-consistency check. | |
| # whyNotSourceFix: dependency completeness belongs to the hash-locked base; | |
| # resolving dependencies during local plugin install would duplicate that | |
| # trust boundary, so the regression constructs the invalid input instead. | |
| # regressionTest: workflow-boundary tests pin the target, script, and | |
| # ordering; build-gate tests pin the base build and failure contract. | |
| # removalCondition: remove only if package installation no longer uses | |
| # --no-deps or an equivalent earlier build gate proves both imports. | |
| - name: Verify DCode profile import gate rejects missing base dependencies | |
| if: ${{ matrix.id == 'ubuntu-repo-cloud-langchain-deepagents-code' }} | |
| shell: bash | |
| run: bash scripts/check-dcode-profile-import-gate.sh | |
| - name: Run live E2E tests | |
| env: | |
| E2E_TARGET_ID: ${{ matrix.id }} | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| TARGET_ID: ${{ matrix.id }} | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/registry-targets.test.ts --selector "^${TARGET_ID}$" | |
| # The sanitizer reads raw traces only after checking the workflow-owned | |
| # runner-temp path, then writes the timing-only file into upload roots. | |
| - name: Build trusted live E2E timing summary | |
| if: always() | |
| env: | |
| TARGET_ID: ${{ matrix.id }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| expected_trace_dir="${RUNNER_TEMP}/nemoclaw-e2e-traces/${TARGET_ID}" | |
| if [ -z "${RUNNER_TEMP}" ] || [ "${NEMOCLAW_TRACE_DIR}" != "${expected_trace_dir}" ]; then | |
| echo "::error::Refusing to sanitize unexpected raw trace path" >&2 | |
| exit 1 | |
| fi | |
| python3 scripts/e2e/sanitize-trace-timing.py \ | |
| "${NEMOCLAW_TRACE_DIR}" \ | |
| "${E2E_ARTIFACT_DIR}/${TARGET_ID}" | |
| # Cleanup intentionally runs after sanitization and before upload so raw | |
| # trace JSON never becomes part of the uploaded artifact surface. | |
| - name: Delete raw live E2E traces | |
| if: always() | |
| env: | |
| TARGET_ID: ${{ matrix.id }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| expected_trace_dir="${RUNNER_TEMP}/nemoclaw-e2e-traces/${TARGET_ID}" | |
| if [ -z "${RUNNER_TEMP}" ] || [ "${NEMOCLAW_TRACE_DIR}" != "${expected_trace_dir}" ]; then | |
| echo "::error::Refusing to delete unexpected raw trace path" >&2 | |
| exit 1 | |
| fi | |
| rm -rf -- "${NEMOCLAW_TRACE_DIR}" | |
| - name: Summarize artifacts | |
| if: always() | |
| env: | |
| TARGET_ID: ${{ matrix.id }} | |
| TARGET_LABEL: ${{ matrix.label }} | |
| run: | | |
| python3 - <<'PY' >> "$GITHUB_STEP_SUMMARY" | |
| import json | |
| import os | |
| from pathlib import Path | |
| root = Path(os.environ["E2E_ARTIFACT_DIR"]) / os.environ["TARGET_ID"] | |
| plan_path = root / "run-plan.json" | |
| print("## E2E Targets") | |
| print() | |
| print("- Project: `e2e-live`") | |
| print(f"- Target: `{os.environ['TARGET_ID']}`") | |
| print(f"- Label: `{os.environ['TARGET_LABEL']}`") | |
| print(f"- Artifact root: `{root}`") | |
| print() | |
| print("| Target | Manifest | Expected state | Suites | Phases |") | |
| print("| --- | --- | --- | --- | --- |") | |
| if plan_path.exists(): | |
| plan = json.loads(plan_path.read_text(encoding="utf-8")) | |
| suites = ", ".join(plan.get("suiteIds") or []) or "(none)" | |
| phases = ", ".join(plan.get("phases") or []) or "(none)" | |
| print( | |
| "| " | |
| f"`{plan.get('targetId') or os.environ['TARGET_ID']}` | " | |
| f"`{plan.get('manifestPath') or 'not-yet-defined'}` | " | |
| f"`{plan.get('expectedStateId') or 'not-yet-defined'}` | " | |
| f"{suites} | {phases} |" | |
| ) | |
| else: | |
| print( | |
| "| " | |
| f"`{os.environ['TARGET_ID']}` | `(missing run-plan.json)` | " | |
| "`(missing run-plan.json)` | `(missing)` | `(missing)` |" | |
| ) | |
| PY | |
| - name: Upload E2E artifacts | |
| if: always() | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: e2e-${{ matrix.id }} | |
| path: | | |
| e2e-artifacts/live/${{ matrix.id }}/run-plan.json | |
| e2e-artifacts/live/${{ matrix.id }}/target.json | |
| e2e-artifacts/live/${{ matrix.id }}/target-result.json | |
| e2e-artifacts/live/${{ matrix.id }}/test-progress.json | |
| e2e-artifacts/live/${{ matrix.id }}/environment.result.json | |
| e2e-artifacts/live/${{ matrix.id }}/onboarding.result.json | |
| e2e-artifacts/live/${{ matrix.id }}/state-validation.result.json | |
| e2e-artifacts/live/${{ matrix.id }}/cloud-onboard-trace-timing-summary.json | |
| e2e-artifacts/live/risk-signal.json | |
| e2e-artifacts/live/${{ matrix.id }}/actions/ | |
| e2e-artifacts/live/${{ matrix.id }}/logs/ | |
| e2e-artifacts/live/${{ matrix.id }}/shell/ | |
| - name: Clean up Docker auth | |
| if: always() | |
| shell: bash | |
| run: bash .github/scripts/docker-auth-cleanup.sh | |
| # Credential-free tests opt in with a tag beside the test. Discovery supplies | |
| # only a validated test ID, file, and Vitest project; this E2E workflow owns | |
| # the shared job's runner, setup, timeout, permissions, and artifact policy. | |
| shared-e2e: | |
| name: Shared E2E (${{ matrix.id }}) | |
| needs: generate-matrix | |
| if: ${{ needs.generate-matrix.outputs.test_matrix != '[]' }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJSON(needs.generate-matrix.outputs.test_matrix) }} | |
| env: | |
| CHECK_DOC_LINKS_REMOTE: "0" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/${{ matrix.id }} | |
| E2E_TARGET_ID: ${{ matrix.id }} | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| repository: ${{ inputs.checkout_repository || github.repository }} | |
| ref: ${{ inputs.checkout_sha || github.sha }} | |
| persist-credentials: false | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Run tagged credential-free test | |
| env: | |
| TEST_FILE: ${{ matrix.file }} | |
| TEST_PROJECT: ${{ matrix.project }} | |
| run: | | |
| set -euo pipefail | |
| npx vitest run --project "${TEST_PROJECT}" "${TEST_FILE}" \ | |
| --tags-filter=e2e/credential-free \ | |
| --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts | |
| - name: Upload test artifacts | |
| if: always() | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| openshell-gateway-auth-contract: | |
| needs: generate-matrix | |
| # Accepted release-gate tradeoff: this resource-heavy live probe remains | |
| # selective because regular PR CI enforces the generated auth/JWT config and | |
| # package-service fail-closed boundary in focused unit tests. Affected PRs | |
| # must explicitly dispatch this job and record its result before merge. | |
| if: ${{ contains(format(',{0},', inputs.jobs), ',openshell-gateway-auth-contract,') || contains(format(',{0},', inputs.targets), ',openshell-gateway-auth-contract,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_DEFAULT_ENABLED: "0" | |
| E2E_TARGET_ID: "openshell-gateway-auth-contract" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openshell-gateway-auth-contract | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_OPENSHELL_PIN_VERSION: "0.0.85" | |
| DOCKER_GRPC_PROBE_IMAGE: "node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| run: 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: Pre-pull pinned gateway auth probe image | |
| run: docker pull "$DOCKER_GRPC_PROBE_IMAGE" | |
| - name: Run OpenShell gateway auth contract live test | |
| run: | | |
| set -euo pipefail | |
| export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH" | |
| if command -v openshell-gateway >/dev/null 2>&1; then | |
| OPENSHELL_GATEWAY_BIN="$(command -v openshell-gateway)" | |
| elif [ -x "$HOME/.local/bin/openshell-gateway" ]; then | |
| OPENSHELL_GATEWAY_BIN="$HOME/.local/bin/openshell-gateway" | |
| else | |
| echo "::error::OpenShell gateway binary not found after install" | |
| ls -la /usr/local/bin/openshell-gateway "$HOME/.local/bin/openshell-gateway" 2>&1 || true | |
| exit 1 | |
| fi | |
| export OPENSHELL_GATEWAY_BIN | |
| echo "Using OPENSHELL_GATEWAY_BIN=$OPENSHELL_GATEWAY_BIN" | |
| "$OPENSHELL_GATEWAY_BIN" --version | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openshell-gateway-auth-source-contract.test.ts | |
| - id: artifact_safety | |
| name: Validate final OpenShell gateway auth contract artifacts | |
| if: always() | |
| run: node --experimental-strip-types --no-warnings tools/e2e/openshell-gateway-auth-artifact-safety.mts "$E2E_ARTIFACT_DIR" | |
| - name: Upload OpenShell gateway auth contract artifacts | |
| if: ${{ always() && steps.artifact_safety.outcome == 'success' && steps.artifact_safety.outputs.approved_path != '' }} | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: e2e-openshell-gateway-auth-contract | |
| path: ${{ steps.artifact_safety.outputs.approved_path }} | |
| - name: Clean up Docker auth | |
| if: always() | |
| shell: bash | |
| run: bash .github/scripts/docker-auth-cleanup.sh | |
| mcp-bridge: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',mcp-bridge,') || contains(format(',{0},', inputs.targets), ',mcp-bridge,') }} | |
| runs-on: ${{ fromJSON(needs.generate-matrix.outputs.runner_routing)[format('mcp-bridge-{0}', matrix.agent)] }} | |
| permissions: | |
| contents: read | |
| # Keep each destructive agent lifecycle on a fresh runner. This bounds the | |
| # blast radius of runner loss and gives every adapter independent evidence. | |
| timeout-minutes: 90 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| agent: [openclaw, hermes, deepagents] | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "mcp-bridge" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/mcp-bridge/${{ matrix.agent }} | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_E2E_SHARD: ${{ matrix.agent }} | |
| NEMOCLAW_MCP_BRIDGE_AGENT: ${{ matrix.agent }} | |
| NEMOCLAW_OPENSHELL_CHANNEL: stable | |
| NEMOCLAW_OPENSHELL_EXACT_MAIN_PROOF: "1" | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| OPENSHELL_DOCKER_SUPERVISOR_IMAGE: ghcr.io/nvidia/openshell/supervisor@sha256:f4226253a3525c3832adac5b38b419a0f27d1e915effe565b5885e20f93cd5e9 | |
| steps: | |
| - id: trusted_hermes_swap | |
| name: Provision trusted Hermes E2E swap | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && matrix.agent == 'hermes' }} | |
| shell: /bin/bash --noprofile --norc -e -o pipefail {0} | |
| env: | |
| BASH_ENV: /dev/null | |
| CHECKOUT_SHA: ${{ inputs.checkout_sha }} | |
| DISPATCH_SHA: ${{ github.sha }} | |
| ENV: /dev/null | |
| EVENT_NAME: ${{ github.event_name }} | |
| EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }} | |
| LC_ALL: C | |
| REF: ${{ github.ref }} | |
| REPOSITORY: ${{ github.repository }} | |
| RUNNER_ARCH_KIND: ${{ runner.arch }} | |
| RUNNER_ENVIRONMENT_KIND: ${{ runner.environment }} | |
| RUNNER_OS_KIND: ${{ runner.os }} | |
| WORKFLOW_SHA: ${{ github.workflow_sha }} | |
| run: &trusted-hermes-e2e-swap | | |
| set -euo pipefail | |
| readonly swap_dir="/mnt/nemoclaw-hermes-e2e-swap" | |
| readonly required_swap_bytes=34359738368 | |
| readonly swap_file_bytes=34359742464 | |
| readonly reserve_bytes=17179869184 | |
| readonly activation_observation_attempts=5 | |
| readonly activation_observation_delay_seconds=1 | |
| swap_file="" | |
| swap_activation_succeeded=0 | |
| fail() { | |
| printf 'Trusted Hermes E2E swap setup failed: %s\n' "$1" >&2 | |
| exit 1 | |
| } | |
| if [[ "${REPOSITORY}" != "NVIDIA/NemoClaw" || "${REF}" != "refs/heads/main" ]]; then | |
| fail "workflow must run from NVIDIA/NemoClaw main" | |
| fi | |
| if [[ "${EVENT_NAME}" != "schedule" && "${EVENT_NAME}" != "workflow_dispatch" ]]; then | |
| fail "workflow event must be schedule or workflow_dispatch" | |
| fi | |
| # PR E2E mode: controller-dispatched PR commit. | |
| if [[ "${EVENT_NAME}" == "workflow_dispatch" && -n "${CHECKOUT_SHA}" ]]; then | |
| if [[ ! "${CHECKOUT_SHA}" =~ ^[0-9a-f]{40}$ ]]; then | |
| fail "checkout SHA must be lowercase 40-hex" | |
| fi | |
| if [[ ! "${EXPECTED_WORKFLOW_SHA}" =~ ^[0-9a-f]{40}$ || "${WORKFLOW_SHA}" != "${EXPECTED_WORKFLOW_SHA}" || "${WORKFLOW_SHA}" != "${DISPATCH_SHA}" ]]; then | |
| fail "workflow source must match the trusted dispatch revision" | |
| fi | |
| else | |
| # Direct-main mode: schedule or manual trigger on main. | |
| if [[ -n "${CHECKOUT_SHA}" || -n "${EXPECTED_WORKFLOW_SHA}" ]]; then | |
| fail "direct main runs must not request an alternate checkout or workflow revision" | |
| fi | |
| if [[ ! "${WORKFLOW_SHA}" =~ ^[0-9a-f]{40}$ || "${WORKFLOW_SHA}" != "${DISPATCH_SHA}" ]]; then | |
| fail "direct main workflow source must match the run revision" | |
| fi | |
| fi | |
| if [[ "${RUNNER_ENVIRONMENT_KIND}" != "github-hosted" || "${RUNNER_OS_KIND}" != "Linux" || "${RUNNER_ARCH_KIND}" != "X64" ]]; then | |
| fail "swap fallback requires an ephemeral GitHub-hosted Linux x64 runner" | |
| fi | |
| mnt_metadata="$(/usr/bin/stat -c "%F:%u:%g" -- /mnt)" | |
| if [[ "${mnt_metadata}" != "directory:0:0" ]]; then | |
| fail "/mnt must be a root-owned directory" | |
| fi | |
| read_active_swap_bytes() { | |
| /usr/bin/sudo -n /usr/sbin/swapon --show=SIZE --bytes --noheadings | | |
| /usr/bin/awk '{ total += $1 } END { printf "%.0f", total }' | |
| } | |
| active_swap_bytes="$(read_active_swap_bytes)" | |
| active_swap_bytes="${active_swap_bytes:-0}" | |
| if [[ ! "${active_swap_bytes}" =~ ^[0-9]+$ ]]; then | |
| fail "unable to determine active swap capacity" | |
| fi | |
| if (( active_swap_bytes >= required_swap_bytes )); then | |
| printf 'Hermes E2E swap is already sufficient: %s bytes active\n' "${active_swap_bytes}" | |
| exit 0 | |
| fi | |
| available_bytes="$(/usr/bin/df --block-size=1 --output=avail /mnt | /usr/bin/tail -n 1 | /usr/bin/tr -d "[:space:]")" | |
| if [[ ! "${available_bytes}" =~ ^[0-9]+$ ]]; then | |
| fail "unable to determine available disk capacity under /mnt" | |
| fi | |
| required_disk_bytes=$((swap_file_bytes + reserve_bytes)) | |
| if (( available_bytes < required_disk_bytes )); then | |
| fail "insufficient disk capacity: ${available_bytes} bytes available, ${required_disk_bytes} required" | |
| fi | |
| if /usr/bin/sudo -n /usr/bin/test -e "${swap_dir}" || /usr/bin/sudo -n /usr/bin/test -L "${swap_dir}"; then | |
| fail "refusing unexpected pre-existing swap path" | |
| fi | |
| directory_created=0 | |
| cleanup_partial_swap() { | |
| status="$?" | |
| if (( status != 0 && directory_created == 1 )); then | |
| if active_swap_names="$(/usr/bin/sudo -n /usr/sbin/swapon --show=NAME --noheadings --raw 2>/dev/null)"; then | |
| fixed_swap_active=0 | |
| while IFS= read -r active_swap_name; do | |
| if [[ -n "${swap_file}" && "${active_swap_name}" == "${swap_file}" ]]; then | |
| fixed_swap_active=1 | |
| break | |
| fi | |
| done <<< "${active_swap_names}" | |
| if (( fixed_swap_active == 1 || swap_activation_succeeded == 1 )); then | |
| if /usr/bin/sudo -n /usr/sbin/swapoff "${swap_file}" 2>/dev/null; then | |
| /usr/bin/sudo -n /usr/bin/rm -f -- "${swap_file}" || true | |
| /usr/bin/sudo -n /usr/bin/rmdir -- "${swap_dir}" || true | |
| else | |
| printf 'Preserving active Hermes E2E swap after setup failure: %s\n' "${swap_file}" >&2 | |
| fi | |
| else | |
| if [[ -n "${swap_file}" ]]; then | |
| /usr/bin/sudo -n /usr/bin/rm -f -- "${swap_file}" || true | |
| fi | |
| /usr/bin/sudo -n /usr/bin/rmdir -- "${swap_dir}" || true | |
| fi | |
| else | |
| printf 'Preserving Hermes E2E swap because active swap could not be queried: %s\n' "${swap_file}" >&2 | |
| fi | |
| fi | |
| trap - EXIT | |
| exit "${status}" | |
| } | |
| trap cleanup_partial_swap EXIT | |
| /usr/bin/sudo -n /usr/bin/mkdir -m 0700 -- "${swap_dir}" | |
| directory_created=1 | |
| directory_metadata="$(/usr/bin/sudo -n /usr/bin/stat -c "%F:%u:%g:%a" -- "${swap_dir}")" | |
| if [[ "${directory_metadata}" != "directory:0:0:700" ]]; then | |
| fail "swap directory must be a root-owned mode-0700 directory" | |
| fi | |
| swap_file="$(/usr/bin/sudo -n /usr/bin/mktemp --tmpdir="${swap_dir}" nemoclaw-hermes.XXXXXXXX.swap)" | |
| if ! /usr/bin/sudo -n /usr/bin/test -f "${swap_file}" || /usr/bin/sudo -n /usr/bin/test -L "${swap_file}"; then | |
| fail "swap file must be a regular non-symlink" | |
| fi | |
| file_metadata="$(/usr/bin/sudo -n /usr/bin/stat -c "%u:%g:%a" -- "${swap_file}")" | |
| if [[ "${file_metadata}" != "0:0:600" ]]; then | |
| fail "swap file must be root-owned mode 0600" | |
| fi | |
| /usr/bin/sudo -n /usr/bin/fallocate -l "${swap_file_bytes}" "${swap_file}" | |
| file_size_bytes="$(/usr/bin/sudo -n /usr/bin/stat -c "%s" -- "${swap_file}")" | |
| if [[ ! "${file_size_bytes}" =~ ^[0-9]+$ || "${file_size_bytes}" -ne "${swap_file_bytes}" ]]; then | |
| fail "swap file size does not match the fixed backing allocation" | |
| fi | |
| remaining_bytes="$(/usr/bin/df --block-size=1 --output=avail /mnt | /usr/bin/tail -n 1 | /usr/bin/tr -d "[:space:]")" | |
| if [[ ! "${remaining_bytes}" =~ ^[0-9]+$ || "${remaining_bytes}" -lt "${reserve_bytes}" ]]; then | |
| fail "swap allocation did not preserve the required disk reserve" | |
| fi | |
| /usr/bin/sudo -n /usr/sbin/mkswap --quiet "${swap_file}" | |
| /usr/bin/sudo -n /usr/sbin/swapon "${swap_file}" | |
| swap_activation_succeeded=1 | |
| observe_provisioned_swap() { | |
| activation_observation_attempt=1 | |
| while (( activation_observation_attempt <= activation_observation_attempts )); do | |
| provisioned_swap_active=0 | |
| if active_swap_names="$(/usr/bin/sudo -n /usr/sbin/swapon --show=NAME --noheadings --raw 2>/dev/null)"; then | |
| while IFS= read -r active_swap_name; do | |
| if [[ "${active_swap_name}" == "${swap_file}" ]]; then | |
| provisioned_swap_active=1 | |
| break | |
| fi | |
| done <<< "${active_swap_names}" | |
| fi | |
| if observed_swap_bytes="$(read_active_swap_bytes 2>/dev/null)"; then | |
| observed_swap_bytes="${observed_swap_bytes:-0}" | |
| if [[ "${observed_swap_bytes}" =~ ^[0-9]+$ ]] && | |
| (( provisioned_swap_active == 1 && observed_swap_bytes >= required_swap_bytes )); then | |
| active_swap_bytes="${observed_swap_bytes}" | |
| return 0 | |
| fi | |
| fi | |
| if (( activation_observation_attempt < activation_observation_attempts )); then | |
| /usr/bin/sleep "${activation_observation_delay_seconds}" | |
| fi | |
| activation_observation_attempt=$((activation_observation_attempt + 1)) | |
| done | |
| return 1 | |
| } | |
| if ! observe_provisioned_swap; then | |
| fail "unable to verify the required active swap capacity after bounded observation" | |
| fi | |
| trap - EXIT | |
| printf 'Hermes E2E swap ready: %s bytes active\n' "${active_swap_bytes}" | |
| /usr/bin/sudo -n /usr/sbin/swapon --show | |
| - 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Initialize runner comparison telemetry | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' && (matrix.agent == 'hermes' || matrix.agent == 'deepagents') }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts initialize | |
| - name: Install and verify cloudflared prerequisite | |
| # Update posture: maintainers review upstream cloudflared releases and | |
| # update the version and reviewed SHA256 together in both explicit MCP | |
| # lanes; mutable package repositories and unreviewed latest releases | |
| # are intentionally rejected by the workflow-contract tests. | |
| env: | |
| CLOUDFLARED_VERSION: "2026.6.1" | |
| CLOUDFLARED_DEB_SHA256: "ccd02ec216c62bfa573395d8f72cb2e91e95cbdf8726a8acc06b3e2d9aa31526" | |
| run: | | |
| set -euo pipefail | |
| cloudflared_deb="${RUNNER_TEMP}/cloudflared-${CLOUDFLARED_VERSION}-linux-amd64.deb" | |
| curl -fL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64.deb" -o "${cloudflared_deb}" | |
| printf '%s %s\n' "${CLOUDFLARED_DEB_SHA256}" "${cloudflared_deb}" | sha256sum -c - | |
| package="$(dpkg-deb -f "${cloudflared_deb}" Package)" | |
| version="$(dpkg-deb -f "${cloudflared_deb}" Version)" | |
| architecture="$(dpkg-deb -f "${cloudflared_deb}" Architecture)" | |
| if [[ "${package}" != "cloudflared" || "${version}" != "${CLOUDFLARED_VERSION}" || "${architecture}" != "amd64" ]]; then | |
| printf 'Unexpected cloudflared package metadata: package=%s version=%s architecture=%s\n' "${package}" "${version}" "${architecture}" >&2 | |
| exit 1 | |
| fi | |
| sudo dpkg -i "${cloudflared_deb}" | |
| cloudflared --version | grep -F "cloudflared version ${CLOUDFLARED_VERSION}" | |
| - name: Generate MCP test TLS | |
| run: bash test/e2e/setup-mcp-test-tls.sh | |
| - name: Install OpenShell CLI | |
| env: | |
| NEMOCLAW_OPENSHELL_FORCE_INSTALL: "1" | |
| run: | | |
| set -euo pipefail | |
| bash scripts/install-openshell.sh | |
| if [[ "$NEMOCLAW_MCP_BRIDGE_AGENT" == "deepagents" ]]; then | |
| mkdir -p "$E2E_ARTIFACT_DIR/mcp-bridge-deepagents" | |
| jq -n '{ | |
| schemaVersion: 1, | |
| sourceRepository: "NVIDIA/OpenShell", | |
| releaseTag: "v0.0.85", | |
| sourceSha: "3dee5570a46076a57a3b056f35f35ebc0861ac85", | |
| artifacts: { | |
| cli: {binarySha256: "222d9d53a142691d7a7de2c692f38e52d24066f9f633d53746c5fef775861bc8"}, | |
| gateway: {binarySha256: "33bb479d936c3c1b17dd475df05747be9de74564fb67d69a4c33cdd01181d02f"}, | |
| standaloneSandbox: {binarySha256: "863ef21ab7ef623f5e7a8728c4e5532b46bfbae3ace3b800665a1c6353a1f7d2"} | |
| } | |
| }' > "$E2E_ARTIFACT_DIR/mcp-bridge-deepagents/openshell-exact-main-provenance.json" | |
| fi | |
| - name: Run MCP OpenShell provider live test | |
| 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" | |
| exit 1 | |
| fi | |
| export OPENSHELL_BIN | |
| "$OPENSHELL_BIN" --version | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/mcp-bridge.test.ts | |
| - name: Finalize runner comparison telemetry | |
| if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' && (matrix.agent == 'hermes' || matrix.agent == 'deepagents') }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts finalize | |
| - id: mcp_artifact_secret_scan | |
| name: Scan MCP artifacts for fixture credentials | |
| if: always() | |
| run: >- | |
| npx tsx tools/e2e/assert-mcp-artifact-secrets-absent.mts | |
| e2e-artifacts/live/mcp-bridge/${{ matrix.agent }} | |
| - name: Upload MCP server artifacts | |
| if: ${{ always() && steps.mcp_artifact_secret_scan.outcome == 'success' }} | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: e2e-mcp-bridge-${{ matrix.agent }} | |
| path: e2e-artifacts/live/mcp-bridge/${{ matrix.agent }}/ | |
| - name: Clean up Docker auth | |
| if: always() | |
| shell: bash | |
| run: bash .github/scripts/docker-auth-cleanup.sh | |
| openshell-credential-generation-window: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',mcp-bridge,') || contains(format(',{0},', inputs.targets), ',mcp-bridge,') || contains(format(',{0},', inputs.jobs), ',openshell-credential-generation-window,') || contains(format(',{0},', inputs.targets), ',openshell-credential-generation-window,') }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| # Keep the credential-generation lifecycle on a fresh runner so it can | |
| # execute in parallel with, and fail independently from, the Deep Agents | |
| # MCP lifecycle without sharing destructive sandbox state. | |
| timeout-minutes: 90 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "openshell-credential-generation-window" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openshell-credential-generation-window | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_OPENSHELL_CHANNEL: stable | |
| NEMOCLAW_OPENSHELL_EXACT_MAIN_PROOF: "1" | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| OPENSHELL_DOCKER_SUPERVISOR_IMAGE: ghcr.io/nvidia/openshell/supervisor@sha256:f4226253a3525c3832adac5b38b419a0f27d1e915effe565b5885e20f93cd5e9 | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install and verify cloudflared prerequisite | |
| env: | |
| CLOUDFLARED_VERSION: "2026.6.1" | |
| CLOUDFLARED_DEB_SHA256: "ccd02ec216c62bfa573395d8f72cb2e91e95cbdf8726a8acc06b3e2d9aa31526" | |
| run: | | |
| set -euo pipefail | |
| cloudflared_deb="${RUNNER_TEMP}/cloudflared-${CLOUDFLARED_VERSION}-linux-amd64.deb" | |
| curl -fL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64.deb" -o "${cloudflared_deb}" | |
| printf '%s %s\n' "${CLOUDFLARED_DEB_SHA256}" "${cloudflared_deb}" | sha256sum -c - | |
| package="$(dpkg-deb -f "${cloudflared_deb}" Package)" | |
| version="$(dpkg-deb -f "${cloudflared_deb}" Version)" | |
| architecture="$(dpkg-deb -f "${cloudflared_deb}" Architecture)" | |
| if [[ "${package}" != "cloudflared" || "${version}" != "${CLOUDFLARED_VERSION}" || "${architecture}" != "amd64" ]]; then | |
| printf 'Unexpected cloudflared package metadata: package=%s version=%s architecture=%s\n' "${package}" "${version}" "${architecture}" >&2 | |
| exit 1 | |
| fi | |
| sudo dpkg -i "${cloudflared_deb}" | |
| cloudflared --version | grep -F "cloudflared version ${CLOUDFLARED_VERSION}" | |
| - name: Generate MCP test TLS | |
| run: bash test/e2e/setup-mcp-test-tls.sh | |
| - name: Install OpenShell CLI | |
| env: | |
| NEMOCLAW_OPENSHELL_FORCE_INSTALL: "1" | |
| run: | | |
| set -euo pipefail | |
| bash scripts/install-openshell.sh | |
| mkdir -p "$E2E_ARTIFACT_DIR/openshell-credential-generation-window" | |
| jq -n '{ | |
| schemaVersion: 1, | |
| sourceRepository: "NVIDIA/OpenShell", | |
| releaseTag: "v0.0.85", | |
| sourceSha: "3dee5570a46076a57a3b056f35f35ebc0861ac85", | |
| artifacts: { | |
| cli: {binarySha256: "222d9d53a142691d7a7de2c692f38e52d24066f9f633d53746c5fef775861bc8"}, | |
| gateway: {binarySha256: "33bb479d936c3c1b17dd475df05747be9de74564fb67d69a4c33cdd01181d02f"}, | |
| standaloneSandbox: {binarySha256: "863ef21ab7ef623f5e7a8728c4e5532b46bfbae3ace3b800665a1c6353a1f7d2"} | |
| } | |
| }' > "$E2E_ARTIFACT_DIR/openshell-credential-generation-window/openshell-exact-main-provenance.json" | |
| - name: Run OpenShell credential generation-window live test | |
| 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" | |
| exit 1 | |
| fi | |
| export OPENSHELL_BIN | |
| "$OPENSHELL_BIN" --version | |
| npx vitest run --project e2e-live \ | |
| test/e2e/live/openshell-credential-generation-window.test.ts \ | |
| -t '^openshell-credential-generation-window$' \ | |
| --no-file-parallelism \ | |
| --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts | |
| - id: credential_window_artifact_secret_scan | |
| name: Scan credential-window artifacts for fixture credentials | |
| if: always() | |
| run: >- | |
| npx tsx tools/e2e/assert-mcp-artifact-secrets-absent.mts | |
| e2e-artifacts/live/openshell-credential-generation-window | |
| - name: Upload credential-window artifacts | |
| if: ${{ always() && steps.credential_window_artifact_secret_scan.outcome == 'success' }} | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: e2e-openshell-credential-generation-window | |
| path: e2e-artifacts/live/openshell-credential-generation-window/ | |
| - name: Clean up Docker auth | |
| if: always() | |
| shell: bash | |
| run: bash .github/scripts/docker-auth-cleanup.sh | |
| mcp-bridge-dev: | |
| needs: generate-matrix | |
| # Moving OpenShell dev artifacts are compatibility evidence only and must | |
| # never enter scheduled or default manual runs without explicit selection. | |
| if: ${{ contains(format(',{0},', inputs.jobs), ',mcp-bridge-dev,') || contains(format(',{0},', inputs.targets), ',mcp-bridge-dev,') }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| timeout-minutes: 90 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| agent: [openclaw, hermes, deepagents] | |
| env: | |
| E2E_JOB: "1" | |
| E2E_DEFAULT_ENABLED: "0" | |
| E2E_TARGET_ID: "mcp-bridge-dev" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/mcp-bridge-dev/${{ matrix.agent }} | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_E2E_SHARD: ${{ matrix.agent }} | |
| NEMOCLAW_MCP_BRIDGE_AGENT: ${{ matrix.agent }} | |
| NEMOCLAW_OPENSHELL_CHANNEL: dev | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - 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 | |
| # if the pins diverge or installation becomes mutable. | |
| env: | |
| CLOUDFLARED_VERSION: "2026.6.1" | |
| CLOUDFLARED_DEB_SHA256: "ccd02ec216c62bfa573395d8f72cb2e91e95cbdf8726a8acc06b3e2d9aa31526" | |
| run: | | |
| set -euo pipefail | |
| cloudflared_deb="${RUNNER_TEMP}/cloudflared-${CLOUDFLARED_VERSION}-linux-amd64.deb" | |
| curl -fL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64.deb" -o "${cloudflared_deb}" | |
| printf '%s %s\n' "${CLOUDFLARED_DEB_SHA256}" "${cloudflared_deb}" | sha256sum -c - | |
| package="$(dpkg-deb -f "${cloudflared_deb}" Package)" | |
| version="$(dpkg-deb -f "${cloudflared_deb}" Version)" | |
| architecture="$(dpkg-deb -f "${cloudflared_deb}" Architecture)" | |
| if [[ "${package}" != "cloudflared" || "${version}" != "${CLOUDFLARED_VERSION}" || "${architecture}" != "amd64" ]]; then | |
| printf 'Unexpected cloudflared package metadata: package=%s version=%s architecture=%s\n' "${package}" "${version}" "${architecture}" >&2 | |
| exit 1 | |
| fi | |
| sudo dpkg -i "${cloudflared_deb}" | |
| cloudflared --version | grep -F "cloudflared version ${CLOUDFLARED_VERSION}" | |
| - name: Generate MCP test TLS | |
| run: bash test/e2e/setup-mcp-test-tls.sh | |
| - name: Revoke Docker auth before unverified dev tooling | |
| shell: bash | |
| run: bash .github/scripts/docker-auth-cleanup.sh | |
| - name: Install OpenShell CLI | |
| env: | |
| NEMOCLAW_ACCEPT_DEV_UNVERIFIED_INSTALL: "1" | |
| NEMOCLAW_OPENSHELL_FORCE_INSTALL: "1" | |
| run: | | |
| set -euo pipefail | |
| bash scripts/install-openshell.sh | |
| - id: mcp_runtime_compatibility | |
| name: Classify OpenShell credential-boundary compatibility | |
| run: | | |
| set -euo pipefail | |
| export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH" | |
| npx tsx tools/e2e/mcp-bridge-runtime-compatibility.mts | |
| - name: Run MCP OpenShell provider live test | |
| if: ${{ steps.mcp_runtime_compatibility.outputs.mode == 'full-lifecycle' }} | |
| 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" | |
| exit 1 | |
| fi | |
| export OPENSHELL_BIN | |
| "$OPENSHELL_BIN" --version | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/mcp-bridge.test.ts | |
| - id: mcp_artifact_secret_scan | |
| name: Scan MCP artifacts for fixture credentials | |
| if: always() | |
| run: >- | |
| npx tsx tools/e2e/assert-mcp-artifact-secrets-absent.mts | |
| e2e-artifacts/live/mcp-bridge-dev/${{ matrix.agent }} | |
| - name: Upload MCP server artifacts | |
| if: ${{ always() && steps.mcp_artifact_secret_scan.outcome == 'success' }} | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: e2e-mcp-bridge-dev-${{ matrix.agent }} | |
| path: e2e-artifacts/live/mcp-bridge-dev/${{ matrix.agent }}/ | |
| - name: Clean up Docker auth | |
| if: always() | |
| shell: bash | |
| run: bash .github/scripts/docker-auth-cleanup.sh | |
| # Live skill injection and real OpenClaw agent-turn contract. | |
| skill-agent: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',skill-agent,') || contains(format(',{0},', inputs.targets), ',skill-agent,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "skill-agent" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/skill-agent | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| # The migrated skill-agent lane invokes helper scripts that call | |
| # `openshell` after the repo CLI onboard command has returned. Install | |
| # and resolve OpenShell in the workflow parent so PATH/OPENSHELL_BIN is | |
| # available to both Vitest and the bash helpers. | |
| run: bash scripts/install-openshell.sh | |
| - name: Run skill-agent live test | |
| 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 | |
| echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" | |
| "$OPENSHELL_BIN" --version | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/skill-agent.test.ts | |
| - name: Upload skill-agent artifacts | |
| if: always() | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: e2e-skill-agent | |
| path: | | |
| e2e-artifacts/live/skill-agent/*/artifact-summary.json | |
| e2e-artifacts/live/skill-agent/*/cleanup.json | |
| e2e-artifacts/live/skill-agent/*/cleanup-skill-agent-summary.json | |
| e2e-artifacts/live/skill-agent/*/target.json | |
| e2e-artifacts/live/skill-agent/*/target-result.json | |
| e2e-artifacts/live/skill-agent/*/test-progress.json | |
| e2e-artifacts/live/skill-agent/*/shell/*.result.json | |
| e2e-artifacts/live/skill-agent/*/shell/*.stdout.txt | |
| e2e-artifacts/live/skill-agent/*/shell/*.stderr.txt | |
| # Direct OpenClaw skills CLI contract. | |
| - name: Clean up Docker auth | |
| if: always() | |
| shell: bash | |
| run: bash .github/scripts/docker-auth-cleanup.sh | |
| openclaw-skill-cli: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',openclaw-skill-cli,') || contains(format(',{0},', inputs.targets), ',openclaw-skill-cli,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "openclaw-skill-cli" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openclaw-skill-cli | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1" | |
| NEMOCLAW_SANDBOX_NAME: "e2e-openclaw-skill-cli" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Run OpenClaw skill CLI live test | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openclaw-skill-cli.test.ts | |
| - name: Upload OpenClaw skill CLI 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 | |
| inference-routing: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',inference-routing,') || contains(format(',{0},', inputs.targets), ',inference-routing,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "inference-routing" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/inference-routing | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install and verify cloudflared prerequisite | |
| # Keep the public HTTPS routing fixture on the same reviewed binary as | |
| # the MCP and tunnel-lifecycle lanes. The checksum and package metadata | |
| # checks prevent a mutable package source from entering PR-safe E2E. | |
| env: | |
| CLOUDFLARED_VERSION: "2026.6.1" | |
| CLOUDFLARED_DEB_SHA256: "ccd02ec216c62bfa573395d8f72cb2e91e95cbdf8726a8acc06b3e2d9aa31526" | |
| run: | | |
| set -euo pipefail | |
| cloudflared_deb="${RUNNER_TEMP}/cloudflared-${CLOUDFLARED_VERSION}-linux-amd64.deb" | |
| curl -fL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64.deb" -o "${cloudflared_deb}" | |
| printf '%s %s\n' "${CLOUDFLARED_DEB_SHA256}" "${cloudflared_deb}" | sha256sum -c - | |
| package="$(dpkg-deb -f "${cloudflared_deb}" Package)" | |
| version="$(dpkg-deb -f "${cloudflared_deb}" Version)" | |
| architecture="$(dpkg-deb -f "${cloudflared_deb}" Architecture)" | |
| if [[ "${package}" != "cloudflared" || "${version}" != "${CLOUDFLARED_VERSION}" || "${architecture}" != "amd64" ]]; then | |
| printf 'Unexpected cloudflared package metadata: package=%s version=%s architecture=%s\n' "${package}" "${version}" "${architecture}" >&2 | |
| exit 1 | |
| fi | |
| sudo dpkg -i "${cloudflared_deb}" | |
| cloudflared --version | grep -F "cloudflared version ${CLOUDFLARED_VERSION}" | |
| - name: Run inference routing live test | |
| # Direct PR-safe E2E coverage proves invalid-key, unreachable-endpoint, | |
| # localhost-compatible routing, namespace-aware HTTPS pinning, DNS | |
| # rebinding resistance, private-target redirect rejection, and | |
| # credential-bearing URL state rejection without live provider quota. | |
| # It also proves generic and Entra-shaped runtime identity through a | |
| # deterministic public OAuth refresh, real OpenShell provider | |
| # attachment/injection, Graph /v1.0/me policy, rotation, and rollback | |
| # without storing a third-party tenant credential in PR CI. | |
| # Provider smokes live in inference-routing-provider-smoke.test.ts; any | |
| # future secret-bearing lane must run that file from trusted main. | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/inference-routing.test.ts | |
| - name: Upload inference routing 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 | |
| cloud-inference: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',cloud-inference,') || contains(format(',{0},', inputs.targets), ',cloud-inference,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 50 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "cloud-inference" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/cloud-inference | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1" | |
| NEMOCLAW_SANDBOX_NAME: "e2e-cloud-inference" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Run cloud inference live test | |
| # The Vitest test | |
| # preserves the legacy install.sh/onboard path, posts a live PONG | |
| # request through sandbox inference.local, and reuses the skill | |
| # filesystem validators from the retained bash helpers. | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/cloud-inference.test.ts | |
| - name: Upload cloud inference 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 | |
| gpu-e2e: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',gpu-e2e,') || contains(format(',{0},', inputs.targets), ',gpu-e2e,') }} | |
| runs-on: linux-amd64-gpu-rtxpro6000-latest-1 | |
| # The 55 minute install command includes a cold 40 minute model pull; | |
| # 35 minutes remain for runner setup, assertions, artifacts, and cleanup. | |
| timeout-minutes: 90 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "gpu-e2e" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/gpu-e2e | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_MODEL: "qwen3.5:9b" | |
| NEMOCLAW_PROVIDER: "ollama" | |
| NEMOCLAW_OLLAMA_PULL_TIMEOUT: "2400" | |
| NEMOCLAW_SANDBOX_NAME: "e2e-gpu-ollama" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| run: bash scripts/install-openshell.sh | |
| - name: Run GPU Ollama live Vitest test | |
| 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 | |
| echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" | |
| "$OPENSHELL_BIN" --version | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/gpu-e2e.test.ts | |
| - name: Upload GPU E2E 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 | |
| agent-turn-latency: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',agent-turn-latency,') || contains(format(',{0},', inputs.targets), ',agent-turn-latency,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 110 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "agent-turn-latency" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/agent-turn-latency | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| OPENSHELL_GATEWAY: "nemoclaw" | |
| NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1" | |
| NEMOCLAW_PROVIDER: custom | |
| NEMOCLAW_ENDPOINT_URL: https://inference-api.nvidia.com/v1 | |
| NEMOCLAW_MODEL: nvidia/nvidia/nemotron-3-ultra | |
| NEMOCLAW_COMPAT_MODEL: nvidia/nvidia/nemotron-3-ultra | |
| NEMOCLAW_PREFERRED_API: openai-completions | |
| steps: | |
| - id: trusted_hermes_swap | |
| name: Provision trusted Hermes E2E swap | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') }} | |
| shell: /bin/bash --noprofile --norc -e -o pipefail {0} | |
| env: | |
| BASH_ENV: /dev/null | |
| CHECKOUT_SHA: ${{ inputs.checkout_sha }} | |
| DISPATCH_SHA: ${{ github.sha }} | |
| ENV: /dev/null | |
| EVENT_NAME: ${{ github.event_name }} | |
| EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }} | |
| LC_ALL: C | |
| REF: ${{ github.ref }} | |
| REPOSITORY: ${{ github.repository }} | |
| RUNNER_ARCH_KIND: ${{ runner.arch }} | |
| RUNNER_ENVIRONMENT_KIND: ${{ runner.environment }} | |
| RUNNER_OS_KIND: ${{ runner.os }} | |
| WORKFLOW_SHA: ${{ github.workflow_sha }} | |
| run: *trusted-hermes-e2e-swap | |
| - 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Initialize runner comparison telemetry | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts initialize | |
| - name: Install OpenShell CLI | |
| run: bash scripts/install-openshell.sh | |
| - name: Run agent turn latency live Vitest test | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| COMPATIBLE_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 | |
| echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" | |
| "$OPENSHELL_BIN" --version | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/agent-turn-latency.test.ts | |
| - name: Finalize runner comparison telemetry | |
| if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts finalize | |
| - name: Upload agent turn latency 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 | |
| kimi-inference-compat: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',kimi-inference-compat,') || contains(format(',{0},', inputs.targets), ',kimi-inference-compat,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 50 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "kimi-inference-compat" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/kimi-inference-compat | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_SANDBOX_NAME: "e2e-kimi-compat" | |
| NEMOCLAW_E2E_INFERENCE_MODE: "mock" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| run: bash scripts/install-openshell.sh | |
| - name: Run Kimi compatibility live Vitest test | |
| run: | | |
| set -euo pipefail | |
| export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH" | |
| export OPENSHELL_BIN="$(command -v openshell || true)" | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/kimi-inference-compat.test.ts | |
| - name: Upload Kimi compatibility 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 | |
| hermes-inference-switch: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',hermes-inference-switch,') || contains(format(',{0},', inputs.targets), ',hermes-inference-switch,') }} | |
| runs-on: ${{ fromJSON(needs.generate-matrix.outputs.runner_routing)['hermes-inference-switch'] }} | |
| permissions: | |
| contents: read | |
| timeout-minutes: 55 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - mode: anthropic | |
| sandbox_name: e2e-hermes-anthropic-inference-switch | |
| switch_provider: compatible-anthropic-endpoint | |
| switch_model: mock-anthropic-model | |
| switch_inference_api: anthropic-messages | |
| switch_mock_anthropic: "1" | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "hermes-inference-switch" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/hermes-inference-switch/${{ matrix.mode }} | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_AGENT: "hermes" | |
| NEMOCLAW_E2E_SHARD: ${{ matrix.mode }} | |
| NEMOCLAW_SANDBOX_NAME: ${{ matrix.sandbox_name }} | |
| NEMOCLAW_SWITCH_PROVIDER: ${{ matrix.switch_provider }} | |
| NEMOCLAW_SWITCH_MODEL: ${{ matrix.switch_model }} | |
| NEMOCLAW_SWITCH_INFERENCE_API: ${{ matrix.switch_inference_api }} | |
| NEMOCLAW_SWITCH_MOCK_ANTHROPIC: ${{ matrix.switch_mock_anthropic }} | |
| OPENSHELL_GATEWAY: "nemoclaw" | |
| steps: | |
| - id: trusted_hermes_swap | |
| name: Provision trusted Hermes E2E swap | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') }} | |
| shell: /bin/bash --noprofile --norc -e -o pipefail {0} | |
| env: | |
| BASH_ENV: /dev/null | |
| CHECKOUT_SHA: ${{ inputs.checkout_sha }} | |
| DISPATCH_SHA: ${{ github.sha }} | |
| ENV: /dev/null | |
| EVENT_NAME: ${{ github.event_name }} | |
| EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }} | |
| LC_ALL: C | |
| REF: ${{ github.ref }} | |
| REPOSITORY: ${{ github.repository }} | |
| RUNNER_ARCH_KIND: ${{ runner.arch }} | |
| RUNNER_ENVIRONMENT_KIND: ${{ runner.environment }} | |
| RUNNER_OS_KIND: ${{ runner.os }} | |
| WORKFLOW_SHA: ${{ github.workflow_sha }} | |
| run: *trusted-hermes-e2e-swap | |
| - 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Initialize runner comparison telemetry | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts initialize | |
| - name: Install OpenShell CLI | |
| run: bash scripts/install-openshell.sh | |
| - name: Run Hermes inference switch live Vitest test | |
| run: | | |
| set -euo pipefail | |
| export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH" | |
| export OPENSHELL_BIN="$(command -v openshell || true)" | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/hermes-inference-switch.test.ts | |
| - name: Finalize runner comparison telemetry | |
| if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts finalize | |
| - name: Upload Hermes inference switch artifacts | |
| if: always() | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: e2e-hermes-inference-switch-${{ matrix.mode }} | |
| path: e2e-artifacts/live/hermes-inference-switch/${{ matrix.mode }}/ | |
| - name: Clean up Docker auth | |
| if: always() | |
| shell: bash | |
| run: bash .github/scripts/docker-auth-cleanup.sh | |
| brave-search: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',brave-search,') || contains(format(',{0},', inputs.targets), ',brave-search,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "brave-search" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/brave-search | |
| 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-brave-search" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| run: bash scripts/install-openshell.sh | |
| - name: Run Brave search live Vitest test | |
| env: | |
| BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }} | |
| 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/brave-search.test.ts | |
| - name: Upload Brave search 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 | |
| ollama-auth-proxy: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',ollama-auth-proxy,') || contains(format(',{0},', inputs.targets), ',ollama-auth-proxy,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "ollama-auth-proxy" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/ollama-auth-proxy | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_E2E_OLLAMA_PORT: "11434" | |
| NEMOCLAW_E2E_OLLAMA_PROXY_PORT: "11435" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| with: | |
| build-cli: "false" | |
| - name: Run Ollama auth proxy live Vitest test | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/ollama-auth-proxy.test.ts | |
| - name: Upload Ollama auth proxy 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 | |
| cron-preflight-inference-local: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',cron-preflight-inference-local,') || contains(format(',{0},', inputs.targets), ',cron-preflight-inference-local,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "cron-preflight-inference-local" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/cron-preflight-inference-local | |
| 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-cron-preflight" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| run: bash scripts/install-openshell.sh | |
| - name: Run cron preflight inference.local live Vitest test | |
| 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/cron-preflight-inference-local.test.ts | |
| - name: Upload cron preflight inference.local 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 | |
| issue-4434-tui-unreachable-inference: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',issue-4434-tui-unreachable-inference,') || contains(format(',{0},', inputs.targets), ',issue-4434-tui-unreachable-inference,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 120 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "issue-4434-tui-unreachable-inference" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/issue-4434-tui-unreachable-inference | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1" | |
| NEMOCLAW_ISSUE_4434_LIVE: "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 | |
| # This free-standing job checks out and executes the PR target ref. Its | |
| # privileged host dependency setup loads the first-party | |
| # host-dependency-setup action pinned to an immutable full SHA, so the | |
| # target ref never runs sudo with its own code. Only expect and iptables | |
| # are allowed here: the TUI driver and egress-isolation assertion require | |
| # them. Workflow contract tests pin the action provenance and package list. | |
| - name: "Install issue #4434 host dependencies" | |
| uses: NVIDIA/NemoClaw/.github/actions/host-dependency-setup@4def1501b34ce586f83b91af50a66b5d22b31d75 | |
| with: | |
| packages: expect iptables | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| run: bash scripts/install-openshell.sh | |
| - name: "Run issue #4434 TUI unreachable inference live test" | |
| 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 | |
| echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" | |
| "$OPENSHELL_BIN" --version | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/issue-4434-tui-unreachable-inference.test.ts | |
| - name: "Upload issue #4434 TUI unreachable inference 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 | |
| dashboard-remote-bind: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',dashboard-remote-bind,') || contains(format(',{0},', inputs.targets), ',dashboard-remote-bind,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 65 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "dashboard-remote-bind" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/dashboard-remote-bind | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_E2E_DASHBOARD_REMOTE_BIND: "1" | |
| NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1" | |
| NEMOCLAW_SANDBOX_NAME: "e2e-dashboard-remote-bind" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Run dashboard remote-bind live test | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/dashboard-remote-bind.test.ts | |
| - name: Upload dashboard remote-bind 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 | |
| sessions-agents-cli: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',sessions-agents-cli,') || contains(format(',{0},', inputs.targets), ',sessions-agents-cli,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 70 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "sessions-agents-cli" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/sessions-agents-cli | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1" | |
| NEMOCLAW_SANDBOX_NAME: "e2e-sessions-agents-cli" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| 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 sessions/agents CLI live test | |
| # This stays as a | |
| # host-side CLI live test because the contract is argv translation, | |
| # OpenClaw gateway dispatch, pending pairing/scope approval, and JSON | |
| # envelope handling for sessions/agents commands. | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH" | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/sessions-agents-cli.test.ts | |
| - name: Upload sessions/agents CLI 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 | |
| hermes-slack: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',hermes-slack,') || contains(format(',{0},', inputs.targets), ',hermes-slack,') }} | |
| runs-on: linux-amd64-cpu4 | |
| timeout-minutes: 75 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "hermes-slack" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/hermes-slack-e2e | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_AGENT: hermes | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_POLICY_TIER: open | |
| NEMOCLAW_RECREATE_SANDBOX: "1" | |
| NEMOCLAW_SANDBOX_NAME: e2e-hermes-slack | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Run Hermes Slack live test | |
| # Preserves the | |
| # linux-amd64-cpu4 Docker/OpenShell/Hermes Slack policy, placeholder, | |
| # provider, secret-boundary, and Python Slack egress contracts. | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| SLACK_BOT_TOKEN: xoxb-test-hermes-slack-token | |
| SLACK_APP_TOKEN: xapp-test-hermes-slack-app-token | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/hermes-slack-e2e.test.ts | |
| - name: Upload Hermes Slack artifacts | |
| if: always() | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: e2e-hermes-slack | |
| path: e2e-artifacts/live/hermes-slack-e2e/ | |
| - name: Clean up Docker auth | |
| if: always() | |
| shell: bash | |
| run: bash .github/scripts/docker-auth-cleanup.sh | |
| hermes-e2e: | |
| needs: generate-matrix | |
| if: ${{ needs.generate-matrix.outputs.hermes_selected == 'true' }} | |
| runs-on: ${{ fromJSON(needs.generate-matrix.outputs.runner_routing)['hermes-e2e'] }} | |
| timeout-minutes: 85 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "hermes-e2e" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/hermes-e2e | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_E2E_INFERENCE_MODE: ${{ inputs.inference_mode || 'mock' }} | |
| NEMOCLAW_E2E_HERMES_DASHBOARD: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_AGENT: hermes | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_RECREATE_SANDBOX: "1" | |
| NEMOCLAW_SANDBOX_NAME: e2e-hermes | |
| NEMOCLAW_ONBOARD_VALIDATION_TIMEOUT_SECONDS: "60" | |
| steps: | |
| - id: trusted_hermes_swap | |
| name: Provision trusted Hermes E2E swap | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && (github.event_name == 'schedule' || inputs.checkout_sha == '' || (github.event_name == 'workflow_dispatch' && inputs.checkout_sha != '' && (contains(format(',{0},', inputs.jobs), ',hermes-e2e,') || contains(format(',{0},', inputs.targets), ',hermes-e2e,') || contains(format(',{0},', inputs.jobs), ',hermes-dashboard,') || contains(format(',{0},', inputs.targets), ',hermes-dashboard,')))) }} | |
| shell: /bin/bash --noprofile --norc -e -o pipefail {0} | |
| env: | |
| BASH_ENV: /dev/null | |
| CHECKOUT_SHA: ${{ inputs.checkout_sha }} | |
| DISPATCH_SHA: ${{ github.sha }} | |
| ENV: /dev/null | |
| EVENT_NAME: ${{ github.event_name }} | |
| EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }} | |
| LC_ALL: C | |
| REF: ${{ github.ref }} | |
| REPOSITORY: ${{ github.repository }} | |
| RUNNER_ARCH_KIND: ${{ runner.arch }} | |
| RUNNER_ENVIRONMENT_KIND: ${{ runner.environment }} | |
| RUNNER_OS_KIND: ${{ runner.os }} | |
| WORKFLOW_SHA: ${{ github.workflow_sha }} | |
| run: *trusted-hermes-e2e-swap | |
| - 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Initialize runner comparison telemetry | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts initialize | |
| - name: Run Hermes live Vitest test | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' && inputs.checkout_sha == '' && (inputs.inference_mode || 'mock') != 'mock' && secrets.NVIDIA_INFERENCE_API_KEY || '' }} | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/hermes-e2e.test.ts | |
| - name: Finalize runner comparison telemetry | |
| if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts finalize | |
| - name: Upload Hermes live Vitest 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 | |
| hermes-gpu-startup: | |
| needs: generate-matrix | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && (contains(format(',{0},', inputs.jobs), ',hermes-gpu-startup,') || contains(format(',{0},', inputs.targets), ',hermes-gpu-startup,')) }} | |
| runs-on: linux-amd64-gpu-rtxpro6000-latest-1 | |
| timeout-minutes: 90 | |
| strategy: | |
| fail-fast: false | |
| max-parallel: 1 | |
| matrix: | |
| include: | |
| - scenario: native | |
| - scenario: fallback | |
| - scenario: compatibility-only | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "hermes-gpu-startup" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/hermes-gpu-startup/${{ matrix.scenario }} | |
| E2E_HERMES_GPU_STARTUP_SCENARIO: ${{ matrix.scenario }} | |
| NEMOCLAW_E2E_SHARD: ${{ matrix.scenario }} | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_AGENT: hermes | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_RECREATE_SANDBOX: "1" | |
| NEMOCLAW_SANDBOX_GPU: "1" | |
| NEMOCLAW_SANDBOX_NAME: e2e-hermes-gpu-startup-${{ matrix.scenario }} | |
| NEMOCLAW_ONBOARD_VALIDATION_TIMEOUT_SECONDS: "60" | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| repository: ${{ inputs.checkout_repository || github.repository }} | |
| ref: ${{ inputs.checkout_sha || github.sha }} | |
| persist-credentials: false | |
| - name: Checkout trusted Hermes GPU runtime fixture | |
| if: ${{ matrix.scenario == 'fallback' }} | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| repository: NVIDIA/NemoClaw | |
| ref: ${{ github.workflow_sha }} | |
| path: .trusted-hermes-gpu-fixture-${{ github.run_id }}-${{ github.run_attempt }} | |
| sparse-checkout: tools/e2e/hermes-gpu-docker-runtime-fixture.sh | |
| sparse-checkout-cone-mode: false | |
| persist-credentials: false | |
| - name: Install trusted Hermes GPU runtime fixture | |
| if: ${{ matrix.scenario == 'fallback' }} | |
| shell: /bin/bash --noprofile --norc -e -o pipefail {0} | |
| env: | |
| BASH_ENV: /dev/null | |
| E2E_HERMES_GPU_STARTUP_SCENARIO: ${{ matrix.scenario }} | |
| ENV: /dev/null | |
| TRUSTED_DISPATCH_SHA: ${{ github.sha }} | |
| TRUSTED_FIXTURE_SHA256: e273c4baa7fe89546d64517cf56eafec30aeda7b355971263605ab1327fade02 | |
| TRUSTED_WORKFLOW_SHA: ${{ github.workflow_sha }} | |
| run: | | |
| set -euo pipefail | |
| trusted_checkout="$GITHUB_WORKSPACE/.trusted-hermes-gpu-fixture-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" | |
| trusted_source="$trusted_checkout/tools/e2e/hermes-gpu-docker-runtime-fixture.sh" | |
| trusted_fixture="/usr/local/libexec/nemoclaw/hermes-gpu-docker-runtime-fixture.${GITHUB_RUN_ID}.${GITHUB_RUN_ATTEMPT}.${E2E_HERMES_GPU_STARTUP_SCENARIO}" | |
| trusted_state_root=/var/lib/nemoclaw-e2e | |
| run_trusted_fixture() { | |
| /usr/bin/sudo -n /usr/bin/env -i \ | |
| PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \ | |
| /bin/bash "$trusted_fixture" "$@" | |
| } | |
| [[ "$TRUSTED_WORKFLOW_SHA" =~ ^[a-f0-9]{40}$ ]] \ | |
| || { echo "Trusted workflow SHA must be an immutable commit" >&2; exit 1; } | |
| [[ "$TRUSTED_DISPATCH_SHA" = "$TRUSTED_WORKFLOW_SHA" ]] \ | |
| || { echo "Trusted fixture must match the dispatched main workflow" >&2; exit 1; } | |
| [[ "$TRUSTED_FIXTURE_SHA256" =~ ^[a-f0-9]{64}$ ]] \ | |
| || { echo "Trusted fixture SHA-256 must be pinned" >&2; exit 1; } | |
| [ "$(/usr/bin/git -C "$trusted_checkout" rev-parse HEAD)" = "$TRUSTED_WORKFLOW_SHA" ] \ | |
| || { echo "Trusted fixture checkout does not match the workflow SHA" >&2; exit 1; } | |
| [ -f "$trusted_source" ] && [ ! -L "$trusted_source" ] \ | |
| || { echo "Trusted Docker fixture must be a regular non-symlink file" >&2; exit 1; } | |
| /usr/bin/sudo /usr/bin/install -d -o root -g root -m 0755 /usr/local/libexec/nemoclaw | |
| /usr/bin/sudo /usr/bin/install -o root -g root -m 0500 \ | |
| "$trusted_source" "$trusted_fixture" | |
| [ "$(/usr/bin/sudo /usr/bin/stat -c '%a %u %g' "$trusted_fixture")" = "500 0 0" ] \ | |
| || { echo "Trusted Docker fixture ownership or mode is invalid" >&2; exit 1; } | |
| printf '%s %s\n' "$TRUSTED_FIXTURE_SHA256" "$trusted_fixture" \ | |
| | /usr/bin/sudo /usr/bin/sha256sum -c - | |
| /usr/bin/sudo /usr/bin/cmp -s "$trusted_source" "$trusted_fixture" \ | |
| || { echo "Installed Docker fixture does not match trusted workflow code" >&2; exit 1; } | |
| /usr/bin/sudo /usr/bin/install -d -o root -g root -m 0700 "$trusted_state_root" | |
| # Recover any root-owned snapshot left by a hard-cancelled earlier run before | |
| # PR-controlled build or test code executes on this persistent GPU runner. | |
| if ! /usr/bin/sudo /usr/bin/find "$trusted_state_root" -mindepth 1 -maxdepth 1 \ | |
| -type d -name 'hermes-gpu-fallback-docker-runtime.*' -print0 \ | |
| | while IFS= read -r -d '' stale_state_dir; do | |
| if ! run_trusted_fixture restore \ | |
| "$stale_state_dir" /etc/docker/daemon.json >/dev/null; then | |
| exit 1 | |
| fi | |
| done; then | |
| echo "Could not recover stale Docker fallback state" >&2 | |
| exit 1 | |
| fi | |
| - *dockerhub-auth | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Reassert trusted Node runtime | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| env: | |
| BASH_ENV: /dev/null | |
| E2E_HERMES_GPU_STARTUP_SCENARIO: ${{ matrix.scenario }} | |
| ENV: /dev/null | |
| NODE_OPTIONS: "" | |
| with: | |
| node-version: "22" | |
| - name: Run Hermes GPU startup live Vitest test | |
| shell: /bin/bash --noprofile --norc -e -o pipefail {0} | |
| env: | |
| BASH_ENV: /dev/null | |
| E2E_HERMES_GPU_STARTUP_SCENARIO: ${{ matrix.scenario }} | |
| ENV: /dev/null | |
| run: | | |
| set -euo pipefail | |
| if [ "$E2E_HERMES_GPU_STARTUP_SCENARIO" = fallback ]; then | |
| umask 077 | |
| daemon_json=/etc/docker/daemon.json | |
| trusted_fixture="/usr/local/libexec/nemoclaw/hermes-gpu-docker-runtime-fixture.${GITHUB_RUN_ID}.${GITHUB_RUN_ATTEMPT}.${E2E_HERMES_GPU_STARTUP_SCENARIO}" | |
| trusted_state_root=/var/lib/nemoclaw-e2e | |
| run_trusted_fixture() { | |
| /usr/bin/sudo -n /usr/bin/env -i \ | |
| PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \ | |
| /bin/bash "$trusted_fixture" "$@" | |
| } | |
| mkdir -p "$E2E_ARTIFACT_DIR" | |
| [ "$(/usr/bin/sudo /usr/bin/stat -c '%a %u %g' "$trusted_fixture")" = "500 0 0" ] \ | |
| || { echo "Trusted Docker fixture ownership or mode changed" >&2; exit 1; } | |
| /usr/bin/sudo /usr/bin/install -d -o root -g root -m 0700 "$trusted_state_root" | |
| state_dir="$(/usr/bin/sudo /usr/bin/mktemp -d \ | |
| "$trusted_state_root/hermes-gpu-fallback-docker-runtime.${GITHUB_RUN_ID}.${GITHUB_RUN_ATTEMPT}.fallback.XXXXXX")" | |
| /usr/bin/sudo /usr/bin/chown root:root "$state_dir" | |
| /usr/bin/sudo /usr/bin/chmod 0700 "$state_dir" | |
| # SOURCE_OF_TRUTH_REVIEW (Hermes GPU fallback fixture; #6110): | |
| # invalidState: a cancelled fallback test leaves the runner-global Docker default | |
| # runtime changed, contaminating later OpenShell jobs on this self-hosted runner. | |
| # sourceBoundary: daemon mutation uses only immutable root-owned workflow_sha | |
| # code; the PR test cannot replace the helper or its root-owned snapshot state. | |
| # whyNotSourceFix: production must fail closed when GPU attachment is ambiguous; | |
| # this fixture needs a real no-GPU partial container on an nvidia-default runner. | |
| # regressionTest: hermes-gpu-startup-workflow-boundary requires this same-step trap. | |
| # removalCondition: remove the daemon fixture when OpenShell can create a provably | |
| # GPU-unattached partial sandbox without consulting Docker's default runtime. | |
| restore_docker_default_runtime() { | |
| local command_status=$? | |
| local restore_status=0 | |
| local restored_runtime="" | |
| trap - EXIT INT TERM | |
| set +e | |
| restored_runtime="$(run_trusted_fixture restore "$state_dir" "$daemon_json")" | |
| restore_status=$? | |
| if [ "$restore_status" -eq 0 ] && [ -n "$restored_runtime" ]; then | |
| printf '%s\n' "$restored_runtime" \ | |
| >"$E2E_ARTIFACT_DIR/docker-default-runtime-restored.txt" \ | |
| || restore_status=1 | |
| fi | |
| if [ "$restore_status" -ne 0 ]; then | |
| exit 1 | |
| fi | |
| exit "$command_status" | |
| } | |
| # OpenShell leaves Runtime unset after the wrapper strips --gpu, so Docker | |
| # otherwise substitutes its daemon default. Force runc only within this | |
| # trap-guarded process so production can prove the partial container has no GPU. | |
| trap restore_docker_default_runtime EXIT | |
| trap 'exit 130' INT | |
| trap 'exit 143' TERM | |
| original_runtime="$(run_trusted_fixture capture "$state_dir" "$daemon_json")" | |
| printf '%s\n' "$original_runtime" \ | |
| >"$E2E_ARTIFACT_DIR/docker-default-runtime-before.txt" | |
| selected_runtime="$(run_trusted_fixture select-runc "$state_dir" "$daemon_json")" | |
| printf '%s\n' "$selected_runtime" \ | |
| >"$E2E_ARTIFACT_DIR/docker-default-runtime-during.txt" | |
| fi | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/hermes-gpu-startup.test.ts | |
| - name: Recover Docker daemon after Hermes GPU fallback fixture | |
| if: always() | |
| shell: /bin/bash --noprofile --norc -e -o pipefail {0} | |
| env: | |
| BASH_ENV: /dev/null | |
| E2E_HERMES_GPU_STARTUP_SCENARIO: ${{ matrix.scenario }} | |
| ENV: /dev/null | |
| run: | | |
| set -euo pipefail | |
| if [ "$E2E_HERMES_GPU_STARTUP_SCENARIO" != fallback ]; then | |
| exit 0 | |
| fi | |
| recovery_failed=0 | |
| restored_runtime="" | |
| trusted_fixture="/usr/local/libexec/nemoclaw/hermes-gpu-docker-runtime-fixture.${GITHUB_RUN_ID}.${GITHUB_RUN_ATTEMPT}.${E2E_HERMES_GPU_STARTUP_SCENARIO}" | |
| trusted_state_root=/var/lib/nemoclaw-e2e | |
| state_prefix="hermes-gpu-fallback-docker-runtime.${GITHUB_RUN_ID}.${GITHUB_RUN_ATTEMPT}.fallback." | |
| run_trusted_fixture() { | |
| /usr/bin/sudo -n /usr/bin/env -i \ | |
| PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \ | |
| /bin/bash "$trusted_fixture" "$@" | |
| } | |
| if ! /usr/bin/sudo /usr/bin/find "$trusted_state_root" -mindepth 1 -maxdepth 1 \ | |
| -type d -name "${state_prefix}*" -print0 \ | |
| | while IFS= read -r -d '' state_dir; do | |
| if ! restored_runtime="$(run_trusted_fixture restore \ | |
| "$state_dir" /etc/docker/daemon.json)"; then | |
| exit 1 | |
| fi | |
| if [ -n "$restored_runtime" ]; then | |
| printf '%s\n' "$restored_runtime" \ | |
| >"$E2E_ARTIFACT_DIR/docker-default-runtime-restored.txt" | |
| fi | |
| done; then | |
| recovery_failed=1 | |
| fi | |
| if [ "$recovery_failed" -ne 0 ]; then | |
| echo "Independent Docker daemon recovery could not prove restoration" >&2 | |
| exit 1 | |
| fi | |
| - name: Remove trusted Hermes GPU runtime fixture | |
| if: ${{ always() && matrix.scenario == 'fallback' }} | |
| shell: /bin/bash --noprofile --norc -e -o pipefail {0} | |
| env: | |
| BASH_ENV: /dev/null | |
| E2E_HERMES_GPU_STARTUP_SCENARIO: ${{ matrix.scenario }} | |
| ENV: /dev/null | |
| run: | | |
| set -euo pipefail | |
| trusted_fixture="/usr/local/libexec/nemoclaw/hermes-gpu-docker-runtime-fixture.${GITHUB_RUN_ID}.${GITHUB_RUN_ATTEMPT}.${E2E_HERMES_GPU_STARTUP_SCENARIO}" | |
| /usr/bin/sudo /usr/bin/rm -f -- "$trusted_fixture" | |
| - name: Upload Hermes GPU startup artifacts | |
| if: always() | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: e2e-hermes-gpu-startup-${{ matrix.scenario }} | |
| path: e2e-artifacts/live/hermes-gpu-startup/${{ matrix.scenario }}/ | |
| - name: Clean up Docker auth | |
| if: always() | |
| shell: bash | |
| run: bash .github/scripts/docker-auth-cleanup.sh | |
| hermes-discord: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',hermes-discord,') || contains(format(',{0},', inputs.targets), ',hermes-discord,') }} | |
| runs-on: ${{ fromJSON(needs.generate-matrix.outputs.runner_routing)['hermes-discord'] }} | |
| timeout-minutes: 90 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "hermes-discord" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/hermes-discord | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1" | |
| NEMOCLAW_SANDBOX_NAME: e2e-hermes-discord | |
| NEMOCLAW_AGENT: hermes | |
| NEMOCLAW_POLICY_TIER: open | |
| OPENSHELL_GATEWAY: nemoclaw | |
| steps: | |
| - id: trusted_hermes_swap | |
| name: Provision trusted Hermes E2E swap | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') }} | |
| shell: /bin/bash --noprofile --norc -e -o pipefail {0} | |
| env: | |
| BASH_ENV: /dev/null | |
| CHECKOUT_SHA: ${{ inputs.checkout_sha }} | |
| DISPATCH_SHA: ${{ github.sha }} | |
| ENV: /dev/null | |
| EVENT_NAME: ${{ github.event_name }} | |
| EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }} | |
| LC_ALL: C | |
| REF: ${{ github.ref }} | |
| REPOSITORY: ${{ github.repository }} | |
| RUNNER_ARCH_KIND: ${{ runner.arch }} | |
| RUNNER_ENVIRONMENT_KIND: ${{ runner.environment }} | |
| RUNNER_OS_KIND: ${{ runner.os }} | |
| WORKFLOW_SHA: ${{ github.workflow_sha }} | |
| run: *trusted-hermes-e2e-swap | |
| - 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Initialize runner comparison telemetry | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts initialize | |
| - name: Run Hermes Discord live test | |
| # Preserves the | |
| # ubuntu-latest Docker/OpenShell/Hermes Discord schema, provider, | |
| # placeholder isolation, native gateway rewrite, and rebuild contracts. | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| DISCORD_BOT_TOKEN: test-fake-discord-token-hermes-e2e | |
| DISCORD_SERVER_IDS: "1491590992753590594" | |
| DISCORD_ALLOWED_IDS: "1005536447329222676" | |
| DISCORD_REQUIRE_MENTION: "0" | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/hermes-discord.test.ts | |
| - name: Finalize runner comparison telemetry | |
| if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts finalize | |
| - name: Upload Hermes Discord 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 | |
| network-policy: | |
| name: Network policy (${{ matrix.scenario }}) | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',network-policy,') || contains(format(',{0},', inputs.targets), ',network-policy,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 90 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - scenario: live-probes | |
| selector: "^network-policy:.+probes$" | |
| sandbox: e2e-net-policy-live-probes | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "network-policy" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/network-policy/${{ matrix.scenario }} | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_E2E_SHARD: ${{ matrix.scenario }} | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1" | |
| NEMOCLAW_SANDBOX_NAME: ${{ matrix.sandbox }} | |
| # Raw OpenShell sandbox commands in the migrated live test must target | |
| # the gateway registered by NemoClaw onboarding even when OpenShell has | |
| # no active gateway selected on the runner. | |
| 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 | |
| # Expect is a reviewed host-tool consumer for the interactive policy-add | |
| # test. This job executes a selected target ref, so privileged setup runs | |
| # from the first-party host-dependency-setup action pinned to an immutable | |
| # full SHA, never that ref. Only expect is allowed here; iptables is scoped | |
| # to the issue #4434 egress-isolation job. | |
| - name: Install network-policy host dependencies | |
| uses: NVIDIA/NemoClaw/.github/actions/host-dependency-setup@4def1501b34ce586f83b91af50a66b5d22b31d75 | |
| with: | |
| packages: expect | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell | |
| # Runs without workflow tokens, Docker credentials, or NVIDIA_INFERENCE_API_KEY. | |
| # scripts/install-openshell.sh pins the OpenShell version and verifies | |
| # release SHA-256 checksums before installation. | |
| 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 network-policy live test | |
| # Free-standing live network policy allow/deny probes. | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/network-policy.test.ts \ | |
| --selector "${{ matrix.selector }}" | |
| - name: Upload network-policy artifacts | |
| if: always() | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: e2e-network-policy-${{ matrix.scenario }} | |
| path: e2e-artifacts/live/network-policy/${{ matrix.scenario }}/ | |
| - name: Clean up Docker auth | |
| if: always() | |
| shell: bash | |
| run: bash .github/scripts/docker-auth-cleanup.sh | |
| common-egress-agent: | |
| name: Common egress agent (${{ matrix.scenario }}) | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',common-egress-agent,') || contains(format(',{0},', inputs.targets), ',common-egress-agent,') }} | |
| runs-on: ${{ fromJSON(needs.generate-matrix.outputs.runner_routing)['common-egress-agent'] }} | |
| timeout-minutes: 60 | |
| strategy: | |
| fail-fast: false | |
| max-parallel: 2 | |
| matrix: | |
| include: | |
| - scenario: openclaw-balanced-weather | |
| selector: "^common-egress.+C1.+$" | |
| - scenario: openclaw-open-reference | |
| selector: "^common-egress.+C2.+$" | |
| - scenario: hermes-open-reference | |
| selector: "^common-egress.+C3.+$" | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "common-egress-agent" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/common-egress-agent/${{ matrix.scenario }} | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_E2E_SHARD: ${{ matrix.scenario }} | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_RECREATE_SANDBOX: "1" | |
| OPENSHELL_GATEWAY: nemoclaw | |
| steps: | |
| - id: trusted_hermes_swap | |
| name: Provision trusted Hermes E2E swap | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && matrix.scenario == 'hermes-open-reference' }} | |
| shell: /bin/bash --noprofile --norc -e -o pipefail {0} | |
| env: | |
| BASH_ENV: /dev/null | |
| CHECKOUT_SHA: ${{ inputs.checkout_sha }} | |
| DISPATCH_SHA: ${{ github.sha }} | |
| ENV: /dev/null | |
| EVENT_NAME: ${{ github.event_name }} | |
| EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }} | |
| LC_ALL: C | |
| REF: ${{ github.ref }} | |
| REPOSITORY: ${{ github.repository }} | |
| RUNNER_ARCH_KIND: ${{ runner.arch }} | |
| RUNNER_ENVIRONMENT_KIND: ${{ runner.environment }} | |
| RUNNER_OS_KIND: ${{ runner.os }} | |
| WORKFLOW_SHA: ${{ github.workflow_sha }} | |
| run: *trusted-hermes-e2e-swap | |
| - 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Initialize runner comparison telemetry | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts initialize | |
| - name: Install OpenShell | |
| # Direct Vitest execution uses bin/nemoclaw.js instead of install.sh, | |
| # so install OpenShell explicitly before onboard and SSH-agent probes. | |
| 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 common-egress agent live test | |
| # Preserves | |
| # OpenClaw balanced/open and Hermes open common-egress agent-turn | |
| # coverage against real sandbox policy and live hosted inference. | |
| env: | |
| BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }} | |
| 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 | |
| echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" | |
| "$OPENSHELL_BIN" --version | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/common-egress-agent.test.ts \ | |
| --selector "${{ matrix.selector }}" | |
| - name: Finalize runner comparison telemetry | |
| if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts finalize | |
| - name: Upload common-egress agent artifacts | |
| if: always() | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: e2e-common-egress-agent-${{ matrix.scenario }} | |
| path: e2e-artifacts/live/common-egress-agent/${{ matrix.scenario }}/ | |
| - name: Clean up Docker auth | |
| if: always() | |
| shell: bash | |
| run: bash .github/scripts/docker-auth-cleanup.sh | |
| shields-config: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',shields-config,') || contains(format(',{0},', inputs.targets), ',shields-config,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "shields-config" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/shields-config | |
| 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-shields | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| with: | |
| build-cli: "false" | |
| - name: Run shields-config live test | |
| # The Vitest test runs | |
| # bash install.sh to preserve installer/onboard fidelity, then probes | |
| # real shields/config behavior against the live sandbox. | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/shields-config.test.ts | |
| - name: Upload shields-config artifacts | |
| if: always() | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: e2e-shields-config | |
| path: | | |
| e2e-artifacts/live/shields-config/ | |
| - name: Clean up Docker auth | |
| if: always() | |
| shell: bash | |
| run: bash .github/scripts/docker-auth-cleanup.sh | |
| hermes-shields-config: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',hermes-shields-config,') || contains(format(',{0},', inputs.targets), ',hermes-shields-config,') }} | |
| runs-on: ${{ fromJSON(needs.generate-matrix.outputs.runner_routing)['hermes-shields-config'] }} | |
| timeout-minutes: 60 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "hermes-shields-config" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/hermes-shields-config | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_AGENT: hermes | |
| NEMOCLAW_SANDBOX_NAME: e2e-hermes-shields | |
| OPENSHELL_GATEWAY: nemoclaw | |
| steps: | |
| - id: trusted_hermes_swap | |
| name: Provision trusted Hermes E2E swap | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') }} | |
| shell: /bin/bash --noprofile --norc -e -o pipefail {0} | |
| env: | |
| BASH_ENV: /dev/null | |
| CHECKOUT_SHA: ${{ inputs.checkout_sha }} | |
| DISPATCH_SHA: ${{ github.sha }} | |
| ENV: /dev/null | |
| EVENT_NAME: ${{ github.event_name }} | |
| EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }} | |
| LC_ALL: C | |
| REF: ${{ github.ref }} | |
| REPOSITORY: ${{ github.repository }} | |
| RUNNER_ARCH_KIND: ${{ runner.arch }} | |
| RUNNER_ENVIRONMENT_KIND: ${{ runner.environment }} | |
| RUNNER_OS_KIND: ${{ runner.os }} | |
| WORKFLOW_SHA: ${{ github.workflow_sha }} | |
| run: *trusted-hermes-e2e-swap | |
| - 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Initialize runner comparison telemetry | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts initialize | |
| - name: Run Hermes shields-config live regression | |
| # This hermetic regression uses a local OpenAI-compatible endpoint and | |
| # proves two complete down/up cycles on a fresh non-root Hermes sandbox. | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/hermes-shields-config.test.ts | |
| - name: Finalize runner comparison telemetry | |
| if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts finalize | |
| - name: Upload Hermes shields-config 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 | |
| rebuild-openclaw: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',rebuild-openclaw,') || contains(format(',{0},', inputs.targets), ',rebuild-openclaw,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 130 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "rebuild-openclaw" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/rebuild-openclaw | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell | |
| # Direct Vitest execution uses bin/nemoclaw.js instead of install.sh, | |
| # so install OpenShell explicitly before onboard/rebuild commands. | |
| 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 OpenClaw rebuild live test | |
| 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/rebuild-openclaw.test.ts | |
| - name: Upload OpenClaw 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 | |
| rebuild-hermes: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',rebuild-hermes,') || contains(format(',{0},', inputs.targets), ',rebuild-hermes,') }} | |
| runs-on: ${{ fromJSON(needs.generate-matrix.outputs.runner_routing)['rebuild-hermes'] }} | |
| timeout-minutes: 90 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "rebuild-hermes" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/rebuild-hermes | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_AGENT: hermes | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_PROVIDER: custom | |
| NEMOCLAW_ENDPOINT_URL: https://inference-api.nvidia.com/v1 | |
| NEMOCLAW_MODEL: nvidia/nvidia/nemotron-3-ultra | |
| NEMOCLAW_COMPAT_MODEL: nvidia/nvidia/nemotron-3-ultra | |
| NEMOCLAW_PREFERRED_API: openai-completions | |
| NEMOCLAW_SANDBOX_NAME: e2e-rebuild-hermes | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Add swap for Hermes image rebuild | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| sudo fallocate -l 32G /mnt/nemoclaw-hermes-rebuild.swap | |
| sudo chmod 0600 /mnt/nemoclaw-hermes-rebuild.swap | |
| sudo mkswap /mnt/nemoclaw-hermes-rebuild.swap | |
| sudo swapon /mnt/nemoclaw-hermes-rebuild.swap | |
| swapon --show | |
| - name: Initialize runner comparison telemetry | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts initialize | |
| - name: Install OpenShell | |
| # Full NemoClaw installer coverage remains in hermes-e2e. Rebuild uses | |
| # the checked-out CLI and installs only its external runtime dependency. | |
| 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 -u GH_TOKEN bash scripts/install-openshell.sh | |
| - name: Run Hermes rebuild live test | |
| # Preserves the checked-out CLI, Docker/OpenShell, Hermes base-image | |
| # rebuild, registry, messaging-placeholder, and backup hygiene boundaries. | |
| 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 | |
| mkdir -p "$E2E_ARTIFACT_DIR" | |
| baseline_file="$E2E_ARTIFACT_DIR/runner-pressure-baseline.jsonl" | |
| phase_baselines_file="$E2E_ARTIFACT_DIR/runner-pressure-phase-baselines.jsonl" | |
| classification_file="$E2E_ARTIFACT_DIR/runner-pressure-classification.jsonl" | |
| test_outcome_file="$E2E_ARTIFACT_DIR/live-test-outcome.json" | |
| export DOCKER_OOM_CONTAINER="$NEMOCLAW_SANDBOX_NAME" | |
| export E2E_PHASE="rebuild-hermes.workflow" | |
| export E2E_RESOURCE_BASELINE_FILE="$baseline_file" | |
| export E2E_RESOURCE_PHASE_BASELINES_FILE="$phase_baselines_file" | |
| export E2E_TERMINAL_CLASSIFICATION_FILE="$classification_file" | |
| export E2E_TEST_OUTCOME_FILE="$test_outcome_file" | |
| npx tsx tools/e2e/runner-pressure.mts snapshot | |
| npx tsx tools/e2e/runner-pressure.mts initialize-evidence | |
| set +e | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/rebuild-hermes.test.ts | |
| test_status="$?" | |
| set -e | |
| if [ "$test_status" -ne 0 ]; then | |
| npx tsx tools/e2e/runner-pressure.mts classify | |
| npx tsx tools/e2e/runner-pressure.mts validate-classification | |
| fi | |
| exit "$test_status" | |
| - name: Finalize runner comparison telemetry | |
| if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts finalize | |
| - name: Upload Hermes 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 | |
| rebuild-hermes-stale-base: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',rebuild-hermes-stale-base,') || contains(format(',{0},', inputs.targets), ',rebuild-hermes-stale-base,') }} | |
| runs-on: ${{ fromJSON(needs.generate-matrix.outputs.runner_routing)['rebuild-hermes-stale-base'] }} | |
| timeout-minutes: 90 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "rebuild-hermes-stale-base" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/rebuild-hermes-stale-base | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_AGENT: hermes | |
| NEMOCLAW_HERMES_STALE_BASE_REBUILD_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_PROVIDER: custom | |
| NEMOCLAW_ENDPOINT_URL: https://inference-api.nvidia.com/v1 | |
| NEMOCLAW_MODEL: nvidia/nvidia/nemotron-3-ultra | |
| NEMOCLAW_COMPAT_MODEL: nvidia/nvidia/nemotron-3-ultra | |
| NEMOCLAW_PREFERRED_API: openai-completions | |
| NEMOCLAW_SANDBOX_NAME: e2e-rebuild-hermes-base | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Add swap for Hermes image rebuild | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| sudo fallocate -l 32G /mnt/nemoclaw-hermes-rebuild.swap | |
| sudo chmod 0600 /mnt/nemoclaw-hermes-rebuild.swap | |
| sudo mkswap /mnt/nemoclaw-hermes-rebuild.swap | |
| sudo swapon /mnt/nemoclaw-hermes-rebuild.swap | |
| swapon --show | |
| - name: Initialize runner comparison telemetry | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts initialize | |
| - name: Install OpenShell | |
| # Full NemoClaw installer coverage remains in hermes-e2e. Rebuild uses | |
| # the checked-out CLI and installs only its external runtime dependency. | |
| 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 -u GH_TOKEN bash scripts/install-openshell.sh | |
| - name: Run Hermes stale-base rebuild live test | |
| # Uses NEMOCLAW_HERMES_STALE_BASE_REBUILD_E2E=1, preserving issue #3025's | |
| # stale cached base-image regression boundary. | |
| 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 | |
| mkdir -p "$E2E_ARTIFACT_DIR" | |
| baseline_file="$E2E_ARTIFACT_DIR/runner-pressure-baseline.jsonl" | |
| phase_baselines_file="$E2E_ARTIFACT_DIR/runner-pressure-phase-baselines.jsonl" | |
| classification_file="$E2E_ARTIFACT_DIR/runner-pressure-classification.jsonl" | |
| test_outcome_file="$E2E_ARTIFACT_DIR/live-test-outcome.json" | |
| export DOCKER_OOM_CONTAINER="$NEMOCLAW_SANDBOX_NAME" | |
| export E2E_PHASE="rebuild-hermes-stale-base.workflow" | |
| export E2E_RESOURCE_BASELINE_FILE="$baseline_file" | |
| export E2E_RESOURCE_PHASE_BASELINES_FILE="$phase_baselines_file" | |
| export E2E_TERMINAL_CLASSIFICATION_FILE="$classification_file" | |
| export E2E_TEST_OUTCOME_FILE="$test_outcome_file" | |
| npx tsx tools/e2e/runner-pressure.mts snapshot | |
| npx tsx tools/e2e/runner-pressure.mts initialize-evidence | |
| set +e | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/rebuild-hermes.test.ts | |
| test_status="$?" | |
| set -e | |
| if [ "$test_status" -ne 0 ]; then | |
| npx tsx tools/e2e/runner-pressure.mts classify | |
| npx tsx tools/e2e/runner-pressure.mts validate-classification | |
| fi | |
| exit "$test_status" | |
| - name: Finalize runner comparison telemetry | |
| if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts finalize | |
| - name: Upload Hermes stale-base 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 | |
| overlayfs-autofix: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',overlayfs-autofix,') || contains(format(',{0},', inputs.targets), ',overlayfs-autofix,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 90 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "overlayfs-autofix" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/overlayfs-autofix | |
| 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-overlayfs | |
| NEMOCLAW_E2E_TIMEOUT_SECONDS: "1500" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Run overlayfs autofix live test | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/overlayfs-autofix.test.ts | |
| - name: Upload overlayfs autofix 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 | |
| state-backup-restore: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',state-backup-restore,') || contains(format(',{0},', inputs.targets), ',state-backup-restore,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "state-backup-restore" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/state-backup-restore | |
| 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-state-backup | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell | |
| 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 state backup restore live test | |
| 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/state-backup-restore.test.ts | |
| - name: Upload state backup restore 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,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 90 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "double-onboard" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/double-onboard | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| run: bash scripts/install-openshell.sh | |
| - name: Run double-onboard live Vitest test | |
| 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 | |
| echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" | |
| "$OPENSHELL_BIN" --version | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/double-onboard.test.ts | |
| - name: Upload double-onboard Vitest 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 | |
| jetson-nvmap-gpu: | |
| needs: generate-matrix | |
| # Explicit-only until a stable Jetson runner is available; otherwise full-suite dispatches remain queued forever. | |
| # Required validation path: dispatch with jobs=jetson-nvmap-gpu or targets=jetson-nvmap-gpu. | |
| # Re-enable default dispatch only after a stable Jetson runner exists, then remove E2E_DEFAULT_ENABLED. | |
| if: ${{ contains(format(',{0},', inputs.jobs), ',jetson-nvmap-gpu,') || contains(format(',{0},', inputs.targets), ',jetson-nvmap-gpu,') }} | |
| # GitHub does not apply timeout-minutes until after runner assignment. Keep | |
| # unconfirmed manual Jetson dispatches on a hosted runner so they fail fast | |
| # instead of queueing forever on an offline self-hosted runner label. | |
| runs-on: ${{ inputs.allow_jetson_runner_queue && (vars.JETSON_E2E_RUNNER_LABEL || 'linux-arm64-gpu-jetson-orin-latest-1') || 'ubuntu-latest' }} | |
| timeout-minutes: 60 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_DEFAULT_ENABLED: "0" | |
| E2E_TARGET_ID: "jetson-nvmap-gpu" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/jetson-nvmap-gpu | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_SANDBOX_NAME: "e2e-jetson-nvmap" | |
| NEMOCLAW_RECREATE_SANDBOX: "1" | |
| NEMOCLAW_PROVIDER: "ollama" | |
| 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 | |
| - name: Guard Jetson runner dispatch | |
| if: ${{ !inputs.allow_jetson_runner_queue }} | |
| env: | |
| JETSON_E2E_RUNNER_LABEL: ${{ vars.JETSON_E2E_RUNNER_LABEL || 'linux-arm64-gpu-jetson-orin-latest-1' }} | |
| run: | | |
| set -euo pipefail | |
| echo "::error::jetson-nvmap-gpu was selected without allow_jetson_runner_queue=true." | |
| echo "::error::GitHub Actions does not apply timeout-minutes while a self-hosted job is queued before runner assignment." | |
| echo "::error::A repository administrator must confirm the runner is online in the authoritative NVIDIA/NemoClaw Settings -> Actions -> Runners inventory before enabling queueing." | |
| echo "::error::Confirm an online runner with label ${JETSON_E2E_RUNNER_LABEL}, then re-run with allow_jetson_runner_queue=true." | |
| exit 1 | |
| - *dockerhub-auth | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Verify Jetson GPU availability | |
| run: | | |
| set -euo pipefail | |
| echo "=== Tegra release ===" | |
| cat /etc/nv_tegra_release 2>/dev/null || echo "(no /etc/nv_tegra_release)" | |
| echo "" | |
| echo "=== /dev/nvmap ===" | |
| ls -l /dev/nvmap 2>/dev/null || echo "(no /dev/nvmap)" | |
| echo "" | |
| echo "=== Docker ===" | |
| docker info --format '{{.ServerVersion}}' | |
| docker info --format '{{json .Runtimes}}' | |
| - name: Run Jetson nvmap GPU live Vitest test | |
| # Keeps the | |
| # Jetson/Tegra host, NVIDIA Docker runtime, install.sh, OpenShell | |
| # sandbox exec, /dev/nvmap, CUDA cuInit(0), and status-proof boundary. | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/jetson-nvmap-gpu.test.ts | |
| - name: Upload Jetson nvmap GPU 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 | |
| concurrent-gateway-ports: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',concurrent-gateway-ports,') || contains(format(',{0},', inputs.targets), ',concurrent-gateway-ports,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 90 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "concurrent-gateway-ports" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/concurrent-gateway-ports | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| run: bash scripts/install-openshell.sh | |
| - name: Run concurrent gateway ports live Vitest test | |
| 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 | |
| echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" | |
| "$OPENSHELL_BIN" --version | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/concurrent-gateway-ports.test.ts | |
| - name: Upload concurrent gateway ports Vitest 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 | |
| onboard-resume: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',onboard-resume,') || contains(format(',{0},', inputs.targets), ',onboard-resume,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "onboard-resume" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/onboard-resume | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_SANDBOX_NAME: "e2e-resume" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| run: env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_INFERENCE_API_KEY -u COMPATIBLE_API_KEY -u GITHUB_TOKEN bash scripts/install-openshell.sh | |
| - name: Run onboard-resume live Vitest test | |
| 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" | |
| exit 1 | |
| fi | |
| export OPENSHELL_BIN | |
| "$OPENSHELL_BIN" --version | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/onboard-resume.test.ts | |
| - name: Upload onboard-resume 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 | |
| full-e2e: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',full-e2e,') || contains(format(',{0},', inputs.targets), ',full-e2e,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 75 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "full-e2e" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/full-e2e | |
| 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-full" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| run: bash scripts/install-openshell.sh | |
| - name: Run full-e2e live Vitest test | |
| 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" | |
| exit 1 | |
| fi | |
| export OPENSHELL_BIN | |
| "$OPENSHELL_BIN" --version | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/full-e2e.test.ts | |
| - name: Upload full-e2e 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 | |
| security-posture: | |
| permissions: | |
| contents: read | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',security-posture,') || contains(format(',{0},', inputs.targets), ',security-posture,') }} | |
| runs-on: ${{ fromJSON(needs.generate-matrix.outputs.runner_routing)[format('security-posture-{0}', matrix.agent)] }} | |
| timeout-minutes: 75 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - agent: openclaw | |
| sandbox_name: e2e-openclaw-security-posture | |
| test_file: test/e2e/live/full-e2e.test.ts | |
| - agent: hermes | |
| sandbox_name: e2e-hermes-security-posture | |
| test_file: test/e2e/live/hermes-e2e.test.ts | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "security-posture" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/security-posture-${{ matrix.agent }} | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_AGENT: ${{ matrix.agent }} | |
| # Legacy-parity contract: enforce a non-root host user. PID 1 must stay | |
| # root long enough to step down child processes, so its uid, bounding | |
| # set, and NoNewPrivs remain evidence unless an opt-in expectation is set. | |
| NEMOCLAW_E2E_EXPECT_NON_ROOT_HOST: "1" | |
| NEMOCLAW_E2E_SECURITY_POSTURE: "1" | |
| NEMOCLAW_E2E_SHARD: ${{ matrix.agent }} | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ONBOARD_VALIDATION_TIMEOUT_SECONDS: "60" | |
| NEMOCLAW_RECREATE_SANDBOX: "1" | |
| NEMOCLAW_SANDBOX_NAME: ${{ matrix.sandbox_name }} | |
| OPENSHELL_GATEWAY: nemoclaw | |
| steps: | |
| - id: trusted_hermes_swap | |
| name: Provision trusted Hermes E2E swap | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && matrix.agent == 'hermes' }} | |
| shell: /bin/bash --noprofile --norc -e -o pipefail {0} | |
| env: | |
| BASH_ENV: /dev/null | |
| CHECKOUT_SHA: ${{ inputs.checkout_sha }} | |
| DISPATCH_SHA: ${{ github.sha }} | |
| ENV: /dev/null | |
| EVENT_NAME: ${{ github.event_name }} | |
| EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }} | |
| LC_ALL: C | |
| REF: ${{ github.ref }} | |
| REPOSITORY: ${{ github.repository }} | |
| RUNNER_ARCH_KIND: ${{ runner.arch }} | |
| RUNNER_ENVIRONMENT_KIND: ${{ runner.environment }} | |
| RUNNER_OS_KIND: ${{ runner.os }} | |
| WORKFLOW_SHA: ${{ github.workflow_sha }} | |
| run: *trusted-hermes-e2e-swap | |
| - 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| with: | |
| build-cli: "false" | |
| - name: Initialize runner comparison telemetry | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' && matrix.agent == 'hermes' }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts initialize | |
| - name: Install OpenShell CLI | |
| run: 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 security posture live Vitest test | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH" | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path "${{ matrix.test_file }}" | |
| - name: Finalize runner comparison telemetry | |
| if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' && matrix.agent == 'hermes' }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts finalize | |
| - name: Upload security posture artifacts | |
| if: always() | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: e2e-security-posture-${{ matrix.agent }} | |
| path: e2e-artifacts/live/security-posture-${{ matrix.agent }}/ | |
| - name: Clean up Docker auth | |
| if: always() | |
| shell: bash | |
| run: bash .github/scripts/docker-auth-cleanup.sh | |
| cloud-onboard: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',cloud-onboard,') || contains(format(',{0},', inputs.targets), ',cloud-onboard,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 70 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "cloud-onboard" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/cloud-onboard | |
| 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-cloud-onboard" | |
| NEMOCLAW_PUBLIC_INSTALL_REF: "${{ inputs.checkout_sha || github.sha }}" | |
| 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 | |
| - name: Configure cloud-onboard trace directory | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| printf 'NEMOCLAW_TRACE_DIR=%s\n' "${RUNNER_TEMP}/nemoclaw-cloud-onboard-traces" >> "${GITHUB_ENV}" | |
| # invalidState: the cloud-onboard DCode TUI check requires a PTY driver, | |
| # but the fixed GitHub-hosted runner image does not provide expect. | |
| # sourceBoundary: privileged host setup runs from the first-party | |
| # host-dependency-setup action pinned to an immutable full SHA, never the | |
| # repository target ref; the check only consumes expect without privilege. | |
| # whyNotSourceFix: GitHub-hosted jobs cannot use a repository-owned host | |
| # image, and caching privileged dpkg state between clean runners is not | |
| # supported. | |
| # regressionTest: workflow-boundary tests pin the ordering, action | |
| # provenance, and package mapping. | |
| # removalCondition: remove when the hosted runner supplies expect or the | |
| # cloud-onboard acceptance check no longer requires a PTY. | |
| - name: Install cloud-onboard DCode TUI host dependencies | |
| uses: NVIDIA/NemoClaw/.github/actions/host-dependency-setup@4def1501b34ce586f83b91af50a66b5d22b31d75 | |
| with: | |
| packages: expect | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| run: bash scripts/install-openshell.sh | |
| - name: Run cloud-onboard live Vitest test | |
| 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" | |
| exit 1 | |
| fi | |
| export OPENSHELL_BIN | |
| "$OPENSHELL_BIN" --version | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/cloud-onboard.test.ts | |
| - name: Build trusted cloud-onboard timing summary | |
| if: always() | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| expected_trace_dir="${RUNNER_TEMP}/nemoclaw-cloud-onboard-traces" | |
| if [ -z "${RUNNER_TEMP}" ] || [ "${NEMOCLAW_TRACE_DIR}" != "${expected_trace_dir}" ]; then | |
| echo "::error::Refusing to sanitize unexpected raw trace path" >&2 | |
| exit 1 | |
| fi | |
| python3 scripts/e2e/sanitize-trace-timing.py \ | |
| "${NEMOCLAW_TRACE_DIR}" \ | |
| "${E2E_ARTIFACT_DIR}" | |
| # The target process must emit full local traces for diagnosis, but those | |
| # traces may contain prompts, environment data, and credential material. | |
| # Keep cleanup as a separate always() step so a sanitizer failure cannot | |
| # bypass it. A runner-level termination is contained by the ephemeral | |
| # GitHub-hosted runner.temp boundary. Remove this step only when the trace | |
| # producer itself emits the allowlisted timing-only schema. | |
| - name: Delete raw cloud-onboard traces | |
| if: always() | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| expected_trace_dir="${RUNNER_TEMP}/nemoclaw-cloud-onboard-traces" | |
| if [ -z "${RUNNER_TEMP}" ] || [ "${NEMOCLAW_TRACE_DIR}" != "${expected_trace_dir}" ]; then | |
| echo "::error::Refusing to delete unexpected raw trace path" >&2 | |
| exit 1 | |
| fi | |
| rm -rf -- "${NEMOCLAW_TRACE_DIR}" | |
| - name: Upload cloud-onboard 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 | |
| gpu-double-onboard: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',gpu-double-onboard,') || contains(format(',{0},', inputs.targets), ',gpu-double-onboard,') }} | |
| runs-on: linux-amd64-gpu-rtxpro6000-latest-1 | |
| timeout-minutes: 100 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "gpu-double-onboard" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/gpu-double-onboard | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_MODEL: "qwen3.5:9b" | |
| NEMOCLAW_SANDBOX_NAME: "e2e-gpu-double-onboard" | |
| NEMOCLAW_PROVIDER: "ollama" | |
| NEMOCLAW_OLLAMA_PROXY_PORT: "11435" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| run: bash scripts/install-openshell.sh | |
| - name: Run gpu-double-onboard live Vitest test | |
| 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" | |
| exit 1 | |
| fi | |
| export OPENSHELL_BIN | |
| "$OPENSHELL_BIN" --version | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/gpu-double-onboard.test.ts | |
| - name: Upload gpu-double-onboard 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 | |
| onboard-repair: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',onboard-repair,') || contains(format(',{0},', inputs.targets), ',onboard-repair,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 75 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "onboard-repair" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/onboard-repair | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_SANDBOX_NAME: "e2e-repair" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| run: bash scripts/install-openshell.sh | |
| - name: Run onboard-repair live Vitest test | |
| 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" | |
| exit 1 | |
| fi | |
| export OPENSHELL_BIN | |
| "$OPENSHELL_BIN" --version | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/onboard-repair.test.ts | |
| - name: Upload onboard-repair 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 | |
| issue-4462-scope-upgrade-approval: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',issue-4462-scope-upgrade-approval,') || contains(format(',{0},', inputs.targets), ',issue-4462-scope-upgrade-approval,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 90 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "issue-4462-scope-upgrade-approval" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/issue-4462-scope-upgrade-approval | |
| 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-issue-4462" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| run: bash scripts/install-openshell.sh | |
| - name: Run issue-4462-scope-upgrade-approval live Vitest test | |
| 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" | |
| exit 1 | |
| fi | |
| export OPENSHELL_BIN | |
| "$OPENSHELL_BIN" --version | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/issue-4462-scope-upgrade-approval.test.ts | |
| - name: Upload issue-4462-scope-upgrade-approval 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 | |
| token-rotation: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',token-rotation,') || contains(format(',{0},', inputs.targets), ',token-rotation,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "token-rotation" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/token-rotation | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Run token rotation live test | |
| # Preserve the original runner class: ubuntu-latest with Docker/OpenShell | |
| # plus the fake OpenAI-compatible endpoint path and fake | |
| # Telegram/Discord/Slack token boundary. | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| TELEGRAM_BOT_TOKEN_A: "test-fake-token-A-rotation-e2e" | |
| TELEGRAM_BOT_TOKEN_B: "test-fake-token-B-rotation-e2e" | |
| DISCORD_BOT_TOKEN_A: "dc-a-rotation-e2e" | |
| DISCORD_BOT_TOKEN_B: "dc-b-rotation-e2e" | |
| SLACK_BOT_TOKEN_A: "xoxb-fake-A-rotation-e2e" | |
| SLACK_BOT_TOKEN_B: "xoxb-fake-B-rotation-e2e" | |
| SLACK_APP_TOKEN_A: "xapp-fake-A-rotation-e2e" | |
| SLACK_APP_TOKEN_B: "xapp-fake-B-rotation-e2e" | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/token-rotation.test.ts | |
| - name: Upload token rotation 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 | |
| messaging-compatible-endpoint: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',messaging-compatible-endpoint,') || contains(format(',{0},', inputs.targets), ',messaging-compatible-endpoint,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "messaging-compatible-endpoint" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/messaging-compatible-endpoint | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_SANDBOX_NAME: "e2e-msg-compat" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Run messaging compatible endpoint live test | |
| # Preserves the fake OpenAI-compatible endpoint, Telegram messaging | |
| # config, inference.local, OpenClaw agent-turn, and proxy hop-header | |
| # strip boundaries without relying on real messaging/provider secrets. | |
| env: | |
| NEMOCLAW_COMPAT_MOCK_API_KEY: "fake-compatible-key-e2e" | |
| TELEGRAM_ALLOWED_IDS: "123456789" | |
| TELEGRAM_BOT_TOKEN: "test-fake-telegram-token-e2e" | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/messaging-compatible-endpoint.test.ts | |
| - name: Upload messaging compatible endpoint 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 | |
| openshell-gateway-upgrade: | |
| name: OpenShell gateway upgrade (${{ matrix.id }}) | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',openshell-gateway-upgrade,') || contains(format(',{0},', inputs.targets), ',openshell-gateway-upgrade,') }} | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 70 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - id: v0.0.36-x86_64 | |
| runner: ubuntu-latest | |
| shard: v0-0-36-x86-64 | |
| nemoclaw_ref: v0.0.36 | |
| nemoclaw_commit: "3351fbdd4eb7d9b80ec471545083956327da2b10" | |
| installer_sha256: "0c42400a0d3867739f1d75d612e069967be4506e169974bbbebf14b7af39144f" | |
| sandbox_base_image_ref: "ghcr.io/nvidia/nemoclaw/sandbox-base@sha256:104151ffadc2ff0b6c815e3c95c2783ced61aee0d0f83fc327cc02be9b7e14e6" | |
| openshell_version: 0.0.36 | |
| openclaw_version: 2026.4.24 | |
| - id: v0.0.55-x86_64 | |
| runner: ubuntu-latest | |
| shard: v0-0-55-x86-64 | |
| nemoclaw_ref: v0.0.55 | |
| nemoclaw_commit: "95d483fe2b6569d68e59493c60f19df09a068e8f" | |
| installer_sha256: "ff8cf448e4d17b00421545a1f333262b615b1b0aa236d0cc5aeaf4e2cae2d897" | |
| sandbox_base_image_ref: "ghcr.io/nvidia/nemoclaw/sandbox-base@sha256:10433a8cd2f2b809dd0fdf983514679e04c0f8aa1ff5bbff675029046033b108" | |
| openshell_version: 0.0.44 | |
| openclaw_version: 2026.5.22 | |
| - id: v0.0.55-aarch64 | |
| runner: ubuntu-24.04-arm | |
| shard: v0-0-55-aarch64 | |
| nemoclaw_ref: v0.0.55 | |
| nemoclaw_commit: "95d483fe2b6569d68e59493c60f19df09a068e8f" | |
| installer_sha256: "ff8cf448e4d17b00421545a1f333262b615b1b0aa236d0cc5aeaf4e2cae2d897" | |
| sandbox_base_image_ref: "ghcr.io/nvidia/nemoclaw/sandbox-base@sha256:10433a8cd2f2b809dd0fdf983514679e04c0f8aa1ff5bbff675029046033b108" | |
| openshell_version: 0.0.44 | |
| openclaw_version: 2026.5.22 | |
| - id: v0.0.74-x86_64 | |
| runner: ubuntu-latest | |
| shard: v0-0-74-x86-64 | |
| nemoclaw_ref: v0.0.74 | |
| nemoclaw_commit: "3a05b54e8ec3e1d5550ec5c728de54af872bffe3" | |
| installer_sha256: "a0cd3feca488d247e53d59d7d8246d2b86e75e95acb5e7d78504b3c0c60fd7db" | |
| sandbox_base_image_ref: "ghcr.io/nvidia/nemoclaw/sandbox-base@sha256:104151ffadc2ff0b6c815e3c95c2783ced61aee0d0f83fc327cc02be9b7e14e6" | |
| openshell_version: 0.0.72 | |
| openclaw_version: 2026.5.27 | |
| - id: v0.0.89-x86_64 | |
| runner: ubuntu-latest | |
| shard: v0-0-89-x86-64 | |
| nemoclaw_ref: v0.0.89 | |
| nemoclaw_commit: "1143aa5cce77f3bad1b3b5588bd7fddbe438237e" | |
| installer_sha256: "00f24959e5ca68104fe91221c0a015dab6a4154618497fa36b969b661f418cc2" | |
| sandbox_base_image_ref: "ghcr.io/nvidia/nemoclaw/sandbox-base@sha256:3265d482f67c9d81ee3a59b0bbad5eb5ea6c705fea81ece8ae888ed12794f7f1" | |
| openshell_version: 0.0.85 | |
| openclaw_version: 2026.6.10 | |
| current_openclaw_version: 2026.7.1 | |
| openclaw_state_upgrade: "1" | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "openshell-gateway-upgrade" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openshell-gateway-upgrade | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_E2E_SHARD: ${{ matrix.shard }} | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_GATEWAY_UPGRADE_SURVIVOR_NAME: "e2e-gateway-upgrade-survivor" | |
| NEMOCLAW_OLD_NEMOCLAW_REF: ${{ matrix.nemoclaw_ref }} | |
| NEMOCLAW_OLD_NEMOCLAW_COMMIT: ${{ matrix.nemoclaw_commit }} | |
| NEMOCLAW_OLD_INSTALLER_SHA256: ${{ matrix.installer_sha256 }} | |
| NEMOCLAW_OLD_SANDBOX_BASE_IMAGE_REF: ${{ matrix.sandbox_base_image_ref }} | |
| NEMOCLAW_OLD_OPENSHELL_VERSION: ${{ matrix.openshell_version }} | |
| NEMOCLAW_OLD_OPENCLAW_VERSION: ${{ matrix.openclaw_version }} | |
| NEMOCLAW_CURRENT_OPENCLAW_VERSION: ${{ matrix.current_openclaw_version }} | |
| NEMOCLAW_OPENCLAW_STATE_UPGRADE_PROOF: ${{ matrix.openclaw_state_upgrade }} | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Run OpenShell gateway upgrade live Vitest test | |
| # Keep the original v0.0.36 fixture on x86_64 and validate the exact | |
| # v0.0.55/OpenShell 0.0.44 regression shape on x86_64 and arm64, plus | |
| # the immediate v0.0.74/OpenShell 0.0.72 predecessor to this bump. The | |
| # v0.0.89 row proves OpenClaw 2026.6.10 state survives the 2026.7.1 | |
| # migration without crossing the OpenShell secret boundary. | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH" | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openshell-gateway-upgrade.test.ts | |
| - name: Upload OpenShell gateway upgrade artifacts | |
| if: always() | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: e2e-openshell-gateway-upgrade-${{ matrix.id }} | |
| - name: Clean up Docker auth | |
| if: always() | |
| shell: bash | |
| run: bash .github/scripts/docker-auth-cleanup.sh | |
| messaging-providers: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',messaging-providers,') || contains(format(',{0},', inputs.targets), ',messaging-providers,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 90 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "messaging-providers" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/messaging-providers | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Run messaging providers live Vitest test | |
| # The test keeps | |
| # the legacy fake-token defaults, optional _REAL secret overrides, | |
| # provider placeholder redaction checks, and WhatsApp QR-only parity. | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| TELEGRAM_BOT_TOKEN_REAL: ${{ secrets.TELEGRAM_BOT_TOKEN_REAL }} | |
| DISCORD_BOT_TOKEN_REAL: ${{ secrets.DISCORD_BOT_TOKEN_REAL }} | |
| SLACK_BOT_TOKEN_REAL: ${{ secrets.SLACK_BOT_TOKEN_REAL }} | |
| SLACK_APP_TOKEN_REAL: ${{ secrets.SLACK_APP_TOKEN_REAL }} | |
| TELEGRAM_ALLOWED_IDS: ${{ secrets.TELEGRAM_ALLOWED_IDS }} | |
| TELEGRAM_AUTHORIZED_CHAT_IDS: ${{ secrets.TELEGRAM_AUTHORIZED_CHAT_IDS }} | |
| TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} | |
| TELEGRAM_CHAT_ID_E2E: ${{ secrets.TELEGRAM_CHAT_ID_E2E }} | |
| DISCORD_CHANNEL_ID_E2E: ${{ secrets.DISCORD_CHANNEL_ID_E2E }} | |
| SLACK_CHANNEL_ID_E2E: ${{ secrets.SLACK_CHANNEL_ID_E2E }} | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/messaging-providers.test.ts | |
| - name: Upload messaging providers 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 | |
| bootstrap-install-smoke: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',bootstrap-install-smoke,') || contains(format(',{0},', inputs.targets), ',bootstrap-install-smoke,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "bootstrap-install-smoke" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/bootstrap-install-smoke | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_SANDBOX_NAME: "e2e-bootstrap" | |
| NEMOCLAW_RECREATE_SANDBOX: "1" | |
| NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1" | |
| NEMOCLAW_PROVIDER: custom | |
| NEMOCLAW_ENDPOINT_URL: https://inference-api.nvidia.com/v1 | |
| NEMOCLAW_MODEL: nvidia/nvidia/nemotron-3-ultra | |
| NEMOCLAW_COMPAT_MODEL: nvidia/nvidia/nemotron-3-ultra | |
| NEMOCLAW_PREFERRED_API: openai-completions | |
| SKIP_DOCKER_PULL: "1" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| with: | |
| build-cli: "false" | |
| - name: Run bootstrap install smoke live test | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| COMPATIBLE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/bootstrap-install-smoke.test.ts | |
| - name: Upload bootstrap install smoke artifacts | |
| if: always() | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| # Provider-routed Model Router inference contract. | |
| - name: Clean up Docker auth | |
| if: always() | |
| shell: bash | |
| run: bash .github/scripts/docker-auth-cleanup.sh | |
| model-router-provider-routed-inference: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',model-router-provider-routed-inference,') || contains(format(',{0},', inputs.targets), ',model-router-provider-routed-inference,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "model-router-provider-routed-inference" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/model-router-provider-routed-inference | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Run Model Router provider-routed inference live test | |
| # Preserves the real provider-routed onboard, host model-router health, and | |
| # sandbox inference.local completion boundaries without adding registry | |
| # or migration-ledger wiring. | |
| env: | |
| NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/model-router-provider-routed-inference.test.ts | |
| - name: Upload Model Router provider-routed inference 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 | |
| snapshot-commands: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',snapshot-commands,') || contains(format(',{0},', inputs.targets), ',snapshot-commands,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 40 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "snapshot-commands" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/snapshot-commands | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_SANDBOX_NAME: "e2e-snapshot" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| with: | |
| build-cli: "false" | |
| - name: Run snapshot commands live test | |
| # Drives install.sh and the real snapshot CLI lifecycle while the test | |
| # supplies a sandbox-reachable fake OpenAI-compatible endpoint. | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/snapshot-commands.test.ts | |
| - name: Upload snapshot commands 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-operations: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',sandbox-operations,') || contains(format(',{0},', inputs.jobs), ',sandbox-rlimits-connect,') || contains(format(',{0},', inputs.targets), ',sandbox-operations,') || contains(format(',{0},', inputs.targets), ',sandbox-rlimits-connect,') }} | |
| runs-on: ubuntu-latest | |
| # The live test receives 45 minutes for two onboards plus process/gateway | |
| # recovery. The remaining 15 minutes cover checkout, build, OpenShell setup, | |
| # artifact upload, and unconditional credential/resource cleanup. | |
| timeout-minutes: 60 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "sandbox-operations" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/sandbox-operations | |
| 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" | |
| # Open permits the scenario's inference/log probes; TC-SBX-11 separately | |
| # proves that sandbox-to-sandbox network isolation remains enforced. | |
| NEMOCLAW_POLICY_TIER: "open" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Verify CLI launcher | |
| run: | | |
| test -x "${NEMOCLAW_CLI_BIN}" | |
| "${NEMOCLAW_CLI_BIN}" --version | |
| - name: Install OpenShell CLI | |
| run: | | |
| 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 operations live test | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH" | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/sandbox-operations.test.ts | |
| - name: Upload sandbox operations 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-survival: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',sandbox-survival,') || contains(format(',{0},', inputs.targets), ',sandbox-survival,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "sandbox-survival" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/sandbox-survival | |
| 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-survival" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Run sandbox survival live test | |
| # This intentionally | |
| # runs install.sh from Vitest to preserve installer/OpenShell/onboard | |
| # fidelity before exercising gateway restart, state survival, and live | |
| # inference.local before and after restart. | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/sandbox-survival.test.ts | |
| - name: Upload sandbox survival 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 | |
| # Scheduled release-baseline coverage for #6108. This runs independently of | |
| # the current lifecycle lane so the exact v0.0.71 build does not extend that | |
| # lane's critical path. | |
| openclaw-plugin-runtime-exdev-release: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',openclaw-plugin-runtime-exdev-release,') || contains(format(',{0},', inputs.targets), ',openclaw-plugin-runtime-exdev-release,') }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| timeout-minutes: 55 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "openclaw-plugin-runtime-exdev-release" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openclaw-plugin-runtime-exdev-release | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_SANDBOX_NAME: "e2e-openclaw-plugin-exdev-release" | |
| 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 | |
| - name: Pre-pull release-matched Docker Hub builder image | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| docker pull node:22-trixie-slim@sha256:2d9f5c76c8f4dd36e8f253bee5d828a83a6c09f36188f0b0414325232e0b175d | |
| - name: Remove Docker auth before release-pinned fixture | |
| if: always() | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| bash .github/scripts/docker-auth-cleanup.sh | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Run OpenClaw custom-plugin release baseline live test | |
| run: | | |
| set -euo pipefail | |
| test -n "${DOCKER_CONFIG:-}" | |
| test ! -e "${DOCKER_CONFIG}" | |
| test -z "${DOCKERHUB_USERNAME:-}" | |
| test -z "${DOCKERHUB_TOKEN:-}" | |
| env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN \ | |
| npx tsx tools/e2e/live-vitest-invocation.mts run \ | |
| --test-path test/e2e/live/openclaw-plugin-runtime-exdev.test.ts \ | |
| --selector release-baseline | |
| - name: Upload OpenClaw plugin release baseline 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 | |
| # Scheduled current-lifecycle coverage for #6108 / #3513 / #3127. This | |
| # proves restart/rebuild persistence and target-side runtime-dependency | |
| # replacement across devices without waiting for the release baseline. | |
| openclaw-plugin-runtime-exdev: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',openclaw-plugin-runtime-exdev,') || contains(format(',{0},', inputs.targets), ',openclaw-plugin-runtime-exdev,') }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| # Two bounded 25-minute onboards plus the 20-minute rebuild and 15-minute | |
| # Vitest buffer need 85 minutes; allow 20 more for setup and teardown. | |
| timeout-minutes: 105 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "openclaw-plugin-runtime-exdev" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openclaw-plugin-runtime-exdev | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_SANDBOX_NAME: "e2e-openclaw-plugin-exdev" | |
| 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 | |
| - name: Pre-pull release-matched Docker Hub builder image | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| docker pull node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba | |
| - name: Remove Docker auth before release-pinned fixture | |
| if: always() | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| bash .github/scripts/docker-auth-cleanup.sh | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Run OpenClaw custom-plugin lifecycle and runtime-deps EXDEV live test | |
| run: | | |
| set -euo pipefail | |
| test -n "${DOCKER_CONFIG:-}" | |
| test ! -e "${DOCKER_CONFIG}" | |
| test -z "${DOCKERHUB_USERNAME:-}" | |
| test -z "${DOCKERHUB_TOKEN:-}" | |
| env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN \ | |
| npx tsx tools/e2e/live-vitest-invocation.mts run \ | |
| --test-path test/e2e/live/openclaw-plugin-runtime-exdev.test.ts \ | |
| --selector current-lifecycle | |
| - name: Upload OpenClaw plugin runtime-deps EXDEV 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 | |
| # The #2603/#3145 OpenClaw websocket protocol/history contract. | |
| openclaw-tui-chat-correlation: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',openclaw-tui-chat-correlation,') || contains(format(',{0},', inputs.targets), ',openclaw-tui-chat-correlation,') }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| timeout-minutes: 75 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "openclaw-tui-chat-correlation" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openclaw-tui-chat-correlation | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| # This target runs the CLI built from this checkout; it does not use the public installer. | |
| # Legacy target_ref dispatches were replaced by branch github.sha and validated checkout_sha runs. | |
| NEMOCLAW_TUI_EXPECTED_CHECKOUT_SHA: "${{ inputs.checkout_sha || github.sha }}" | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1" | |
| NEMOCLAW_PROVIDER: custom | |
| NEMOCLAW_ENDPOINT_URL: https://inference-api.nvidia.com/v1 | |
| NEMOCLAW_MODEL: nvidia/nvidia/nemotron-3-ultra | |
| NEMOCLAW_COMPAT_MODEL: nvidia/nvidia/nemotron-3-ultra | |
| NEMOCLAW_PREFERRED_API: openai-completions | |
| 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 | |
| # The #6194 terminal regression uses Expect as its PTY driver. Privileged | |
| # host setup runs from the first-party host-dependency-setup action pinned | |
| # to an immutable full SHA: the selected target ref may consume expect but | |
| # cannot expand the package allowlist or run sudo with its own code. | |
| # invalidState: hosted-runner Ubuntu mirrors can fail transiently during update. | |
| # sourceBoundary: only the pinned trusted action chooses the exact root-installed package. | |
| # whyNotSourceFix: GitHub's Ubuntu image and configured repository move together, so a | |
| # fixed package version would make the target brittle across routine runner refreshes. | |
| # The runner's configured Ubuntu repository is therefore an accepted trust source. | |
| # regressionTest: e2e-host-dependency-workflow-boundary rejects package or provenance drift. | |
| # removalCondition: remove this step when the hosted image provides Expect itself. | |
| - name: Install OpenClaw TUI host dependencies | |
| uses: NVIDIA/NemoClaw/.github/actions/host-dependency-setup@4def1501b34ce586f83b91af50a66b5d22b31d75 | |
| with: | |
| packages: expect | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Run OpenClaw TUI chat correlation live test | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| COMPATIBLE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openclaw-tui-chat-correlation.test.ts | |
| - name: Upload OpenClaw TUI chat correlation artifacts | |
| if: always() | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| # ── Free-standing recovery targets (#2701) ───────────────────────── | |
| # Recovery / disruption targets don't fit the steady-state expected-state | |
| # registry that drives `live` above. They run as free-standing | |
| # Vitest test files using the same `e2e-live` project, framework | |
| # fixtures, and live-project gate — just outside the matrix. | |
| # | |
| # First failing-test-first guard for #2701 (gateway recovery does not | |
| # restore the /tmp guard chain after pod recreate). Will fail on `main` | |
| # until the #2701 fix lands; flips green afterwards. | |
| - name: Clean up Docker auth | |
| if: always() | |
| shell: bash | |
| run: bash .github/scripts/docker-auth-cleanup.sh | |
| gateway-guard-recovery: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',gateway-guard-recovery,') || contains(format(',{0},', inputs.targets), ',gateway-guard-recovery,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "gateway-guard-recovery" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/gateway-guard-recovery | |
| 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 onboard registers the gateway under the canonical name | |
| # "nemoclaw" (src/lib/actions/sandbox/connect.ts:NEMOCLAW_GATEWAY_NAME) | |
| # but does not call `openshell gateway select` to mark it active. The | |
| # SandboxClient and recovery probes invoke `openshell sandbox exec` | |
| # directly, which fails with "No active gateway" when no active | |
| # gateway is configured. Setting OPENSHELL_GATEWAY here tells openshell | |
| # to use the named gateway for every invocation (per `openshell` -h | |
| # GATEWAY FLAGS: env: OPENSHELL_GATEWAY=). | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| # This target invokes `bin/nemoclaw.js onboard` directly, so install | |
| # OpenShell explicitly just as install.sh would. | |
| run: bash scripts/install-openshell.sh | |
| - name: Run Vitest gateway-guard-recovery target | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| # OpenShell installs to /usr/local/bin on GitHub-hosted runners | |
| # (writable by the runner user, no sudo) or to ~/.local/bin in | |
| # NEMOCLAW_NON_INTERACTIVE mode when /usr/local/bin is not writable. | |
| # See scripts/install-openshell.sh:394-425. Cover both paths. | |
| export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH" | |
| # Resolve the actual install path so the framework's SandboxClient | |
| # can spawn it without relying on PATH inheritance from the test | |
| # process (the framework also accepts OPENSHELL_BIN as an override). | |
| 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 | |
| echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" | |
| "$OPENSHELL_BIN" --version | |
| npx vitest run \ | |
| --project e2e-live \ | |
| test/e2e/live/gateway-guard-recovery.test.ts \ | |
| --reporter=default --reporter=test/e2e/risk-signal-reporter.ts --silent=false | |
| - name: Upload 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 | |
| openclaw-inference-switch: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',openclaw-inference-switch,') || contains(format(',{0},', inputs.targets), ',openclaw-inference-switch,') }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| timeout-minutes: 90 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - mode: anthropic | |
| sandbox_name: e2e-openclaw-anthropic-inference-switch | |
| switch_provider: compatible-anthropic-endpoint | |
| switch_model: mock-anthropic-model | |
| switch_inference_api: anthropic-messages | |
| switch_mock_anthropic: "1" | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "openclaw-inference-switch" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openclaw-inference-switch/${{ matrix.mode }} | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_AGENT: "openclaw" | |
| NEMOCLAW_E2E_SHARD: ${{ matrix.mode }} | |
| NEMOCLAW_SANDBOX_NAME: ${{ matrix.sandbox_name }} | |
| NEMOCLAW_SWITCH_PROVIDER: ${{ matrix.switch_provider }} | |
| NEMOCLAW_SWITCH_MODEL: ${{ matrix.switch_model }} | |
| NEMOCLAW_SWITCH_INFERENCE_API: ${{ matrix.switch_inference_api }} | |
| NEMOCLAW_SWITCH_MOCK_ANTHROPIC: ${{ matrix.switch_mock_anthropic }} | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Run OpenClaw inference switch live test | |
| # Preserves the real install.sh/OpenShell/Docker/managed-proxy boundary while | |
| # Vitest owns the route, config, registry, inference.local, and agent | |
| # assertions. | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openclaw-inference-switch.test.ts | |
| - name: Upload OpenClaw inference switch artifacts | |
| if: always() | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: e2e-openclaw-inference-switch-${{ matrix.mode }} | |
| path: e2e-artifacts/live/openclaw-inference-switch/${{ matrix.mode }}/ | |
| - name: Clean up Docker auth | |
| if: always() | |
| shell: bash | |
| run: bash .github/scripts/docker-auth-cleanup.sh | |
| # Bedrock Runtime compatible Anthropic endpoint contract. | |
| bedrock-runtime-compatible-anthropic: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',bedrock-runtime-compatible-anthropic,') || contains(format(',{0},', inputs.targets), ',bedrock-runtime-compatible-anthropic,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| agent: [openclaw, hermes] | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "bedrock-runtime-compatible-anthropic" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/bedrock-runtime-compatible-anthropic/${{ matrix.agent }} | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_RECREATE_SANDBOX: "1" | |
| NEMOCLAW_AGENT: ${{ matrix.agent }} | |
| NEMOCLAW_E2E_SHARD: ${{ matrix.agent }} | |
| NEMOCLAW_SANDBOX_NAME: e2e-bedrock-${{ matrix.agent }} | |
| OPENSHELL_GATEWAY: "nemoclaw" | |
| steps: | |
| - id: trusted_hermes_swap | |
| name: Provision trusted Hermes E2E swap | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && matrix.agent == 'hermes' }} | |
| shell: /bin/bash --noprofile --norc -e -o pipefail {0} | |
| env: | |
| BASH_ENV: /dev/null | |
| CHECKOUT_SHA: ${{ inputs.checkout_sha }} | |
| DISPATCH_SHA: ${{ github.sha }} | |
| ENV: /dev/null | |
| EVENT_NAME: ${{ github.event_name }} | |
| EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }} | |
| LC_ALL: C | |
| REF: ${{ github.ref }} | |
| REPOSITORY: ${{ github.repository }} | |
| RUNNER_ARCH_KIND: ${{ runner.arch }} | |
| RUNNER_ENVIRONMENT_KIND: ${{ runner.environment }} | |
| RUNNER_OS_KIND: ${{ runner.os }} | |
| WORKFLOW_SHA: ${{ github.workflow_sha }} | |
| run: *trusted-hermes-e2e-swap | |
| - 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Run Bedrock Runtime compatible Anthropic live test | |
| # Preserves the fake Bedrock Runtime endpoint, /etc/hosts mapping, | |
| # source CLI onboard, OpenShell adapter route, agent runtime probes, | |
| # and leak-scan contract. | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/bedrock-runtime-compatible-anthropic.test.ts | |
| - name: Upload Bedrock Runtime compatible Anthropic artifacts | |
| if: always() | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: e2e-bedrock-runtime-compatible-anthropic-${{ matrix.agent }} | |
| path: e2e-artifacts/live/bedrock-runtime-compatible-anthropic/${{ matrix.agent }}/ | |
| - name: Clean up Docker auth | |
| if: always() | |
| shell: bash | |
| run: bash .github/scripts/docker-auth-cleanup.sh | |
| device-auth-health: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',device-auth-health,') || contains(format(',{0},', inputs.targets), ',device-auth-health,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 40 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "device-auth-health" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/device-auth-health | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_SANDBOX_NAME: "e2e-health-auth" | |
| NEMOCLAW_DASHBOARD_PORT: "18789" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| run: bash scripts/install-openshell.sh | |
| - name: Run device auth health live Vitest test | |
| 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/device-auth-health.test.ts | |
| - name: Upload device auth health 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 | |
| channels-add-remove: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',channels-add-remove,') || contains(format(',{0},', inputs.targets), ',channels-add-remove,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 75 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "channels-add-remove" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/channels-add-remove | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_SANDBOX_NAME: "e2e-channels-add-remove" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell | |
| 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 channels add/remove live test | |
| # Preserves the | |
| # real OpenClaw + Docker/OpenShell boundary for onboard-empty, | |
| # channels add, rebuild, gateway credential reuse, policy-list, and | |
| # channels remove cleanup; the test owns its authenticated local | |
| # compatible-inference baseline. | |
| env: | |
| TELEGRAM_BOT_TOKEN: "test-fake-telegram-token-add-remove-e2e" | |
| TELEGRAM_ALLOWED_IDS: "123456789" | |
| TELEGRAM_REQUIRE_MENTION: "0" | |
| 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/channels-add-remove.test.ts | |
| - name: Upload channels add/remove 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 | |
| telegram-injection: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',telegram-injection,') || contains(format(',{0},', inputs.targets), ',telegram-injection,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "telegram-injection" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/telegram-injection | |
| 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-telegram-injection" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell | |
| 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 Telegram injection live test | |
| # Preserves the | |
| # real OpenShell sandbox boundary for shell metacharacter payloads, | |
| # process-table leak checks, and validateName rejection probes. | |
| 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/telegram-injection.test.ts | |
| - name: Upload Telegram injection 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 | |
| channels-stop-start: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',channels-stop-start,') || contains(format(',{0},', inputs.targets), ',channels-stop-start,') }} | |
| runs-on: ${{ fromJSON(needs.generate-matrix.outputs.runner_routing)[format('channels-stop-start-{0}', matrix.agent)] }} | |
| timeout-minutes: 90 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| agent: [openclaw, hermes] | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "channels-stop-start" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/channels-stop-start/${{ matrix.agent }} | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1" | |
| NEMOCLAW_E2E_SHARD: ${{ matrix.agent }} | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_AGENT: ${{ matrix.agent }} | |
| NEMOCLAW_CHANNELS_STOP_START_AGENT: ${{ matrix.agent }} | |
| NEMOCLAW_SANDBOX_NAME: e2e-channels-stop-start-${{ matrix.agent }} | |
| OPENSHELL_GATEWAY: "nemoclaw" | |
| steps: | |
| - id: trusted_hermes_swap | |
| name: Provision trusted Hermes E2E swap | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && matrix.agent == 'hermes' }} | |
| shell: /bin/bash --noprofile --norc -e -o pipefail {0} | |
| env: | |
| BASH_ENV: /dev/null | |
| CHECKOUT_SHA: ${{ inputs.checkout_sha }} | |
| DISPATCH_SHA: ${{ github.sha }} | |
| ENV: /dev/null | |
| EVENT_NAME: ${{ github.event_name }} | |
| EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }} | |
| LC_ALL: C | |
| REF: ${{ github.ref }} | |
| REPOSITORY: ${{ github.repository }} | |
| RUNNER_ARCH_KIND: ${{ runner.arch }} | |
| RUNNER_ENVIRONMENT_KIND: ${{ runner.environment }} | |
| RUNNER_OS_KIND: ${{ runner.os }} | |
| WORKFLOW_SHA: ${{ github.workflow_sha }} | |
| run: *trusted-hermes-e2e-swap | |
| - 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Initialize runner comparison telemetry | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' && matrix.agent == 'hermes' }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts initialize | |
| - name: Install OpenShell | |
| 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 channels stop/start live test | |
| # Preserves the | |
| # OpenClaw/Hermes messaging channel stop/start, rebuild, provider | |
| # reuse, registry, policy-list, and in-sandbox config contracts. | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| TELEGRAM_BOT_TOKEN: test-fake-telegram-token-stop-start-${{ matrix.agent }} | |
| DISCORD_BOT_TOKEN: test-fake-discord-token-stop-start-${{ matrix.agent }} | |
| SLACK_BOT_TOKEN: xoxb-fake-slack-token-stop-start-${{ matrix.agent }} | |
| SLACK_APP_TOKEN: xapp-fake-slack-token-stop-start-${{ matrix.agent }} | |
| WECHAT_BOT_TOKEN: test-fake-wechat-token-stop-start-${{ matrix.agent }} | |
| 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/channels-stop-start.test.ts | |
| - name: Finalize runner comparison telemetry | |
| if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' && matrix.agent == 'hermes' }} | |
| continue-on-error: true | |
| shell: bash | |
| run: npx tsx tools/e2e/runner-comparison.mts finalize | |
| - name: Upload channels stop/start artifacts | |
| if: always() | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: e2e-channels-stop-start-${{ matrix.agent }} | |
| path: e2e-artifacts/live/channels-stop-start/${{ matrix.agent }}/ | |
| - name: Clean up Docker auth | |
| if: always() | |
| shell: bash | |
| run: bash .github/scripts/docker-auth-cleanup.sh | |
| openclaw-slack-pairing: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',openclaw-slack-pairing,') || contains(format(',{0},', inputs.targets), ',openclaw-slack-pairing,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "openclaw-slack-pairing" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openclaw-slack-pairing | |
| 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-openclaw-slack-pairing" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| 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 OpenClaw Slack pairing live test | |
| # Preserves | |
| # fake Slack Socket Mode/REST token rewrite and connect-shell approval. | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| SLACK_BOT_TOKEN: xoxb-fake-slack-pairing-e2e | |
| SLACK_APP_TOKEN: xapp-fake-slack-pairing-e2e | |
| 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 | |
| echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" | |
| "$OPENSHELL_BIN" --version | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openclaw-slack-pairing.test.ts | |
| - name: Upload OpenClaw Slack pairing 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 | |
| issue-2478-crash-loop-recovery: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',issue-2478-crash-loop-recovery,') || contains(format(',{0},', inputs.targets), ',issue-2478-crash-loop-recovery,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "issue-2478-crash-loop-recovery" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/issue-2478-crash-loop-recovery | |
| NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js | |
| NEMOCLAW_RUN_LIVE_E2E: "1" | |
| NEMOCLAW_NON_INTERACTIVE: "1" | |
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" | |
| NEMOCLAW_SANDBOX_NAME: "e2e-2478" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| run: bash scripts/install-openshell.sh | |
| - name: "Run issue #2478 crash-loop recovery live Vitest test" | |
| 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 | |
| echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" | |
| "$OPENSHELL_BIN" --version | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/issue-2478-crash-loop-recovery.test.ts | |
| - name: "Upload issue #2478 crash-loop recovery 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 | |
| openclaw-discord-pairing: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',openclaw-discord-pairing,') || contains(format(',{0},', inputs.targets), ',openclaw-discord-pairing,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "openclaw-discord-pairing" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openclaw-discord-pairing | |
| 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-openclaw-discord-pairing" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install OpenShell CLI | |
| 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 OpenClaw Discord pairing live test | |
| # Preserves | |
| # fake Discord Gateway token rewrite and connect-shell pairing approval. | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| DISCORD_BOT_TOKEN: test-fake-discord-pairing-e2e | |
| 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 | |
| echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" | |
| "$OPENSHELL_BIN" --version | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openclaw-discord-pairing.test.ts | |
| - name: Upload OpenClaw Discord pairing 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 | |
| tunnel-lifecycle: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',tunnel-lifecycle,') || contains(format(',{0},', inputs.targets), ',tunnel-lifecycle,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 75 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "tunnel-lifecycle" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/tunnel-lifecycle | |
| 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-tunnel-lifecycle" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| - name: Install and verify cloudflared prerequisite | |
| env: | |
| CLOUDFLARED_VERSION: "2026.6.1" | |
| CLOUDFLARED_DEB_SHA256: "ccd02ec216c62bfa573395d8f72cb2e91e95cbdf8726a8acc06b3e2d9aa31526" | |
| run: | | |
| set -euo pipefail | |
| if command -v cloudflared >/dev/null 2>&1; then | |
| if cloudflared --version | grep -F "cloudflared version ${CLOUDFLARED_VERSION}" >/dev/null; then | |
| cloudflared --version | |
| exit 0 | |
| fi | |
| fi | |
| cloudflared_deb="${RUNNER_TEMP}/cloudflared-${CLOUDFLARED_VERSION}-linux-amd64.deb" | |
| curl -fL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64.deb" -o "${cloudflared_deb}" | |
| printf '%s %s\n' "${CLOUDFLARED_DEB_SHA256}" "${cloudflared_deb}" | sha256sum -c - | |
| package="$(dpkg-deb -f "${cloudflared_deb}" Package)" | |
| version="$(dpkg-deb -f "${cloudflared_deb}" Version)" | |
| architecture="$(dpkg-deb -f "${cloudflared_deb}" Architecture)" | |
| if [ "${package}" != "cloudflared" ] || [ "${version}" != "${CLOUDFLARED_VERSION}" ] || [ "${architecture}" != "amd64" ]; then | |
| printf 'Unexpected cloudflared package metadata: package=%s version=%s architecture=%s\n' "${package}" "${version}" "${architecture}" >&2 | |
| exit 1 | |
| fi | |
| sudo dpkg -i "${cloudflared_deb}" | |
| cloudflared --version | grep -F "cloudflared version ${CLOUDFLARED_VERSION}" | |
| - name: Run tunnel lifecycle live test | |
| # This preserves the | |
| # real Docker/OpenShell onboard, host cloudflared quick-tunnel, | |
| # local-dashboard readiness, public tunnel probe, and stop/status | |
| # cleanup boundaries under Vitest. | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/tunnel-lifecycle.test.ts | |
| - name: Upload tunnel lifecycle 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 | |
| spark-install: | |
| needs: generate-matrix | |
| if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',spark-install,') || contains(format(',{0},', inputs.targets), ',spark-install,') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| env: | |
| E2E_JOB: "1" | |
| E2E_TARGET_ID: "spark-install" | |
| E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/spark-install | |
| 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_FRESH: "1" | |
| NEMOCLAW_SANDBOX_NAME: "e2e-spark-install-ci" | |
| NEMOCLAW_PROVIDER: "cloud" | |
| 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 | |
| - name: Prepare E2E workspace | |
| uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 | |
| with: | |
| build-cli: "false" | |
| - name: Run Spark install live test | |
| # This preserves the | |
| # Linux + Docker prerequisite gate, the plain non-interactive | |
| # install.sh/public curl|bash boundary, install-log retention, PATH | |
| # refresh, and post-install nemoclaw/openshell/--help checks. | |
| env: | |
| NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/spark-install.test.ts | |
| - name: Upload Spark install 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 | |
| # ── PR result comment ───────────────────────────────────────────────────── | |
| # Posts a results table on the open PR for the dispatching branch (or the | |
| # PR identified by `inputs.pr_number`). `if: always()` so the comment lands | |
| # even when target jobs failed — that's the whole point of a result | |
| # comment. | |
| report-to-pr: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| # This entire workflow is dispatch-only. Keeping selective jobs in `needs` | |
| # makes the report wait for and record any requested job without adding | |
| # skipped checks to the normal pull_request workflow. | |
| needs: &e2e-result-jobs | |
| [ | |
| base-image-publication, | |
| generate-matrix, | |
| retired-selector-compatibility, | |
| staging-brev-launchable, | |
| live, | |
| shared-e2e, | |
| openshell-gateway-auth-contract, | |
| mcp-bridge, | |
| openshell-credential-generation-window, | |
| mcp-bridge-dev, | |
| skill-agent, | |
| openclaw-skill-cli, | |
| inference-routing, | |
| cloud-inference, | |
| gpu-e2e, | |
| agent-turn-latency, | |
| kimi-inference-compat, | |
| hermes-inference-switch, | |
| brave-search, | |
| ollama-auth-proxy, | |
| cron-preflight-inference-local, | |
| dashboard-remote-bind, | |
| sessions-agents-cli, | |
| hermes-e2e, | |
| hermes-gpu-startup, | |
| hermes-slack, | |
| hermes-discord, | |
| network-policy, | |
| common-egress-agent, | |
| shields-config, | |
| hermes-shields-config, | |
| rebuild-openclaw, | |
| rebuild-hermes, | |
| rebuild-hermes-stale-base, | |
| overlayfs-autofix, | |
| state-backup-restore, | |
| openshell-gateway-upgrade, | |
| token-rotation, | |
| messaging-compatible-endpoint, | |
| messaging-providers, | |
| bootstrap-install-smoke, | |
| double-onboard, | |
| jetson-nvmap-gpu, | |
| concurrent-gateway-ports, | |
| full-e2e, | |
| security-posture, | |
| cloud-onboard, | |
| gpu-double-onboard, | |
| onboard-repair, | |
| issue-4462-scope-upgrade-approval, | |
| onboard-resume, | |
| model-router-provider-routed-inference, | |
| sandbox-operations, | |
| sandbox-survival, | |
| snapshot-commands, | |
| openclaw-plugin-runtime-exdev-release, | |
| openclaw-plugin-runtime-exdev, | |
| openclaw-tui-chat-correlation, | |
| gateway-guard-recovery, | |
| issue-4434-tui-unreachable-inference, | |
| openclaw-inference-switch, | |
| bedrock-runtime-compatible-anthropic, | |
| issue-2478-crash-loop-recovery, | |
| device-auth-health, | |
| channels-add-remove, | |
| tunnel-lifecycle, | |
| telegram-injection, | |
| openclaw-discord-pairing, | |
| openclaw-slack-pairing, | |
| channels-stop-start, | |
| spark-install, | |
| ] | |
| if: ${{ always() && github.event_name == 'workflow_dispatch' && inputs.checkout_sha == '' }} | |
| permissions: | |
| actions: read | |
| contents: read | |
| # The issue-comment endpoint accepts pull request write permission for PR comments. | |
| # Keep issues: write absent so this job cannot restore general issue routing. | |
| pull-requests: write | |
| steps: | |
| - name: Check out the trusted E2E reporting helper | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: ${{ github.workflow_sha }} | |
| persist-credentials: false | |
| sparse-checkout: | | |
| tools/e2e/report-e2e-results.mts | |
| tools/e2e/selector-aliases.mts | |
| sparse-checkout-cone-mode: false | |
| - name: Post E2E target results to PR | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| EXPLICIT_ONLY_JOBS: ${{ needs.generate-matrix.outputs.explicit_only_jobs }} | |
| TEST_MATRIX: ${{ needs.generate-matrix.outputs.test_matrix }} | |
| JOB_PR_NUMBER: ${{ inputs.pr_number }} | |
| JOB_TARGETS: ${{ inputs.targets }} | |
| JOBS: ${{ inputs.jobs }} | |
| NEEDS_JSON: ${{ toJSON(needs) }} | |
| with: | |
| script: | | |
| const path = require('node:path'); | |
| const { pathToFileURL } = require('node:url'); | |
| const { resolveReportPr, loadReportJobs, renderE2eReport } = await import( | |
| pathToFileURL(path.join(process.env.GITHUB_WORKSPACE, 'tools/e2e/report-e2e-results.mts')).href | |
| ); | |
| const needs = JSON.parse(process.env.NEEDS_JSON || '{}'); | |
| const prNumber = await resolveReportPr({ github, context, core, env: process.env }); | |
| if (prNumber === undefined) return; | |
| const { apiJobs, loaded } = await loadReportJobs({ github, context, core }); | |
| const report = renderE2eReport({ | |
| needs, | |
| env: process.env, | |
| apiJobs, | |
| apiJobsLoaded: loaded, | |
| context, | |
| }); | |
| if (report.fatal) { | |
| core.setFailed(report.fatal); | |
| return; | |
| } | |
| for (const warning of report.warnings) core.warning(warning); | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body: report.body, | |
| }); | |
| # ── Scheduled/manual scorecard ──────────────────────────────────────────── | |
| scorecard: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| needs: *e2e-result-jobs | |
| if: ${{ always() && (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.checkout_sha == '')) }} | |
| permissions: | |
| actions: read | |
| contents: read | |
| steps: | |
| - name: Checkout scorecard builders | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| repository: ${{ inputs.checkout_repository || github.repository }} | |
| ref: ${{ inputs.checkout_sha || github.sha }} | |
| persist-credentials: false | |
| sparse-checkout: | | |
| ci/onboard-performance-budget.json | |
| scripts/audit-test-runtime.mts | |
| scripts/scorecard | |
| sparse-checkout-cone-mode: false | |
| - name: Download E2E progress artifacts | |
| continue-on-error: true | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| path: ${{ runner.temp }}/e2e-runtime-audit | |
| pattern: e2e-* | |
| - name: Generate E2E scorecard | |
| id: scorecard | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| EXPLICIT_ONLY_JOBS: ${{ needs.generate-matrix.outputs.explicit_only_jobs }} | |
| JOBS: ${{ inputs.jobs }} | |
| RUNTIME_ARTIFACTS: ${{ runner.temp }}/e2e-runtime-audit | |
| RUNTIME_SUMMARY_FILE: ${{ runner.temp }}/e2e-runtime-summary.json | |
| TARGETS: ${{ inputs.targets }} | |
| NEEDS_JSON: ${{ toJSON(needs) }} | |
| with: | |
| script: | | |
| const path = require('path'); | |
| const { buildScorecard } = require( | |
| path.join(process.env.GITHUB_WORKSPACE, 'scripts/scorecard/coordinate-scorecard.mts'), | |
| ); | |
| const scorecardJobs = require( | |
| path.join(process.env.GITHUB_WORKSPACE, 'scripts/scorecard/summarize-jobs.mts'), | |
| ); | |
| const traceTiming = require( | |
| path.join(process.env.GITHUB_WORKSPACE, 'scripts/scorecard/analyze-trace-timing.mts'), | |
| ); | |
| const runtimeAudit = require( | |
| path.join(process.env.GITHUB_WORKSPACE, 'scripts/audit-test-runtime.mts'), | |
| ); | |
| const runtimeHistory = require( | |
| path.join(process.env.GITHUB_WORKSPACE, 'scripts/scorecard/analyze-runtime-history.mts'), | |
| ); | |
| const firstTurnLatency = require( | |
| path.join(process.env.GITHUB_WORKSPACE, 'scripts/scorecard/analyze-first-turn-latency.mts'), | |
| ); | |
| const needs = JSON.parse(process.env.NEEDS_JSON || '{}'); | |
| // GitHub's jobs API is the canonical source because `needs.live` | |
| // collapses every matrix target into one result and has no job URL. | |
| // The typed helper owns and tests the degraded `needs` fallback. | |
| const apiJobs = await scorecardJobs.loadWorkflowRunJobs({ github, context, core }); | |
| let runtimeSummaryMarkdown; | |
| let runtimeHistoryRows = null; | |
| try { | |
| const runtimeRows = runtimeAudit.auditTestRuntime([process.env.RUNTIME_ARTIFACTS]); | |
| runtimeSummaryMarkdown = runtimeAudit.formatRuntimeAuditSummary(runtimeRows); | |
| runtimeHistoryRows = runtimeAudit.collectRuntimeHistorySamples([ | |
| process.env.RUNTIME_ARTIFACTS, | |
| ]); | |
| } catch { | |
| core.warning('E2E test phase runtime summary unavailable: invalid progress artifact'); | |
| runtimeSummaryMarkdown = [ | |
| '## E2E Test Phase Runtime', | |
| '', | |
| 'The summary is unavailable because a `test-progress.json` artifact was invalid.', | |
| '', | |
| ].join('\n'); | |
| } | |
| const runtimeHistoryMarkdown = runtimeHistoryRows === null | |
| ? [ | |
| '## E2E Nightly Runtime Trend', | |
| '', | |
| 'The trend is unavailable because a `test-progress.json` artifact was invalid.', | |
| '', | |
| ].join('\n') | |
| : await runtimeHistory.buildRuntimeHistory( | |
| { github, context, core }, | |
| runtimeHistoryRows, | |
| process.env.RUNTIME_SUMMARY_FILE, | |
| { | |
| currentFirstTurnLatency: | |
| firstTurnLatency.readCurrentFirstTurnLatencySample( | |
| process.env.RUNTIME_ARTIFACTS, | |
| ), | |
| loadPriorNightlySummaries: runtimeHistory.loadPriorNightlySummaries, | |
| }, | |
| ); | |
| const trace = await traceTiming.buildTraceTimingResult({ github, context, core }); | |
| if (trace.budgetWarningMessage) core.warning(trace.budgetWarningMessage); | |
| const { summaryMarkdown, scorecardData, slackData } = buildScorecard({ | |
| eventName: context.eventName, | |
| actor: context.actor || '', | |
| serverUrl: context.serverUrl, | |
| repo: context.repo, | |
| runId: context.runId, | |
| rawJobs: process.env.JOBS || '', | |
| rawTargets: process.env.TARGETS || '', | |
| rawExplicitOnly: process.env.EXPLICIT_ONLY_JOBS || '', | |
| needs, | |
| apiJobs, | |
| trace, | |
| today: new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric' }), | |
| }); | |
| await core.summary | |
| .addRaw(`${summaryMarkdown}\n\n${runtimeSummaryMarkdown}\n${runtimeHistoryMarkdown}`) | |
| .write(); | |
| core.setOutput('scorecardData', JSON.stringify(scorecardData)); | |
| core.setOutput('slackData', JSON.stringify(slackData)); | |
| - name: Post scorecard to Slack | |
| # Webhook secrets never enter branch-dispatched runs. The payload is | |
| # computed in the preceding no-secret step; this fixed publisher does | |
| # not load code from the checked-out workflow ref. | |
| if: ${{ steps.scorecard.outputs.slackData != '' && github.ref == 'refs/heads/main' }} | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| SLACK_WEBHOOK_URL_DAILY: ${{ secrets.SLACK_WEBHOOK_URL_DAILY }} | |
| SLACK_WEBHOOK_URL_FULLRUN: ${{ secrets.SLACK_WEBHOOK_URL_FULLRUN }} | |
| SLACK_WEBHOOK_URL_PREVIEW: ${{ secrets.SLACK_WEBHOOK_URL_PREVIEW }} | |
| SLACK_DATA: ${{ steps.scorecard.outputs.slackData }} | |
| POST_TO_SLACK: ${{ inputs.post_to_slack }} | |
| with: | |
| script: | | |
| const data = JSON.parse(process.env.SLACK_DATA); | |
| const envByChannel = { | |
| daily: 'SLACK_WEBHOOK_URL_DAILY', | |
| fullrun: 'SLACK_WEBHOOK_URL_FULLRUN', | |
| preview: 'SLACK_WEBHOOK_URL_PREVIEW', | |
| }; | |
| const channel = data?.channel; | |
| const payload = data?.payload; | |
| const attachments = payload?.attachments; | |
| const validPayload = | |
| Object.hasOwn(envByChannel, channel) && | |
| payload !== null && | |
| typeof payload === 'object' && | |
| typeof payload.text === 'string' && | |
| Array.isArray(attachments) && | |
| attachments.every( | |
| (attachment) => | |
| attachment !== null && | |
| typeof attachment === 'object' && | |
| typeof attachment.color === 'string' && | |
| Array.isArray(attachment.blocks), | |
| ); | |
| if (!validPayload) { | |
| core.setFailed('Invalid precomputed Slack payload'); | |
| return; | |
| } | |
| if (channel === 'preview' && process.env.POST_TO_SLACK !== 'true') { | |
| core.info('Selective dispatch without post_to_slack — skipping'); | |
| return; | |
| } | |
| const webhookUrl = process.env[envByChannel[channel]]; | |
| if (!webhookUrl) { | |
| core.info(`Slack webhook for "${channel}" not configured — skipping`); | |
| return; | |
| } | |
| const response = await fetch(webhookUrl, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(data.payload), | |
| }); | |
| if (!response.ok) { | |
| core.setFailed(`Slack webhook returned ${response.status}`); | |
| } | |
| - name: Upload E2E runtime summary | |
| if: ${{ always() && github.event_name == 'schedule' }} | |
| uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 | |
| with: | |
| name: e2e-runtime-summary | |
| path: ${{ runner.temp }}/e2e-runtime-summary.json |