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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/e2e-standard-profile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ on:
managed_image_receipt:
required: true
type: string
managed_image_catalog:
required: true
type: string
workload_source:
required: true
type: string
Expand Down Expand Up @@ -113,6 +116,7 @@ jobs:
env:
E2E_JOB: "1"
E2E_MANAGED_IMAGE_REVISION: ${{ inputs.managed_image_revision }}
NEMOCLAW_E2E_MANAGED_IMAGE_CATALOG_JSON: ${{ inputs.managed_image_catalog }}
E2E_WORKLOAD_SOURCE: ${{ inputs.workload_source }}
E2E_TARGET_ID: ${{ inputs.target_id }}
NEMOCLAW_RUN_LIVE_E2E: "1"
Expand Down
55 changes: 44 additions & 11 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ jobs:
dcode_base_ref: ${{ steps.validate_dcode_base.outputs.base_ref }}
managed_image_receipt: ${{ steps.validate_managed_cohort.outputs.receipt }}
managed_image_revision: ${{ steps.validate_managed_cohort.outputs.revision }}
workload_source: ${{ steps.select_pr_source.outputs.workload_source || 'managed-image' }}
managed_image_catalog: ${{ steps.select_pr_source.outputs.catalog }}
workload_source: managed-image
permissions:
actions: read
contents: read
Expand Down Expand Up @@ -218,7 +219,7 @@ jobs:
node-version: 22

- id: select_pr_source
name: Select PR workload source
name: Resolve exact PR managed-image publication
if: ${{ inputs.pr_number != '' }}
env:
BASE_SHA: ${{ inputs.base_sha }}
Expand All @@ -229,21 +230,40 @@ jobs:
shell: bash
run: |
set -euo pipefail
workload_source="$(node --experimental-strip-types --no-warnings tools/e2e/pr-managed-image-publication.mts select-source)"
case "$workload_source" in
managed-image|local-dockerfile) ;;
*) echo "::error::PR workload source is invalid" >&2; exit 1 ;;
catalog_path="${RUNNER_TEMP}/pr-managed-image-catalog.json"
rm -f -- "$catalog_path"
selection="$(node --experimental-strip-types --no-warnings tools/e2e/pr-managed-image-publication.mts "$catalog_path")"
case "$selection" in
base-cohort)
[[ ! -e "$catalog_path" && ! -L "$catalog_path" ]] || {
echo "::error::base-cohort selection produced a candidate catalog" >&2
exit 1
}
;;
candidate-catalog)
[[ -f "$catalog_path" && ! -L "$catalog_path" && -s "$catalog_path" ]] || {
echo "::error::exact PR managed-image catalog is invalid" >&2
exit 1
}
catalog="$(jq -ce . "$catalog_path")"
(( ${#catalog} <= 65536 )) || {
echo "::error::exact PR managed-image catalog exceeds the output limit" >&2
exit 1
}
printf 'catalog=%s\n' "$catalog" >>"$GITHUB_OUTPUT"
;;
*) echo "::error::PR managed-image selection is invalid" >&2; exit 1 ;;
esac
printf 'workload_source=%s\n' "$workload_source" >>"$GITHUB_OUTPUT"
printf 'selection=%s\n' "$selection" >>"$GITHUB_OUTPUT"

- id: publication
name: Select base and optional managed-image publication
env:
EXPECTED_SHA: ${{ steps.publication_mode.outputs.expected_sha }}
GITHUB_TOKEN: ${{ github.token }}
PUBLICATION_HISTORY_ALLOW_NON_HEAD: ${{ steps.publication_mode.outputs.allow_non_head }}
REQUIRE_MANAGED_IMAGE_PUBLICATION: ${{ steps.select_pr_source.outputs.workload_source == 'local-dockerfile' && '0' || '1' }}
SELECT_NEAREST_SUCCESSFUL_PUBLICATION: ${{ steps.select_pr_source.outputs.workload_source == 'local-dockerfile' && '0' || steps.publication_mode.outputs.select_nearest_successful }}
REQUIRE_MANAGED_IMAGE_PUBLICATION: ${{ steps.select_pr_source.outputs.selection == 'candidate-catalog' && '0' || '1' }}
SELECT_NEAREST_SUCCESSFUL_PUBLICATION: ${{ steps.select_pr_source.outputs.selection == 'candidate-catalog' && '0' || steps.publication_mode.outputs.select_nearest_successful }}
shell: bash
run: |
set -euo pipefail
Expand Down Expand Up @@ -273,7 +293,7 @@ jobs:

- id: download_managed_cohort
name: Download immutable managed-image cohort contract
if: ${{ inputs.pr_number == '' || steps.select_pr_source.outputs.workload_source == 'managed-image' }}
if: ${{ inputs.pr_number == '' || steps.select_pr_source.outputs.selection == 'base-cohort' }}
env:
GITHUB_TOKEN: ${{ github.token }}
PUBLICATION_ARTIFACT_KIND: managed-image-cohort
Expand All @@ -284,7 +304,7 @@ jobs:

- id: validate_managed_cohort
name: Validate immutable managed-image cohort contract
if: ${{ inputs.pr_number == '' || steps.select_pr_source.outputs.workload_source == 'managed-image' }}
if: ${{ inputs.pr_number == '' || steps.select_pr_source.outputs.selection == 'base-cohort' }}
env:
PUBLICATION_HEAD_SHA: ${{ steps.publication.outputs.head_sha }}
PUBLICATION_RUN_ATTEMPT: ${{ steps.publication.outputs.run_attempt }}
Expand Down Expand Up @@ -2676,6 +2696,7 @@ jobs:
E2E_MANAGED_IMAGE_REVISION: ${{ needs.base-image-publication.outputs.managed_image_revision }}
E2E_MANAGED_IMAGE_COHORT_RECEIPT: ${{ needs.base-image-publication.outputs.managed_image_receipt }}
E2E_WORKLOAD_SOURCE: ${{ needs.generate-matrix.outputs.workload_source }}
NEMOCLAW_E2E_MANAGED_IMAGE_CATALOG_JSON: ${{ needs.base-image-publication.outputs.managed_image_catalog }}
NEMOCLAW_LANGCHAIN_DEEPAGENTS_CODE_SANDBOX_BASE_IMAGE_REF: ${{ needs.generate-matrix.outputs.workload_source == 'managed-image' && needs.base-image-publication.outputs.dcode_base_ref || '' }}
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
Expand Down Expand Up @@ -2971,6 +2992,7 @@ jobs:
cli_artifact_provenance: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }}
managed_image_revision: ${{ needs.base-image-publication.outputs.managed_image_revision }}
managed_image_receipt: ${{ needs.base-image-publication.outputs.managed_image_receipt }}
managed_image_catalog: ${{ needs.base-image-publication.outputs.managed_image_catalog }}
workload_source: ${{ needs.generate-matrix.outputs.workload_source }}
credential_boundary: no provider credential
target_id: ${{ matrix.target_id }}
Expand Down Expand Up @@ -3013,6 +3035,7 @@ jobs:
cli_artifact_provenance: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }}
managed_image_revision: ${{ needs.base-image-publication.outputs.managed_image_revision }}
managed_image_receipt: ${{ needs.base-image-publication.outputs.managed_image_receipt }}
managed_image_catalog: ${{ needs.base-image-publication.outputs.managed_image_catalog }}
workload_source: ${{ needs.generate-matrix.outputs.workload_source }}
credential_boundary: NVIDIA API key
target_id: ${{ matrix.target_id }}
Expand Down Expand Up @@ -3056,6 +3079,7 @@ jobs:
cli_artifact_provenance: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }}
managed_image_revision: ${{ needs.base-image-publication.outputs.managed_image_revision }}
managed_image_receipt: ${{ needs.base-image-publication.outputs.managed_image_receipt }}
managed_image_catalog: ${{ needs.base-image-publication.outputs.managed_image_catalog }}
workload_source: ${{ needs.generate-matrix.outputs.workload_source }}
credential_boundary: NVIDIA inference API key
target_id: ${{ matrix.target_id }}
Expand Down Expand Up @@ -3099,6 +3123,7 @@ jobs:
cli_artifact_provenance: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }}
managed_image_revision: ${{ needs.base-image-publication.outputs.managed_image_revision }}
managed_image_receipt: ${{ needs.base-image-publication.outputs.managed_image_receipt }}
managed_image_catalog: ${{ needs.base-image-publication.outputs.managed_image_catalog }}
workload_source: ${{ needs.generate-matrix.outputs.workload_source }}
credential_boundary: GitHub read token
target_id: ${{ matrix.target_id }}
Expand Down Expand Up @@ -3142,6 +3167,7 @@ jobs:
cli_artifact_provenance: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }}
managed_image_revision: ${{ needs.base-image-publication.outputs.managed_image_revision }}
managed_image_receipt: ${{ needs.base-image-publication.outputs.managed_image_receipt }}
managed_image_catalog: ${{ needs.base-image-publication.outputs.managed_image_catalog }}
workload_source: ${{ needs.generate-matrix.outputs.workload_source }}
credential_boundary: Brave and NVIDIA inference API keys
target_id: ${{ matrix.target_id }}
Expand Down Expand Up @@ -3340,6 +3366,7 @@ jobs:
E2E_MANAGED_IMAGE_REVISION: ${{ needs.base-image-publication.outputs.managed_image_revision }}
E2E_MANAGED_IMAGE_COHORT_RECEIPT: ${{ needs.base-image-publication.outputs.managed_image_receipt }}
E2E_WORKLOAD_SOURCE: ${{ needs.generate-matrix.outputs.workload_source }}
NEMOCLAW_E2E_MANAGED_IMAGE_CATALOG_JSON: ${{ needs.base-image-publication.outputs.managed_image_catalog }}
E2E_JOB: "1"
E2E_TARGET_ID: "mcp-bridge"
E2E_OBSERVABLE_OUTCOME: "Stable OpenShell MCP bridge reaches tools and inference"
Expand Down Expand Up @@ -3675,6 +3702,7 @@ jobs:
E2E_MANAGED_IMAGE_REVISION: ${{ needs.base-image-publication.outputs.managed_image_revision }}
E2E_MANAGED_IMAGE_COHORT_RECEIPT: ${{ needs.base-image-publication.outputs.managed_image_receipt }}
E2E_WORKLOAD_SOURCE: ${{ needs.generate-matrix.outputs.workload_source }}
NEMOCLAW_E2E_MANAGED_IMAGE_CATALOG_JSON: ${{ needs.base-image-publication.outputs.managed_image_catalog }}
E2E_JOB: "1"
E2E_TARGET_ID: "openshell-credential-generation-window"
E2E_AGENT_RUNTIME: "openclaw"
Expand Down Expand Up @@ -3850,6 +3878,7 @@ jobs:
E2E_MANAGED_IMAGE_REVISION: ${{ needs.base-image-publication.outputs.managed_image_revision }}
E2E_MANAGED_IMAGE_COHORT_RECEIPT: ${{ needs.base-image-publication.outputs.managed_image_receipt }}
E2E_WORKLOAD_SOURCE: ${{ needs.generate-matrix.outputs.workload_source }}
NEMOCLAW_E2E_MANAGED_IMAGE_CATALOG_JSON: ${{ needs.base-image-publication.outputs.managed_image_catalog }}
E2E_JOB: "1"
E2E_TARGET_ID: "mcp-bridge-dev"
E2E_OBSERVABLE_OUTCOME: "Development OpenShell MCP bridge reaches tools and inference"
Expand Down Expand Up @@ -5021,6 +5050,7 @@ jobs:
E2E_MANAGED_IMAGE_REVISION: ${{ needs.base-image-publication.outputs.managed_image_revision }}
E2E_MANAGED_IMAGE_COHORT_RECEIPT: ${{ needs.base-image-publication.outputs.managed_image_receipt }}
E2E_WORKLOAD_SOURCE: ${{ needs.generate-matrix.outputs.workload_source }}
NEMOCLAW_E2E_MANAGED_IMAGE_CATALOG_JSON: ${{ needs.base-image-publication.outputs.managed_image_catalog }}
E2E_JOB: "1"
E2E_TARGET_ID: "hermes-e2e"
E2E_AGENT_RUNTIME: "hermes"
Expand Down Expand Up @@ -5131,6 +5161,7 @@ jobs:
E2E_MANAGED_IMAGE_REVISION: ${{ needs.base-image-publication.outputs.managed_image_revision }}
E2E_MANAGED_IMAGE_COHORT_RECEIPT: ${{ needs.base-image-publication.outputs.managed_image_receipt }}
E2E_WORKLOAD_SOURCE: ${{ needs.generate-matrix.outputs.workload_source }}
NEMOCLAW_E2E_MANAGED_IMAGE_CATALOG_JSON: ${{ needs.base-image-publication.outputs.managed_image_catalog }}
E2E_JOB: "1"
E2E_TARGET_ID: "hermes-gpu-startup"
E2E_AGENT_RUNTIME: "hermes"
Expand Down Expand Up @@ -5438,6 +5469,7 @@ jobs:
E2E_MANAGED_IMAGE_REVISION: ${{ needs.base-image-publication.outputs.managed_image_revision }}
E2E_MANAGED_IMAGE_COHORT_RECEIPT: ${{ needs.base-image-publication.outputs.managed_image_receipt }}
E2E_WORKLOAD_SOURCE: ${{ needs.generate-matrix.outputs.workload_source }}
NEMOCLAW_E2E_MANAGED_IMAGE_CATALOG_JSON: ${{ needs.base-image-publication.outputs.managed_image_catalog }}
E2E_JOB: "1"
E2E_TARGET_ID: "cloud-onboard"
E2E_AGENT_RUNTIME: "openclaw"
Expand Down Expand Up @@ -5569,6 +5601,7 @@ jobs:
E2E_MANAGED_IMAGE_REVISION: ${{ needs.base-image-publication.outputs.managed_image_revision }}
E2E_MANAGED_IMAGE_COHORT_RECEIPT: ${{ needs.base-image-publication.outputs.managed_image_receipt }}
E2E_WORKLOAD_SOURCE: ${{ needs.generate-matrix.outputs.workload_source }}
NEMOCLAW_E2E_MANAGED_IMAGE_CATALOG_JSON: ${{ needs.base-image-publication.outputs.managed_image_catalog }}
E2E_JOB: "1"
E2E_TARGET_ID: "messaging-providers"
E2E_AGENT_RUNTIME: "openclaw"
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/managed-images.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,8 @@ jobs:
E2E_JOB: "1"
E2E_TARGET_ID: managed-image-activation
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_E2E_EXPECTED_SHA: ${{ github.event.pull_request.head.sha }}
NEMOCLAW_E2E_SHARD: default
NEMOCLAW_MANAGED_ACTIVATION_CATALOG: ${{ github.workspace }}/managed-pr-catalog.json
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_RUN_LIVE_E2E: "1"
Expand All @@ -887,6 +889,15 @@ jobs:
with:
node-version: 22.19.0

- name: Bind E2E correlation identity
shell: bash
run: |
set -euo pipefail
correlation_id="$(node --input-type=module -e \
'import { randomUUID } from "node:crypto"; console.log(randomUUID())')"
[[ "$correlation_id" =~ ^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$ ]]
printf 'NEMOCLAW_E2E_CORRELATION_ID=%s\n' "$correlation_id" >> "$GITHUB_ENV"

- name: Download exact published all-agent contracts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
Expand Down
7 changes: 6 additions & 1 deletion src/lib/onboard/managed-workload/onboard-orchestration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,9 @@ export function createManagedWorkloadOnboardRuntime(
: {
...discoveredRuntimeCapabilities,
managedImageSelectionPolicy: "prefer-managed" as const,
managedImages: input.stockManagedRuntime ? discoveredRuntimeCapabilities.managedImages : null,
managedImages: input.stockManagedRuntime
? discoveredRuntimeCapabilities.managedImages
: null,
};
const runtimeProvider = resolveRuntimeProviderBundle(
input.computePlan.driverName,
Expand Down Expand Up @@ -273,6 +275,9 @@ export function createManagedWorkloadOnboardRuntime(
customDockerfilePath: input.customDockerfilePath,
runtime: runtimeCapabilities,
version: getVersion({ rootDir: input.rootDir }),
...(!input.tempManagedRuntimeCatalog && liveCatalog?.catalog
? { catalog: liveCatalog.catalog }
: {}),
catalogPath: input.tempManagedRuntimeCatalog ?? liveCatalog?.path ?? null,
...(liveCatalog ? { expectedCatalogRevision: liveCatalog.revision } : {}),
...(catalogRevision ? { catalogRevision } : {}),
Expand Down
76 changes: 75 additions & 1 deletion src/lib/onboard/sandbox-workload-preparation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
installedManagedImageCatalogRevision,
liveE2eManagedImageCatalog,
prepareSandboxWorkloadSource,
readLiveE2eManagedImageCatalogContracts,
SandboxWorkloadPreparationError,
} from "./workload/preparation";
import { resolveSandboxWorkloadRuntimeCapabilities } from "./workload/runtime";
Expand Down Expand Up @@ -179,6 +180,14 @@ describe("sandbox workload preparation", () => {
NEMOCLAW_E2E_MANAGED_IMAGE_CATALOG: catalogPath,
}),
).toEqual({ path: catalogPath, revision: REVISION });
expect(
liveE2eManagedImageCatalog({
GITHUB_ACTIONS: "true",
NEMOCLAW_RUN_LIVE_E2E: "1",
NEMOCLAW_E2E_EXPECTED_SHA: REVISION,
NEMOCLAW_E2E_MANAGED_IMAGE_CATALOG_JSON: JSON.stringify(CATALOG),
}),
).toEqual({ catalog: CATALOG, revision: REVISION });
expect(
liveE2eManagedImageCatalog({
GITHUB_ACTIONS: "true",
Expand All @@ -194,6 +203,13 @@ describe("sandbox workload preparation", () => {
NEMOCLAW_E2E_MANAGED_IMAGE_CATALOG: catalogPath,
}),
).toBeNull();
expect(
liveE2eManagedImageCatalog({
GITHUB_ACTIONS: "true",
GITHUB_WORKSPACE: path.join(fixtureRoot, "empty-workspace"),
NEMOCLAW_RUN_LIVE_E2E: "1",
}),
).toBeNull();
expect(
liveE2eManagedImageCatalog({
GITHUB_ACTIONS: "true",
Expand All @@ -219,6 +235,49 @@ describe("sandbox workload preparation", () => {
NEMOCLAW_E2E_MANAGED_IMAGE_CATALOG: catalogPath,
}),
).toThrow("requires an exact candidate revision");
expect(() =>
liveE2eManagedImageCatalog({
GITHUB_ACTIONS: "true",
NEMOCLAW_RUN_LIVE_E2E: "1",
NEMOCLAW_E2E_EXPECTED_SHA: REVISION,
NEMOCLAW_E2E_MANAGED_IMAGE_CATALOG: catalogPath,
NEMOCLAW_E2E_MANAGED_IMAGE_CATALOG_JSON: JSON.stringify(CATALOG),
}),
).toThrow("conflicting authorities");
} finally {
fs.rmSync(fixtureRoot, { force: true, recursive: true });
}
});

it("validates every contract in an inline live E2E catalog", () => {
const selected = liveE2eManagedImageCatalog({
GITHUB_ACTIONS: "true",
NEMOCLAW_RUN_LIVE_E2E: "1",
NEMOCLAW_E2E_EXPECTED_SHA: REVISION,
NEMOCLAW_E2E_MANAGED_IMAGE_CATALOG_JSON: JSON.stringify(CATALOG),
});

expect(selected).not.toBeNull();
expect(readLiveE2eManagedImageCatalogContracts(selected!)).toEqual(
new Map(SHIPPED_MANAGED_IMAGE_AGENTS.map((agent, index) => [agent, contract(agent, index)])),
);
});

it("reads a regular live E2E catalog without following a symbolic link", () => {
const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-live-e2e-catalog-"));
const catalogPath = path.join(fixtureRoot, "catalog.json");
const symlinkPath = path.join(fixtureRoot, "catalog-link.json");
fs.writeFileSync(catalogPath, JSON.stringify(CATALOG), { mode: 0o600 });
fs.symlinkSync(catalogPath, symlinkPath);
try {
expect(
readLiveE2eManagedImageCatalogContracts({ path: catalogPath, revision: REVISION }),
).toEqual(
new Map(SHIPPED_MANAGED_IMAGE_AGENTS.map((agent, index) => [agent, contract(agent, index)])),
);
expect(() =>
readLiveE2eManagedImageCatalogContracts({ path: symlinkPath, revision: REVISION }),
).toThrow("must be a bounded regular file");
} finally {
fs.rmSync(fixtureRoot, { force: true, recursive: true });
}
Expand Down Expand Up @@ -364,6 +423,21 @@ describe("sandbox workload preparation", () => {
}
});

it("loads an exact inline all-agent catalog without using the registry resolver", async () => {
const resolveCatalog = vi.fn(async () => CATALOG);

const prepared = await prepareSandboxWorkloadSource(
{ ...input("hermes"), catalog: CATALOG, expectedCatalogRevision: REVISION },
{ resolveCatalog },
);

expect(resolveCatalog).not.toHaveBeenCalled();
expect(prepared.source).toMatchObject({
kind: "managed-image",
contract: { source: { revision: REVISION } },
});
});

it("rejects a symlinked local managed-image catalog before selection (#7744)", async () => {
const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-managed-catalog-"));
const catalogPath = path.join(fixtureRoot, "catalog.json");
Expand Down Expand Up @@ -673,7 +747,7 @@ describe("sandbox workload preparation", () => {
{ ...input("pi"), acceptedCandidateContract: contract("pi", 3) },
{ resolveCatalog },
),
).rejects.toThrow("requires an exact managed image catalog file");
).rejects.toThrow("requires an exact managed image catalog");
expect(resolveCatalog).not.toHaveBeenCalled();
});

Expand Down
Loading
Loading