Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions .github/actions/restore-e2e-cli-artifact/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# 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
.build.sourceRevision == $candidateSha 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 "$GITHUB_WORKSPACE/dist" && ! -L "$GITHUB_WORKSPACE/dist" ]] ||
{ echo "::error::consumer unexpectedly built dist before artifact restore"; exit 1; }
restore_dir="$(mktemp -d "${RUNNER_TEMP}/nemoclaw-cli-restore.XXXXXX")"
trap 'rm -rf -- "$restore_dir"' EXIT
tar --no-same-owner --no-same-permissions -xf "$payload" -C "$restore_dir"
test -s "$restore_dir/dist/nemoclaw.js" ||
{ echo "::error::restored CLI artifact is missing dist/nemoclaw.js"; exit 1; }
jq -e --arg candidateSha "$CANDIDATE_SHA" '
type == "object" and
(keys | sort) == ["nemoclawVersion", "sourceRevision"] and
(.nemoclawVersion | strings | length > 0) and
.sourceRevision == $candidateSha
' "$restore_dir/dist/build-identity.json" >/dev/null ||
{ echo "::error::restored CLI build identity does not match the candidate SHA"; exit 1; }
mv "$restore_dir/dist" "$GITHUB_WORKSPACE/dist"
node "$GITHUB_WORKSPACE/bin/nemoclaw.js" --version >/dev/null
Loading
Loading