From 01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 Mon Sep 17 00:00:00 2001 From: Charan Jagwani Date: Thu, 30 Jul 2026 13:19:57 -0700 Subject: [PATCH 01/11] ci(e2e): add exact-commit CLI restore action Signed-off-by: Charan Jagwani --- .../restore-e2e-cli-artifact/action.yaml | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 .github/actions/restore-e2e-cli-artifact/action.yaml diff --git a/.github/actions/restore-e2e-cli-artifact/action.yaml b/.github/actions/restore-e2e-cli-artifact/action.yaml new file mode 100644 index 00000000000..8d99dc0f24f --- /dev/null +++ b/.github/actions/restore-e2e-cli-artifact/action.yaml @@ -0,0 +1,163 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: restore-e2e-cli-artifact +description: Verify and restore the exact-commit CLI artifact for an E2E job. + +inputs: + provenance-json: + description: Exact producer artifact, candidate, and workflow provenance. + required: true + +runs: + using: composite + steps: + - name: Validate exact-commit CLI artifact identity + id: identity + env: + CALLER_WORKFLOW_SHA: ${{ github.workflow_sha }} + PROVENANCE_JSON: ${{ inputs.provenance-json }} + shell: bash + run: | + set -euo pipefail + jq -e ' + type == "object" and + (keys | sort) == [ + "artifactDigest", + "artifactId", + "artifactName", + "candidateRepository", + "candidateSha", + "kind", + "payloadSha256", + "runAttempt", + "runId", + "workflowSha" + ] and + .kind == "nemoclaw-e2e-cli-provenance-v1" and + (.artifactId | strings | test("^[1-9][0-9]*$")) and + (.artifactDigest | strings | test("^[a-f0-9]{64}$")) and + (.artifactName | strings) and + (.candidateRepository | strings | test("^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$")) and + (.candidateSha | strings | test("^[a-f0-9]{40}$")) and + (.payloadSha256 | strings | test("^[a-f0-9]{64}$")) and + (.workflowSha | strings | test("^[a-f0-9]{40}$")) and + (.runId | strings | test("^[1-9][0-9]*$")) and + (.runAttempt | strings | test("^[1-9][0-9]*$")) and + .artifactName == ("nemoclaw-cli-" + .candidateSha + "-" + .payloadSha256) + ' <<<"$PROVENANCE_JSON" >/dev/null || + { echo "::error::producer CLI artifact provenance is invalid"; exit 1; } + + artifact_id="$(jq -r '.artifactId' <<<"$PROVENANCE_JSON")" + candidate_repository="$(jq -r '.candidateRepository' <<<"$PROVENANCE_JSON")" + candidate_sha="$(jq -r '.candidateSha' <<<"$PROVENANCE_JSON")" + run_attempt="$(jq -r '.runAttempt' <<<"$PROVENANCE_JSON")" + run_id="$(jq -r '.runId' <<<"$PROVENANCE_JSON")" + workflow_sha="$(jq -r '.workflowSha' <<<"$PROVENANCE_JSON")" + [[ "$(git rev-parse --verify HEAD)" == "$candidate_sha" ]] || + { echo "::error::consumer checkout does not match the producer candidate SHA"; exit 1; } + [[ "$workflow_sha" == "$CALLER_WORKFLOW_SHA" ]] || + { echo "::error::consumer and producer workflow SHAs differ"; exit 1; } + [[ "$run_id" == "$GITHUB_RUN_ID" && "$run_attempt" == "$GITHUB_RUN_ATTEMPT" ]] || + { echo "::error::consumer and producer workflow run identities differ"; exit 1; } + + remote_url="$(git remote get-url origin)" + case "$remote_url" in + https://github.com/*) remote_repository="${remote_url#https://github.com/}" ;; + git@github.com:*) remote_repository="${remote_url#git@github.com:}" ;; + *) echo "::error::consumer checkout repository URL is invalid"; exit 1 ;; + esac + remote_repository="${remote_repository%.git}" + [[ "$remote_repository" == "$candidate_repository" ]] || + { echo "::error::consumer checkout repository does not match producer provenance"; exit 1; } + + jq -r ' + "artifact_digest=" + .artifactDigest, + "artifact_id=" + .artifactId, + "artifact_name=" + .artifactName, + "candidate_repository=" + .candidateRepository, + "candidate_sha=" + .candidateSha, + "payload_sha256=" + .payloadSha256, + "run_attempt=" + .runAttempt, + "run_id=" + .runId, + "workflow_sha=" + .workflowSha + ' <<<"$PROVENANCE_JSON" >>"$GITHUB_OUTPUT" + + - name: Download exact-commit CLI artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + artifact-ids: ${{ steps.identity.outputs.artifact_id }} + path: ${{ runner.temp }}/nemoclaw-cli-artifact + digest-mismatch: error + + - name: Verify and restore exact-commit CLI artifact + env: + ARTIFACT_NAME: ${{ steps.identity.outputs.artifact_name }} + CANDIDATE_REPOSITORY: ${{ steps.identity.outputs.candidate_repository }} + CANDIDATE_SHA: ${{ steps.identity.outputs.candidate_sha }} + PAYLOAD_SHA256: ${{ steps.identity.outputs.payload_sha256 }} + RUN_ATTEMPT: ${{ steps.identity.outputs.run_attempt }} + RUN_ID: ${{ steps.identity.outputs.run_id }} + WORKFLOW_SHA: ${{ steps.identity.outputs.workflow_sha }} + shell: bash + run: | + set -euo pipefail + artifact_dir="${RUNNER_TEMP}/nemoclaw-cli-artifact" + manifest="$artifact_dir/manifest.json" + payload="$artifact_dir/nemoclaw-cli.tar" + test -s "$manifest" && test -s "$payload" || + { echo "::error::exact-commit CLI artifact is incomplete"; exit 1; } + [[ "$(node --version)" =~ ^v22\.[0-9]+\.[0-9]+$ ]] || + { echo "::error::consumer must restore the CLI under the pinned Node 22 toolchain"; exit 1; } + source_tree="$(git rev-parse 'HEAD^{tree}')" + lockfile_sha256="$(sha256sum package-lock.json | awk '{print $1}')" + jq -e \ + --arg artifactName "$ARTIFACT_NAME" \ + --arg candidateRepository "$CANDIDATE_REPOSITORY" \ + --arg candidateSha "$CANDIDATE_SHA" \ + --arg lockfileSha256 "$lockfile_sha256" \ + --arg payloadSha256 "$PAYLOAD_SHA256" \ + --arg runAttempt "$RUN_ATTEMPT" \ + --arg runId "$RUN_ID" \ + --arg sourceTree "$source_tree" \ + --arg workflowSha "$WORKFLOW_SHA" \ + ' + .kind == "nemoclaw-e2e-cli-artifact-v1" and + .artifactName == $artifactName and + .candidate.repository == $candidateRepository and + .candidate.sha == $candidateSha and + .candidate.sourceTree == $sourceTree and + .candidate.lockfileSha256 == $lockfileSha256 and + .workflow.sha == $workflowSha and + .workflow.runId == $runId and + .workflow.runAttempt == $runAttempt and + (.toolchain.node | strings | test("^v22\\.[0-9]+\\.[0-9]+$")) and + (.toolchain.npm | strings | test("^[0-9]+\\.[0-9]+\\.[0-9]+$")) and + .toolchain.runnerOs == "Linux" and + .toolchain.runnerArch == "X64" and + .build.command == "npm run build:cli" and + .payload.file == "nemoclaw-cli.tar" and + .payload.sha256 == $payloadSha256 + ' "$manifest" >/dev/null || + { echo "::error::exact-commit CLI artifact provenance mismatch"; exit 1; } + actual_payload_sha256="$(sha256sum "$payload" | awk '{print $1}')" + [[ "$actual_payload_sha256" == "$PAYLOAD_SHA256" ]] || + { echo "::error::exact-commit CLI artifact payload digest mismatch"; exit 1; } + while IFS= read -r member; do + case "$member" in + dist | dist/*) ;; + *) echo "::error::CLI artifact contains an unsafe member: $member"; exit 1 ;; + esac + case "/$member/" in + *"/../"* | *"/./"*) echo "::error::CLI artifact contains traversal: $member"; exit 1 ;; + esac + done < <(tar -tf "$payload") + tar -tvf "$payload" | + awk 'substr($1, 1, 1) != "-" && substr($1, 1, 1) != "d" { exit 1 }' || + { echo "::error::CLI artifact contains a link or special file"; exit 1; } + [[ ! -e dist ]] || + { echo "::error::consumer unexpectedly built dist before artifact restore"; exit 1; } + tar --no-same-owner --no-same-permissions -xf "$payload" -C "$GITHUB_WORKSPACE" + test -s dist/nemoclaw.js || + { echo "::error::restored CLI artifact is missing dist/nemoclaw.js"; exit 1; } + node bin/nemoclaw.js --version >/dev/null From 3d3c33a26edb03ae1f7ab577dc7e97653812b7c5 Mon Sep 17 00:00:00 2001 From: Charan Jagwani Date: Thu, 30 Jul 2026 13:36:53 -0700 Subject: [PATCH 02/11] ci(e2e): reuse exact-commit CLI artifact Signed-off-by: Charan Jagwani --- .github/workflows/e2e.yaml | 609 +++++++++++++++++- test/e2e/README.md | 55 ++ .../cli-artifact-workflow-boundary.test.ts | 163 +++++ .../prepare-e2e-workflow-boundary.test.ts | 24 +- .../rebuild-hermes-workflow-boundary.test.ts | 12 +- ...unner-comparison-workflow-boundary.test.ts | 2 +- tools/e2e/cli-artifact-workflow-boundary.mts | 351 ++++++++++ .../hermes-gpu-startup-workflow-boundary.mts | 5 +- tools/e2e/prepare-e2e-workflow-boundary.mts | 11 +- .../runner-comparison-workflow-boundary.mts | 14 +- ...upload-e2e-artifacts-workflow-boundary.mts | 10 +- tools/e2e/workflow-boundary.mts | 18 +- 12 files changed, 1244 insertions(+), 30 deletions(-) create mode 100644 test/e2e/support/cli-artifact-workflow-boundary.test.ts create mode 100644 tools/e2e/cli-artifact-workflow-boundary.mts diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 770c9f12dd8..fc9a5a57b97 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -157,6 +157,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 outputs: + cli_artifact_provenance: ${{ steps.record_cli_artifact.outputs.provenance }} matrix: ${{ steps.controller_matrix.outputs.matrix || steps.matrix.outputs.matrix }} test_matrix: ${{ steps.matrix.outputs.test_matrix }} hermes_selected: ${{ steps.matrix.outputs.hermes_selected }} @@ -369,8 +370,159 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + + - id: package_cli_artifact + name: Package exact-commit CLI + env: + CANDIDATE_REPOSITORY: ${{ inputs.checkout_repository || github.repository }} + CANDIDATE_SHA: ${{ inputs.checkout_sha || github.sha }} + RUN_ATTEMPT: ${{ github.run_attempt }} + RUN_ID: ${{ github.run_id }} + WORKFLOW_SHA: ${{ github.workflow_sha }} + shell: bash + run: | + set -euo pipefail + [[ "$CANDIDATE_REPOSITORY" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || + { echo "::error::candidate repository is invalid"; exit 1; } + [[ "$CANDIDATE_SHA" =~ ^[a-f0-9]{40}$ ]] || + { echo "::error::candidate SHA must be a lowercase 40-character SHA"; exit 1; } + [[ "$WORKFLOW_SHA" =~ ^[a-f0-9]{40}$ ]] || + { echo "::error::workflow SHA must be a lowercase 40-character SHA"; exit 1; } + [[ "$(git rev-parse --verify HEAD)" == "$CANDIDATE_SHA" ]] || + { echo "::error::checked-out commit does not match the artifact candidate SHA"; exit 1; } + [[ "$RUN_ATTEMPT" =~ ^[1-9][0-9]*$ && "$RUN_ID" =~ ^[1-9][0-9]*$ ]] || + { echo "::error::workflow run identity is invalid"; exit 1; } + test -s dist/nemoclaw.js || + { echo "::error::authoritative CLI build did not produce dist/nemoclaw.js"; exit 1; } + + artifact_dir="${RUNNER_TEMP}/nemoclaw-cli-artifact" + install -d -m 0700 "$artifact_dir" + payload="$artifact_dir/nemoclaw-cli.tar" + manifest="$artifact_dir/manifest.json" + tar \ + --sort=name \ + --mtime=@0 \ + --owner=0 \ + --group=0 \ + --numeric-owner \ + -cf "$payload" \ + dist + payload_sha256="$(sha256sum "$payload" | awk '{print $1}')" + source_tree="$(git rev-parse 'HEAD^{tree}')" + lockfile_sha256="$(sha256sum package-lock.json | awk '{print $1}')" + node_version="$(node --version)" + npm_version="$(npm --version)" + artifact_name="nemoclaw-cli-${CANDIDATE_SHA}-${payload_sha256}" + + jq -n \ + --arg artifactName "$artifact_name" \ + --arg buildCommand "npm run build:cli" \ + --arg candidateRepository "$CANDIDATE_REPOSITORY" \ + --arg candidateSha "$CANDIDATE_SHA" \ + --arg lockfileSha256 "$lockfile_sha256" \ + --arg nodeVersion "$node_version" \ + --arg npmVersion "$npm_version" \ + --arg payloadFile "nemoclaw-cli.tar" \ + --arg payloadSha256 "$payload_sha256" \ + --arg runAttempt "$RUN_ATTEMPT" \ + --arg runId "$RUN_ID" \ + --arg runnerArch "$RUNNER_ARCH" \ + --arg runnerOs "$RUNNER_OS" \ + --arg sourceTree "$source_tree" \ + --arg workflowSha "$WORKFLOW_SHA" \ + '{ + kind: "nemoclaw-e2e-cli-artifact-v1", + artifactName: $artifactName, + candidate: { + repository: $candidateRepository, + sha: $candidateSha, + sourceTree: $sourceTree, + lockfileSha256: $lockfileSha256 + }, + workflow: { + sha: $workflowSha, + runId: $runId, + runAttempt: $runAttempt + }, + toolchain: { + node: $nodeVersion, + npm: $npmVersion, + runnerOs: $runnerOs, + runnerArch: $runnerArch + }, + build: { + command: $buildCommand + }, + payload: { + file: $payloadFile, + sha256: $payloadSha256 + } + }' >"$manifest" + chmod 0600 "$manifest" "$payload" + printf 'artifact_name=%s\n' "$artifact_name" >>"$GITHUB_OUTPUT" + printf 'candidate_sha=%s\n' "$CANDIDATE_SHA" >>"$GITHUB_OUTPUT" + printf 'payload_sha256=%s\n' "$payload_sha256" >>"$GITHUB_OUTPUT" + + - id: upload_cli_artifact + name: Publish content-addressed CLI artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - build-cli: "false" + name: ${{ steps.package_cli_artifact.outputs.artifact_name }} + path: ${{ runner.temp }}/nemoclaw-cli-artifact/ + if-no-files-found: error + retention-days: 3 + compression-level: 0 + + - id: record_cli_artifact + name: Record CLI artifact provenance + env: + ARTIFACT_DIGEST: ${{ steps.upload_cli_artifact.outputs.artifact-digest }} + ARTIFACT_ID: ${{ steps.upload_cli_artifact.outputs.artifact-id }} + ARTIFACT_NAME: ${{ steps.package_cli_artifact.outputs.artifact_name }} + CANDIDATE_REPOSITORY: ${{ inputs.checkout_repository || github.repository }} + CANDIDATE_SHA: ${{ steps.package_cli_artifact.outputs.candidate_sha }} + PAYLOAD_SHA256: ${{ steps.package_cli_artifact.outputs.payload_sha256 }} + RUN_ATTEMPT: ${{ github.run_attempt }} + RUN_ID: ${{ github.run_id }} + WORKFLOW_SHA: ${{ github.workflow_sha }} + shell: bash + run: | + set -euo pipefail + [[ "$ARTIFACT_ID" =~ ^[1-9][0-9]*$ ]] || + { echo "::error::artifact upload did not return an immutable artifact ID"; exit 1; } + [[ "$ARTIFACT_DIGEST" =~ ^[a-f0-9]{64}$ ]] || + { echo "::error::artifact upload did not return a SHA-256 digest"; exit 1; } + provenance="$(jq -cn \ + --arg artifactDigest "$ARTIFACT_DIGEST" \ + --arg artifactId "$ARTIFACT_ID" \ + --arg artifactName "$ARTIFACT_NAME" \ + --arg candidateRepository "$CANDIDATE_REPOSITORY" \ + --arg candidateSha "$CANDIDATE_SHA" \ + --arg payloadSha256 "$PAYLOAD_SHA256" \ + --arg runAttempt "$RUN_ATTEMPT" \ + --arg runId "$RUN_ID" \ + --arg workflowSha "$WORKFLOW_SHA" \ + '{ + kind: "nemoclaw-e2e-cli-provenance-v1", + artifactDigest: $artifactDigest, + artifactId: $artifactId, + artifactName: $artifactName, + candidateRepository: $candidateRepository, + candidateSha: $candidateSha, + payloadSha256: $payloadSha256, + workflowSha: $workflowSha, + runId: $runId, + runAttempt: $runAttempt + }')" + printf 'provenance=%s\n' "$provenance" >>"$GITHUB_OUTPUT" + { + echo "## Exact-commit CLI artifact" + echo + echo "- Candidate: \`${CANDIDATE_SHA}\`" + echo "- Artifact: \`${ARTIFACT_NAME}\` (ID \`${ARTIFACT_ID}\`)" + echo "- GitHub archive digest: \`${ARTIFACT_DIGEST}\`" + echo "- Payload digest: \`${PAYLOAD_SHA256}\`" + } >>"$GITHUB_STEP_SUMMARY" - id: matrix name: Generate E2E target matrix @@ -454,6 +606,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Verify retired selector replacements env: @@ -624,6 +783,13 @@ jobs: # command writes raw traces under runner temp, never under upload roots. - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} # invalidState: a profile plugin installed with --no-deps can import even # when an incomplete base image omitted its required upstream packages. @@ -780,6 +946,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Run tagged credential-free test env: @@ -824,6 +997,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - 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 @@ -1091,6 +1271,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Initialize runner comparison telemetry if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' && (matrix.agent == 'hermes' || matrix.agent == 'deepagents') }} @@ -1216,6 +1403,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install and verify cloudflared prerequisite env: @@ -1331,6 +1525,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install and verify cloudflared prerequisite # Update posture: keep this dev compatibility lane on the same reviewed @@ -1436,6 +1637,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI # The migrated skill-agent lane invokes helper scripts that call @@ -1512,6 +1720,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Run OpenClaw skill CLI live test env: @@ -1550,6 +1765,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install and verify cloudflared prerequisite # Keep the public HTTPS routing fixture on the same reviewed binary as @@ -1622,6 +1844,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Run cloud inference live test # The Vitest test @@ -1673,6 +1902,13 @@ jobs: - *dockerhub-auth - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI run: bash scripts/install-openshell.sh - name: Run GPU Ollama live Vitest test @@ -1751,6 +1987,13 @@ jobs: - *dockerhub-auth - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Initialize runner comparison telemetry if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} continue-on-error: true @@ -1818,6 +2061,13 @@ jobs: - *dockerhub-auth - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI run: bash scripts/install-openshell.sh - name: Run Kimi compatibility live Vitest test @@ -1905,6 +2155,13 @@ jobs: - *dockerhub-auth - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Initialize runner comparison telemetry if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} continue-on-error: true @@ -1965,6 +2222,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI run: bash scripts/install-openshell.sh @@ -2065,6 +2329,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI run: bash scripts/install-openshell.sh @@ -2135,6 +2406,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI run: bash scripts/install-openshell.sh @@ -2194,6 +2472,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Run dashboard remote-bind live test env: @@ -2236,6 +2521,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI run: | @@ -2294,6 +2586,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Run Hermes Slack live test # Preserves the @@ -2369,6 +2668,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Initialize runner comparison telemetry if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} @@ -2507,6 +2813,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Reassert trusted Node runtime uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 @@ -2708,6 +3021,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Initialize runner comparison telemetry if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} @@ -2791,6 +3111,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell # Runs without workflow tokens, Docker credentials, or NVIDIA_INFERENCE_API_KEY. @@ -2883,6 +3210,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Initialize runner comparison telemetry if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} @@ -3040,6 +3374,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Initialize runner comparison telemetry if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }} @@ -3092,6 +3433,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell # Direct Vitest execution uses bin/nemoclaw.js instead of install.sh, @@ -3163,6 +3511,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Add swap for Hermes image rebuild shell: bash @@ -3278,6 +3633,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Add swap for Hermes image rebuild shell: bash @@ -3385,6 +3747,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell env: @@ -3449,6 +3818,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell env: @@ -3513,6 +3889,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Run overlayfs autofix live test env: @@ -3557,6 +3940,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell env: @@ -3620,6 +4010,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI run: | @@ -3679,6 +4076,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI run: bash scripts/install-openshell.sh @@ -3757,6 +4161,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Verify Jetson GPU availability run: | @@ -3812,6 +4223,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI run: bash scripts/install-openshell.sh @@ -3868,6 +4286,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - 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 @@ -3923,6 +4348,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI run: bash scripts/install-openshell.sh @@ -4110,6 +4542,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI run: bash scripts/install-openshell.sh @@ -4201,6 +4640,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI run: bash scripts/install-openshell.sh @@ -4255,6 +4701,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI run: bash scripts/install-openshell.sh @@ -4310,6 +4763,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI run: bash scripts/install-openshell.sh @@ -4363,6 +4823,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Run token rotation live test # Preserve the original runner class: ubuntu-latest with Docker/OpenShell @@ -4415,6 +4882,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Run messaging compatible endpoint live test # Preserves the fake OpenAI-compatible endpoint, Telegram messaging @@ -4524,6 +4998,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Run OpenShell gateway upgrade live Vitest test # Keep the original v0.0.36 fixture on x86_64 and validate the exact @@ -4575,6 +5056,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Run messaging providers live Vitest test # The test keeps @@ -4680,6 +5168,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Run Model Router provider-routed inference live test # Preserves the real provider-routed onboard, host model-router health, and @@ -4776,6 +5271,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Verify CLI launcher run: | @@ -4835,6 +5337,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Run sandbox survival live test # This intentionally @@ -4900,6 +5409,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Run OpenClaw custom-plugin release baseline live test run: | @@ -4968,6 +5484,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Run OpenClaw custom-plugin lifecycle and runtime-deps EXDEV live test run: | @@ -5040,6 +5563,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Run OpenClaw TUI chat correlation live test env: @@ -5102,6 +5632,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI # This target invokes `bin/nemoclaw.js onboard` directly, so install @@ -5198,6 +5735,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Run OpenClaw inference switch live test # Preserves @@ -5277,6 +5821,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Initialize runner comparison telemetry if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' && matrix.agent == 'hermes' }} @@ -5339,6 +5890,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI run: bash scripts/install-openshell.sh @@ -5395,6 +5953,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell env: @@ -5465,6 +6030,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell env: @@ -5558,6 +6130,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Initialize runner comparison telemetry if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' && matrix.agent == 'hermes' }} @@ -5644,6 +6223,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI run: | @@ -5709,6 +6295,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI run: bash scripts/install-openshell.sh @@ -5767,6 +6360,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI run: | @@ -5832,6 +6432,13 @@ jobs: - name: Prepare E2E workspace uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75 + with: + build-cli: "false" + + - name: Restore exact-commit CLI artifact + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + with: + provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install and verify cloudflared prerequisite env: diff --git a/test/e2e/README.md b/test/e2e/README.md index c81309351c6..8870ecb3884 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -25,6 +25,61 @@ before those targets run; local runners must provide it themselves. ## CI execution shape +### Exact-Commit CLI Artifact + +The `generate-matrix` job owns the workflow's authoritative `npm run build:cli` invocation. +It packages `dist/` once and publishes a content-addressed artifact for 65 current consumers. +Those jobs still run the pinned preparation action for Node.js and dependency installation, but they set `build-cli: "false"`. + +For a pull request (PR) dispatch, `checkout_sha` identifies the candidate source that the live jobs test. +The trusted workflow still runs from `github.workflow_sha`. +Direct scheduled and manual runs use `github.sha` as the candidate when `checkout_sha` is empty. +The artifact manifest records both identities, the source tree, lockfile digest, workflow run and attempt, Node.js and npm versions, runner platform, build command, and payload digest. +The artifact name contains the candidate commit SHA and payload SHA-256 digest. +The producer emits one `nemoclaw-e2e-cli-provenance-v1` JSON object through the `cli_artifact_provenance` job output. +Each consumer passes that object as the restore action's only `provenance-json` input. + +Each consumer invokes the repository-owned `restore-e2e-cli-artifact` composite action at full commit SHA `01f9a8da96e349717bfcd8c457e8380cf6bf3ff3`. +The workflow does not load the action implementation from the candidate checkout. +The pinned action rejects extra or missing provenance fields and compares the candidate checkout, repository, workflow SHA, run ID, and attempt before download. +It then downloads by immutable artifact ID with digest mismatch handling set to `error`. +It rejects a missing or malformed upload digest, a different candidate SHA, or a manifest that does not match the current source, workflow, run, toolchain contract, and payload. +It also rejects an unsafe archive member, link, special file, or preexisting `dist/` directory before extraction. +The restored CLI must report its version through `bin/nemoclaw.js`. +These checks keep the candidate source identity separate from the trusted workflow identity and fail closed before a live test runs. + +The pre-change baseline uses GitHub Actions composite-step timings from these workflow runs: + +| Workflow run | Job | Tested candidate | `Build CLI` duration | +| --- | --- | --- | --- | +| [30574154335](https://github.com/NVIDIA/NemoClaw/actions/runs/30574154335) | `cloud-inference` | `385f598` | 18.740 seconds | +| [30574154335](https://github.com/NVIDIA/NemoClaw/actions/runs/30574154335) | `cloud-onboard` | `385f598` | 18.793 seconds | +| [30503498077](https://github.com/NVIDIA/NemoClaw/actions/runs/30503498077) | `Shared E2E (vllm-docker-storage)` | `d52d459` | 18.756 seconds | + +The observed sample median is 18.756 seconds. +The implementation removes 64 duplicate build invocations from the pre-change inventory. +At the sample median, the theoretical gross reduction is `(64 × 18.756) / 60 = 20.01` runner-minutes before artifact transfer overhead. +This estimate does not predict workflow wall time because the producer dependency, runner queues, and transfer steps affect the critical path. + +Record post-change measurements only from passing CI runs. +Use comparable job selections, runner labels, and first attempts. +Record the producer build, upload, consumer download, verification, restoration, job, and workflow durations. +Sum the affected step durations for runner-minute comparison, and compare matched job and workflow elapsed times for wall-time comparison. +Identify each result by workflow run, tested commit SHA, trusted workflow SHA, and attempt. +Do not substitute a theoretical value for post-change CI evidence. + +The retained historical fixtures have this artifact disposition: + +| Fixture | Disposition | +| --- | --- | +| `openshell-gateway-upgrade` | Keep the historical installer commit and SHA-256 digest, sandbox image digest, and reviewed OpenClaw npm URL and SHA-512 integrity in the target. The target must install the historical package before it exercises the current upgrade path. | +| `upgrade-stale-sandbox` | Keep construction of the old OpenClaw image and stale registry state inside the target. A published old image would bypass the legacy-state construction boundary. | +| `rebuild-openclaw` | Keep the reviewed old-base build inside the target. The target must build and create the old sandbox before it exercises the current rebuild path. | + +These targets can consume the shared artifact for the current candidate CLI. +They must not use it to replace a historical installer, package, image, or version boundary that the target tests. +The gateway fixture's remote historical inputs are already bound to immutable commits and cryptographic digests, so this change does not republish them as workflow artifacts. + The sandbox image workflow builds the Hermes production image in the dedicated 30-minute `build-hermes-sandbox-image` job. It uses full-SHA-pinned Buildx actions and a GitHub Actions cache scoped to the runner OS and architecture. diff --git a/test/e2e/support/cli-artifact-workflow-boundary.test.ts b/test/e2e/support/cli-artifact-workflow-boundary.test.ts new file mode 100644 index 00000000000..4633cdd6b37 --- /dev/null +++ b/test/e2e/support/cli-artifact-workflow-boundary.test.ts @@ -0,0 +1,163 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { execFileSync, spawnSync } from "node:child_process"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; +import { + CLI_ARTIFACT_PACKAGE_STEP, + CLI_ARTIFACT_PUBLISH_STEP, + CLI_ARTIFACT_RESTORE_STEP, + validateCliArtifactRestoreAction, + validateCliArtifactWorkflowBoundary, +} from "../../../tools/e2e/cli-artifact-workflow-boundary.mts"; +import { + type CompositeAction, + readRepoText, + readWorkflow, + readYaml, + type Workflow, +} from "../../helpers/e2e-workflow-contract"; + +const CANDIDATE_SHA = execFileSync("git", ["rev-parse", "HEAD"], { + encoding: "utf8", +}).trim(); +const PAYLOAD_SHA256 = "b".repeat(64); + +function runIdentityValidation(overrides: Record = {}) { + const action = readYaml(".github/actions/restore-e2e-cli-artifact/action.yaml"); + const workflowSha = "d".repeat(40); + const outputDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "cli-artifact-identity-")); + return spawnSync("bash", ["-c", action.runs.steps[0]!.run!], { + encoding: "utf8", + env: { + ...process.env, + CALLER_WORKFLOW_SHA: workflowSha, + GITHUB_OUTPUT: path.join(outputDirectory, "github-output"), + GITHUB_RUN_ATTEMPT: "1", + GITHUB_RUN_ID: "98765", + PROVENANCE_JSON: JSON.stringify({ + kind: "nemoclaw-e2e-cli-provenance-v1", + artifactDigest: "c".repeat(64), + artifactId: "12345", + artifactName: `nemoclaw-cli-${CANDIDATE_SHA}-${PAYLOAD_SHA256}`, + candidateRepository: "NVIDIA/NemoClaw", + candidateSha: CANDIDATE_SHA, + payloadSha256: PAYLOAD_SHA256, + runAttempt: "1", + runId: "98765", + workflowSha, + ...overrides, + }), + }, + }); +} + +function workflowFixture(): Workflow { + return JSON.parse(JSON.stringify(readWorkflow())) as Workflow; +} + +function requireStep(workflow: Workflow, jobName: string, stepName: string) { + const step = workflow.jobs[jobName]?.steps?.find((candidate) => candidate.name === stepName); + expect(step, `${jobName} must contain ${stepName}`).toBeDefined(); + return step!; +} + +describe("exact-commit CLI artifact workflow boundary", () => { + it("builds once and gives every build-backed E2E job the verified artifact", () => { + expect(validateCliArtifactWorkflowBoundary(readWorkflow())).toEqual([]); + }); + + it("accepts an exact producer and consumer identity", () => { + const result = runIdentityValidation(); + expect(result.status, result.stderr).toBe(0); + }); + + it.each([ + ["empty artifact ID", { artifactId: "" }], + ["prefixed upload digest", { artifactDigest: `sha256:${"c".repeat(64)}` }], + ["malformed candidate SHA", { candidateSha: "abc" }], + ["different candidate SHA", { candidateSha: "e".repeat(40) }], + ["unbound artifact name", { artifactName: `nemoclaw-cli-${CANDIDATE_SHA}` }], + ["malformed payload digest", { payloadSha256: "abc" }], + ["malformed workflow SHA", { workflowSha: "abc" }], + ["unknown provenance field", { unexpected: "value" }], + ])("fails closed for %s", (_case, overrides) => { + expect(runIdentityValidation(overrides).status).not.toBe(0); + }); + + it("rejects producer identity and content-addressing drift", () => { + const workflow = workflowFixture(); + const producer = workflow.jobs["generate-matrix"]; + producer.outputs!.cli_artifact_provenance = + "${{ steps.upload_cli_artifact.outputs.artifact-url }}"; + const packageStep = requireStep(workflow, "generate-matrix", CLI_ARTIFACT_PACKAGE_STEP); + packageStep.env!.WORKFLOW_SHA = "${{ inputs.checkout_sha }}"; + packageStep.run = packageStep.run!.replace( + 'artifact_name="nemoclaw-cli-${CANDIDATE_SHA}-${payload_sha256}"', + 'artifact_name="nemoclaw-cli-${CANDIDATE_SHA}"', + ); + const uploadStep = requireStep(workflow, "generate-matrix", CLI_ARTIFACT_PUBLISH_STEP); + uploadStep.uses = "actions/upload-artifact@v7"; + + expect(validateCliArtifactWorkflowBoundary(workflow)).toEqual( + expect.arrayContaining([ + "generate-matrix must expose exact cli_artifact_provenance provenance", + "CLI artifact package step must bind candidate and trusted workflow identities explicitly", + 'CLI artifact package step must contain artifact_name="nemoclaw-cli-${CANDIDATE_SHA}-${payload_sha256}"', + "CLI artifact upload must use the immutable content-addressed upload contract", + ]), + ); + }); + + it("rejects incomplete consumer provenance and a mutable action reference", () => { + const workflow = workflowFixture(); + const restore = requireStep(workflow, "sandbox-operations", CLI_ARTIFACT_RESTORE_STEP); + restore.uses = "NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@main"; + restore.with = { "provenance-json": "${{ inputs.checkout_sha }}" }; + + expect(validateCliArtifactWorkflowBoundary(workflow)).toContain( + "sandbox-operations must use the immutable complete CLI artifact restore contract", + ); + }); + + it("rejects action implementation drift that weakens extraction or payload verification", () => { + const directory = fs.mkdtempSync(path.join(os.tmpdir(), "cli-artifact-action-")); + const actionPath = path.join(directory, "action.yaml"); + const source = readRepoText(".github/actions/restore-e2e-cli-artifact/action.yaml") + .replace("tar --no-same-owner --no-same-permissions", "tar") + .replace('[[ "$actual_payload_sha256" == "$PAYLOAD_SHA256" ]]', '[[ -s "$payload" ]]'); + fs.writeFileSync(actionPath, source); + + expect(validateCliArtifactRestoreAction(actionPath)).toEqual( + expect.arrayContaining([ + "CLI artifact restore action must match its immutable workflow pin", + "CLI artifact payload verification must contain tar --no-same-owner --no-same-permissions", + 'CLI artifact payload verification must contain [[ "$actual_payload_sha256" == "$PAYLOAD_SHA256" ]]', + ]), + ); + }); + + it("rejects missing consumer restoration", () => { + const workflow = workflowFixture(); + workflow.jobs["cloud-inference"].steps = workflow.jobs["cloud-inference"].steps!.filter( + (step) => step.name !== CLI_ARTIFACT_RESTORE_STEP, + ); + + expect(validateCliArtifactWorkflowBoundary(workflow)).toContain( + "cloud-inference must verify and restore the exact CLI artifact exactly once", + ); + }); + + it("keeps installer-backed no-build jobs outside the artifact handoff", () => { + const workflow = workflowFixture(); + const inheritedRestore = requireStep(workflow, "sandbox-operations", CLI_ARTIFACT_RESTORE_STEP); + workflow.jobs["security-posture"].steps!.push(inheritedRestore); + + expect(validateCliArtifactWorkflowBoundary(workflow)).toContain( + "security-posture must not consume the shared CLI artifact", + ); + }); +}); diff --git a/test/e2e/support/prepare-e2e-workflow-boundary.test.ts b/test/e2e/support/prepare-e2e-workflow-boundary.test.ts index c22d2af0038..5c9cd0b6193 100644 --- a/test/e2e/support/prepare-e2e-workflow-boundary.test.ts +++ b/test/e2e/support/prepare-e2e-workflow-boundary.test.ts @@ -27,7 +27,7 @@ type Workflow = { }; describe("prepare-e2e workflow boundary", () => { - it("keeps one canonical bootstrap invocation on every E2E execution job", () => { + it("keeps one canonical bootstrap invocation and one authoritative CLI build", () => { expect(validatePrepareE2eAction()).toEqual([]); expect(validatePrepareE2eInvocations(readWorkflow())).toEqual([]); }); @@ -89,10 +89,16 @@ describe("prepare-e2e workflow boundary", () => { it("rejects build-mode, duplicate-step, and ordering drift", () => { const workflow = readWorkflow() as Workflow; - const buildJob = workflow.jobs["sandbox-operations"]; - const buildPrepare = buildJob.steps!.find((step) => step.uses === PREPARE_E2E_ACTION)!; - buildPrepare.with = { "build-cli": "false" }; - buildJob.steps!.splice(buildJob.steps!.indexOf(buildPrepare), 0, { + const artifactProducer = workflow.jobs["generate-matrix"]; + const producerPrepare = artifactProducer.steps!.find( + (step) => step.uses === PREPARE_E2E_ACTION, + )!; + producerPrepare.with = { "build-cli": "false" }; + + const consumerJob = workflow.jobs["sandbox-operations"]; + const consumerPrepare = consumerJob.steps!.find((step) => step.uses === PREPARE_E2E_ACTION)!; + delete consumerPrepare.with; + consumerJob.steps!.splice(consumerJob.steps!.indexOf(consumerPrepare), 0, { name: "Build CLI", run: "npm run build:cli", }); @@ -103,7 +109,7 @@ describe("prepare-e2e workflow boundary", () => { const sharedJob = workflow.jobs["shared-e2e"]; const sharedPrepare = sharedJob.steps!.find((step) => step.uses === PREPARE_E2E_ACTION)!; - sharedPrepare.with = { "build-cli": "false" }; + delete sharedPrepare.with; sharedJob.env!.E2E_EXECUTION_PROFILE = "credential-free"; sharedJob.env!.E2E_JOB = "1"; @@ -120,14 +126,16 @@ describe("prepare-e2e workflow boundary", () => { expect(validatePrepareE2eInvocations(workflow)).toEqual( expect.arrayContaining([ - "sandbox-operations prepare-e2e must use the default CLI build", + "generate-matrix prepare-e2e must own the only default CLI build", + "generate-matrix prepare-e2e invocation must not override its canonical contract", + "sandbox-operations prepare-e2e must set build-cli to false", "sandbox-operations prepare-e2e invocation must not override its canonical contract", "sandbox-operations must not duplicate prepare-e2e step 'Build CLI'", "bootstrap-install-smoke prepare-e2e must set build-cli to false", "bootstrap-install-smoke prepare-e2e invocation must not override its canonical contract", "shared-e2e must not declare E2E_EXECUTION_PROFILE", "shared-e2e must not declare E2E_JOB", - "shared-e2e prepare-e2e must use the default CLI build", + "shared-e2e prepare-e2e must set build-cli to false", "shared-e2e prepare-e2e invocation must not override its canonical contract", "inference-routing must not load prepare-e2e from the target checkout", "inference-routing must use prepare-e2e exactly once", diff --git a/test/e2e/support/rebuild-hermes-workflow-boundary.test.ts b/test/e2e/support/rebuild-hermes-workflow-boundary.test.ts index b7c873169c8..2fab248a5a8 100644 --- a/test/e2e/support/rebuild-hermes-workflow-boundary.test.ts +++ b/test/e2e/support/rebuild-hermes-workflow-boundary.test.ts @@ -31,7 +31,9 @@ function bootstrapJob(jobName: JobName): { { name: "Prepare E2E workspace", uses: "NVIDIA/NemoClaw/.github/actions/prepare-e2e@immutable", + with: { "build-cli": "false" }, }, + { name: "Restore exact-commit CLI artifact" }, { name: "Install OpenShell", run: INSTALL_OPENSHELL }, { name: runStepName, @@ -55,9 +57,9 @@ describe("Hermes rebuild bootstrap workflow boundary", () => { "rebuild-hermes-stale-base", ] as const)("%s rejects bootstrap trust-boundary drift (#7144)", (jobName) => { const job = bootstrapJob(jobName); - const [prepare, install, run] = job.steps; + const [prepare, restore, install, run] = job.steps; job.env.NEMOCLAW_CLI_BIN = "${{ github.workspace }}/evil/bin/nemoclaw.js"; - prepare.with = { "build-cli": "false" }; + delete prepare.with; install.env = { NVIDIA_INFERENCE_API_KEY: "${{ secrets.NVIDIA_INFERENCE_API_KEY }}", }; @@ -68,12 +70,12 @@ describe("Hermes rebuild bootstrap workflow boundary", () => { }; run.run = "tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/rebuild-hermes.test.ts"; - job.steps = [run, install, prepare]; + job.steps = [run, install, restore, prepare]; const errors = validateRebuildHermesBootstrapBoundary(jobName, job); expect(errors).toContain(`${jobName} job must point NEMOCLAW_CLI_BIN at the repo CLI`); expect(errors).toContain( - `${jobName} workspace preparation must use the default checked-out CLI build`, + `${jobName} workspace preparation must defer to the exact-commit CLI artifact`, ); expect(errors).toContain( "step 'Install OpenShell' run script must include env -u DOCKER_CONFIG", @@ -88,7 +90,7 @@ describe("Hermes rebuild bootstrap workflow boundary", () => { expect(errors).toContain(`${jobName} step '${run.name}' env must not include NVIDIA_API_KEY`); expect(errors).toContain(`step '${run.name}' run script must include OPENSHELL_BIN`); expect(errors).toContain( - `${jobName} must build the CLI before installing OpenShell and running Vitest`, + `${jobName} must restore the exact-commit CLI before installing OpenShell and running Vitest`, ); }); }); diff --git a/test/e2e/support/runner-comparison-workflow-boundary.test.ts b/test/e2e/support/runner-comparison-workflow-boundary.test.ts index b0d67e558eb..f42896615c7 100644 --- a/test/e2e/support/runner-comparison-workflow-boundary.test.ts +++ b/test/e2e/support/runner-comparison-workflow-boundary.test.ts @@ -166,7 +166,7 @@ describe("runner comparison E2E workflow boundary (#7140)", () => { ]; const expectedInitializeError = REBUILD_JOBS.includes(jobId as (typeof REBUILD_JOBS)[number]) ? `${jobId} must establish rebuild swap before initializing runner comparison telemetry` - : `${jobId} must initialize runner comparison telemetry immediately after prepare-e2e`; + : `${jobId} must initialize runner comparison telemetry immediately after CLI bootstrap`; expect(validateRunnerComparisonWorkflow(lateInitialize)).toContain(expectedInitializeError); const afterPublication = loadWorkflow(); diff --git a/tools/e2e/cli-artifact-workflow-boundary.mts b/tools/e2e/cli-artifact-workflow-boundary.mts new file mode 100644 index 00000000000..4e60bd67e42 --- /dev/null +++ b/tools/e2e/cli-artifact-workflow-boundary.mts @@ -0,0 +1,351 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { createHash } from "node:crypto"; +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { isDeepStrictEqual } from "node:util"; +import YAML from "yaml"; +import { + CLI_ARTIFACT_PRODUCER_JOB, + PREPARE_E2E_ACTION, + PREPARE_E2E_NO_BUILD_JOBS, +} from "./prepare-e2e-workflow-boundary.mts"; + +export const CLI_ARTIFACT_DOWNLOAD_ACTION = + "actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c"; +export const CLI_ARTIFACT_UPLOAD_ACTION = + "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a"; +export const CLI_ARTIFACT_RESTORE_ACTION = + "NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3"; +export const CLI_ARTIFACT_PACKAGE_STEP = "Package exact-commit CLI"; +export const CLI_ARTIFACT_PUBLISH_STEP = "Publish content-addressed CLI artifact"; +export const CLI_ARTIFACT_RESTORE_STEP = "Restore exact-commit CLI artifact"; + +const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", ".."); +const DEFAULT_RESTORE_ACTION_PATH = join( + REPO_ROOT, + ".github", + "actions", + "restore-e2e-cli-artifact", + "action.yaml", +); +const RESTORE_ACTION_CONTENT_SHA256 = + "8e0d07a2521651868f3ab1e29dd1e9a0d1494ffd807dfd1d661a5989c9755c8f"; +const CLI_ARTIFACT_DOWNLOAD_STEP = "Download exact-commit CLI artifact"; +const CLI_ARTIFACT_VERIFY_STEP = "Verify and restore exact-commit CLI artifact"; +const CLI_ARTIFACT_PROVENANCE_STEP = "Record CLI artifact provenance"; + +type WorkflowRecord = Record; +type WorkflowStep = WorkflowRecord & { + env?: WorkflowRecord; + id?: string; + name?: string; + run?: string; + uses?: string; + with?: WorkflowRecord; +}; + +function record(value: unknown): WorkflowRecord { + return value && typeof value === "object" && !Array.isArray(value) + ? (value as WorkflowRecord) + : {}; +} + +function steps(value: unknown): WorkflowStep[] { + return Array.isArray(value) ? (value as WorkflowStep[]) : []; +} + +function requireFragments( + errors: string[], + owner: string, + source: unknown, + fragments: readonly string[], +): void { + const script = typeof source === "string" ? source : ""; + for (const fragment of fragments) { + if (!script.includes(fragment)) errors.push(`${owner} must contain ${fragment}`); + } +} + +export function validateCliArtifactRestoreAction( + actionPath = DEFAULT_RESTORE_ACTION_PATH, +): string[] { + const errors: string[] = []; + const actionSource = readFileSync(actionPath, "utf8"); + if (createHash("sha256").update(actionSource).digest("hex") !== RESTORE_ACTION_CONTENT_SHA256) { + errors.push("CLI artifact restore action must match its immutable workflow pin"); + } + const action = record(YAML.parse(actionSource)); + const inputNames = ["provenance-json"]; + const inputs = record(action.inputs); + if ( + !isDeepStrictEqual(Object.keys(inputs).sort(), inputNames) || + !inputNames.every((name) => record(inputs[name]).required === true) + ) { + errors.push("CLI artifact restore action must require the complete provenance input set"); + } + const actionSteps = steps(record(action.runs).steps); + if (record(action.runs).using !== "composite" || actionSteps.length !== 3) { + errors.push("CLI artifact restore action must keep the three-step composite boundary"); + return errors; + } + const [identity, download, restore] = actionSteps; + if ( + identity?.name !== "Validate exact-commit CLI artifact identity" || + identity.id !== "identity" || + identity.shell !== "bash" || + !isDeepStrictEqual(record(identity.env), { + CALLER_WORKFLOW_SHA: "${{ github.workflow_sha }}", + PROVENANCE_JSON: "${{ inputs.provenance-json }}", + }) + ) { + errors.push("CLI artifact restore action must validate identity before download"); + } + requireFragments(errors, "CLI artifact identity validation", identity?.run, [ + "(keys | sort) == [", + '.kind == "nemoclaw-e2e-cli-provenance-v1"', + '.artifactId | strings | test("^[1-9][0-9]*$")', + '.artifactDigest | strings | test("^[a-f0-9]{64}$")', + '.candidateSha | strings | test("^[a-f0-9]{40}$")', + '.payloadSha256 | strings | test("^[a-f0-9]{64}$")', + '.workflowSha | strings | test("^[a-f0-9]{40}$")', + '.artifactName == ("nemoclaw-cli-" + .candidateSha + "-" + .payloadSha256)', + 'git rev-parse --verify HEAD)" == "$candidate_sha"', + '[[ "$workflow_sha" == "$CALLER_WORKFLOW_SHA" ]]', + '[[ "$run_id" == "$GITHUB_RUN_ID" && "$run_attempt" == "$GITHUB_RUN_ATTEMPT" ]]', + '[[ "$remote_repository" == "$candidate_repository" ]]', + '<<<"$PROVENANCE_JSON" >>"$GITHUB_OUTPUT"', + ]); + if ( + download?.name !== CLI_ARTIFACT_DOWNLOAD_STEP || + download.uses !== CLI_ARTIFACT_DOWNLOAD_ACTION || + !isDeepStrictEqual(record(download.with), { + "artifact-ids": "${{ steps.identity.outputs.artifact_id }}", + path: "${{ runner.temp }}/nemoclaw-cli-artifact", + "digest-mismatch": "error", + }) + ) { + errors.push("CLI artifact restore action must download by immutable ID and reject mismatch"); + } + if (restore?.name !== CLI_ARTIFACT_VERIFY_STEP || restore.shell !== "bash") { + errors.push("CLI artifact restore action must verify the downloaded payload in bash"); + } + if ( + !isDeepStrictEqual(record(restore?.env), { + ARTIFACT_NAME: "${{ steps.identity.outputs.artifact_name }}", + CANDIDATE_REPOSITORY: "${{ steps.identity.outputs.candidate_repository }}", + CANDIDATE_SHA: "${{ steps.identity.outputs.candidate_sha }}", + PAYLOAD_SHA256: "${{ steps.identity.outputs.payload_sha256 }}", + RUN_ATTEMPT: "${{ steps.identity.outputs.run_attempt }}", + RUN_ID: "${{ steps.identity.outputs.run_id }}", + WORKFLOW_SHA: "${{ steps.identity.outputs.workflow_sha }}", + }) + ) { + errors.push("CLI artifact restore action must pass validated identity to payload verification"); + } + requireFragments(errors, "CLI artifact payload verification", restore?.run, [ + ".candidate.sha == $candidateSha", + ".candidate.sourceTree == $sourceTree", + ".candidate.lockfileSha256 == $lockfileSha256", + ".workflow.sha == $workflowSha", + ".workflow.runId == $runId", + ".workflow.runAttempt == $runAttempt", + ".payload.sha256 == $payloadSha256", + '[[ "$actual_payload_sha256" == "$PAYLOAD_SHA256" ]]', + '*) echo "::error::CLI artifact contains an unsafe member', + "CLI artifact contains a link or special file", + "[[ ! -e dist ]]", + "tar --no-same-owner --no-same-permissions", + "node bin/nemoclaw.js --version", + ]); + return errors; +} + +function validateProducer(errors: string[], producer: WorkflowRecord): void { + const outputs = record(producer.outputs); + const requiredOutputs = { + cli_artifact_provenance: "${{ steps.record_cli_artifact.outputs.provenance }}", + }; + for (const [name, value] of Object.entries(requiredOutputs)) { + if (outputs[name] !== value) { + errors.push(`${CLI_ARTIFACT_PRODUCER_JOB} must expose exact ${name} provenance`); + } + } + + const producerSteps = steps(producer.steps); + const packageSteps = producerSteps.filter((step) => step.name === CLI_ARTIFACT_PACKAGE_STEP); + const uploadSteps = producerSteps.filter((step) => step.name === CLI_ARTIFACT_PUBLISH_STEP); + const provenanceSteps = producerSteps.filter( + (step) => step.name === CLI_ARTIFACT_PROVENANCE_STEP, + ); + if (packageSteps.length !== 1) { + errors.push(`${CLI_ARTIFACT_PRODUCER_JOB} must package the CLI artifact exactly once`); + } + if (uploadSteps.length !== 1) { + errors.push(`${CLI_ARTIFACT_PRODUCER_JOB} must publish the CLI artifact exactly once`); + } + if (provenanceSteps.length !== 1) { + errors.push(`${CLI_ARTIFACT_PRODUCER_JOB} must record CLI artifact provenance exactly once`); + } + const packageStep = packageSteps[0]; + const uploadStep = uploadSteps[0]; + const provenanceStep = provenanceSteps[0]; + if (!packageStep || !uploadStep || !provenanceStep) return; + + if (packageStep.id !== "package_cli_artifact" || packageStep.shell !== "bash") { + errors.push("CLI artifact package step must use the canonical id and bash shell"); + } + if ( + !isDeepStrictEqual(record(packageStep.env), { + CANDIDATE_REPOSITORY: "${{ inputs.checkout_repository || github.repository }}", + CANDIDATE_SHA: "${{ inputs.checkout_sha || github.sha }}", + RUN_ATTEMPT: "${{ github.run_attempt }}", + RUN_ID: "${{ github.run_id }}", + WORKFLOW_SHA: "${{ github.workflow_sha }}", + }) + ) { + errors.push( + "CLI artifact package step must bind candidate and trusted workflow identities explicitly", + ); + } + requireFragments(errors, "CLI artifact package step", packageStep.run, [ + 'git rev-parse --verify HEAD)" == "$CANDIDATE_SHA"', + "test -s dist/nemoclaw.js", + "--sort=name", + "--mtime=@0", + "source_tree=\"$(git rev-parse 'HEAD^{tree}')\"", + 'lockfile_sha256="$(sha256sum package-lock.json', + 'artifact_name="nemoclaw-cli-${CANDIDATE_SHA}-${payload_sha256}"', + 'kind: "nemoclaw-e2e-cli-artifact-v1"', + "sha: $candidateSha", + "sha: $workflowSha", + "sha256: $payloadSha256", + ]); + + if ( + uploadStep.id !== "upload_cli_artifact" || + uploadStep.uses !== CLI_ARTIFACT_UPLOAD_ACTION || + !isDeepStrictEqual(record(uploadStep.with), { + name: "${{ steps.package_cli_artifact.outputs.artifact_name }}", + path: "${{ runner.temp }}/nemoclaw-cli-artifact/", + "if-no-files-found": "error", + "retention-days": 3, + "compression-level": 0, + }) + ) { + errors.push("CLI artifact upload must use the immutable content-addressed upload contract"); + } + if ( + provenanceStep.id !== "record_cli_artifact" || + !isDeepStrictEqual(record(provenanceStep.env), { + ARTIFACT_DIGEST: "${{ steps.upload_cli_artifact.outputs.artifact-digest }}", + ARTIFACT_ID: "${{ steps.upload_cli_artifact.outputs.artifact-id }}", + ARTIFACT_NAME: "${{ steps.package_cli_artifact.outputs.artifact_name }}", + CANDIDATE_REPOSITORY: "${{ inputs.checkout_repository || github.repository }}", + CANDIDATE_SHA: "${{ steps.package_cli_artifact.outputs.candidate_sha }}", + PAYLOAD_SHA256: "${{ steps.package_cli_artifact.outputs.payload_sha256 }}", + RUN_ATTEMPT: "${{ github.run_attempt }}", + RUN_ID: "${{ github.run_id }}", + WORKFLOW_SHA: "${{ github.workflow_sha }}", + }) + ) { + errors.push("CLI artifact provenance step must consume the immutable upload outputs"); + } + requireFragments(errors, "CLI artifact provenance step", provenanceStep.run, [ + '[[ "$ARTIFACT_ID" =~ ^[1-9][0-9]*$ ]]', + '[[ "$ARTIFACT_DIGEST" =~ ^[a-f0-9]{64}$ ]]', + 'kind: "nemoclaw-e2e-cli-provenance-v1"', + "artifactDigest: $artifactDigest", + "candidateRepository: $candidateRepository", + "workflowSha: $workflowSha", + 'printf \'provenance=%s\\n\' "$provenance" >>"$GITHUB_OUTPUT"', + 'echo "- Candidate: \\`${CANDIDATE_SHA}\\`"', + 'echo "- Payload digest: \\`${PAYLOAD_SHA256}\\`"', + ]); + + const prepareIndex = producerSteps.findIndex((step) => step.uses === PREPARE_E2E_ACTION); + const packageIndex = producerSteps.indexOf(packageStep); + const uploadIndex = producerSteps.indexOf(uploadStep); + const provenanceIndex = producerSteps.indexOf(provenanceStep); + if ( + !( + prepareIndex >= 0 && + prepareIndex < packageIndex && + packageIndex < uploadIndex && + uploadIndex < provenanceIndex + ) + ) { + errors.push("CLI artifact producer must build, package, upload, then record provenance"); + } +} + +function validateConsumer( + errors: string[], + jobName: string, + job: WorkflowRecord, + jobSteps: WorkflowStep[], +): void { + if (job.needs !== CLI_ARTIFACT_PRODUCER_JOB) { + errors.push(`${jobName} must depend directly on the CLI artifact producer`); + } + const prepareIndex = jobSteps.findIndex((step) => step.uses === PREPARE_E2E_ACTION); + const restoreSteps = jobSteps.filter( + (step) => step.name === CLI_ARTIFACT_RESTORE_STEP || step.uses === CLI_ARTIFACT_RESTORE_ACTION, + ); + if (restoreSteps.length !== 1) { + errors.push(`${jobName} must verify and restore the exact CLI artifact exactly once`); + } + const restore = restoreSteps[0]; + if (!restore) return; + + if ( + restore.uses !== CLI_ARTIFACT_RESTORE_ACTION || + !isDeepStrictEqual(record(restore.with), { + "provenance-json": "${{ needs.generate-matrix.outputs.cli_artifact_provenance }}", + }) + ) { + errors.push(`${jobName} must use the immutable complete CLI artifact restore contract`); + } + const restoreIndex = jobSteps.indexOf(restore); + if (!(prepareIndex >= 0 && prepareIndex < restoreIndex)) { + errors.push(`${jobName} must prepare before restoring the CLI artifact`); + } +} + +export function validateCliArtifactWorkflowBoundary( + workflow: WorkflowRecord, + actionPath = DEFAULT_RESTORE_ACTION_PATH, +): string[] { + const errors = validateCliArtifactRestoreAction(actionPath); + const jobs = record(workflow.jobs); + const producer = record(jobs[CLI_ARTIFACT_PRODUCER_JOB]); + if (Object.keys(producer).length === 0) { + return [`workflow is missing CLI artifact producer ${CLI_ARTIFACT_PRODUCER_JOB}`]; + } + validateProducer(errors, producer); + + for (const [jobName, value] of Object.entries(jobs)) { + const job = record(value); + const jobSteps = steps(job.steps); + const usesPrepare = jobSteps.some((step) => step.uses === PREPARE_E2E_ACTION); + const artifactSteps = jobSteps.filter( + (step) => + step.name === CLI_ARTIFACT_RESTORE_STEP || + (typeof step.uses === "string" && + step.uses.startsWith("NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@")), + ); + const shouldConsume = + usesPrepare && + jobName !== CLI_ARTIFACT_PRODUCER_JOB && + !PREPARE_E2E_NO_BUILD_JOBS.has(jobName); + if (shouldConsume) { + validateConsumer(errors, jobName, job, jobSteps); + } else if (artifactSteps.length > 0) { + errors.push(`${jobName} must not consume the shared CLI artifact`); + } + } + + return errors; +} diff --git a/tools/e2e/hermes-gpu-startup-workflow-boundary.mts b/tools/e2e/hermes-gpu-startup-workflow-boundary.mts index e4fee00e9d7..18495f9a776 100644 --- a/tools/e2e/hermes-gpu-startup-workflow-boundary.mts +++ b/tools/e2e/hermes-gpu-startup-workflow-boundary.mts @@ -7,6 +7,7 @@ import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import YAML from "yaml"; +import { CLI_ARTIFACT_RESTORE_STEP } from "./cli-artifact-workflow-boundary.mts"; /** * SOURCE_OF_TRUTH_REVIEW @@ -241,13 +242,15 @@ if ! @run restore`; } const run = stringValue(runStep.run); const pi = steps.findIndex((step) => step.name === "Prepare E2E workspace"); + const restoreI = steps.findIndex((step) => step.name === CLI_ARTIFACT_RESTORE_STEP); const ni = steps.findIndex((step) => step.name === "Reassert trusted Node runtime"); const node = steps[ni]; if ( runStep.shell !== BASH || !trustedEnv(runStep) || pi < 0 || - ni !== pi + 1 || + restoreI <= pi || + ni !== restoreI + 1 || ni + 1 !== steps.indexOf(runStep) || node?.uses !== "actions/setup-node@820762786026740c76f36085b0efc47a31fe5020" || asRecord(node?.with)["node-version"] !== "22" || diff --git a/tools/e2e/prepare-e2e-workflow-boundary.mts b/tools/e2e/prepare-e2e-workflow-boundary.mts index 1fce8f61247..edd9896cba1 100644 --- a/tools/e2e/prepare-e2e-workflow-boundary.mts +++ b/tools/e2e/prepare-e2e-workflow-boundary.mts @@ -21,11 +21,11 @@ export const PREPARE_E2E_ACTION = PREPARE_E2E_ACTION_PROVENANCE.reference; export const PREPARE_E2E_STEP = "Prepare E2E workspace"; const CHECKOUT_LOCAL_PREPARE_E2E_ACTION = "./.github/actions/prepare-e2e"; +export const CLI_ARTIFACT_PRODUCER_JOB = "generate-matrix"; const PREINSTALLED_E2E_JOBS = new Set(["staging-brev-launchable"]); const RETIRED_SELECTOR_COMPATIBILITY_JOB = "retired-selector-compatibility"; -const NO_BUILD_JOBS = new Set([ - "generate-matrix", +export const PREPARE_E2E_NO_BUILD_JOBS = new Set([ "bootstrap-install-smoke", "ollama-auth-proxy", "security-posture", @@ -116,7 +116,6 @@ export function validatePrepareE2eInvocations(workflow: WorkflowRecord): string[ }) .map(([jobName]) => jobName), ); - const sharedE2eJob = jobs[SHARED_E2E_JOB_ID]; if (sharedE2eJob === undefined) { errors.push(`prepare-e2e shared job is missing: ${SHARED_E2E_JOB_ID}`); @@ -151,9 +150,9 @@ export function validatePrepareE2eInvocations(workflow: WorkflowRecord): string[ errors.push(`${jobName} prepare-e2e step must be named '${PREPARE_E2E_STEP}'`); } const withInputs = record(prepare.with); - const shouldBuild = !NO_BUILD_JOBS.has(jobName); + const shouldBuild = jobName === CLI_ARTIFACT_PRODUCER_JOB; if (shouldBuild && Object.keys(withInputs).length !== 0) { - errors.push(`${jobName} prepare-e2e must use the default CLI build`); + errors.push(`${jobName} prepare-e2e must own the only default CLI build`); } if (!shouldBuild && !isDeepStrictEqual(withInputs, { "build-cli": "false" })) { errors.push(`${jobName} prepare-e2e must set build-cli to false`); @@ -179,7 +178,7 @@ export function validatePrepareE2eInvocations(workflow: WorkflowRecord): string[ } } - for (const jobName of NO_BUILD_JOBS) { + for (const jobName of PREPARE_E2E_NO_BUILD_JOBS) { if (!expectedJobs.has(jobName)) errors.push(`prepare-e2e no-build job is missing: ${jobName}`); } return errors; diff --git a/tools/e2e/runner-comparison-workflow-boundary.mts b/tools/e2e/runner-comparison-workflow-boundary.mts index a212cd1d34f..3ae383dd121 100644 --- a/tools/e2e/runner-comparison-workflow-boundary.mts +++ b/tools/e2e/runner-comparison-workflow-boundary.mts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { isDeepStrictEqual } from "node:util"; +import { CLI_ARTIFACT_RESTORE_STEP } from "./cli-artifact-workflow-boundary.mts"; import { PREPARE_E2E_STEP } from "./prepare-e2e-workflow-boundary.mts"; import { UPLOAD_E2E_ARTIFACTS_ACTION } from "./upload-e2e-artifacts-workflow-boundary.mts"; @@ -204,19 +205,26 @@ export function validateRunnerComparisonWorkflow(workflowValue: unknown): string if (!initialize || !finalize) continue; const prepare = jobSteps.findIndex((step) => step.name === PREPARE_E2E_STEP); + const restore = jobSteps.findIndex((step) => step.name === CLI_ARTIFACT_RESTORE_STEP); + const bootstrapEnd = restore >= 0 ? restore : prepare; const initializeIndex = jobSteps.indexOf(initialize); const finalizeIndex = jobSteps.indexOf(finalize); const publish = publicationIndex(jobSteps); if (HERMES_REBUILD_SWAP_JOBS.has(jobId)) { const swapIndex = jobSteps.findIndex((step) => step.name === HERMES_REBUILD_SWAP_STEP); - if (prepare < 0 || swapIndex !== prepare + 1 || initializeIndex !== swapIndex + 1) { + if ( + prepare < 0 || + bootstrapEnd < prepare || + swapIndex !== bootstrapEnd + 1 || + initializeIndex !== swapIndex + 1 + ) { errors.push( `${jobId} must establish rebuild swap before initializing runner comparison telemetry`, ); } - } else if (prepare < 0 || initializeIndex !== prepare + 1) { + } else if (prepare < 0 || bootstrapEnd < prepare || initializeIndex !== bootstrapEnd + 1) { errors.push( - `${jobId} must initialize runner comparison telemetry immediately after prepare-e2e`, + `${jobId} must initialize runner comparison telemetry immediately after CLI bootstrap`, ); } if (publish < 0 || finalizeIndex !== publish - 1) { diff --git a/tools/e2e/upload-e2e-artifacts-workflow-boundary.mts b/tools/e2e/upload-e2e-artifacts-workflow-boundary.mts index c0142ad13da..9abc39c47de 100644 --- a/tools/e2e/upload-e2e-artifacts-workflow-boundary.mts +++ b/tools/e2e/upload-e2e-artifacts-workflow-boundary.mts @@ -7,6 +7,10 @@ import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { isDeepStrictEqual } from "node:util"; import YAML from "yaml"; +import { + CLI_ARTIFACT_PUBLISH_STEP, + CLI_ARTIFACT_UPLOAD_ACTION, +} from "./cli-artifact-workflow-boundary.mts"; import { SHARED_E2E_JOB_ID } from "./credential-free-tests.mts"; const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", ".."); @@ -418,7 +422,11 @@ export function validateUploadE2eArtifactsInvocations(workflow: WorkflowRecord): if (uses.startsWith(CHECKOUT_LOCAL_UPLOAD_E2E_ARTIFACTS_ACTION)) { errors.push(`${jobName} must not load upload-e2e-artifacts from the target checkout`); } - if (uses.startsWith(UPLOAD_ARTIFACT_ACTION_PREFIX)) { + const isExactCommitCliArtifactUpload = + jobName === "generate-matrix" && + step.name === CLI_ARTIFACT_PUBLISH_STEP && + uses === CLI_ARTIFACT_UPLOAD_ACTION; + if (uses.startsWith(UPLOAD_ARTIFACT_ACTION_PREFIX) && !isExactCommitCliArtifactUpload) { errors.push(`${jobName} must not invoke actions/upload-artifact directly`); } if ( diff --git a/tools/e2e/workflow-boundary.mts b/tools/e2e/workflow-boundary.mts index 8c32e5dafe3..b0f9c3c81ce 100644 --- a/tools/e2e/workflow-boundary.mts +++ b/tools/e2e/workflow-boundary.mts @@ -7,6 +7,10 @@ import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { isDeepStrictEqual } from "node:util"; import YAML from "yaml"; +import { + CLI_ARTIFACT_RESTORE_STEP, + validateCliArtifactWorkflowBoundary, +} from "./cli-artifact-workflow-boundary.mts"; import { CREDENTIAL_FREE_TEST_TAG, discoverCredentialFreeTests, @@ -1813,9 +1817,10 @@ export function validateRebuildHermesBootstrapBoundary( const steps = asSteps(job.steps); const prepareWorkspace = requireJobStep(errors, jobName, steps, "Prepare E2E workspace"); - if (Object.keys(asRecord(prepareWorkspace?.with)).length !== 0) { - errors.push(`${jobName} workspace preparation must use the default checked-out CLI build`); + if (!isDeepStrictEqual(asRecord(prepareWorkspace?.with), { "build-cli": "false" })) { + errors.push(`${jobName} workspace preparation must defer to the exact-commit CLI artifact`); } + const restoreCli = requireJobStep(errors, jobName, steps, CLI_ARTIFACT_RESTORE_STEP); const installOpenShell = requireJobStep(errors, jobName, steps, "Install OpenShell"); requireRunContains(errors, installOpenShell, "bash scripts/install-openshell.sh"); @@ -1867,14 +1872,18 @@ export function validateRebuildHermesBootstrapBoundary( if ( prepareWorkspace && + restoreCli && installOpenShell && runVitest && !( - steps.indexOf(prepareWorkspace) < steps.indexOf(installOpenShell) && + steps.indexOf(prepareWorkspace) < steps.indexOf(restoreCli) && + steps.indexOf(restoreCli) < steps.indexOf(installOpenShell) && steps.indexOf(installOpenShell) < steps.indexOf(runVitest) ) ) { - errors.push(`${jobName} must build the CLI before installing OpenShell and running Vitest`); + errors.push( + `${jobName} must restore the exact-commit CLI before installing OpenShell and running Vitest`, + ); } return errors; } @@ -4388,6 +4397,7 @@ export function validateE2eWorkflow(workflowValue: unknown): string[] { const workflow = asRecord(workflowValue); const errors: string[] = []; errors.push(...validatePrepareE2eWorkflowBoundary(workflow)); + errors.push(...validateCliArtifactWorkflowBoundary(workflow)); errors.push(...validateUploadE2eArtifactsWorkflowBoundary(workflow)); errors.push(...validateHermesDashboardWorkflow(workflow as unknown as HermesDashboardWorkflow)); errors.push(...validateHermesGpuStartupWorkflow(workflow)); From b16bef960230b9eafaa255c9055a70ed2b1ad075 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Fri, 31 Jul 2026 17:46:20 -0700 Subject: [PATCH 03/11] ci(e2e): bind hardened artifact restore Signed-off-by: Carlos Villela --- .github/workflows/e2e.yaml | 126 +++++++++---------- test/e2e/README.md | 32 +++-- tools/e2e/cli-artifact-workflow-boundary.mts | 2 +- 3 files changed, 83 insertions(+), 77 deletions(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index c33af132aaf..d23db75f000 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -620,7 +620,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -798,7 +798,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -961,7 +961,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1012,7 +1012,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1286,7 +1286,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1418,7 +1418,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1540,7 +1540,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1652,7 +1652,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1735,7 +1735,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1780,7 +1780,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1859,7 +1859,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1917,7 +1917,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI @@ -2002,7 +2002,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Initialize runner comparison telemetry @@ -2076,7 +2076,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI @@ -2163,7 +2163,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Initialize runner comparison telemetry @@ -2227,7 +2227,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -2334,7 +2334,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -2411,7 +2411,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -2477,7 +2477,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -2526,7 +2526,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -2591,7 +2591,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -2673,7 +2673,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -2818,7 +2818,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3026,7 +3026,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3116,7 +3116,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3215,7 +3215,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3379,7 +3379,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3438,7 +3438,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3516,7 +3516,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3638,7 +3638,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3754,7 +3754,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3825,7 +3825,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3876,7 +3876,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3943,7 +3943,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4028,7 +4028,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4090,7 +4090,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4153,7 +4153,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4215,7 +4215,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4409,7 +4409,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4507,7 +4507,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4568,7 +4568,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4630,7 +4630,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4690,7 +4690,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4749,7 +4749,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4865,7 +4865,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4923,7 +4923,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5035,7 +5035,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5138,7 +5138,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5204,7 +5204,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5276,7 +5276,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5351,7 +5351,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5430,7 +5430,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5499,7 +5499,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5595,7 +5595,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5657,7 +5657,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5712,7 +5712,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5775,7 +5775,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5852,7 +5852,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5952,7 +5952,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6045,7 +6045,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6117,7 +6117,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6182,7 +6182,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6254,7 +6254,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} diff --git a/test/e2e/README.md b/test/e2e/README.md index bad7a36f2e7..d170c05266e 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -28,8 +28,11 @@ before those targets run; local runners must provide it themselves. ### Exact-Commit CLI Artifact The `generate-matrix` job owns the workflow's authoritative `npm run build:cli` invocation. -It packages `dist/` once and publishes a content-addressed artifact for 65 current consumers. -Those jobs still run the pinned preparation action for Node.js and dependency installation, but they set `build-cli: "false"`. +It packages `dist/` once and publishes a content-addressed artifact for 63 restore consumers. +Before artifact reuse, the workflow ran 63 independent CLI builds. +The shared producer reduces this total to one and eliminates 62 duplicate build invocations. +Each consumer still runs the pinned preparation action for Node.js and dependency installation. +It sets `build-cli: "false"`. For a pull request (PR) dispatch, `checkout_sha` identifies the candidate source that the live jobs test. The trusted workflow still runs from `github.workflow_sha`. @@ -39,12 +42,13 @@ The artifact name contains the candidate commit SHA and payload SHA-256 digest. The producer emits one `nemoclaw-e2e-cli-provenance-v1` JSON object through the `cli_artifact_provenance` job output. Each consumer passes that object as the restore action's only `provenance-json` input. -Each consumer invokes the repository-owned `restore-e2e-cli-artifact` composite action at full commit SHA `01f9a8da96e349717bfcd8c457e8380cf6bf3ff3`. +Each consumer invokes the repository-owned `restore-e2e-cli-artifact` composite action at a full commit SHA. The workflow does not load the action implementation from the candidate checkout. The pinned action rejects extra or missing provenance fields and compares the candidate checkout, repository, workflow SHA, run ID, and attempt before download. It then downloads by immutable artifact ID with digest mismatch handling set to `error`. It rejects a missing or malformed upload digest, a different candidate SHA, or a manifest that does not match the current source, workflow, run, toolchain contract, and payload. It also rejects an unsafe archive member, link, special file, or preexisting `dist/` directory before extraction. +Before activation, the action requires staged `dist/build-identity.json` to name the candidate commit SHA. The restored CLI must report its version through `bin/nemoclaw.js`. These checks keep the candidate source identity separate from the trusted workflow identity and fail closed before a live test runs. @@ -56,15 +60,18 @@ The pre-change baseline uses GitHub Actions composite-step timings from these wo | [30574154335](https://github.com/NVIDIA/NemoClaw/actions/runs/30574154335) | `cloud-onboard` | `385f598` | 18.793 seconds | | [30503498077](https://github.com/NVIDIA/NemoClaw/actions/runs/30503498077) | `Shared E2E (vllm-docker-storage)` | `d52d459` | 18.756 seconds | -The observed sample median is 18.756 seconds. -The implementation removes 64 duplicate build invocations from the pre-change inventory. -At the sample median, the theoretical gross reduction is `(64 × 18.756) / 60 = 20.01` runner-minutes before artifact transfer overhead. -This estimate does not predict workflow wall time because the producer dependency, runner queues, and transfer steps affect the critical path. - -Record post-change measurements only from passing CI runs. -Use comparable job selections, runner labels, and first attempts. -Record the producer build, upload, consumer download, verification, restoration, job, and workflow durations. -Sum the affected step durations for runner-minute comparison, and compare matched job and workflow elapsed times for wall-time comparison. +The three observed build steps have a median duration of 18.756 seconds. +These observations are inputs to theoretical analysis only. +The implementation replaces 63 independent builds with one producer build and eliminates 62 duplicate invocations. +Artifact upload, download, validation, and the producer dependency add runtime and can affect the workflow critical path. +Do not use the build-step median to claim savings in runner time or workflow elapsed time. + +A trusted PR E2E dispatch tests candidate code but executes `.github/workflows/e2e.yaml` from `main`. +It therefore cannot measure this workflow change before merge. +Post-change measurements require a passing post-merge `main` run. +Use matching job selections, runner labels, and first attempts. +Record the producer build, upload, consumer download, validation, activation, job, and workflow durations. +Sum the affected step durations for runner-time comparison, and compare matched job and workflow elapsed times. Identify each result by workflow run, tested commit SHA, trusted workflow SHA, and attempt. Do not substitute a theoretical value for post-change CI evidence. @@ -73,7 +80,6 @@ The retained historical fixtures have this artifact disposition: | Fixture | Disposition | | --- | --- | | `openshell-gateway-upgrade` | Keep the historical installer commit and SHA-256 digest, sandbox image digest, and reviewed OpenClaw npm URL and SHA-512 integrity in the target. The target must install the historical package before it exercises the current upgrade path. | -| `upgrade-stale-sandbox` | Keep construction of the old OpenClaw image and stale registry state inside the target. A published old image would bypass the legacy-state construction boundary. | | `rebuild-openclaw` | Keep the reviewed old-base build inside the target. The target must build and create the old sandbox before it exercises the current rebuild path. | These targets can consume the shared artifact for the current candidate CLI. diff --git a/tools/e2e/cli-artifact-workflow-boundary.mts b/tools/e2e/cli-artifact-workflow-boundary.mts index e0d308ae70e..a588e740063 100644 --- a/tools/e2e/cli-artifact-workflow-boundary.mts +++ b/tools/e2e/cli-artifact-workflow-boundary.mts @@ -18,7 +18,7 @@ export const CLI_ARTIFACT_DOWNLOAD_ACTION = export const CLI_ARTIFACT_UPLOAD_ACTION = "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a"; export const CLI_ARTIFACT_RESTORE_ACTION = - "NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@01f9a8da96e349717bfcd8c457e8380cf6bf3ff3"; + "NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246"; export const CLI_ARTIFACT_PACKAGE_STEP = "Package exact-commit CLI"; export const CLI_ARTIFACT_PUBLISH_STEP = "Publish content-addressed CLI artifact"; export const CLI_ARTIFACT_RESTORE_STEP = "Restore exact-commit CLI artifact"; From 61b85ff04887efb096c9130e3cd05cbc9eb7c991 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Fri, 31 Jul 2026 17:53:40 -0700 Subject: [PATCH 04/11] test(e2e): keep artifact fixtures linear Signed-off-by: Carlos Villela --- .../cli-artifact-workflow-boundary.test.ts | 85 ++++++++++++++----- 1 file changed, 62 insertions(+), 23 deletions(-) diff --git a/test/e2e/support/cli-artifact-workflow-boundary.test.ts b/test/e2e/support/cli-artifact-workflow-boundary.test.ts index 7cd82a280ec..89d78db8b1f 100644 --- a/test/e2e/support/cli-artifact-workflow-boundary.test.ts +++ b/test/e2e/support/cli-artifact-workflow-boundary.test.ts @@ -70,10 +70,63 @@ type RestoreFixtureOptions = { preexistingDist?: boolean; }; +type ArchiveFixtureContext = { + buildIdentitySha: string; + payload: string; + payloadRoot: string; +}; + function sha256File(file: string): string { return createHash("sha256").update(fs.readFileSync(file)).digest("hex"); } +function writeDistArchive( + context: ArchiveFixtureContext, + customizeDist: (dist: string) => void, +): void { + const dist = path.join(context.payloadRoot, "dist"); + fs.mkdirSync(dist); + fs.writeFileSync(path.join(dist, "nemoclaw.js"), 'console.log("nemoclaw v0.0.0");\n'); + fs.writeFileSync( + path.join(dist, "build-identity.json"), + `${JSON.stringify({ + nemoclawVersion: "0.0.0", + sourceRevision: context.buildIdentitySha, + })}\n`, + ); + customizeDist(dist); + execFileSync("tar", ["-cf", context.payload, "-C", context.payloadRoot, "dist"]); +} + +function writeValidArchive(context: ArchiveFixtureContext): void { + writeDistArchive(context, () => undefined); +} + +function writeLinkArchive(context: ArchiveFixtureContext): void { + writeDistArchive(context, (dist) => { + fs.symlinkSync("nemoclaw.js", path.join(dist, "linked-cli.js")); + }); +} + +function writeNonDistArchive(context: ArchiveFixtureContext): void { + fs.writeFileSync(path.join(context.payloadRoot, "outside.txt"), "outside dist\n"); + execFileSync("tar", ["-cf", context.payload, "-C", context.payloadRoot, "outside.txt"]); +} + +const ARCHIVE_FIXTURE_WRITERS = { + link: writeLinkArchive, + "non-dist": writeNonDistArchive, + valid: writeValidArchive, +} satisfies Record< + NonNullable, + (context: ArchiveFixtureContext) => void +>; + +function createPreexistingDist(workspace: string): void { + fs.mkdirSync(path.join(workspace, "dist")); + fs.writeFileSync(path.join(workspace, "dist", "existing.txt"), "preserve\n"); +} + function runRestoreValidation(options: RestoreFixtureOptions = {}) { const root = fs.mkdtempSync(path.join(os.tmpdir(), "cli-artifact-restore-")); const workspace = path.join(root, "workspace"); @@ -119,25 +172,11 @@ function runRestoreValidation(options: RestoreFixtureOptions = {}) { }).trim(); const payload = path.join(artifactDirectory, "nemoclaw-cli.tar"); - if (options.archive === "non-dist") { - fs.writeFileSync(path.join(payloadRoot, "outside.txt"), "outside dist\n"); - execFileSync("tar", ["-cf", payload, "-C", payloadRoot, "outside.txt"]); - } else { - const dist = path.join(payloadRoot, "dist"); - fs.mkdirSync(dist); - fs.writeFileSync(path.join(dist, "nemoclaw.js"), 'console.log("nemoclaw v0.0.0");\n'); - fs.writeFileSync( - path.join(dist, "build-identity.json"), - `${JSON.stringify({ - nemoclawVersion: "0.0.0", - sourceRevision: options.buildIdentitySha ?? candidateSha, - })}\n`, - ); - if (options.archive === "link") { - fs.symlinkSync("nemoclaw.js", path.join(dist, "linked-cli.js")); - } - execFileSync("tar", ["-cf", payload, "-C", payloadRoot, "dist"]); - } + ARCHIVE_FIXTURE_WRITERS[options.archive ?? "valid"]({ + buildIdentitySha: options.buildIdentitySha ?? candidateSha, + payload, + payloadRoot, + }); const actualPayloadSha256 = sha256File(payload); const expectedPayloadSha256 = options.expectedPayloadSha256 ?? actualPayloadSha256; @@ -172,10 +211,10 @@ function runRestoreValidation(options: RestoreFixtureOptions = {}) { `#!/usr/bin/env bash\nset -euo pipefail\nif [[ "$#" -eq 1 && "$1" == "--version" ]]; then\n echo v22.23.1\n exit 0\nfi\nexec ${JSON.stringify(process.execPath)} "$@"\n`, { mode: 0o755 }, ); - if (options.preexistingDist) { - fs.mkdirSync(path.join(workspace, "dist")); - fs.writeFileSync(path.join(workspace, "dist", "existing.txt"), "preserve\n"); - } + const prepareWorkspace = options.preexistingDist + ? createPreexistingDist + : (_workspace: string) => undefined; + prepareWorkspace(workspace); const action = readYaml(".github/actions/restore-e2e-cli-artifact/action.yaml"); const result = spawnSync("bash", ["-c", action.runs.steps[2]!.run!], { From 849f9951c5843f500f9583c5017e1a66344ef3d3 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Fri, 31 Jul 2026 18:15:19 -0700 Subject: [PATCH 05/11] test(e2e): cover artifact provenance mismatches Signed-off-by: Carlos Villela --- .../e2e/support/cli-artifact-workflow-boundary.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/e2e/support/cli-artifact-workflow-boundary.test.ts b/test/e2e/support/cli-artifact-workflow-boundary.test.ts index 89d78db8b1f..02532b89b0e 100644 --- a/test/e2e/support/cli-artifact-workflow-boundary.test.ts +++ b/test/e2e/support/cli-artifact-workflow-boundary.test.ts @@ -284,6 +284,16 @@ describe("exact-commit CLI artifact workflow boundary", () => { expect(runIdentityValidation(overrides).status).not.toBe(0); }); + it.each([ + ["workflow SHA", { workflowSha: "e".repeat(40) }, "consumer and producer workflow SHAs differ"], + ["run ID", { runId: "98766" }, "consumer and producer workflow run identities differ"], + ["run attempt", { runAttempt: "2" }, "consumer and producer workflow run identities differ"], + ])("rejects a mismatched %s before artifact download", (_case, overrides, expectedError) => { + const result = runIdentityValidation(overrides); + expect(result.status, `${result.stdout}${result.stderr}`).not.toBe(0); + expect(`${result.stdout}${result.stderr}`).toContain(expectedError); + }); + it("restores a payload whose compiled identity matches the candidate commit (#7915)", () => { const fixture = runRestoreValidation(); try { From e0840fc578e69197f5fdc5fff68a5f64669a40f6 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Fri, 31 Jul 2026 18:33:35 -0700 Subject: [PATCH 06/11] test(e2e): cover artifact repository provenance Signed-off-by: Carlos Villela --- test/e2e/support/cli-artifact-workflow-boundary.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/e2e/support/cli-artifact-workflow-boundary.test.ts b/test/e2e/support/cli-artifact-workflow-boundary.test.ts index 02532b89b0e..6ac831dbcff 100644 --- a/test/e2e/support/cli-artifact-workflow-boundary.test.ts +++ b/test/e2e/support/cli-artifact-workflow-boundary.test.ts @@ -285,6 +285,11 @@ describe("exact-commit CLI artifact workflow boundary", () => { }); it.each([ + [ + "candidate repository", + { candidateRepository: "example/other-repository" }, + "consumer checkout repository does not match producer provenance", + ], ["workflow SHA", { workflowSha: "e".repeat(40) }, "consumer and producer workflow SHAs differ"], ["run ID", { runId: "98766" }, "consumer and producer workflow run identities differ"], ["run attempt", { runAttempt: "2" }, "consumer and producer workflow run identities differ"], From 1a5b5bc944ab7ba891f1c8b7fec55c80461618ab Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Fri, 31 Jul 2026 18:45:59 -0700 Subject: [PATCH 07/11] test(e2e): cover artifact archive traversal Signed-off-by: Carlos Villela --- .../cli-artifact-workflow-boundary.test.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/test/e2e/support/cli-artifact-workflow-boundary.test.ts b/test/e2e/support/cli-artifact-workflow-boundary.test.ts index 6ac831dbcff..116732a0fab 100644 --- a/test/e2e/support/cli-artifact-workflow-boundary.test.ts +++ b/test/e2e/support/cli-artifact-workflow-boundary.test.ts @@ -63,7 +63,7 @@ function workflowFixture(): Workflow { } type RestoreFixtureOptions = { - archive?: "valid" | "non-dist" | "link"; + archive?: "valid" | "non-dist" | "link" | "traversal"; buildIdentitySha?: string; expectedPayloadSha256?: string; manifestCandidateSha?: string; @@ -113,9 +113,22 @@ function writeNonDistArchive(context: ArchiveFixtureContext): void { execFileSync("tar", ["-cf", context.payload, "-C", context.payloadRoot, "outside.txt"]); } +function writeTraversalArchive(context: ArchiveFixtureContext): void { + fs.writeFileSync(path.join(context.payloadRoot, "outside.txt"), "outside dist\n"); + execFileSync("tar", [ + "-cf", + context.payload, + "--transform=s|^outside.txt$|dist/../outside.txt|", + "-C", + context.payloadRoot, + "outside.txt", + ]); +} + const ARCHIVE_FIXTURE_WRITERS = { link: writeLinkArchive, "non-dist": writeNonDistArchive, + traversal: writeTraversalArchive, valid: writeValidArchive, } satisfies Record< NonNullable, @@ -339,6 +352,10 @@ describe("exact-commit CLI artifact workflow boundary", () => { ); }); + it("rejects traversal through a dist-prefixed archive member before extraction (#7915)", () => { + expectRestoreFailure({ archive: "traversal" }, "CLI artifact contains traversal"); + }); + it("rejects an archive link before artifact extraction (#7915)", () => { expectRestoreFailure({ archive: "link" }, "CLI artifact contains a link or special file"); }); From 7d9477d96bb11f388966a4d7af7cb8ebed868f4b Mon Sep 17 00:00:00 2001 From: Charan Jagwani Date: Wed, 5 Aug 2026 02:09:37 -0700 Subject: [PATCH 08/11] fix(e2e): harden exact-commit artifact restore --- .github/workflows/e2e.yaml | 124 +++++++++--------- test/e2e/README.md | 12 +- .../cli-artifact-workflow-boundary.test.ts | 8 +- tools/e2e/cli-artifact-workflow-boundary.mts | 8 +- tools/e2e/prepare-e2e-workflow-boundary.mts | 16 ++- 5 files changed, 94 insertions(+), 74 deletions(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index a30d0a52410..9dbddbc0258 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -620,7 +620,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -798,7 +798,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -961,7 +961,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1012,7 +1012,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1286,7 +1286,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1418,7 +1418,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1540,7 +1540,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1652,7 +1652,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1735,7 +1735,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1780,7 +1780,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1859,7 +1859,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1917,7 +1917,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI @@ -2876,7 +2876,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Initialize runner comparison telemetry @@ -2950,7 +2950,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI @@ -3037,7 +3037,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Initialize runner comparison telemetry @@ -3101,7 +3101,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3208,7 +3208,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3285,7 +3285,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3351,7 +3351,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3400,7 +3400,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3465,7 +3465,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3547,7 +3547,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3692,7 +3692,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3900,7 +3900,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3990,7 +3990,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4089,7 +4089,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4253,7 +4253,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4312,7 +4312,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4390,7 +4390,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4512,7 +4512,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4628,7 +4628,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4679,7 +4679,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4746,7 +4746,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4831,7 +4831,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4893,7 +4893,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4956,7 +4956,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5018,7 +5018,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5212,7 +5212,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5310,7 +5310,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5371,7 +5371,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5433,7 +5433,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5493,7 +5493,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5552,7 +5552,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5668,7 +5668,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5726,7 +5726,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5838,7 +5838,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5941,7 +5941,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6007,7 +6007,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6079,7 +6079,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6154,7 +6154,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6233,7 +6233,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6302,7 +6302,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6398,7 +6398,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6480,7 +6480,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6535,7 +6535,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6598,7 +6598,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6675,7 +6675,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6775,7 +6775,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6868,7 +6868,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6940,7 +6940,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -7005,7 +7005,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -7077,7 +7077,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246 + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} diff --git a/test/e2e/README.md b/test/e2e/README.md index 81b072a030e..c12a7cce8ea 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -25,12 +25,14 @@ before those targets run; local runners must provide it themselves. ### Exact-Commit CLI Artifact -The `generate-matrix` job owns the workflow's authoritative `npm run build:cli` invocation. -It packages `dist/` once and publishes a content-addressed artifact for 63 restore consumers. -Before artifact reuse, the workflow ran 63 independent CLI builds. -The shared producer reduces this total to one and eliminates 62 duplicate build invocations. +The `generate-matrix` job owns the workflow's authoritative candidate CLI build. +It packages `dist/` once and publishes a content-addressed artifact for 62 consumer jobs. +Before artifact reuse, those 62 jobs each built the candidate CLI. +The shared producer retains one candidate build and eliminates 61 duplicate build invocations. Each consumer still runs the pinned preparation action for Node.js and dependency installation. Each consumer sets `build-cli: "false"` on that action. +The `managed-image-protected-runtime` qualification is separate. +It builds the trusted workflow checkout and never executes or restores candidate CLI code. For a pull request (PR) dispatch, `checkout_sha` identifies the candidate source that the live jobs test. The trusted workflow still runs from `github.workflow_sha`. @@ -60,7 +62,7 @@ The pre-change baseline uses GitHub Actions composite-step timings from these wo The three observed build steps have a median duration of 18.756 seconds. These observations are inputs to theoretical analysis only. -The implementation replaces 63 independent builds with one producer build and eliminates 62 duplicate invocations. +The implementation replaces 62 independent candidate-workspace builds with one producer build and eliminates 61 duplicate invocations. Artifact upload, download, validation, and the producer dependency add runtime and can affect the workflow critical path. Do not use the build-step median to claim savings in runner time or workflow elapsed time. diff --git a/test/e2e/support/cli-artifact-workflow-boundary.test.ts b/test/e2e/support/cli-artifact-workflow-boundary.test.ts index 4e139ff86c4..87b18282646 100644 --- a/test/e2e/support/cli-artifact-workflow-boundary.test.ts +++ b/test/e2e/support/cli-artifact-workflow-boundary.test.ts @@ -119,10 +119,14 @@ function writeNonDistArchive(context: ArchiveFixtureContext): void { function writeTraversalArchive(context: ArchiveFixtureContext): void { fs.writeFileSync(path.join(context.payloadRoot, "outside.txt"), "outside dist\n"); + const transform = + process.platform === "darwin" + ? ["-s", "|^outside.txt$|dist/../outside.txt|"] + : ["--transform=s|^outside.txt$|dist/../outside.txt|"]; execFileSync("tar", [ "-cf", context.payload, - "--transform=s|^outside.txt$|dist/../outside.txt|", + ...transform, "-C", context.payloadRoot, "outside.txt", @@ -325,7 +329,7 @@ describe("exact-commit CLI artifact workflow boundary", () => { [ "different candidate SHA", { candidateSha: "e".repeat(40) }, - "consumer checkout does not match the producer candidate SHA", + "producer CLI artifact provenance is invalid", ], [ "unbound artifact name", diff --git a/tools/e2e/cli-artifact-workflow-boundary.mts b/tools/e2e/cli-artifact-workflow-boundary.mts index 618734ad336..2c1373e6126 100644 --- a/tools/e2e/cli-artifact-workflow-boundary.mts +++ b/tools/e2e/cli-artifact-workflow-boundary.mts @@ -11,6 +11,7 @@ import { CLI_ARTIFACT_PRODUCER_JOB, PREPARE_E2E_ACTION, PREPARE_E2E_NO_BUILD_JOBS, + PREPARE_E2E_TRUSTED_BUILD_JOBS, } from "./prepare-e2e-workflow-boundary.mts"; export const CLI_ARTIFACT_DOWNLOAD_ACTION = @@ -18,7 +19,7 @@ export const CLI_ARTIFACT_DOWNLOAD_ACTION = export const CLI_ARTIFACT_UPLOAD_ACTION = "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a"; export const CLI_ARTIFACT_RESTORE_ACTION = - "NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@e5a55a8be89d4a3dfd44b743c7190544ef2f5246"; + "NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@4b911e40f3f41ce500d69330ca9280b79ceede0f"; export const CLI_ARTIFACT_PACKAGE_STEP = "Package exact-commit CLI"; export const CLI_ARTIFACT_PUBLISH_STEP = "Publish content-addressed CLI artifact"; export const CLI_ARTIFACT_RESTORE_STEP = "Restore exact-commit CLI artifact"; @@ -32,7 +33,7 @@ const DEFAULT_RESTORE_ACTION_PATH = join( "action.yaml", ); const RESTORE_ACTION_CONTENT_SHA256 = - "9b289c07e932bd882d9e3db80a052fc3adf40102f0239da50410b171feda1765"; + "bae2b6b99b46f7d007be9b792d7179fbef30c38d31623849e025d7341f179d0d"; const CLI_ARTIFACT_DOWNLOAD_STEP = "Download exact-commit CLI artifact"; const CLI_ARTIFACT_VERIFY_STEP = "Verify and restore exact-commit CLI artifact"; const CLI_ARTIFACT_PROVENANCE_STEP = "Record CLI artifact provenance"; @@ -353,7 +354,8 @@ export function validateCliArtifactWorkflowBoundary( const shouldConsume = usesPrepare && jobName !== CLI_ARTIFACT_PRODUCER_JOB && - !PREPARE_E2E_NO_BUILD_JOBS.has(jobName); + !PREPARE_E2E_NO_BUILD_JOBS.has(jobName) && + !PREPARE_E2E_TRUSTED_BUILD_JOBS.has(jobName); if (shouldConsume) { validateConsumer(errors, jobName, job, jobSteps); } else if (artifactSteps.length > 0) { diff --git a/tools/e2e/prepare-e2e-workflow-boundary.mts b/tools/e2e/prepare-e2e-workflow-boundary.mts index fad3397b04e..53f3c3dc98a 100644 --- a/tools/e2e/prepare-e2e-workflow-boundary.mts +++ b/tools/e2e/prepare-e2e-workflow-boundary.mts @@ -36,6 +36,8 @@ export const PREPARE_E2E_NO_BUILD_JOBS = new Set([ "spark-install", ]); +export const PREPARE_E2E_TRUSTED_BUILD_JOBS = new Set(["managed-image-protected-runtime"]); + type WorkflowRecord = Record; type WorkflowStep = WorkflowRecord & { name?: string; @@ -152,9 +154,14 @@ export function validatePrepareE2eInvocations(workflow: WorkflowRecord): string[ errors.push(`${jobName} prepare-e2e step must be named '${PREPARE_E2E_STEP}'`); } const withInputs = record(prepare.with); - const shouldBuild = jobName === CLI_ARTIFACT_PRODUCER_JOB; + const shouldBuild = + jobName === CLI_ARTIFACT_PRODUCER_JOB || PREPARE_E2E_TRUSTED_BUILD_JOBS.has(jobName); if (shouldBuild && Object.keys(withInputs).length !== 0) { - errors.push(`${jobName} prepare-e2e must own the only default CLI build`); + errors.push( + jobName === CLI_ARTIFACT_PRODUCER_JOB + ? `${jobName} prepare-e2e must own the only default CLI build` + : `${jobName} prepare-e2e must use its default trusted CLI build`, + ); } if (!shouldBuild && !isDeepStrictEqual(withInputs, { "build-cli": "false" })) { errors.push(`${jobName} prepare-e2e must set build-cli to false`); @@ -183,6 +190,11 @@ export function validatePrepareE2eInvocations(workflow: WorkflowRecord): string[ for (const jobName of PREPARE_E2E_NO_BUILD_JOBS) { if (!expectedJobs.has(jobName)) errors.push(`prepare-e2e no-build job is missing: ${jobName}`); } + for (const jobName of PREPARE_E2E_TRUSTED_BUILD_JOBS) { + if (!expectedJobs.has(jobName)) { + errors.push(`prepare-e2e trusted-build job is missing: ${jobName}`); + } + } return errors; } From 2017982648b9efb94b8557acf3485017d395b0b0 Mon Sep 17 00:00:00 2001 From: Charan Jagwani Date: Wed, 5 Aug 2026 02:14:26 -0700 Subject: [PATCH 09/11] chore(ci): lower stale architecture budgets --- ci/source-architecture-budget.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ci/source-architecture-budget.json b/ci/source-architecture-budget.json index 4e3f1dacd40..dc2d40b9100 100644 --- a/ci/source-architecture-budget.json +++ b/ci/source-architecture-budget.json @@ -27,7 +27,7 @@ "src/lib/runner.ts": 88, "src/lib/security/redact.ts": 51, "src/lib/state/onboard-session.ts": 36, - "src/lib/state/registry.ts": 99, + "src/lib/state/registry.ts": 98, "src/lib/state/state-root.ts": 22, "src/lib/subprocess-env.ts": 24, "src/lib/validation.ts": 25 @@ -47,7 +47,7 @@ "src/lib/actions/uninstall/run-plan.ts": 26, "src/lib/inference/onboard-probes.ts": 21, "src/lib/inference/vllm.ts": 21, - "src/lib/onboard.ts": 222, + "src/lib/onboard.ts": 219, "src/lib/onboard/machine/handlers/sandbox.ts": 21, "src/lib/sandbox/config.ts": 22, "src/lib/shields/index.ts": 23 @@ -55,7 +55,7 @@ }, "allowedCycles": [], "maxRootFiles": { - "src/lib/onboard": 308, + "src/lib/onboard": 307, "src/lib/actions": 19, "src/lib/actions/sandbox": 184, "src/lib/state": 37, From ed788a167ad51847a84166fc99aed7da5d60589a Mon Sep 17 00:00:00 2001 From: Charan Jagwani Date: Wed, 5 Aug 2026 02:30:27 -0700 Subject: [PATCH 10/11] test(e2e): keep artifact fixtures branchless --- .../cli-artifact-workflow-boundary.test.ts | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/test/e2e/support/cli-artifact-workflow-boundary.test.ts b/test/e2e/support/cli-artifact-workflow-boundary.test.ts index 87b18282646..096f48fbca6 100644 --- a/test/e2e/support/cli-artifact-workflow-boundary.test.ts +++ b/test/e2e/support/cli-artifact-workflow-boundary.test.ts @@ -143,19 +143,26 @@ const ARCHIVE_FIXTURE_WRITERS = { (context: ArchiveFixtureContext) => void >; -function createPreexistingDist( - workspace: string, - preexistingDist: NonNullable, -): void { +function writeDanglingDistSymlink(workspace: string): void { + const dist = path.join(workspace, "dist"); + fs.symlinkSync("missing-dist", dist); +} + +function writePreexistingDistDirectory(workspace: string): void { const dist = path.join(workspace, "dist"); - if (preexistingDist === "dangling-symlink") { - fs.symlinkSync("missing-dist", dist); - return; - } fs.mkdirSync(dist); fs.writeFileSync(path.join(workspace, "dist", "existing.txt"), "preserve\n"); } +const PREEXISTING_DIST_WRITERS = { + "dangling-symlink": writeDanglingDistSymlink, + directory: writePreexistingDistDirectory, + none: () => undefined, +} satisfies Record< + NonNullable | "none", + (workspace: string) => void +>; + function runRestoreValidation(options: RestoreFixtureOptions = {}) { const root = fs.mkdtempSync(path.join(os.tmpdir(), "cli-artifact-restore-")); const workspace = path.join(root, "workspace"); @@ -240,7 +247,7 @@ function runRestoreValidation(options: RestoreFixtureOptions = {}) { `#!/usr/bin/env bash\nset -euo pipefail\nif [[ "$#" -eq 1 && "$1" == "--version" ]]; then\n echo v22.23.1\n exit 0\nfi\nexec ${JSON.stringify(process.execPath)} "$@"\n`, { mode: 0o755 }, ); - if (options.preexistingDist) createPreexistingDist(workspace, options.preexistingDist); + PREEXISTING_DIST_WRITERS[options.preexistingDist ?? "none"](workspace); const action = readYaml(".github/actions/restore-e2e-cli-artifact/action.yaml"); const result = spawnSync("bash", ["-c", action.runs.steps[2]!.run!], { From 7a60b5214afae8c6931707d15898724bf853cfee Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Wed, 5 Aug 2026 03:46:35 -0700 Subject: [PATCH 11/11] docs(e2e): clarify CLI artifact trust boundary --- .github/workflows/e2e.yaml | 6 +- test/e2e/README.md | 126 +++++++++++------- .../cli-artifact-workflow-boundary.test.ts | 6 +- .../prepare-e2e-workflow-boundary.test.ts | 2 +- ...unner-comparison-workflow-boundary.test.ts | 8 +- tools/e2e/cli-artifact-workflow-boundary.mts | 4 +- .../runner-comparison-workflow-boundary.mts | 6 +- 7 files changed, 97 insertions(+), 61 deletions(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 9dbddbc0258..8173c20f284 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -393,16 +393,16 @@ jobs: [[ "$RUN_ATTEMPT" =~ ^[1-9][0-9]*$ && "$RUN_ID" =~ ^[1-9][0-9]*$ ]] || { echo "::error::workflow run identity is invalid"; exit 1; } test -s dist/nemoclaw.js || - { echo "::error::authoritative CLI build did not produce dist/nemoclaw.js"; exit 1; } + { echo "::error::candidate CLI build did not produce dist/nemoclaw.js"; exit 1; } test -s dist/build-identity.json || - { echo "::error::authoritative CLI build did not produce dist/build-identity.json"; exit 1; } + { echo "::error::candidate CLI build did not produce dist/build-identity.json"; exit 1; } jq -e --arg candidateSha "$CANDIDATE_SHA" ' type == "object" and (keys | sort) == ["nemoclawVersion", "sourceRevision"] and (.nemoclawVersion | strings | length > 0) and .sourceRevision == $candidateSha ' dist/build-identity.json >/dev/null || - { echo "::error::authoritative CLI build identity does not match the candidate SHA"; exit 1; } + { echo "::error::candidate CLI build identity does not match the candidate commit SHA"; exit 1; } artifact_dir="${RUNNER_TEMP}/nemoclaw-cli-artifact" install -d -m 0700 "$artifact_dir" diff --git a/test/e2e/README.md b/test/e2e/README.md index c12a7cce8ea..bf1815f95b0 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -23,36 +23,58 @@ before those targets run; local runners must provide it themselves. ## CI execution shape -### Exact-Commit CLI Artifact - -The `generate-matrix` job owns the workflow's authoritative candidate CLI build. -It packages `dist/` once and publishes a content-addressed artifact for 62 consumer jobs. -Before artifact reuse, those 62 jobs each built the candidate CLI. -The shared producer retains one candidate build and eliminates 61 duplicate build invocations. -Each consumer still runs the pinned preparation action for Node.js and dependency installation. -Each consumer sets `build-cli: "false"` on that action. -The `managed-image-protected-runtime` qualification is separate. -It builds the trusted workflow checkout and never executes or restores candidate CLI code. - -For a pull request (PR) dispatch, `checkout_sha` identifies the candidate source that the live jobs test. -The trusted workflow still runs from `github.workflow_sha`. -Direct scheduled and manual runs use `github.sha` as the candidate when `checkout_sha` is empty. -The artifact manifest records both identities, the source tree, lockfile digest, workflow run and attempt, Node.js and npm versions, runner platform, build command, and payload digest. +### Candidate CLI Artifact + +The candidate CLI comes from the source commit that an E2E run tests. +The `generate-matrix` job builds it once and publishes `dist/` as a content-addressed artifact. +The workflow has 62 artifact-using job definitions. +Each selected job execution restores the artifact instead of running `npm run build:cli`. +Each selected job still runs the pinned preparation action to install Node.js and project dependencies. +It sets `build-cli: "false"` so the preparation action does not rebuild the CLI. +The `managed-image-protected-runtime` qualification does not use this artifact. +It builds the CLI from the trusted workflow checkout and never executes or restores the candidate CLI. + +#### Artifact Identity + +For a pull request (PR) run, `checkout_sha` identifies the candidate source commit. +The trusted workflow runs from `github.workflow_sha`. +A scheduled or manual run uses `github.sha` when `checkout_sha` is empty. + +The artifact manifest records these values: + +- The candidate repository and commit SHA. +- The trusted workflow SHA, run ID, and attempt. +- The source tree and lockfile digests. +- The Node.js and npm versions, runner platform, and build command. +- The payload digest. + The artifact name contains the candidate commit SHA and payload SHA-256 digest. -The producer emits one `nemoclaw-e2e-cli-provenance-v1` JSON object through the `cli_artifact_provenance` job output. -Each consumer passes that object as the restore action's only `provenance-json` input. +The `generate-matrix` job emits one `nemoclaw-e2e-cli-provenance-v1` JSON object through its `cli_artifact_provenance` output. +Each artifact-using job passes that object as the restore action's only `provenance-json` input. -Each consumer invokes the repository-owned `restore-e2e-cli-artifact` composite action at a full commit SHA. +Each artifact-using job invokes the repository-owned `restore-e2e-cli-artifact` composite action at a full commit SHA. The workflow does not load the action implementation from the candidate checkout. -The pinned action rejects extra or missing provenance fields and compares the candidate checkout, repository, workflow SHA, run ID, and attempt before download. -It then downloads by immutable artifact ID with digest mismatch handling set to `error`. -It rejects a missing or malformed upload digest, a different candidate SHA, or a manifest that does not match the current source, workflow, run, toolchain contract, and payload. -It also rejects an unsafe archive member, link, special file, or preexisting `dist/` directory before extraction. -Before activation, the action requires staged `dist/build-identity.json` to name the candidate commit SHA. -The restored CLI must report its version through `bin/nemoclaw.js`. -These checks keep the candidate source identity separate from the trusted workflow identity and fail closed before a live test runs. +Before download, the action rejects extra or missing provenance fields. +It also compares the candidate checkout, repository, workflow SHA, run ID, and attempt with the provenance object. +The action downloads the artifact by immutable ID and sets digest mismatch handling to `error`. + +Before the action restores `dist/` into the workspace, it verifies these conditions: -The pre-change baseline uses GitHub Actions composite-step timings from these workflow runs: +- The upload digest is present and well formed. +- The candidate SHA matches the expected commit. +- The manifest matches the source, workflow run, toolchain contract, and payload. +- The archive contains no path traversal, links, special files, or files outside `dist/`. +- The candidate checkout has no preexisting `dist/` path. +- The staged `dist/build-identity.json` names the candidate commit SHA. + +If a pre-restore check fails, the action stops before it moves `dist/` into the workspace. +After the checks pass, the action moves `dist/` and runs `bin/nemoclaw.js --version`. +If the version command fails, the action stops before the live test runs. +This boundary keeps candidate source separate from the trusted workflow implementation. + +#### Timing Baseline + +The pre-change baseline uses GitHub Actions `Build CLI` step timings from these workflow runs: | Workflow run | Job | Tested candidate | `Build CLI` duration | | --- | --- | --- | --- | @@ -61,30 +83,37 @@ The pre-change baseline uses GitHub Actions composite-step timings from these wo | [30503498077](https://github.com/NVIDIA/NemoClaw/actions/runs/30503498077) | `Shared E2E (vllm-docker-storage)` | `d52d459` | 18.756 seconds | The three observed build steps have a median duration of 18.756 seconds. -These observations are inputs to theoretical analysis only. -The implementation replaces 62 independent candidate-workspace builds with one producer build and eliminates 61 duplicate invocations. -Artifact upload, download, validation, and the producer dependency add runtime and can affect the workflow critical path. +This baseline measures only the replaced build step. +Artifact upload, download, validation, and the dependency on `generate-matrix` add runtime and can affect the workflow critical path. Do not use the build-step median to claim savings in runner time or workflow elapsed time. -A trusted PR E2E dispatch tests candidate code but executes `.github/workflows/e2e.yaml` from `main`. -It therefore cannot measure this workflow change before merge. -Post-change measurements require a passing post-merge `main` run. -Use matching job selections, runner labels, and first attempts. -Record the producer build, upload, consumer download, validation, activation, job, and workflow durations. -Sum the affected step durations for runner-time comparison, and compare matched job and workflow elapsed times. -Identify each result by workflow run, tested commit SHA, trusted workflow SHA, and attempt. +A trusted PR E2E run tests candidate code but executes `.github/workflows/e2e.yaml` from `main`. +The PR run cannot measure this workflow change before merge. +After merge, use a passing `main` run and complete these steps: + +1. Match the job selection, runner labels, and first attempt to the baseline. +2. Record durations for the candidate build, artifact upload, artifact download, combined verification and restore step, job, and workflow. +3. Sum affected step durations for runner-time comparison. +4. Compare matched job and workflow elapsed times. +5. Identify each result by workflow run, tested commit SHA, trusted workflow SHA, and attempt. + Do not substitute a theoretical value for post-change CI evidence. -The retained historical fixtures have this artifact disposition: +#### Historical Fixtures + +The historical fixtures retain these version boundaries: -| Fixture | Disposition | +| Fixture | Required boundary | | --- | --- | -| `openshell-gateway-upgrade` | Keep the historical installer commit and SHA-256 digest, sandbox image digest, and reviewed OpenClaw npm URL and SHA-512 integrity in the target. The target must install the historical package before it exercises the current upgrade path. | -| `rebuild-openclaw` | Keep the reviewed old-base build inside the target. The target must build and create the old sandbox before it exercises the current rebuild path. | +| `openshell-gateway-upgrade` | Retain the historical installer commit and SHA-256 digest, sandbox image digest, and reviewed OpenClaw npm URL and SHA-512 integrity. Install the historical package before testing the candidate upgrade path. | +| `rebuild-openclaw` | Retain the reviewed old-base build in the target. Build and create the old sandbox before testing the candidate rebuild path. | + +These targets may restore the shared artifact for the candidate CLI. +They must not replace a historical installer, package, image, or version boundary with that artifact. +The gateway fixture already binds its remote historical inputs to immutable commits and cryptographic digests. +The workflow does not republish those inputs as artifacts. -These targets can consume the shared artifact for the current candidate CLI. -They must not use it to replace a historical installer, package, image, or version boundary that the target tests. -The gateway fixture's remote historical inputs are already bound to immutable commits and cryptographic digests, so this change does not republish them as workflow artifacts. +### Hermes Sandbox Image Artifact The sandbox image workflow builds the Hermes production image in the dedicated 30-minute `build-hermes-sandbox-image` job. It uses full-SHA-pinned Buildx @@ -414,8 +443,7 @@ larger runner. Each execution writes one bounded, ordered v2 time series to the canonical `runner-comparison.jsonl` ledger. It contains: -- an `initialize` endpoint after workspace preparation and any fixed-capacity - rebuild swap; +- an `initialize` endpoint after exact-commit artifact restoration, or after workspace preparation for `security-posture`; the rebuild jobs initialize after their fixed-capacity swap; - a distinct `scenario-start` for every test handled by the execution; - a `periodic` sample on an approximately 15-second fixed cadence for `rebuild-hermes` and `rebuild-hermes-stale-base`, and an approximately @@ -469,8 +497,12 @@ selection and ranking observation; `breakdown.vmRssKb` is the immediately following procfs observation and may differ when a live process changes memory. The finalizer validates the complete ledger before writing -`runner-comparison-summary.json`. The v2 summary reports the sampled -post-prepare window; CPU average and busiest interval; one-minute load; +`runner-comparison-summary.json`. The v2 summary reports the sampled window from +`initialize` until immediately before artifact scanning or upload. For artifact-using +jobs, initialization follows artifact restoration and any required rebuild swap. For +the Hermes `security-posture` shard, initialization follows workspace preparation, +so the window includes OpenShell installation and installer-backed NemoClaw setup. +The summary reports CPU average and busiest interval; one-minute load; available, cached, reclaimable, swap, root-cgroup current/peak/limit, and endpoint OOM-counter evidence; memory and I/O pressure; workspace bytes and inodes; Docker image, container, and build-cache usage; largest container diff --git a/test/e2e/support/cli-artifact-workflow-boundary.test.ts b/test/e2e/support/cli-artifact-workflow-boundary.test.ts index 096f48fbca6..1015e91df8d 100644 --- a/test/e2e/support/cli-artifact-workflow-boundary.test.ts +++ b/test/e2e/support/cli-artifact-workflow-boundary.test.ts @@ -295,7 +295,7 @@ function requireStep(workflow: Workflow, jobName: string, stepName: string) { } describe("exact-commit CLI artifact workflow boundary", () => { - it("builds once and gives every build-backed E2E job the verified artifact", () => { + it("builds the candidate CLI once and requires every artifact-using job to restore it", () => { expect(validateCliArtifactWorkflowBoundary(readWorkflow())).toEqual([]); }); @@ -316,7 +316,7 @@ describe("exact-commit CLI artifact workflow boundary", () => { } }); - it("accepts an exact producer and consumer identity", () => { + it("accepts matching artifact, candidate source, and workflow identities", () => { const result = runIdentityValidation(); expect(result.status, result.stderr).toBe(0); }); @@ -526,7 +526,7 @@ describe("exact-commit CLI artifact workflow boundary", () => { ); }); - it("keeps installer-backed no-build jobs outside the artifact handoff", () => { + it("excludes installer-backed jobs from the shared CLI artifact", () => { const workflow = workflowFixture(); const inheritedRestore = requireStep(workflow, "sandbox-operations", CLI_ARTIFACT_RESTORE_STEP); workflow.jobs["security-posture"].steps!.push(inheritedRestore); diff --git a/test/e2e/support/prepare-e2e-workflow-boundary.test.ts b/test/e2e/support/prepare-e2e-workflow-boundary.test.ts index 5c9cd0b6193..c6891de04a9 100644 --- a/test/e2e/support/prepare-e2e-workflow-boundary.test.ts +++ b/test/e2e/support/prepare-e2e-workflow-boundary.test.ts @@ -27,7 +27,7 @@ type Workflow = { }; describe("prepare-e2e workflow boundary", () => { - it("keeps one canonical bootstrap invocation and one authoritative CLI build", () => { + it("requires one workspace preparation step per E2E job and one candidate CLI build in generate-matrix", () => { expect(validatePrepareE2eAction()).toEqual([]); expect(validatePrepareE2eInvocations(readWorkflow())).toEqual([]); }); diff --git a/test/e2e/support/runner-comparison-workflow-boundary.test.ts b/test/e2e/support/runner-comparison-workflow-boundary.test.ts index 8b896bb983a..e7df1699515 100644 --- a/test/e2e/support/runner-comparison-workflow-boundary.test.ts +++ b/test/e2e/support/runner-comparison-workflow-boundary.test.ts @@ -148,7 +148,9 @@ describe("runner comparison E2E workflow boundary (#7140)", () => { ); }); - it.each(JOBS)("keeps %s telemetry around the entire post-prepare job", (jobId) => { + it.each( + JOBS, + )("places %s telemetry after workspace preparation, artifact restore when used, and required rebuild swap, and before artifact scanning or upload", (jobId) => { const lateInitialize = loadWorkflow(); const lateSteps = lateInitialize.jobs[jobId]!.steps; const initializeIndex = lateSteps.indexOf( @@ -160,7 +162,9 @@ describe("runner comparison E2E workflow boundary (#7140)", () => { ]; const expectedInitializeError = REBUILD_JOBS.includes(jobId as (typeof REBUILD_JOBS)[number]) ? `${jobId} must establish rebuild swap before initializing runner comparison telemetry` - : `${jobId} must initialize runner comparison telemetry immediately after CLI bootstrap`; + : jobId === "security-posture" + ? `${jobId} must initialize runner comparison telemetry immediately after workspace preparation` + : `${jobId} must initialize runner comparison telemetry immediately after CLI artifact restore`; expect(validateRunnerComparisonWorkflow(lateInitialize)).toContain(expectedInitializeError); const afterPublication = loadWorkflow(); diff --git a/tools/e2e/cli-artifact-workflow-boundary.mts b/tools/e2e/cli-artifact-workflow-boundary.mts index 2c1373e6126..72272013c18 100644 --- a/tools/e2e/cli-artifact-workflow-boundary.mts +++ b/tools/e2e/cli-artifact-workflow-boundary.mts @@ -205,7 +205,7 @@ function validateProducer(errors: string[], producer: WorkflowRecord): void { if (!packageStep || !uploadStep || !provenanceStep) return; if (packageStep.id !== "package_cli_artifact" || packageStep.shell !== "bash") { - errors.push("CLI artifact package step must use the canonical id and bash shell"); + errors.push("CLI artifact package step must use id package_cli_artifact and the Bash shell"); } if ( !isDeepStrictEqual(record(packageStep.env), { @@ -225,7 +225,7 @@ function validateProducer(errors: string[], producer: WorkflowRecord): void { "test -s dist/nemoclaw.js", "test -s dist/build-identity.json", ".sourceRevision == $candidateSha", - "authoritative CLI build identity does not match the candidate SHA", + "candidate CLI build identity does not match the candidate commit SHA", "--sort=name", "--mtime=@0", "source_tree=\"$(git rev-parse 'HEAD^{tree}')\"", diff --git a/tools/e2e/runner-comparison-workflow-boundary.mts b/tools/e2e/runner-comparison-workflow-boundary.mts index bcc964feede..9ada8d28f17 100644 --- a/tools/e2e/runner-comparison-workflow-boundary.mts +++ b/tools/e2e/runner-comparison-workflow-boundary.mts @@ -195,9 +195,9 @@ export function validateRunnerComparisonWorkflow(workflowValue: unknown): string const prepare = jobSteps.findIndex((step) => step.name === PREPARE_E2E_STEP); const restore = jobSteps.findIndex((step) => step.name === CLI_ARTIFACT_RESTORE_STEP); - // Comparison jobs in PREPARE_E2E_NO_BUILD_JOBS, including security-posture, - // end CLI bootstrap at workspace preparation because they do not restore the artifact. + // security-posture installs the CLI after workspace preparation and does not restore the artifact. const bootstrapEnd = restore >= 0 ? restore : prepare; + const initializationBoundary = restore >= 0 ? "CLI artifact restore" : "workspace preparation"; const initializeIndex = jobSteps.indexOf(initialize); const finalizeIndex = jobSteps.indexOf(finalize); const publish = publicationIndex(jobSteps); @@ -215,7 +215,7 @@ export function validateRunnerComparisonWorkflow(workflowValue: unknown): string } } else if (prepare < 0 || bootstrapEnd < prepare || initializeIndex !== bootstrapEnd + 1) { errors.push( - `${jobId} must initialize runner comparison telemetry immediately after CLI bootstrap`, + `${jobId} must initialize runner comparison telemetry immediately after ${initializationBoundary}`, ); } if (publish < 0 || finalizeIndex !== publish - 1) {