From f084b3d2e0905bdf600a6d5fb7b775aec53ec718 Mon Sep 17 00:00:00 2001 From: San Dang Date: Thu, 6 Aug 2026 20:45:13 +0700 Subject: [PATCH 1/8] fix(e2e): rerun full workflow for CLI artifact --- test/e2e/README.md | 17 +++++++++-------- test/e2e/docs/README.md | 9 +++++---- test/e2e/support/main-run-retry.test.ts | 7 +++++-- tools/e2e/main-run-retry.mts | 2 +- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/test/e2e/README.md b/test/e2e/README.md index e1172bce88c..ea775e83083 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -14,7 +14,7 @@ before those targets run; local runners must provide it themselves. failures from approved `main` workflows and requests one full rerun only when every non-passing job has authenticated GitHub-hosted runner-loss evidence. - `.github/workflows/e2e-main-retry.yaml` evaluates eligible `E2E main` push - attempts, requests at most two failed-job reruns, and uploads attempt evidence. + attempts, requests at most two full workflow reruns, and uploads attempt evidence. - The `staging-brev-launchable` job in `.github/workflows/e2e.yaml` validates the baked candidate without installing or copying NemoClaw source. - Platform workflows such as macOS, WSL, sandbox image, and regression E2E @@ -455,11 +455,12 @@ set, incomplete listing, custom or self-hosted label, changed evidence, or ambiguous pagination prevents recovery. For eligible `E2E main` push runs, `E2E / Main Retry` asks GitHub Actions to -rerun failed jobs after a workflow failure. It can request two retries. The -controller does not verify that GitHub schedules a different runner, so do not -treat a retry as evidence of a fresh host. It ignores manual runs and source runs -superseded by a newer `main` push. The controller checks out only trusted -default-branch code and receives no repository secrets. +rerun the full workflow after a workflow failure. A full rerun rebuilds the +attempt-bound candidate CLI artifact before consumer jobs start. The controller +can request two reruns. It does not verify that GitHub schedules a different +runner, so do not treat a rerun as evidence of a fresh host. It ignores manual +runs and source runs superseded by a newer `main` push. The controller checks out +only trusted default-branch code and receives no repository secrets. The runner-allocation and internal-error failures handled by Hosted Runner Recovery originate in GitHub Actions, outside repository-controlled workflow @@ -696,8 +697,8 @@ Each push to `main` triggers the default E2E suite. The central workflow has no scheduled trigger. When an eligible `E2E main` push workflow concludes with `failure`, -`E2E / Main Retry` asks GitHub Actions to rerun the failed jobs. The controller -permits two retries but does not verify that GitHub schedules a different runner. +`E2E / Main Retry` asks GitHub Actions to rerun the full workflow. The controller +permits two reruns but does not verify that GitHub schedules a different runner. After evaluation succeeds, it uploads an artifact named for the current attempt. The artifact contains one `attempts` summary for each source attempt through the current attempt. The `totalRunnerMinutes` field contains the cumulative runner diff --git a/test/e2e/docs/README.md b/test/e2e/docs/README.md index a333abb8cfd..c11ba9353f4 100644 --- a/test/e2e/docs/README.md +++ b/test/e2e/docs/README.md @@ -136,10 +136,11 @@ The aggregate local command rebuilds the CLI before Vitest starts and runs E2E test files serially. It does not retry a failed test. After an eligible `E2E main` push workflow fails, `E2E / Main Retry` asks -GitHub Actions to rerun its failed jobs. It can request two reruns, for three total -attempts. The controller does not verify that GitHub schedules a different runner, -so do not treat a retry as evidence of a fresh host. If a later attempt succeeds, -the source workflow concludes with `success`. The evidence sets `action` to +GitHub Actions to rerun the full workflow. Each rerun rebuilds the attempt-bound +candidate CLI artifact. The controller can request two reruns, for three total +attempts. It does not verify that GitHub schedules a different runner, so do not +treat a rerun as evidence of a fresh host. If a later attempt succeeds, the +source workflow concludes with `success`. The evidence sets `action` to `passed-after-retry` and `flaky` to `true`. After the controller evaluates attempt N, it uploads an artifact named for that diff --git a/test/e2e/support/main-run-retry.test.ts b/test/e2e/support/main-run-retry.test.ts index b3e63be9852..30be51c1ed4 100644 --- a/test/e2e/support/main-run-retry.test.ts +++ b/test/e2e/support/main-run-retry.test.ts @@ -92,7 +92,7 @@ async function evaluate(options: { attempt: number; conclusion: string; latestRu } describe("main E2E retry controller", () => { - it.each([1, 2])("requests retry %s before the attempt limit", async (attempt) => { + it.each([1, 2])("requests a full workflow rerun after attempt %s fails", async (attempt) => { const { evidence, requests } = await evaluate({ attempt, conclusion: "failure" }); expect(E2E_MAX_RETRIES).toBe(2); @@ -104,8 +104,11 @@ describe("main E2E retry controller", () => { }); expect(requests).toContainEqual({ method: "POST", - path: `repos/${REPOSITORY}/actions/runs/${RUN_ID}/rerun-failed-jobs`, + path: `repos/${REPOSITORY}/actions/runs/${RUN_ID}/rerun`, }); + // A failed-job rerun skips the successful CLI artifact producer. + // The consumer then reuses attempt-bound provenance from the prior attempt. + expect(requests.some((request) => request.path.endsWith("/rerun-failed-jobs"))).toBe(false); }); it("stops after the third failed attempt", async () => { diff --git a/tools/e2e/main-run-retry.mts b/tools/e2e/main-run-retry.mts index 139a1c20259..e5d765280d9 100755 --- a/tools/e2e/main-run-retry.mts +++ b/tools/e2e/main-run-retry.mts @@ -274,7 +274,7 @@ export async function evaluateMainRunRetry(options: { if (!validateLatestRun(await request(latestPath), source)) { throw new Error("a newer E2E main push appeared before retry request"); } - await request(`${runPath}/rerun-failed-jobs`, { method: "POST" }); + await request(`${runPath}/rerun`, { method: "POST" }); } return { schemaVersion: 1, From c246409193a31133cab10c8a3589001cc0d59eb3 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Thu, 6 Aug 2026 09:31:30 -0700 Subject: [PATCH 2/8] fix(e2e): reuse CLI artifact for failed-job retries --- .../restore-e2e-cli-artifact/action.yaml | 14 +++++++--- test/e2e/README.md | 22 ++++++++------- test/e2e/docs/README.md | 15 ++++++----- .../cli-artifact-workflow-boundary.test.ts | 27 +++++++++++++++---- test/e2e/support/main-run-retry.test.ts | 12 +++++---- tools/e2e/cli-artifact-workflow-boundary.mts | 8 +++--- tools/e2e/main-run-retry.mts | 2 +- 7 files changed, 67 insertions(+), 33 deletions(-) diff --git a/.github/actions/restore-e2e-cli-artifact/action.yaml b/.github/actions/restore-e2e-cli-artifact/action.yaml index 868bb0d20df..b108569ddf6 100644 --- a/.github/actions/restore-e2e-cli-artifact/action.yaml +++ b/.github/actions/restore-e2e-cli-artifact/action.yaml @@ -58,8 +58,12 @@ runs: { 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; } + [[ "$GITHUB_RUN_ATTEMPT" =~ ^[1-9][0-9]*$ ]] || + { echo "::error::consumer workflow run attempt is invalid"; exit 1; } + [[ "$run_id" == "$GITHUB_RUN_ID" ]] || + { echo "::error::consumer and producer workflow run IDs differ"; exit 1; } + (( run_attempt <= GITHUB_RUN_ATTEMPT )) || + { echo "::error::producer workflow attempt is newer than the consumer attempt"; exit 1; } remote_url="$(git remote get-url origin)" case "$remote_url" in @@ -78,10 +82,12 @@ runs: "candidate_repository=" + .candidateRepository, "candidate_sha=" + .candidateSha, "payload_sha256=" + .payloadSha256, + "producer_run_attempt=" + .runAttempt, "run_attempt=" + .runAttempt, "run_id=" + .runId, "workflow_sha=" + .workflowSha ' <<<"$PROVENANCE_JSON" >>"$GITHUB_OUTPUT" + printf 'consumer_run_attempt=%s\n' "$GITHUB_RUN_ATTEMPT" >>"$GITHUB_OUTPUT" - name: Download exact-commit CLI artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 @@ -96,7 +102,7 @@ runs: 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 }} + PRODUCER_RUN_ATTEMPT: ${{ steps.identity.outputs.producer_run_attempt }} RUN_ID: ${{ steps.identity.outputs.run_id }} WORKFLOW_SHA: ${{ steps.identity.outputs.workflow_sha }} shell: bash @@ -117,7 +123,7 @@ runs: --arg candidateSha "$CANDIDATE_SHA" \ --arg lockfileSha256 "$lockfile_sha256" \ --arg payloadSha256 "$PAYLOAD_SHA256" \ - --arg runAttempt "$RUN_ATTEMPT" \ + --arg runAttempt "$PRODUCER_RUN_ATTEMPT" \ --arg runId "$RUN_ID" \ --arg sourceTree "$source_tree" \ --arg workflowSha "$WORKFLOW_SHA" \ diff --git a/test/e2e/README.md b/test/e2e/README.md index 0ea7046902e..ea59d96b2d3 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -14,7 +14,7 @@ before those targets run; local runners must provide it themselves. failures from approved `main` workflows and requests one full rerun only when every non-passing job has authenticated GitHub-hosted runner-loss evidence. - `.github/workflows/e2e-main-retry.yaml` evaluates eligible `E2E main` push - attempts, requests at most two full workflow reruns, and uploads attempt evidence. + attempts, requests at most two failed-job reruns, and uploads attempt evidence. - The `staging-brev-launchable` job in `.github/workflows/e2e.yaml` validates the baked candidate without installing or copying NemoClaw source. - Platform workflows such as macOS, WSL, sandbox image, and regression E2E @@ -455,12 +455,15 @@ set, incomplete listing, custom or self-hosted label, changed evidence, or ambiguous pagination prevents recovery. For eligible `E2E main` push runs, `E2E / Main Retry` asks GitHub Actions to -rerun the full workflow after a workflow failure. A full rerun rebuilds the -attempt-bound candidate CLI artifact before consumer jobs start. The controller -can request two reruns. It does not verify that GitHub schedules a different -runner, so do not treat a rerun as evidence of a fresh host. It ignores manual -runs and source runs superseded by a newer `main` push. The controller checks out -only trusted default-branch code and receives no repository secrets. +rerun failed jobs and their dependent jobs. A successful CLI artifact producer is +not rerun. Consumers can reuse its immutable, content-addressed artifact from an +earlier attempt of the same workflow run. Restore validation requires the same +run, workflow, candidate, immutable artifact ID, and payload digest, and rejects a +producer attempt newer than the consumer attempt. The controller can request two +reruns. It does not verify that GitHub schedules a different runner, so do not +treat a rerun as evidence of a fresh host. It ignores manual runs and source runs +superseded by a newer `main` push. The controller checks out only trusted +default-branch code and receives no repository secrets. The runner-allocation and internal-error failures handled by Hosted Runner Recovery originate in GitHub Actions, outside repository-controlled workflow @@ -728,8 +731,9 @@ them in their issuing services. If cleanup fails, remove the recorded Brev workspace. Rotate or revoke each credential to remove later access. When an eligible `E2E main` push workflow concludes with `failure`, -`E2E / Main Retry` asks GitHub Actions to rerun the full workflow. The controller -permits two reruns but does not verify that GitHub schedules a different runner. +`E2E / Main Retry` asks GitHub Actions to rerun failed jobs and their dependents. +The controller permits two reruns but does not verify that GitHub schedules a +different runner. After evaluation succeeds, it uploads an artifact named for the current attempt. The artifact contains one `attempts` summary for each source attempt through the current attempt. The `totalRunnerMinutes` field contains the cumulative runner diff --git a/test/e2e/docs/README.md b/test/e2e/docs/README.md index 8f716784eee..dcd8497b3a5 100644 --- a/test/e2e/docs/README.md +++ b/test/e2e/docs/README.md @@ -136,12 +136,15 @@ The aggregate local command rebuilds the CLI before Vitest starts and runs E2E test files serially. It does not retry a failed test. After an eligible `E2E main` push workflow fails, `E2E / Main Retry` asks -GitHub Actions to rerun the full workflow. Each rerun rebuilds the attempt-bound -candidate CLI artifact. The controller can request two reruns, for three total -attempts. It does not verify that GitHub schedules a different runner, so do not -treat a rerun as evidence of a fresh host. If a later attempt succeeds, the -source workflow concludes with `success`. The evidence sets `action` to -`passed-after-retry` and `flaky` to `true`. +GitHub Actions to rerun failed jobs and their dependents. A successful CLI +artifact producer is not rerun. Consumers reuse its immutable, content-addressed +artifact from the earlier producer attempt in the same workflow run. Restore +validation rejects another run, workflow, candidate, artifact, payload, or a +producer attempt newer than the consumer attempt. The controller can request two +reruns, for three total attempts. It does not verify that GitHub schedules a +different runner, so do not treat a rerun as evidence of a fresh host. If a later +attempt succeeds, the source workflow concludes with `success`. The evidence sets +`action` to `passed-after-retry` and `flaky` to `true`. After the controller evaluates attempt N, it uploads an artifact named for that attempt. The artifact contains one `attempts` entry for each source attempt through diff --git a/test/e2e/support/cli-artifact-workflow-boundary.test.ts b/test/e2e/support/cli-artifact-workflow-boundary.test.ts index f1755ff3150..070e711eb55 100644 --- a/test/e2e/support/cli-artifact-workflow-boundary.test.ts +++ b/test/e2e/support/cli-artifact-workflow-boundary.test.ts @@ -29,7 +29,7 @@ const PAYLOAD_SHA256 = "b".repeat(64); const CONTENT_ADDRESSED_ARTIFACT_NAME = `artifact_name="nemoclaw-cli-\${CANDIDATE_SHA}-\${payload_sha256}"`; const UNBOUND_ARTIFACT_NAME = `artifact_name="nemoclaw-cli-\${CANDIDATE_SHA}"`; -function runIdentityValidation(overrides: Record = {}) { +function runIdentityValidation(overrides: Record = {}, consumerAttempt = "1") { 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-")); @@ -40,7 +40,7 @@ function runIdentityValidation(overrides: Record = {}) { ...process.env, CALLER_WORKFLOW_SHA: workflowSha, GITHUB_OUTPUT: path.join(outputDirectory, "github-output"), - GITHUB_RUN_ATTEMPT: "1", + GITHUB_RUN_ATTEMPT: consumerAttempt, GITHUB_RUN_ID: "98765", PROVENANCE_JSON: JSON.stringify({ kind: "nemoclaw-e2e-cli-provenance-v1", @@ -346,7 +346,7 @@ function runRestoreValidation(options: RestoreFixtureOptions = {}) { GITHUB_WORKSPACE: workspace, PATH: `${toolDirectory}:${process.env.PATH ?? ""}`, PAYLOAD_SHA256: expectedPayloadSha256, - RUN_ATTEMPT: "1", + PRODUCER_RUN_ATTEMPT: "1", RUN_ID: "98765", RUNNER_TEMP: runnerTemp, WORKFLOW_SHA: workflowSha, @@ -407,6 +407,19 @@ describe("exact-commit CLI artifact workflow boundary", () => { expect(result.status, result.stderr).toBe(0); }); + it("reuses an immutable producer artifact during a later failed-job rerun", () => { + const result = runIdentityValidation({ runAttempt: "1" }, "2"); + expect(result.status, result.stderr).toBe(0); + }); + + it("rejects an invalid consumer attempt", () => { + const result = runIdentityValidation({}, "0"); + expect(result.status, `${result.stdout}${result.stderr}`).not.toBe(0); + expect(`${result.stdout}${result.stderr}`).toContain( + "consumer workflow run attempt is invalid", + ); + }); + it.each([ ["empty artifact ID", { artifactId: "" }, "producer CLI artifact provenance is invalid"], [ @@ -457,8 +470,12 @@ describe("exact-commit CLI artifact workflow boundary", () => { "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"], + ["run ID", { runId: "98766" }, "consumer and producer workflow run IDs differ"], + [ + "future producer attempt", + { runAttempt: "2" }, + "producer workflow attempt is newer than the consumer attempt", + ], ])("rejects a mismatched %s before artifact download", (_case, overrides, expectedError) => { const result = runIdentityValidation(overrides); expect(result.status, `${result.stdout}${result.stderr}`).not.toBe(0); diff --git a/test/e2e/support/main-run-retry.test.ts b/test/e2e/support/main-run-retry.test.ts index 30be51c1ed4..8ff942e2018 100644 --- a/test/e2e/support/main-run-retry.test.ts +++ b/test/e2e/support/main-run-retry.test.ts @@ -92,7 +92,7 @@ async function evaluate(options: { attempt: number; conclusion: string; latestRu } describe("main E2E retry controller", () => { - it.each([1, 2])("requests a full workflow rerun after attempt %s fails", async (attempt) => { + it.each([1, 2])("requests failed-job rerun after attempt %s fails", async (attempt) => { const { evidence, requests } = await evaluate({ attempt, conclusion: "failure" }); expect(E2E_MAX_RETRIES).toBe(2); @@ -104,11 +104,13 @@ describe("main E2E retry controller", () => { }); expect(requests).toContainEqual({ method: "POST", - path: `repos/${REPOSITORY}/actions/runs/${RUN_ID}/rerun`, + path: `repos/${REPOSITORY}/actions/runs/${RUN_ID}/rerun-failed-jobs`, }); - // A failed-job rerun skips the successful CLI artifact producer. - // The consumer then reuses attempt-bound provenance from the prior attempt. - expect(requests.some((request) => request.path.endsWith("/rerun-failed-jobs"))).toBe(false); + expect( + requests.some( + (request) => request.path === `repos/${REPOSITORY}/actions/runs/${RUN_ID}/rerun`, + ), + ).toBe(false); }); it("stops after the third failed attempt", async () => { diff --git a/tools/e2e/cli-artifact-workflow-boundary.mts b/tools/e2e/cli-artifact-workflow-boundary.mts index 7c12e649f2f..d9db90098e0 100644 --- a/tools/e2e/cli-artifact-workflow-boundary.mts +++ b/tools/e2e/cli-artifact-workflow-boundary.mts @@ -33,7 +33,7 @@ const DEFAULT_RESTORE_ACTION_PATH = join( "action.yaml", ); const RESTORE_ACTION_CONTENT_SHA256 = - "f90d4532a64473817b183856e225b401b201d71aaafa75bd7cf8d63a3deeb76f"; + "3a81ad631b839aa938eaaf1ad6777bab247204bf86fbca3c43c326a44dfb9c6c"; 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"; @@ -217,7 +217,9 @@ export function validateCliArtifactRestoreAction( '.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" ]]', + '[[ "$GITHUB_RUN_ATTEMPT" =~ ^[1-9][0-9]*$ ]]', + '[[ "$run_id" == "$GITHUB_RUN_ID" ]]', + "(( run_attempt <= GITHUB_RUN_ATTEMPT ))", '[[ "$remote_repository" == "$candidate_repository" ]]', '<<<"$PROVENANCE_JSON" >>"$GITHUB_OUTPUT"', ]); @@ -241,7 +243,7 @@ export function validateCliArtifactRestoreAction( 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 }}", + PRODUCER_RUN_ATTEMPT: "${{ steps.identity.outputs.producer_run_attempt }}", RUN_ID: "${{ steps.identity.outputs.run_id }}", WORKFLOW_SHA: "${{ steps.identity.outputs.workflow_sha }}", }) diff --git a/tools/e2e/main-run-retry.mts b/tools/e2e/main-run-retry.mts index e5d765280d9..139a1c20259 100755 --- a/tools/e2e/main-run-retry.mts +++ b/tools/e2e/main-run-retry.mts @@ -274,7 +274,7 @@ export async function evaluateMainRunRetry(options: { if (!validateLatestRun(await request(latestPath), source)) { throw new Error("a newer E2E main push appeared before retry request"); } - await request(`${runPath}/rerun`, { method: "POST" }); + await request(`${runPath}/rerun-failed-jobs`, { method: "POST" }); } return { schemaVersion: 1, From 5a63023ea87dcd0fc17adc1be1afaf5708cac28d Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Thu, 6 Aug 2026 09:42:58 -0700 Subject: [PATCH 3/8] ci(e2e): pin reusable CLI restore action --- .github/workflows/e2e.yaml | 124 +++++++++--------- test/e2e/README.md | 39 +++--- test/e2e/docs/README.md | 21 +-- .../cli-artifact-workflow-boundary.test.ts | 4 +- tools/e2e/cli-artifact-workflow-boundary.mts | 4 +- 5 files changed, 96 insertions(+), 96 deletions(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 1c0e06319fd..ad77c5f199a 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -624,7 +624,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -801,7 +801,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -964,7 +964,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1011,7 +1011,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1285,7 +1285,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1417,7 +1417,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1537,7 +1537,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1649,7 +1649,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1732,7 +1732,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1777,7 +1777,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1856,7 +1856,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -1914,7 +1914,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI @@ -2892,7 +2892,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Initialize runner comparison telemetry @@ -2966,7 +2966,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Install OpenShell CLI @@ -3053,7 +3053,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} - name: Initialize runner comparison telemetry @@ -3117,7 +3117,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3224,7 +3224,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3301,7 +3301,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3367,7 +3367,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3416,7 +3416,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3481,7 +3481,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3563,7 +3563,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3708,7 +3708,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -3916,7 +3916,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4006,7 +4006,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4105,7 +4105,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4269,7 +4269,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4328,7 +4328,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4406,7 +4406,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4528,7 +4528,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4644,7 +4644,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4695,7 +4695,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4762,7 +4762,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4843,7 +4843,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4905,7 +4905,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -4968,7 +4968,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5030,7 +5030,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5224,7 +5224,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5322,7 +5322,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5383,7 +5383,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5445,7 +5445,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5505,7 +5505,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5564,7 +5564,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5680,7 +5680,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5738,7 +5738,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5850,7 +5850,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -5953,7 +5953,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6019,7 +6019,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6088,7 +6088,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6160,7 +6160,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6239,7 +6239,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6308,7 +6308,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6404,7 +6404,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6486,7 +6486,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6541,7 +6541,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6604,7 +6604,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6681,7 +6681,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6781,7 +6781,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6874,7 +6874,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -6946,7 +6946,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -7011,7 +7011,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} @@ -7083,7 +7083,7 @@ jobs: build-cli: "false" - name: Restore exact-commit CLI artifact - uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@d34b11e8b895760d6505d54e2cfc520332cb2a9c + uses: NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3 with: provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }} diff --git a/test/e2e/README.md b/test/e2e/README.md index ea59d96b2d3..a50f2414ac2 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -56,7 +56,8 @@ Each artifact-using job passes that object as the restore action's only `provena 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. 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 requires the candidate checkout SHA, repository, workflow SHA, and run ID to match the provenance object. +The producer attempt must not be newer than the consumer attempt. The action downloads the artifact by immutable ID and sets digest mismatch handling to `error`. Before the action restores root `dist/` and `nemoclaw/dist/shared/` into the workspace, it verifies these conditions: @@ -454,16 +455,17 @@ workflow's approved runner labels. An ordinary assertion failure, mixed failure set, incomplete listing, custom or self-hosted label, changed evidence, or ambiguous pagination prevents recovery. -For eligible `E2E main` push runs, `E2E / Main Retry` asks GitHub Actions to -rerun failed jobs and their dependent jobs. A successful CLI artifact producer is -not rerun. Consumers can reuse its immutable, content-addressed artifact from an -earlier attempt of the same workflow run. Restore validation requires the same -run, workflow, candidate, immutable artifact ID, and payload digest, and rejects a -producer attempt newer than the consumer attempt. The controller can request two -reruns. It does not verify that GitHub schedules a different runner, so do not -treat a rerun as evidence of a fresh host. It ignores manual runs and source runs -superseded by a newer `main` push. The controller checks out only trusted -default-branch code and receives no repository secrets. +For eligible `E2E main` push runs, `E2E / Main Retry` asks GitHub Actions to rerun failed jobs and their dependent jobs. +A successful CLI artifact producer is not rerun. +The workflow retains its CLI artifact for 3 days. +During that period, consumers can reuse the immutable, content-addressed artifact from an earlier producer attempt in the same workflow run. +If the artifact is unavailable when a consumer downloads it, restoration fails because the failed-job rerun does not rerun the successful producer. +Restore validation requires the recorded run ID, workflow SHA, candidate repository and SHA, immutable artifact ID, and payload digest. +It rejects a producer attempt newer than the consumer attempt. +The controller can request two reruns. +It does not verify that GitHub schedules a different runner, so do not treat a rerun as evidence of a fresh host. +It ignores manual runs and source runs superseded by a newer `main` push. +The controller checks out only trusted default-branch code and receives no repository secrets. The runner-allocation and internal-error failures handled by Hosted Runner Recovery originate in GitHub Actions, outside repository-controlled workflow @@ -730,16 +732,13 @@ These credentials remain valid until they expire or an administrator revokes them in their issuing services. If cleanup fails, remove the recorded Brev workspace. Rotate or revoke each credential to remove later access. -When an eligible `E2E main` push workflow concludes with `failure`, -`E2E / Main Retry` asks GitHub Actions to rerun failed jobs and their dependents. -The controller permits two reruns but does not verify that GitHub schedules a -different runner. +When an eligible `E2E main` push workflow concludes with `failure`, `E2E / Main Retry` asks GitHub Actions to rerun failed jobs and their dependent jobs. +The controller permits two reruns but does not verify that GitHub schedules a different runner. After evaluation succeeds, it uploads an artifact named for the current attempt. -The artifact contains one `attempts` summary for each source attempt through the -current attempt. The `totalRunnerMinutes` field contains the cumulative runner -time for those summaries. A later successful attempt sets `action` to -`passed-after-retry` and `flaky` to `true`. The controller does not retry manual -PR runs or a run superseded by a newer `main` push. +The artifact contains one `attempts` summary for each source attempt through the current attempt. +The `totalRunnerMinutes` field contains the cumulative runner time for those summaries. +A later successful attempt sets `action` to `passed-after-retry` and `flaky` to `true`. +The controller does not retry manual PR runs or a run superseded by a newer `main` push. For a PR revision run, a repository maintainer or administrator leaves `jobs` and `targets` empty. The run selects: diff --git a/test/e2e/docs/README.md b/test/e2e/docs/README.md index dcd8497b3a5..01f553b35b6 100644 --- a/test/e2e/docs/README.md +++ b/test/e2e/docs/README.md @@ -135,16 +135,17 @@ npm run test:runtime-audit -- e2e-artifacts/run-1 e2e-artifacts/run-2 The aggregate local command rebuilds the CLI before Vitest starts and runs E2E test files serially. It does not retry a failed test. -After an eligible `E2E main` push workflow fails, `E2E / Main Retry` asks -GitHub Actions to rerun failed jobs and their dependents. A successful CLI -artifact producer is not rerun. Consumers reuse its immutable, content-addressed -artifact from the earlier producer attempt in the same workflow run. Restore -validation rejects another run, workflow, candidate, artifact, payload, or a -producer attempt newer than the consumer attempt. The controller can request two -reruns, for three total attempts. It does not verify that GitHub schedules a -different runner, so do not treat a rerun as evidence of a fresh host. If a later -attempt succeeds, the source workflow concludes with `success`. The evidence sets -`action` to `passed-after-retry` and `flaky` to `true`. +After an eligible `E2E main` push workflow fails, `E2E / Main Retry` asks GitHub Actions to rerun failed jobs and their dependent jobs. +A successful CLI artifact producer is not rerun. +The workflow retains its CLI artifact for 3 days. +During that period, consumers reuse the immutable, content-addressed artifact from the earlier producer attempt in the same workflow run. +If the artifact is unavailable when a consumer downloads it, restoration fails because the failed-job rerun does not rerun the successful producer. +Restore validation requires the recorded run ID, workflow SHA, candidate repository and SHA, immutable artifact ID, and payload digest. +It rejects a producer attempt that is newer than the consumer attempt. +The controller can request two reruns, for three total attempts. +It does not verify that GitHub schedules a different runner, so do not treat a rerun as evidence of a fresh host. +If a later attempt succeeds, the source workflow concludes with `success`. +The evidence sets `action` to `passed-after-retry` and `flaky` to `true`. After the controller evaluates attempt N, it uploads an artifact named for that attempt. The artifact contains one `attempts` entry for each source attempt through diff --git a/test/e2e/support/cli-artifact-workflow-boundary.test.ts b/test/e2e/support/cli-artifact-workflow-boundary.test.ts index 070e711eb55..1e39bbbb62f 100644 --- a/test/e2e/support/cli-artifact-workflow-boundary.test.ts +++ b/test/e2e/support/cli-artifact-workflow-boundary.test.ts @@ -407,12 +407,12 @@ describe("exact-commit CLI artifact workflow boundary", () => { expect(result.status, result.stderr).toBe(0); }); - it("reuses an immutable producer artifact during a later failed-job rerun", () => { + it("accepts producer provenance from an earlier attempt of the same workflow run", () => { const result = runIdentityValidation({ runAttempt: "1" }, "2"); expect(result.status, result.stderr).toBe(0); }); - it("rejects an invalid consumer attempt", () => { + it("rejects consumer workflow attempt zero", () => { const result = runIdentityValidation({}, "0"); expect(result.status, `${result.stdout}${result.stderr}`).not.toBe(0); expect(`${result.stdout}${result.stderr}`).toContain( diff --git a/tools/e2e/cli-artifact-workflow-boundary.mts b/tools/e2e/cli-artifact-workflow-boundary.mts index d9db90098e0..00bcb440235 100644 --- a/tools/e2e/cli-artifact-workflow-boundary.mts +++ b/tools/e2e/cli-artifact-workflow-boundary.mts @@ -19,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@d34b11e8b895760d6505d54e2cfc520332cb2a9c"; + "NVIDIA/NemoClaw/.github/actions/restore-e2e-cli-artifact@c246409193a31133cab10c8a3589001cc0d59eb3"; 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"; @@ -40,7 +40,7 @@ const CLI_ARTIFACT_PROVENANCE_STEP = "Record CLI artifact provenance"; const CANDIDATE_CHECKOUT_STEP_CONTENT_SHA256 = "3578a053cede863f7aa4814d8399b4ca21ea0b77cee712e6d549c684818f11dd"; const CLI_ARTIFACT_WORKFLOW_CONTRACT_SHA256 = - "3cae1755d65b5a491ce24f483c88f9dc08fb3eab1f43be7ba35f0de24d03ea22"; + "5d09301f3d37c6d2f588379fa8c94ef33d00efb8e25dc475bcfba24377fd8365"; const CLI_ARTIFACT_CONSUMER_JOB_NAMES = [ "agent-turn-latency", "bedrock-runtime-compatible-anthropic", From 4dad1c8f53d8e514a6cbd190a578ebe605569326 Mon Sep 17 00:00:00 2001 From: Apurv Kumaria Date: Thu, 6 Aug 2026 10:06:28 -0700 Subject: [PATCH 4/8] test(e2e): cover cross-attempt artifact restore Signed-off-by: Apurv Kumaria --- .../cli-artifact-workflow-boundary.test.ts | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/test/e2e/support/cli-artifact-workflow-boundary.test.ts b/test/e2e/support/cli-artifact-workflow-boundary.test.ts index 1e39bbbb62f..be12d32f01a 100644 --- a/test/e2e/support/cli-artifact-workflow-boundary.test.ts +++ b/test/e2e/support/cli-artifact-workflow-boundary.test.ts @@ -34,12 +34,13 @@ function runIdentityValidation(overrides: Record = {}, consumer const workflowSha = "d".repeat(40); const outputDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "cli-artifact-identity-")); try { - return spawnSync("bash", ["-c", action.runs.steps[0]!.run!], { + const outputPath = path.join(outputDirectory, "github-output"); + const result = 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_OUTPUT: outputPath, GITHUB_RUN_ATTEMPT: consumerAttempt, GITHUB_RUN_ID: "98765", PROVENANCE_JSON: JSON.stringify({ @@ -57,6 +58,10 @@ function runIdentityValidation(overrides: Record = {}, consumer }), }, }); + return { + ...result, + outputs: fs.existsSync(outputPath) ? fs.readFileSync(outputPath, "utf8") : "", + }; } finally { fs.rmSync(outputDirectory, { force: true, recursive: true }); } @@ -78,11 +83,13 @@ type RestoreFixtureOptions = { buildIdentitySha?: string; expectedPayloadSha256?: string; manifestCandidateSha?: string; + manifestRunAttempt?: string; preexistingDist?: | "dangling-symlink" | "directory" | "plugin-directory" | "symlinked-plugin-parent"; + producerRunAttempt?: string; }; type ArchiveFixtureContext = { @@ -314,7 +321,11 @@ function runRestoreValidation(options: RestoreFixtureOptions = {}) { sourceTree, lockfileSha256: sha256File(path.join(workspace, "package-lock.json")), }, - workflow: { sha: workflowSha, runId: "98765", runAttempt: "1" }, + workflow: { + sha: workflowSha, + runId: "98765", + runAttempt: options.manifestRunAttempt ?? options.producerRunAttempt ?? "1", + }, toolchain: { node: "v22.23.1", npm: "10.9.2", @@ -346,7 +357,7 @@ function runRestoreValidation(options: RestoreFixtureOptions = {}) { GITHUB_WORKSPACE: workspace, PATH: `${toolDirectory}:${process.env.PATH ?? ""}`, PAYLOAD_SHA256: expectedPayloadSha256, - PRODUCER_RUN_ATTEMPT: "1", + PRODUCER_RUN_ATTEMPT: options.producerRunAttempt ?? "1", RUN_ID: "98765", RUNNER_TEMP: runnerTemp, WORKFLOW_SHA: workflowSha, @@ -407,9 +418,31 @@ describe("exact-commit CLI artifact workflow boundary", () => { expect(result.status, result.stderr).toBe(0); }); - it("accepts producer provenance from an earlier attempt of the same workflow run", () => { - const result = runIdentityValidation({ runAttempt: "1" }, "2"); - expect(result.status, result.stderr).toBe(0); + it("restores an earlier-attempt producer artifact during a later failed-job rerun", () => { + const identity = runIdentityValidation({ runAttempt: "1" }, "2"); + expect(identity.status, identity.stderr).toBe(0); + expect(identity.outputs).toContain("producer_run_attempt=1\n"); + expect(identity.outputs).toContain("consumer_run_attempt=2\n"); + + const fixture = runRestoreValidation({ producerRunAttempt: "1" }); + try { + expect(fixture.result.status, fixture.output).toBe(0); + expect(fs.existsSync(path.join(fixture.workspace, "dist", "nemoclaw.js"))).toBe(true); + expect( + fs.existsSync( + path.join(fixture.workspace, "nemoclaw", "dist", "shared", "sandbox-name.cjs"), + ), + ).toBe(true); + } finally { + fixture.cleanup(); + } + }); + + it("rejects a producer attempt that does not match the restored artifact manifest", () => { + expectRestoreFailure( + { manifestRunAttempt: "2", producerRunAttempt: "1" }, + "exact-commit CLI artifact provenance mismatch", + ); }); it("rejects consumer workflow attempt zero", () => { From 921657291ac44b73d25a54678aa0953ab10b3904 Mon Sep 17 00:00:00 2001 From: San Dang Date: Fri, 7 Aug 2026 00:05:05 +0700 Subject: [PATCH 5/8] test(e2e): cover artifact reuse restore boundary --- test/e2e/README.md | 3 +- test/e2e/docs/README.md | 3 +- .../cli-artifact-workflow-boundary.test.ts | 83 +++++++++++++++---- 3 files changed, 70 insertions(+), 19 deletions(-) diff --git a/test/e2e/README.md b/test/e2e/README.md index a50f2414ac2..eb8cf965793 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -460,7 +460,8 @@ A successful CLI artifact producer is not rerun. The workflow retains its CLI artifact for 3 days. During that period, consumers can reuse the immutable, content-addressed artifact from an earlier producer attempt in the same workflow run. If the artifact is unavailable when a consumer downloads it, restoration fails because the failed-job rerun does not rerun the successful producer. -Restore validation requires the recorded run ID, workflow SHA, candidate repository and SHA, immutable artifact ID, and payload digest. +Restore validation binds the producer provenance to the workflow run, workflow SHA, and candidate checkout. +It downloads by immutable artifact ID and verifies the manifest and the payload digest. It rejects a producer attempt newer than the consumer attempt. The controller can request two reruns. It does not verify that GitHub schedules a different runner, so do not treat a rerun as evidence of a fresh host. diff --git a/test/e2e/docs/README.md b/test/e2e/docs/README.md index 01f553b35b6..737d52ecd01 100644 --- a/test/e2e/docs/README.md +++ b/test/e2e/docs/README.md @@ -140,7 +140,8 @@ A successful CLI artifact producer is not rerun. The workflow retains its CLI artifact for 3 days. During that period, consumers reuse the immutable, content-addressed artifact from the earlier producer attempt in the same workflow run. If the artifact is unavailable when a consumer downloads it, restoration fails because the failed-job rerun does not rerun the successful producer. -Restore validation requires the recorded run ID, workflow SHA, candidate repository and SHA, immutable artifact ID, and payload digest. +Restore validation binds the producer provenance to the workflow run, workflow SHA, and candidate checkout. +It downloads by immutable artifact ID and verifies the manifest and the payload digest. It rejects a producer attempt that is newer than the consumer attempt. The controller can request two reruns, for three total attempts. It does not verify that GitHub schedules a different runner, so do not treat a rerun as evidence of a fresh host. diff --git a/test/e2e/support/cli-artifact-workflow-boundary.test.ts b/test/e2e/support/cli-artifact-workflow-boundary.test.ts index be12d32f01a..40cfbfae41d 100644 --- a/test/e2e/support/cli-artifact-workflow-boundary.test.ts +++ b/test/e2e/support/cli-artifact-workflow-boundary.test.ts @@ -81,6 +81,7 @@ type RestoreFixtureOptions = { | "shared-module-directory" | "traversal"; buildIdentitySha?: string; + consumerRunAttempt?: string; expectedPayloadSha256?: string; manifestCandidateSha?: string; manifestRunAttempt?: string; @@ -273,6 +274,9 @@ function runRestoreValidation(options: RestoreFixtureOptions = {}) { { mode: 0o755 }, ); execFileSync("git", ["init", "--quiet"], { cwd: workspace }); + execFileSync("git", ["remote", "add", "origin", "https://github.com/NVIDIA/NemoClaw.git"], { + cwd: workspace, + }); execFileSync("git", ["add", "."], { cwd: workspace }); execFileSync( "git", @@ -309,6 +313,7 @@ function runRestoreValidation(options: RestoreFixtureOptions = {}) { const actualPayloadSha256 = sha256File(payload); const expectedPayloadSha256 = options.expectedPayloadSha256 ?? actualPayloadSha256; const artifactName = `nemoclaw-cli-${candidateSha}-${expectedPayloadSha256}`; + const producerRunAttempt = options.producerRunAttempt ?? "1"; const workflowSha = "d".repeat(40); fs.writeFileSync( path.join(artifactDirectory, "manifest.json"), @@ -346,28 +351,72 @@ function runRestoreValidation(options: RestoreFixtureOptions = {}) { 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!], { + const githubOutput = path.join(root, "github-output"); + const identityResult = spawnSync("bash", ["-c", action.runs.steps[0]!.run!], { cwd: workspace, encoding: "utf8", env: { ...process.env, - ARTIFACT_NAME: artifactName, - CANDIDATE_REPOSITORY: "NVIDIA/NemoClaw", - CANDIDATE_SHA: candidateSha, + CALLER_WORKFLOW_SHA: workflowSha, + GITHUB_OUTPUT: githubOutput, + GITHUB_RUN_ATTEMPT: options.consumerRunAttempt ?? producerRunAttempt, + GITHUB_RUN_ID: "98765", + PROVENANCE_JSON: JSON.stringify({ + kind: "nemoclaw-e2e-cli-provenance-v1", + artifactDigest: "c".repeat(64), + artifactId: "12345", + artifactName, + candidateRepository: "NVIDIA/NemoClaw", + candidateSha, + payloadSha256: expectedPayloadSha256, + runAttempt: producerRunAttempt, + runId: "98765", + workflowSha, + }), + }, + }); + if (identityResult.status !== 0) { + return { + candidateSha, + cleanup: () => fs.rmSync(root, { force: true, recursive: true }), + output: `${identityResult.stdout}${identityResult.stderr}`, + result: identityResult, + runnerTemp, + workspace, + }; + } + const identityOutputs = Object.fromEntries( + fs + .readFileSync(githubOutput, "utf8") + .trim() + .split("\n") + .map((line) => { + const separator = line.indexOf("="); + return [line.slice(0, separator), line.slice(separator + 1)]; + }), + ); + const restoreResult = spawnSync("bash", ["-c", action.runs.steps[2]!.run!], { + cwd: workspace, + encoding: "utf8", + env: { + ...process.env, + ARTIFACT_NAME: identityOutputs.artifact_name, + CANDIDATE_REPOSITORY: identityOutputs.candidate_repository, + CANDIDATE_SHA: identityOutputs.candidate_sha, GITHUB_WORKSPACE: workspace, PATH: `${toolDirectory}:${process.env.PATH ?? ""}`, - PAYLOAD_SHA256: expectedPayloadSha256, - PRODUCER_RUN_ATTEMPT: options.producerRunAttempt ?? "1", - RUN_ID: "98765", + PAYLOAD_SHA256: identityOutputs.payload_sha256, + PRODUCER_RUN_ATTEMPT: identityOutputs.producer_run_attempt, + RUN_ID: identityOutputs.run_id, RUNNER_TEMP: runnerTemp, - WORKFLOW_SHA: workflowSha, + WORKFLOW_SHA: identityOutputs.workflow_sha, }, }); return { candidateSha, cleanup: () => fs.rmSync(root, { force: true, recursive: true }), - output: `${result.stdout}${result.stderr}`, - result, + output: `${identityResult.stdout}${identityResult.stderr}${restoreResult.stdout}${restoreResult.stderr}`, + result: restoreResult, runnerTemp, workspace, }; @@ -418,15 +467,15 @@ describe("exact-commit CLI artifact workflow boundary", () => { expect(result.status, result.stderr).toBe(0); }); - it("restores an earlier-attempt producer artifact during a later failed-job rerun", () => { - const identity = runIdentityValidation({ runAttempt: "1" }, "2"); - expect(identity.status, identity.stderr).toBe(0); - expect(identity.outputs).toContain("producer_run_attempt=1\n"); - expect(identity.outputs).toContain("consumer_run_attempt=2\n"); - - const fixture = runRestoreValidation({ producerRunAttempt: "1" }); + it("reuses an immutable producer artifact during a later failed-job rerun", () => { + const fixture = runRestoreValidation({ consumerRunAttempt: "2", producerRunAttempt: "1" }); try { expect(fixture.result.status, fixture.output).toBe(0); + expect( + JSON.parse( + fs.readFileSync(path.join(fixture.workspace, "dist", "build-identity.json"), "utf8"), + ), + ).toEqual({ nemoclawVersion: "0.0.0", sourceRevision: fixture.candidateSha }); expect(fs.existsSync(path.join(fixture.workspace, "dist", "nemoclaw.js"))).toBe(true); expect( fs.existsSync( From e5ee97b4ff77e85c0516c89b023a0923efe7d83b Mon Sep 17 00:00:00 2001 From: San Dang Date: Fri, 7 Aug 2026 00:17:11 +0700 Subject: [PATCH 6/8] test(e2e): avoid raw restore output in assertion --- test/e2e/support/cli-artifact-workflow-boundary.test.ts | 2 +- 1 file changed, 1 insertion(+), 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 40cfbfae41d..bd07299a0e3 100644 --- a/test/e2e/support/cli-artifact-workflow-boundary.test.ts +++ b/test/e2e/support/cli-artifact-workflow-boundary.test.ts @@ -470,7 +470,7 @@ describe("exact-commit CLI artifact workflow boundary", () => { it("reuses an immutable producer artifact during a later failed-job rerun", () => { const fixture = runRestoreValidation({ consumerRunAttempt: "2", producerRunAttempt: "1" }); try { - expect(fixture.result.status, fixture.output).toBe(0); + expect(fixture.result.status, "cross-attempt CLI artifact restore failed").toBe(0); expect( JSON.parse( fs.readFileSync(path.join(fixture.workspace, "dist", "build-identity.json"), "utf8"), From 44728fca9801cdbf4cf51e0686739a04cf86a6ec Mon Sep 17 00:00:00 2001 From: Apurv Kumaria Date: Thu, 6 Aug 2026 10:16:45 -0700 Subject: [PATCH 7/8] test(e2e): bound artifact restore diagnostics Signed-off-by: Apurv Kumaria --- test/e2e/support/cli-artifact-workflow-boundary.test.ts | 2 +- 1 file changed, 1 insertion(+), 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 bd07299a0e3..c78ff2f68c6 100644 --- a/test/e2e/support/cli-artifact-workflow-boundary.test.ts +++ b/test/e2e/support/cli-artifact-workflow-boundary.test.ts @@ -464,7 +464,7 @@ describe("exact-commit CLI artifact workflow boundary", () => { it("accepts matching artifact, candidate source, and workflow identities", () => { const result = runIdentityValidation(); - expect(result.status, result.stderr).toBe(0); + expect(result.status, "matching artifact identity validation failed").toBe(0); }); it("reuses an immutable producer artifact during a later failed-job rerun", () => { From b401641127a93ffeaaf1ab5d0b1254b304212ec3 Mon Sep 17 00:00:00 2001 From: Apurv Kumaria Date: Thu, 6 Aug 2026 17:09:41 -0700 Subject: [PATCH 8/8] test(e2e): keep restore fixture linear Signed-off-by: Apurv Kumaria --- .../cli-artifact-workflow-boundary.test.ts | 72 +++++++++---------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/test/e2e/support/cli-artifact-workflow-boundary.test.ts b/test/e2e/support/cli-artifact-workflow-boundary.test.ts index c78ff2f68c6..49b3bfce8fd 100644 --- a/test/e2e/support/cli-artifact-workflow-boundary.test.ts +++ b/test/e2e/support/cli-artifact-workflow-boundary.test.ts @@ -375,47 +375,43 @@ function runRestoreValidation(options: RestoreFixtureOptions = {}) { }), }, }); - if (identityResult.status !== 0) { - return { - candidateSha, - cleanup: () => fs.rmSync(root, { force: true, recursive: true }), - output: `${identityResult.stdout}${identityResult.stderr}`, - result: identityResult, - runnerTemp, - workspace, - }; - } - const identityOutputs = Object.fromEntries( - fs - .readFileSync(githubOutput, "utf8") - .trim() - .split("\n") - .map((line) => { - const separator = line.indexOf("="); - return [line.slice(0, separator), line.slice(separator + 1)]; - }), - ); - const restoreResult = spawnSync("bash", ["-c", action.runs.steps[2]!.run!], { - cwd: workspace, - encoding: "utf8", - env: { - ...process.env, - ARTIFACT_NAME: identityOutputs.artifact_name, - CANDIDATE_REPOSITORY: identityOutputs.candidate_repository, - CANDIDATE_SHA: identityOutputs.candidate_sha, - GITHUB_WORKSPACE: workspace, - PATH: `${toolDirectory}:${process.env.PATH ?? ""}`, - PAYLOAD_SHA256: identityOutputs.payload_sha256, - PRODUCER_RUN_ATTEMPT: identityOutputs.producer_run_attempt, - RUN_ID: identityOutputs.run_id, - RUNNER_TEMP: runnerTemp, - WORKFLOW_SHA: identityOutputs.workflow_sha, - }, - }); + const runRestoreStep = () => { + const identityOutputs = Object.fromEntries( + fs + .readFileSync(githubOutput, "utf8") + .trim() + .split("\n") + .map((line) => { + const separator = line.indexOf("="); + return [line.slice(0, separator), line.slice(separator + 1)]; + }), + ); + return spawnSync("bash", ["-c", action.runs.steps[2]!.run!], { + cwd: workspace, + encoding: "utf8", + env: { + ...process.env, + ARTIFACT_NAME: identityOutputs.artifact_name, + CANDIDATE_REPOSITORY: identityOutputs.candidate_repository, + CANDIDATE_SHA: identityOutputs.candidate_sha, + GITHUB_WORKSPACE: workspace, + PATH: `${toolDirectory}:${process.env.PATH ?? ""}`, + PAYLOAD_SHA256: identityOutputs.payload_sha256, + PRODUCER_RUN_ATTEMPT: identityOutputs.producer_run_attempt, + RUN_ID: identityOutputs.run_id, + RUNNER_TEMP: runnerTemp, + WORKFLOW_SHA: identityOutputs.workflow_sha, + }, + }); + }; + const identitySucceeded = identityResult.status === 0; + const restoreResult = identitySucceeded ? runRestoreStep() : identityResult; return { candidateSha, cleanup: () => fs.rmSync(root, { force: true, recursive: true }), - output: `${identityResult.stdout}${identityResult.stderr}${restoreResult.stdout}${restoreResult.stderr}`, + output: `${identityResult.stdout}${identityResult.stderr}${ + identitySucceeded ? `${restoreResult.stdout}${restoreResult.stderr}` : "" + }`, result: restoreResult, runnerTemp, workspace,