From 72a0eab6609ce9b32f1c104b05c12b3fec613132 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Tue, 18 Aug 2026 23:39:29 -0400 Subject: [PATCH 1/8] test(onboard): expose dashboard reservation receiver loss Signed-off-by: Julie Yaunches --- src/lib/onboard/dashboard-port.test.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/lib/onboard/dashboard-port.test.ts b/src/lib/onboard/dashboard-port.test.ts index 4d2e983cba6..764f70c8827 100644 --- a/src/lib/onboard/dashboard-port.test.ts +++ b/src/lib/onboard/dashboard-port.test.ts @@ -375,6 +375,29 @@ describe("dashboard port reservation", () => { await closeServer(listener); }); + it("releases the selected port when finalization calls the extracted scope callback (#9568)", async () => { + const port = await unusedLoopbackPort(); + + await withDashboardPortReservationScope(async (scope) => { + scope.current = await reserveDashboardPort(port); + await assert.rejects( + listenOnLoopback(port), + (error: NodeJS.ErrnoException) => error.code === "EADDRINUSE", + ); + + const finalizationDashboard = { releasePort: scope.release }; + await finalizationDashboard.releasePort(); + + assert.equal(scope.current, null); + const listener = await listenOnLoopback(port); + try { + assert.equal(listener.listening, true); + } finally { + await closeServer(listener); + } + }); + }); + it("reselects before sandbox creation when a listener wins the allocation race (#8798)", async () => { const attempts: number[] = []; const warnings: string[] = []; From 67327620caea9247565b6a28b754a07a5f21ddaf Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Wed, 19 Aug 2026 00:06:23 -0400 Subject: [PATCH 2/8] fix(onboard): preserve the dashboard reservation scope Signed-off-by: Julie Yaunches --- src/lib/onboard/dashboard-port.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/onboard/dashboard-port.ts b/src/lib/onboard/dashboard-port.ts index 5e35400facc..bbdcf78836b 100644 --- a/src/lib/onboard/dashboard-port.ts +++ b/src/lib/onboard/dashboard-port.ts @@ -610,9 +610,9 @@ export async function withDashboardPortReservationScope( ): Promise { const scope: DashboardPortReservationScope = { current: null, - async release() { - const reservation = this.current; - this.current = null; + release: async () => { + const reservation = scope.current; + scope.current = null; await reservation?.release(); }, }; From b08b9c656f8fa17e186999843b052a88dd039ca4 Mon Sep 17 00:00:00 2001 From: Prekshi Vyas Date: Tue, 18 Aug 2026 22:02:54 -0700 Subject: [PATCH 3/8] test(onboard): close unexpected dashboard listener Signed-off-by: Prekshi Vyas --- src/lib/onboard/dashboard-port.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/onboard/dashboard-port.test.ts b/src/lib/onboard/dashboard-port.test.ts index 764f70c8827..fef64cb861f 100644 --- a/src/lib/onboard/dashboard-port.test.ts +++ b/src/lib/onboard/dashboard-port.test.ts @@ -380,8 +380,12 @@ describe("dashboard port reservation", () => { await withDashboardPortReservationScope(async (scope) => { scope.current = await reserveDashboardPort(port); + const blockedAttempt = listenOnLoopback(port).then(async (listener) => { + await closeServer(listener); + throw new Error("expected dashboard reservation to hold the port"); + }); await assert.rejects( - listenOnLoopback(port), + blockedAttempt, (error: NodeJS.ErrnoException) => error.code === "EADDRINUSE", ); From 914acd158c96ce3c011221a2c6bfd05e71e15750 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Wed, 19 Aug 2026 01:16:37 -0400 Subject: [PATCH 4/8] test(onboard): close dashboard port probes Signed-off-by: Julie Yaunches --- src/lib/onboard/dashboard-port.test.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/lib/onboard/dashboard-port.test.ts b/src/lib/onboard/dashboard-port.test.ts index fef64cb861f..2470261bdf1 100644 --- a/src/lib/onboard/dashboard-port.test.ts +++ b/src/lib/onboard/dashboard-port.test.ts @@ -40,6 +40,11 @@ async function closeServer(server: Server): Promise { }); } +async function listenAndCloseOnLoopback(port: number): Promise { + const server = await listenOnLoopback(port); + await closeServer(server); +} + async function unusedLoopbackPort(): Promise { const server = await listenOnLoopback(0); const address = server.address(); @@ -363,7 +368,7 @@ describe("dashboard port reservation", () => { withDashboardPortReservationScope(async (scope) => { scope.current = await reserveDashboardPort(port); await assert.rejects( - listenOnLoopback(port), + listenAndCloseOnLoopback(port), (error: NodeJS.ErrnoException) => error.code === "EADDRINUSE", ); throw new Error("sandbox build failed"); @@ -380,12 +385,8 @@ describe("dashboard port reservation", () => { await withDashboardPortReservationScope(async (scope) => { scope.current = await reserveDashboardPort(port); - const blockedAttempt = listenOnLoopback(port).then(async (listener) => { - await closeServer(listener); - throw new Error("expected dashboard reservation to hold the port"); - }); await assert.rejects( - blockedAttempt, + listenAndCloseOnLoopback(port), (error: NodeJS.ErrnoException) => error.code === "EADDRINUSE", ); From b93d57d78c704896391d96a6b71861733c345730 Mon Sep 17 00:00:00 2001 From: Prekshi Vyas Date: Tue, 18 Aug 2026 23:20:52 -0700 Subject: [PATCH 5/8] test(installer): keep Hermes fixture under trusted root Signed-off-by: Prekshi Vyas --- test/install-hermes-portable-active.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/install-hermes-portable-active.test.ts b/test/install-hermes-portable-active.test.ts index 89b539c27b4..d7131034d38 100644 --- a/test/install-hermes-portable-active.test.ts +++ b/test/install-hermes-portable-active.test.ts @@ -3,7 +3,6 @@ import { spawnSync } from "node:child_process"; import fs from "node:fs"; -import os from "node:os"; import path from "node:path"; import { describe, expect, it, vi } from "vitest"; @@ -73,7 +72,7 @@ function cloneWithInstaller( describe("Hermes portable installer admission", testTimeoutOptions(60_000), () => { it("activates one schema-5 receipt from a private checkout and validates both installer sources (#9211)", async () => { - const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-hermes-admission-")); + const fixtureRoot = fs.mkdtempSync(path.join(ROOT, ".nemoclaw-hermes-admission-")); fs.chmodSync(fixtureRoot, 0o700); const stateDir = path.join(fixtureRoot, "state"); const homeDir = path.join(fixtureRoot, "home"); From 018f2741c106b60aaae7ee1680ad0b7ff287a5eb Mon Sep 17 00:00:00 2001 From: Prekshi Vyas Date: Wed, 19 Aug 2026 00:07:51 -0700 Subject: [PATCH 6/8] ci: retry transient GHCR PR digest pulls --- .github/workflows/managed-images.yaml | 4 +- scripts/checks/pull-public-exact-digest.sh | 64 ++++++++ test/e2e/RETRY_INVENTORY.md | 1 + test/helpers/vitest-watch-triggers.ts | 7 + ...managed-image-publication-workflow.test.ts | 5 +- test/pull-public-exact-digest.test.ts | 141 ++++++++++++++++++ test/vitest-watch-triggers.test.ts | 4 + 7 files changed, 222 insertions(+), 4 deletions(-) create mode 100755 scripts/checks/pull-public-exact-digest.sh create mode 100644 test/pull-public-exact-digest.test.ts diff --git a/.github/workflows/managed-images.yaml b/.github/workflows/managed-images.yaml index 98c4b486ca5..d0de92e7daa 100644 --- a/.github/workflows/managed-images.yaml +++ b/.github/workflows/managed-images.yaml @@ -782,9 +782,7 @@ jobs: echo "ERROR: published PR manifest bytes do not match the build digest" >&2 exit 1 } - anonymous_config="$(mktemp -d "$RUNNER_TEMP/managed-pr-anonymous.XXXXXX")" - trap 'rm -rf -- "$anonymous_config"' EXIT - DOCKER_CONFIG="$anonymous_config" docker pull --platform linux/amd64 "$reference" + scripts/checks/pull-public-exact-digest.sh "$reference" linux/amd64 release="v$(node -p 'require("./package.json").version')" contract_dir="$RUNNER_TEMP/managed-pr-contract" mkdir -p "$contract_dir" diff --git a/scripts/checks/pull-public-exact-digest.sh b/scripts/checks/pull-public-exact-digest.sh new file mode 100755 index 00000000000..6782a16a0d0 --- /dev/null +++ b/scripts/checks/pull-public-exact-digest.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +if [ "$#" -ne 2 ]; then + echo "usage: $0 " >&2 + exit 2 +fi + +reference="$1" +platform="$2" +max_attempts=5 +retry_delays=(2 4 8 16) + +if [[ ! "$reference" =~ ^ghcr\.io/[a-z0-9._/-]+@sha256:[a-f0-9]{64}$ ]]; then + echo "ERROR: public image reference must be an exact lowercase GHCR digest" >&2 + exit 2 +fi +if [ "$platform" != "linux/amd64" ]; then + echo "ERROR: public image pull platform must be linux/amd64" >&2 + exit 2 +fi +if [ -z "${RUNNER_TEMP:-}" ] || [ ! -d "$RUNNER_TEMP" ]; then + echo "ERROR: RUNNER_TEMP must name an existing directory" >&2 + exit 2 +fi + +anonymous_config="$(mktemp -d "$RUNNER_TEMP/managed-pr-anonymous.XXXXXX")" +attempt_log="$anonymous_config/pull.log" +chmod 700 "$anonymous_config" +trap 'rm -rf -- "$anonymous_config"' EXIT + +for ((attempt = 1; attempt <= max_attempts; attempt += 1)); do + : >"$attempt_log" + if env -u DOCKER_AUTH_CONFIG DOCKER_CONFIG="$anonymous_config" \ + docker pull --platform "$platform" "$reference" >"$attempt_log" 2>&1; then + if [ "$attempt" -eq 1 ]; then + outcome="passed-first-attempt" + else + outcome="passed-after-retry" + fi + echo "::notice::GHCR anonymous exact-digest pull outcome=$outcome attempt=$attempt/$max_attempts" + exit 0 + else + status="$?" + fi + + last_line="$(awk 'NF { line=$0 } END { sub(/\r$/, "", line); print line }' "$attempt_log")" + if [ "$last_line" != "ERROR: $reference: not found" ]; then + echo "::error::GHCR anonymous exact-digest pull outcome=failed-no-retry attempt=$attempt/$max_attempts docker-exit=$status" >&2 + exit "$status" + fi + + if [ "$attempt" -eq "$max_attempts" ]; then + echo "::error::GHCR anonymous exact-digest pull outcome=exhausted attempt=$attempt/$max_attempts failure=not-found" >&2 + exit "$status" + fi + + delay="${retry_delays[$((attempt - 1))]}" + echo "::warning::GHCR anonymous exact-digest pull outcome=transient-external attempt=$attempt/$max_attempts retry-in=${delay}s" >&2 + sleep "$delay" +done diff --git a/test/e2e/RETRY_INVENTORY.md b/test/e2e/RETRY_INVENTORY.md index b7219b1cf53..d00a515f457 100644 --- a/test/e2e/RETRY_INVENTORY.md +++ b/test/e2e/RETRY_INVENTORY.md @@ -17,6 +17,7 @@ Exhaustion remains failed. | `hosted-runner-recovery` | Confirmed GitHub-hosted runner loss; `tools/e2e/hosted-runner-recovery.mts`, `tools/e2e/hosted-runner-loss*.mts` | Authenticated runner-allocation or internal-runner evidence that remains identical across 2 consecutive reads | 2 immediate evidence reads and at most 1 recovery request; no delay | GitHub reruns a workflow attempt | GitHub Actions | Dedicated runner-loss classifications | Source and recovery run links plus authenticated job evidence | External owner; governed by #7146, not this policy | | `pr-rerun-reconciliation` | PR E2E dispatch reconciliation; `tools/e2e/pr-e2e-dispatch-reconciliation.mts`, `tools/e2e/pr-e2e-retry-receipt.mts` | Trusted dispatch receipt state | Contract-defined single reconciliation | Reconciles workflow and commit identity before action | GitHub Actions | Receipt-specific terminal states | Signed workflow identity and receipt | External scope; governed by #7206 | | `github-publication-read` | GitHub API reads; `tools/e2e/base-image-publication.mts` | Fetch error, 408, rate limit, or 5xx | 3 attempts; Retry-After/rate-limit reset or linear delay capped at 10s | Read-only | GitHub API | Returned parsed selection on success; thrown terminal HTTP/fetch error on failure or exhaustion | Caller artifact records the returned publication selection; terminal errors identify exhausted fetch or HTTP status without response content | Eligible bounded read; existing implementation retained | +| `ghcr-pr-exact-digest-visibility` | Anonymous pull after same-repository PR publication; `scripts/checks/pull-public-exact-digest.sh`, `.github/workflows/managed-images.yaml` | Final nonempty Docker output line is exactly `ERROR: : not found`; every other Docker failure is terminal | 5 attempts; exponential 2s, 4s, 8s, then 16s | Read-only registry request for one immutable digest; any local image-cache write repeats the same content-addressed result | GHCR | `passed-first-attempt`, `passed-after-retry`, `failed-no-retry`, or `exhausted` | Sanitized outcome, attempt number, total attempts, retry delay or Docker exit status; raw Docker output, credentials, and environment values are excluded | Eligible bounded external read after the independently fetched manifest bytes match the publication digest; every non-exact failure remains terminal and no workflow rerun occurs | | `trusted-controller-collaborator-permission-read` | Collaborator-permission reads for manual PR dispatch and Launchable E2E dispatch; `.github/workflows/e2e.yaml` | Curl exit 5, 6, 7, 16, 18, 28, 35, 52, 55, 56, 92, 95, or 96; HTTP 408, 429, or 5xx | 3 attempts; linear 1s then 2s | Read-only GitHub API request | GitHub API | Transient API read versus terminal authentication, authorization, actor, or response failure | Operation name, attempt number, and sanitized failure class or HTTP status; no response body, header, or token | Eligible bounded read; HTTP 401, 403, 404, and 422, malformed responses, actor failures, and insufficient roles remain terminal; no cached permission or workflow rerun | | `pr-exact-openclaw-mcp-repetition` | Complete OpenClaw trusted-private MCP bridge acceptance; `.github/workflows/managed-images.yaml`, `test/e2e/live/mcp-bridge.test.ts` | Either independent matrix execution fails | 2 required executions on fresh runners; 0 workflow or test retries | Each execution creates and cleans up its own sandbox against the same exact candidate publication cohort | NemoClaw | Each execution passes or fails independently; both must pass | Existing redacted MCP diagnostics, request ledger, cleanup evidence, and fixture-credential scan for each matrix pass | Fixed acceptance repetition required by #8746; not a retry, and one pass never masks the other | | `github-exact-artifact-content-read` | Bound base-image or PR managed-image contract artifact; `tools/e2e/exact-artifact-download.mts`, `tools/e2e/pr-managed-image-publication.mts` | Transport failure, HTTP 408, HTTP 429, or HTTP 5xx while reading one pre-bound artifact ID | 3 attempts; Retry-After or linear delay capped at 10s | Read-only request against one immutable artifact ID, name, size, digest, producer run, attempt, and producer commit | GitHub artifact service | `passed-first-attempt`, `passed-after-retry`, `exhausted` for transient exhaustion, or `failed-no-retry` for terminal HTTP; identity, size, digest, archive, and contract failures throw without an aggregate outcome or `failureClass` | Content-read attempts log only the sanitized operation, attempt, HTTP status or transport class, and outcome; thrown validation failures expose only their bounded error message, never headers, body, token, signed URL, or artifact content | Standalone bounded content read; it does not use `retry-policy.ts` or `RetryEvidence`, and all identity, integrity, archive, and contract failures remain terminal | diff --git a/test/helpers/vitest-watch-triggers.ts b/test/helpers/vitest-watch-triggers.ts index b1632c0e5cb..6aef16cd817 100644 --- a/test/helpers/vitest-watch-triggers.ts +++ b/test/helpers/vitest-watch-triggers.ts @@ -133,6 +133,13 @@ export const vitestWatchTriggerPatterns: VitestWatchTriggerPattern[] = [ "test/dcode-base-image-workflow.test.ts", ), }, + { + pattern: /(?:^|\/)scripts\/checks\/pull-public-exact-digest\.sh$/, + testsToRun: runTests( + "test/pull-public-exact-digest.test.ts", + "test/managed-image-publication-workflow.test.ts", + ), + }, { pattern: /(?:^|\/)scripts\/e2e\/sanitize-trace-timing\.py$/, testsToRun: runTests( diff --git a/test/managed-image-publication-workflow.test.ts b/test/managed-image-publication-workflow.test.ts index 862047b40ae..ba29f8b6c42 100644 --- a/test/managed-image-publication-workflow.test.ts +++ b/test/managed-image-publication-workflow.test.ts @@ -675,7 +675,10 @@ describe("complete managed-image publication workflow", () => { expect(exportContract.if).toBe(sameRepository); expect(uploadContract.if).toBe(sameRepository); expect(steps.indexOf(logout)).toBeLessThan(steps.indexOf(exportContract)); - expect(exportContract.run).toContain('DOCKER_CONFIG="$anonymous_config" docker pull'); + expect(exportContract.run).toContain( + 'scripts/checks/pull-public-exact-digest.sh "$reference" linux/amd64', + ); + expect(exportContract.run).not.toContain('docker pull --platform linux/amd64 "$reference"'); expect(exportContract.run).toContain("revision: $revision"); expect(JSON.stringify(prBuilder).match(/secrets\.GITHUB_TOKEN/gu)).toHaveLength(1); expect(JSON.stringify(prBuilder)).not.toContain("github.token"); diff --git a/test/pull-public-exact-digest.test.ts b/test/pull-public-exact-digest.test.ts new file mode 100644 index 00000000000..5fae737a697 --- /dev/null +++ b/test/pull-public-exact-digest.test.ts @@ -0,0 +1,141 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { 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"; + +const repoRoot = path.resolve(import.meta.dirname, ".."); +const puller = path.join(repoRoot, "scripts/checks/pull-public-exact-digest.sh"); +const reference = `ghcr.io/nvidia/nemoclaw/langchain-deepagents-code-sandbox@sha256:${"a".repeat(64)}`; + +type Scenario = "exhausted" | "success" | "terminal" | "transient-then-success"; + +function runPuller(scenario: Scenario, candidateReference = reference) { + const temporaryRoot = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-public-pull-")); + const fakeBin = path.join(temporaryRoot, "bin"); + const countFile = path.join(temporaryRoot, "count"); + const configLog = path.join(temporaryRoot, "docker-configs"); + const sleepLog = path.join(temporaryRoot, "sleeps"); + fs.mkdirSync(fakeBin); + fs.writeFileSync( + path.join(fakeBin, "docker"), + `#!/usr/bin/env bash +set -euo pipefail +count=0 +if [ -f "$COUNT_FILE" ]; then + count="$(cat "$COUNT_FILE")" +fi +count=$((count + 1)) +printf '%s\n' "$count" >"$COUNT_FILE" +printf '%s\n' "$DOCKER_CONFIG" >>"$CONFIG_LOG" +[ -z "\${DOCKER_AUTH_CONFIG+x}" ] || exit 91 +[ "$*" = "pull --platform linux/amd64 $EXPECTED_REFERENCE" ] || exit 90 +if [ "$SCENARIO" = "terminal" ]; then + echo "denied: permission_denied" >&2 + exit 41 +fi +if [ "$SCENARIO" = "exhausted" ] || { [ "$SCENARIO" = "transient-then-success" ] && [ "$count" -eq 1 ]; }; then + echo "ERROR: $EXPECTED_REFERENCE: not found" >&2 + exit 42 +fi +echo "pulled $EXPECTED_REFERENCE" +`, + { mode: 0o755 }, + ); + fs.writeFileSync( + path.join(fakeBin, "sleep"), + '#!/usr/bin/env bash\nset -euo pipefail\nprintf \'%s\\n\' "$1" >>"$SLEEP_LOG"\n', + { mode: 0o755 }, + ); + + try { + const result = spawnSync(puller, [candidateReference, "linux/amd64"], { + encoding: "utf8", + env: { + ...process.env, + CONFIG_LOG: configLog, + COUNT_FILE: countFile, + DOCKER_AUTH_CONFIG: "must-not-reach-docker", + EXPECTED_REFERENCE: candidateReference, + PATH: `${fakeBin}:${process.env.PATH ?? ""}`, + RUNNER_TEMP: temporaryRoot, + SCENARIO: scenario, + SLEEP_LOG: sleepLog, + }, + }); + const count = fs.existsSync(countFile) ? Number(fs.readFileSync(countFile, "utf8").trim()) : 0; + const configs = fs.existsSync(configLog) + ? fs.readFileSync(configLog, "utf8").trim().split("\n") + : []; + const sleeps = fs.existsSync(sleepLog) + ? fs.readFileSync(sleepLog, "utf8").trim().split("\n") + : []; + return { + ...result, + configs, + configsWereRemoved: configs.every((config) => !fs.existsSync(config)), + count, + sleeps, + }; + } finally { + fs.rmSync(temporaryRoot, { force: true, recursive: true }); + } +} + +describe("pull-public-exact-digest", () => { + it("passes once with a credential-free Docker configuration", () => { + const result = runPuller("success"); + + expect(result.status, result.stderr).toBe(0); + expect(result.count).toBe(1); + expect(result.sleeps).toEqual([]); + expect(result.configsWereRemoved).toBe(true); + expect(result.stdout).toContain("outcome=passed-first-attempt attempt=1/5"); + }); + + it("retries the exact transient GHCR not-found result and removes anonymous state", () => { + const result = runPuller("transient-then-success"); + + expect(result.status, result.stderr).toBe(0); + expect(result.count).toBe(2); + expect(result.sleeps).toEqual(["2"]); + expect(new Set(result.configs).size).toBe(1); + expect(result.configsWereRemoved).toBe(true); + expect(result.stderr).toContain("outcome=transient-external attempt=1/5 retry-in=2s"); + expect(result.stdout).toContain("outcome=passed-after-retry attempt=2/5"); + expect(result.stdout + result.stderr).not.toContain(`${reference}: not found`); + }); + + it("does not retry a non-exact Docker error", () => { + const result = runPuller("terminal"); + + expect(result.status).toBe(41); + expect(result.count).toBe(1); + expect(result.sleeps).toEqual([]); + expect(result.configsWereRemoved).toBe(true); + expect(result.stderr).toContain("outcome=failed-no-retry attempt=1/5 docker-exit=41"); + expect(result.stderr).not.toContain("permission_denied"); + }); + + it("fails after the bounded retry schedule is exhausted", () => { + const result = runPuller("exhausted"); + + expect(result.status).toBe(42); + expect(result.count).toBe(5); + expect(result.sleeps).toEqual(["2", "4", "8", "16"]); + expect(result.configsWereRemoved).toBe(true); + expect(result.stderr).toContain("outcome=exhausted attempt=5/5 failure=not-found"); + }); + + it("rejects a mutable or non-GHCR reference before Docker runs", () => { + const result = runPuller("terminal", "docker.io/nvidia/nemoclaw:latest"); + + expect(result.status).toBe(2); + expect(result.count).toBe(0); + expect(result.stderr).toContain("must be an exact lowercase GHCR digest"); + }); +}); diff --git a/test/vitest-watch-triggers.test.ts b/test/vitest-watch-triggers.test.ts index 9c1906f4118..a7431c0f14a 100644 --- a/test/vitest-watch-triggers.test.ts +++ b/test/vitest-watch-triggers.test.ts @@ -158,6 +158,10 @@ describe("Vitest opaque-input watch triggers", () => { "test/managed-image-publication-workflow.test.ts", "test/dcode-base-image-workflow.test.ts", ]); + expect(triggeredBy("scripts/checks/pull-public-exact-digest.sh")).toEqual([ + "test/pull-public-exact-digest.test.ts", + "test/managed-image-publication-workflow.test.ts", + ]); expect(triggeredBy("scripts/e2e/sanitize-trace-timing.py")).toEqual([ "test/e2e/support/e2e-scorecard.test.ts", "test/e2e/support/sanitize-trace-timing.test.ts", From c3ec20416dafac5501c38e4a32867454b34c333f Mon Sep 17 00:00:00 2001 From: Prekshi Vyas Date: Wed, 19 Aug 2026 00:25:34 -0700 Subject: [PATCH 7/8] test: keep managed image workflow within budget --- test/helpers/vitest-watch-triggers.ts | 7 ++++++ ...managed-image-publication-workflow.test.ts | 4 ---- test/pull-public-exact-digest.test.ts | 22 +++++++++++++++++++ test/vitest-watch-triggers.test.ts | 5 +++++ 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/test/helpers/vitest-watch-triggers.ts b/test/helpers/vitest-watch-triggers.ts index 6aef16cd817..7bd81e883f9 100644 --- a/test/helpers/vitest-watch-triggers.ts +++ b/test/helpers/vitest-watch-triggers.ts @@ -113,6 +113,13 @@ export const vitestWatchTriggerPatterns: VitestWatchTriggerPattern[] = [ "test/dcode-base-image-workflow.test.ts", ), }, + { + pattern: /(?:^|\/)\.github\/workflows\/managed-images\.yaml$/, + testsToRun: runTests( + "test/managed-image-publication-workflow.test.ts", + "test/pull-public-exact-digest.test.ts", + ), + }, { pattern: /(?:^|\/)\.github\/actions\/build-base-image-platform\/action\.yaml$/, testsToRun: runTests( diff --git a/test/managed-image-publication-workflow.test.ts b/test/managed-image-publication-workflow.test.ts index ba29f8b6c42..c3d9013c7eb 100644 --- a/test/managed-image-publication-workflow.test.ts +++ b/test/managed-image-publication-workflow.test.ts @@ -675,10 +675,6 @@ describe("complete managed-image publication workflow", () => { expect(exportContract.if).toBe(sameRepository); expect(uploadContract.if).toBe(sameRepository); expect(steps.indexOf(logout)).toBeLessThan(steps.indexOf(exportContract)); - expect(exportContract.run).toContain( - 'scripts/checks/pull-public-exact-digest.sh "$reference" linux/amd64', - ); - expect(exportContract.run).not.toContain('docker pull --platform linux/amd64 "$reference"'); expect(exportContract.run).toContain("revision: $revision"); expect(JSON.stringify(prBuilder).match(/secrets\.GITHUB_TOKEN/gu)).toHaveLength(1); expect(JSON.stringify(prBuilder)).not.toContain("github.token"); diff --git a/test/pull-public-exact-digest.test.ts b/test/pull-public-exact-digest.test.ts index 5fae737a697..622fcf8996f 100644 --- a/test/pull-public-exact-digest.test.ts +++ b/test/pull-public-exact-digest.test.ts @@ -11,6 +11,10 @@ import { describe, expect, it } from "vitest"; const repoRoot = path.resolve(import.meta.dirname, ".."); const puller = path.join(repoRoot, "scripts/checks/pull-public-exact-digest.sh"); const reference = `ghcr.io/nvidia/nemoclaw/langchain-deepagents-code-sandbox@sha256:${"a".repeat(64)}`; +const managedImagesWorkflow = fs.readFileSync( + path.join(repoRoot, ".github/workflows/managed-images.yaml"), + "utf8", +); type Scenario = "exhausted" | "success" | "terminal" | "transient-then-success"; @@ -87,6 +91,24 @@ echo "pulled $EXPECTED_REFERENCE" } describe("pull-public-exact-digest", () => { + it("routes the published PR pull through the bounded anonymous helper", () => { + const exportStepStart = managedImagesWorkflow.indexOf( + " - name: Export exact published PR managed-image contract", + ); + const exportStepEnd = managedImagesWorkflow.indexOf( + " - name: Upload exact published PR managed-image contract", + exportStepStart, + ); + expect(exportStepStart).toBeGreaterThan(-1); + expect(exportStepEnd).toBeGreaterThan(exportStepStart); + + const exportStep = managedImagesWorkflow.slice(exportStepStart, exportStepEnd); + expect(exportStep).toContain( + 'scripts/checks/pull-public-exact-digest.sh "$reference" linux/amd64', + ); + expect(exportStep).not.toContain('docker pull --platform linux/amd64 "$reference"'); + }); + it("passes once with a credential-free Docker configuration", () => { const result = runPuller("success"); diff --git a/test/vitest-watch-triggers.test.ts b/test/vitest-watch-triggers.test.ts index a7431c0f14a..aeadebe0e31 100644 --- a/test/vitest-watch-triggers.test.ts +++ b/test/vitest-watch-triggers.test.ts @@ -140,6 +140,11 @@ describe("Vitest opaque-input watch triggers", () => { "test/managed-image-publication-workflow.test.ts", "test/dcode-base-image-workflow.test.ts", ]); + expect(triggeredBy(".github/workflows/managed-images.yaml")).toEqual([ + "test/pi-candidate-runtime-artifacts.test.ts", + "test/managed-image-publication-workflow.test.ts", + "test/pull-public-exact-digest.test.ts", + ]); expect(triggeredBy("scripts/export-managed-base-image-contract.sh")).toEqual([ "test/managed-base-image-contract.test.ts", "test/managed-image-publication-workflow.test.ts", From 5cb0a14212767093499d5a50fb81bdf523d43fea Mon Sep 17 00:00:00 2001 From: Prekshi Vyas Date: Wed, 19 Aug 2026 00:28:02 -0700 Subject: [PATCH 8/8] ci: keep dashboard fix scoped Remove the unrelated GHCR digest retry feature from this dashboard-port pull request. This follows the author's blocking scope review. The retry behavior can be proposed and reviewed independently. Signed-off-by: Prekshi Vyas --- .github/workflows/managed-images.yaml | 4 +- scripts/checks/pull-public-exact-digest.sh | 64 ------- test/e2e/RETRY_INVENTORY.md | 1 - test/helpers/vitest-watch-triggers.ts | 14 -- ...managed-image-publication-workflow.test.ts | 1 + test/pull-public-exact-digest.test.ts | 163 ------------------ test/vitest-watch-triggers.test.ts | 9 - 7 files changed, 4 insertions(+), 252 deletions(-) delete mode 100755 scripts/checks/pull-public-exact-digest.sh delete mode 100644 test/pull-public-exact-digest.test.ts diff --git a/.github/workflows/managed-images.yaml b/.github/workflows/managed-images.yaml index d0de92e7daa..98c4b486ca5 100644 --- a/.github/workflows/managed-images.yaml +++ b/.github/workflows/managed-images.yaml @@ -782,7 +782,9 @@ jobs: echo "ERROR: published PR manifest bytes do not match the build digest" >&2 exit 1 } - scripts/checks/pull-public-exact-digest.sh "$reference" linux/amd64 + anonymous_config="$(mktemp -d "$RUNNER_TEMP/managed-pr-anonymous.XXXXXX")" + trap 'rm -rf -- "$anonymous_config"' EXIT + DOCKER_CONFIG="$anonymous_config" docker pull --platform linux/amd64 "$reference" release="v$(node -p 'require("./package.json").version')" contract_dir="$RUNNER_TEMP/managed-pr-contract" mkdir -p "$contract_dir" diff --git a/scripts/checks/pull-public-exact-digest.sh b/scripts/checks/pull-public-exact-digest.sh deleted file mode 100755 index 6782a16a0d0..00000000000 --- a/scripts/checks/pull-public-exact-digest.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env bash -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -set -euo pipefail - -if [ "$#" -ne 2 ]; then - echo "usage: $0 " >&2 - exit 2 -fi - -reference="$1" -platform="$2" -max_attempts=5 -retry_delays=(2 4 8 16) - -if [[ ! "$reference" =~ ^ghcr\.io/[a-z0-9._/-]+@sha256:[a-f0-9]{64}$ ]]; then - echo "ERROR: public image reference must be an exact lowercase GHCR digest" >&2 - exit 2 -fi -if [ "$platform" != "linux/amd64" ]; then - echo "ERROR: public image pull platform must be linux/amd64" >&2 - exit 2 -fi -if [ -z "${RUNNER_TEMP:-}" ] || [ ! -d "$RUNNER_TEMP" ]; then - echo "ERROR: RUNNER_TEMP must name an existing directory" >&2 - exit 2 -fi - -anonymous_config="$(mktemp -d "$RUNNER_TEMP/managed-pr-anonymous.XXXXXX")" -attempt_log="$anonymous_config/pull.log" -chmod 700 "$anonymous_config" -trap 'rm -rf -- "$anonymous_config"' EXIT - -for ((attempt = 1; attempt <= max_attempts; attempt += 1)); do - : >"$attempt_log" - if env -u DOCKER_AUTH_CONFIG DOCKER_CONFIG="$anonymous_config" \ - docker pull --platform "$platform" "$reference" >"$attempt_log" 2>&1; then - if [ "$attempt" -eq 1 ]; then - outcome="passed-first-attempt" - else - outcome="passed-after-retry" - fi - echo "::notice::GHCR anonymous exact-digest pull outcome=$outcome attempt=$attempt/$max_attempts" - exit 0 - else - status="$?" - fi - - last_line="$(awk 'NF { line=$0 } END { sub(/\r$/, "", line); print line }' "$attempt_log")" - if [ "$last_line" != "ERROR: $reference: not found" ]; then - echo "::error::GHCR anonymous exact-digest pull outcome=failed-no-retry attempt=$attempt/$max_attempts docker-exit=$status" >&2 - exit "$status" - fi - - if [ "$attempt" -eq "$max_attempts" ]; then - echo "::error::GHCR anonymous exact-digest pull outcome=exhausted attempt=$attempt/$max_attempts failure=not-found" >&2 - exit "$status" - fi - - delay="${retry_delays[$((attempt - 1))]}" - echo "::warning::GHCR anonymous exact-digest pull outcome=transient-external attempt=$attempt/$max_attempts retry-in=${delay}s" >&2 - sleep "$delay" -done diff --git a/test/e2e/RETRY_INVENTORY.md b/test/e2e/RETRY_INVENTORY.md index d00a515f457..b7219b1cf53 100644 --- a/test/e2e/RETRY_INVENTORY.md +++ b/test/e2e/RETRY_INVENTORY.md @@ -17,7 +17,6 @@ Exhaustion remains failed. | `hosted-runner-recovery` | Confirmed GitHub-hosted runner loss; `tools/e2e/hosted-runner-recovery.mts`, `tools/e2e/hosted-runner-loss*.mts` | Authenticated runner-allocation or internal-runner evidence that remains identical across 2 consecutive reads | 2 immediate evidence reads and at most 1 recovery request; no delay | GitHub reruns a workflow attempt | GitHub Actions | Dedicated runner-loss classifications | Source and recovery run links plus authenticated job evidence | External owner; governed by #7146, not this policy | | `pr-rerun-reconciliation` | PR E2E dispatch reconciliation; `tools/e2e/pr-e2e-dispatch-reconciliation.mts`, `tools/e2e/pr-e2e-retry-receipt.mts` | Trusted dispatch receipt state | Contract-defined single reconciliation | Reconciles workflow and commit identity before action | GitHub Actions | Receipt-specific terminal states | Signed workflow identity and receipt | External scope; governed by #7206 | | `github-publication-read` | GitHub API reads; `tools/e2e/base-image-publication.mts` | Fetch error, 408, rate limit, or 5xx | 3 attempts; Retry-After/rate-limit reset or linear delay capped at 10s | Read-only | GitHub API | Returned parsed selection on success; thrown terminal HTTP/fetch error on failure or exhaustion | Caller artifact records the returned publication selection; terminal errors identify exhausted fetch or HTTP status without response content | Eligible bounded read; existing implementation retained | -| `ghcr-pr-exact-digest-visibility` | Anonymous pull after same-repository PR publication; `scripts/checks/pull-public-exact-digest.sh`, `.github/workflows/managed-images.yaml` | Final nonempty Docker output line is exactly `ERROR: : not found`; every other Docker failure is terminal | 5 attempts; exponential 2s, 4s, 8s, then 16s | Read-only registry request for one immutable digest; any local image-cache write repeats the same content-addressed result | GHCR | `passed-first-attempt`, `passed-after-retry`, `failed-no-retry`, or `exhausted` | Sanitized outcome, attempt number, total attempts, retry delay or Docker exit status; raw Docker output, credentials, and environment values are excluded | Eligible bounded external read after the independently fetched manifest bytes match the publication digest; every non-exact failure remains terminal and no workflow rerun occurs | | `trusted-controller-collaborator-permission-read` | Collaborator-permission reads for manual PR dispatch and Launchable E2E dispatch; `.github/workflows/e2e.yaml` | Curl exit 5, 6, 7, 16, 18, 28, 35, 52, 55, 56, 92, 95, or 96; HTTP 408, 429, or 5xx | 3 attempts; linear 1s then 2s | Read-only GitHub API request | GitHub API | Transient API read versus terminal authentication, authorization, actor, or response failure | Operation name, attempt number, and sanitized failure class or HTTP status; no response body, header, or token | Eligible bounded read; HTTP 401, 403, 404, and 422, malformed responses, actor failures, and insufficient roles remain terminal; no cached permission or workflow rerun | | `pr-exact-openclaw-mcp-repetition` | Complete OpenClaw trusted-private MCP bridge acceptance; `.github/workflows/managed-images.yaml`, `test/e2e/live/mcp-bridge.test.ts` | Either independent matrix execution fails | 2 required executions on fresh runners; 0 workflow or test retries | Each execution creates and cleans up its own sandbox against the same exact candidate publication cohort | NemoClaw | Each execution passes or fails independently; both must pass | Existing redacted MCP diagnostics, request ledger, cleanup evidence, and fixture-credential scan for each matrix pass | Fixed acceptance repetition required by #8746; not a retry, and one pass never masks the other | | `github-exact-artifact-content-read` | Bound base-image or PR managed-image contract artifact; `tools/e2e/exact-artifact-download.mts`, `tools/e2e/pr-managed-image-publication.mts` | Transport failure, HTTP 408, HTTP 429, or HTTP 5xx while reading one pre-bound artifact ID | 3 attempts; Retry-After or linear delay capped at 10s | Read-only request against one immutable artifact ID, name, size, digest, producer run, attempt, and producer commit | GitHub artifact service | `passed-first-attempt`, `passed-after-retry`, `exhausted` for transient exhaustion, or `failed-no-retry` for terminal HTTP; identity, size, digest, archive, and contract failures throw without an aggregate outcome or `failureClass` | Content-read attempts log only the sanitized operation, attempt, HTTP status or transport class, and outcome; thrown validation failures expose only their bounded error message, never headers, body, token, signed URL, or artifact content | Standalone bounded content read; it does not use `retry-policy.ts` or `RetryEvidence`, and all identity, integrity, archive, and contract failures remain terminal | diff --git a/test/helpers/vitest-watch-triggers.ts b/test/helpers/vitest-watch-triggers.ts index 7bd81e883f9..b1632c0e5cb 100644 --- a/test/helpers/vitest-watch-triggers.ts +++ b/test/helpers/vitest-watch-triggers.ts @@ -113,13 +113,6 @@ export const vitestWatchTriggerPatterns: VitestWatchTriggerPattern[] = [ "test/dcode-base-image-workflow.test.ts", ), }, - { - pattern: /(?:^|\/)\.github\/workflows\/managed-images\.yaml$/, - testsToRun: runTests( - "test/managed-image-publication-workflow.test.ts", - "test/pull-public-exact-digest.test.ts", - ), - }, { pattern: /(?:^|\/)\.github\/actions\/build-base-image-platform\/action\.yaml$/, testsToRun: runTests( @@ -140,13 +133,6 @@ export const vitestWatchTriggerPatterns: VitestWatchTriggerPattern[] = [ "test/dcode-base-image-workflow.test.ts", ), }, - { - pattern: /(?:^|\/)scripts\/checks\/pull-public-exact-digest\.sh$/, - testsToRun: runTests( - "test/pull-public-exact-digest.test.ts", - "test/managed-image-publication-workflow.test.ts", - ), - }, { pattern: /(?:^|\/)scripts\/e2e\/sanitize-trace-timing\.py$/, testsToRun: runTests( diff --git a/test/managed-image-publication-workflow.test.ts b/test/managed-image-publication-workflow.test.ts index c3d9013c7eb..862047b40ae 100644 --- a/test/managed-image-publication-workflow.test.ts +++ b/test/managed-image-publication-workflow.test.ts @@ -675,6 +675,7 @@ describe("complete managed-image publication workflow", () => { expect(exportContract.if).toBe(sameRepository); expect(uploadContract.if).toBe(sameRepository); expect(steps.indexOf(logout)).toBeLessThan(steps.indexOf(exportContract)); + expect(exportContract.run).toContain('DOCKER_CONFIG="$anonymous_config" docker pull'); expect(exportContract.run).toContain("revision: $revision"); expect(JSON.stringify(prBuilder).match(/secrets\.GITHUB_TOKEN/gu)).toHaveLength(1); expect(JSON.stringify(prBuilder)).not.toContain("github.token"); diff --git a/test/pull-public-exact-digest.test.ts b/test/pull-public-exact-digest.test.ts deleted file mode 100644 index 622fcf8996f..00000000000 --- a/test/pull-public-exact-digest.test.ts +++ /dev/null @@ -1,163 +0,0 @@ -// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { 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"; - -const repoRoot = path.resolve(import.meta.dirname, ".."); -const puller = path.join(repoRoot, "scripts/checks/pull-public-exact-digest.sh"); -const reference = `ghcr.io/nvidia/nemoclaw/langchain-deepagents-code-sandbox@sha256:${"a".repeat(64)}`; -const managedImagesWorkflow = fs.readFileSync( - path.join(repoRoot, ".github/workflows/managed-images.yaml"), - "utf8", -); - -type Scenario = "exhausted" | "success" | "terminal" | "transient-then-success"; - -function runPuller(scenario: Scenario, candidateReference = reference) { - const temporaryRoot = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-public-pull-")); - const fakeBin = path.join(temporaryRoot, "bin"); - const countFile = path.join(temporaryRoot, "count"); - const configLog = path.join(temporaryRoot, "docker-configs"); - const sleepLog = path.join(temporaryRoot, "sleeps"); - fs.mkdirSync(fakeBin); - fs.writeFileSync( - path.join(fakeBin, "docker"), - `#!/usr/bin/env bash -set -euo pipefail -count=0 -if [ -f "$COUNT_FILE" ]; then - count="$(cat "$COUNT_FILE")" -fi -count=$((count + 1)) -printf '%s\n' "$count" >"$COUNT_FILE" -printf '%s\n' "$DOCKER_CONFIG" >>"$CONFIG_LOG" -[ -z "\${DOCKER_AUTH_CONFIG+x}" ] || exit 91 -[ "$*" = "pull --platform linux/amd64 $EXPECTED_REFERENCE" ] || exit 90 -if [ "$SCENARIO" = "terminal" ]; then - echo "denied: permission_denied" >&2 - exit 41 -fi -if [ "$SCENARIO" = "exhausted" ] || { [ "$SCENARIO" = "transient-then-success" ] && [ "$count" -eq 1 ]; }; then - echo "ERROR: $EXPECTED_REFERENCE: not found" >&2 - exit 42 -fi -echo "pulled $EXPECTED_REFERENCE" -`, - { mode: 0o755 }, - ); - fs.writeFileSync( - path.join(fakeBin, "sleep"), - '#!/usr/bin/env bash\nset -euo pipefail\nprintf \'%s\\n\' "$1" >>"$SLEEP_LOG"\n', - { mode: 0o755 }, - ); - - try { - const result = spawnSync(puller, [candidateReference, "linux/amd64"], { - encoding: "utf8", - env: { - ...process.env, - CONFIG_LOG: configLog, - COUNT_FILE: countFile, - DOCKER_AUTH_CONFIG: "must-not-reach-docker", - EXPECTED_REFERENCE: candidateReference, - PATH: `${fakeBin}:${process.env.PATH ?? ""}`, - RUNNER_TEMP: temporaryRoot, - SCENARIO: scenario, - SLEEP_LOG: sleepLog, - }, - }); - const count = fs.existsSync(countFile) ? Number(fs.readFileSync(countFile, "utf8").trim()) : 0; - const configs = fs.existsSync(configLog) - ? fs.readFileSync(configLog, "utf8").trim().split("\n") - : []; - const sleeps = fs.existsSync(sleepLog) - ? fs.readFileSync(sleepLog, "utf8").trim().split("\n") - : []; - return { - ...result, - configs, - configsWereRemoved: configs.every((config) => !fs.existsSync(config)), - count, - sleeps, - }; - } finally { - fs.rmSync(temporaryRoot, { force: true, recursive: true }); - } -} - -describe("pull-public-exact-digest", () => { - it("routes the published PR pull through the bounded anonymous helper", () => { - const exportStepStart = managedImagesWorkflow.indexOf( - " - name: Export exact published PR managed-image contract", - ); - const exportStepEnd = managedImagesWorkflow.indexOf( - " - name: Upload exact published PR managed-image contract", - exportStepStart, - ); - expect(exportStepStart).toBeGreaterThan(-1); - expect(exportStepEnd).toBeGreaterThan(exportStepStart); - - const exportStep = managedImagesWorkflow.slice(exportStepStart, exportStepEnd); - expect(exportStep).toContain( - 'scripts/checks/pull-public-exact-digest.sh "$reference" linux/amd64', - ); - expect(exportStep).not.toContain('docker pull --platform linux/amd64 "$reference"'); - }); - - it("passes once with a credential-free Docker configuration", () => { - const result = runPuller("success"); - - expect(result.status, result.stderr).toBe(0); - expect(result.count).toBe(1); - expect(result.sleeps).toEqual([]); - expect(result.configsWereRemoved).toBe(true); - expect(result.stdout).toContain("outcome=passed-first-attempt attempt=1/5"); - }); - - it("retries the exact transient GHCR not-found result and removes anonymous state", () => { - const result = runPuller("transient-then-success"); - - expect(result.status, result.stderr).toBe(0); - expect(result.count).toBe(2); - expect(result.sleeps).toEqual(["2"]); - expect(new Set(result.configs).size).toBe(1); - expect(result.configsWereRemoved).toBe(true); - expect(result.stderr).toContain("outcome=transient-external attempt=1/5 retry-in=2s"); - expect(result.stdout).toContain("outcome=passed-after-retry attempt=2/5"); - expect(result.stdout + result.stderr).not.toContain(`${reference}: not found`); - }); - - it("does not retry a non-exact Docker error", () => { - const result = runPuller("terminal"); - - expect(result.status).toBe(41); - expect(result.count).toBe(1); - expect(result.sleeps).toEqual([]); - expect(result.configsWereRemoved).toBe(true); - expect(result.stderr).toContain("outcome=failed-no-retry attempt=1/5 docker-exit=41"); - expect(result.stderr).not.toContain("permission_denied"); - }); - - it("fails after the bounded retry schedule is exhausted", () => { - const result = runPuller("exhausted"); - - expect(result.status).toBe(42); - expect(result.count).toBe(5); - expect(result.sleeps).toEqual(["2", "4", "8", "16"]); - expect(result.configsWereRemoved).toBe(true); - expect(result.stderr).toContain("outcome=exhausted attempt=5/5 failure=not-found"); - }); - - it("rejects a mutable or non-GHCR reference before Docker runs", () => { - const result = runPuller("terminal", "docker.io/nvidia/nemoclaw:latest"); - - expect(result.status).toBe(2); - expect(result.count).toBe(0); - expect(result.stderr).toContain("must be an exact lowercase GHCR digest"); - }); -}); diff --git a/test/vitest-watch-triggers.test.ts b/test/vitest-watch-triggers.test.ts index aeadebe0e31..9c1906f4118 100644 --- a/test/vitest-watch-triggers.test.ts +++ b/test/vitest-watch-triggers.test.ts @@ -140,11 +140,6 @@ describe("Vitest opaque-input watch triggers", () => { "test/managed-image-publication-workflow.test.ts", "test/dcode-base-image-workflow.test.ts", ]); - expect(triggeredBy(".github/workflows/managed-images.yaml")).toEqual([ - "test/pi-candidate-runtime-artifacts.test.ts", - "test/managed-image-publication-workflow.test.ts", - "test/pull-public-exact-digest.test.ts", - ]); expect(triggeredBy("scripts/export-managed-base-image-contract.sh")).toEqual([ "test/managed-base-image-contract.test.ts", "test/managed-image-publication-workflow.test.ts", @@ -163,10 +158,6 @@ describe("Vitest opaque-input watch triggers", () => { "test/managed-image-publication-workflow.test.ts", "test/dcode-base-image-workflow.test.ts", ]); - expect(triggeredBy("scripts/checks/pull-public-exact-digest.sh")).toEqual([ - "test/pull-public-exact-digest.test.ts", - "test/managed-image-publication-workflow.test.ts", - ]); expect(triggeredBy("scripts/e2e/sanitize-trace-timing.py")).toEqual([ "test/e2e/support/e2e-scorecard.test.ts", "test/e2e/support/sanitize-trace-timing.test.ts",