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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/base-image.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,20 @@ on:
# with tools/e2e/base-image-publication.mts.
- "Dockerfile"
- "agents/**"
- "ci/pi-agent-qualification-v1-*.json"
- "nemoclaw/**"
- "nemoclaw-blueprint/**"
- "scripts/**"
- "test/e2e/live/managed-image-activation-e2e*.ts"
- "test/e2e/live/mcp-bridge*.ts"
- "src/lib/actions/sandbox/mcp-bridge-*.ts"
- "src/lib/actions/sandbox/openshell-child-visible-credentials.v*.json"
- "src/lib/actions/sandbox/rebuild-post-restore-phase.ts"
- "src/lib/agent/candidate-authority.ts"
- "src/lib/core/json-types.ts"
- "src/lib/core/ports.ts"
- "src/lib/messaging/**"
- "src/lib/onboard/managed-bootstrap/envelope.ts"
- "src/lib/onboard/managed-startup/**"
- "src/lib/onboard/managed-workload/onboard-orchestration.ts"
- "src/lib/onboard/**"
- "src/lib/security/credential-hash.ts"
- "src/lib/state/paths.ts"
- "src/lib/state/state-root.ts"
Expand Down
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
64 changes: 49 additions & 15 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,41 @@ 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 "::notice::Jetson dispatch is unavailable because the exact PR managed-image catalog qualifies linux/amd64 only."
;;
*) 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 +294,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 +305,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 @@ -358,8 +379,8 @@ jobs:
run: |
set -euo pipefail

[[ "$WORKFLOW_EVENT" == "workflow_dispatch" && "$WORKFLOW_REF" == refs/heads/* ]] || {
echo "::error::Manual PR E2E must be dispatched from this repository branch" >&2
[[ "$WORKFLOW_EVENT" == "workflow_dispatch" && "$WORKFLOW_REF" == "refs/heads/main" ]] || {
echo "::error::Manual PR E2E must be dispatched from trusted main" >&2
exit 1
}
[[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]] || { echo "::error::pr_number must be a positive integer" >&2; exit 1; }
Expand Down Expand Up @@ -725,7 +746,7 @@ jobs:
if [[ "$WORKFLOW_REPOSITORY" == "NVIDIA/NemoClaw" &&
"$NVIDIA_OWNED" == "true" &&
"$EVENT_NAME" == "workflow_dispatch" &&
"$REF" == refs/heads/* &&
"$REF" == "refs/heads/main" &&
"$CHECKOUT_SHA" =~ ^[a-f0-9]{40}$ &&
"$WORKFLOW_SHA" =~ ^[a-f0-9]{40}$ &&
"$EXPECTED_WORKFLOW_SHA" == "$WORKFLOW_SHA" &&
Expand Down Expand Up @@ -2676,6 +2697,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 +2993,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 +3036,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 +3080,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 +3124,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 +3168,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 +3367,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 +3703,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 +3879,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 +5051,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 +5162,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 @@ -5392,7 +5424,7 @@ jobs:

jetson-nvmap-gpu:
needs: [base-image-publication, generate-matrix]
if: ${{ always() && needs['base-image-publication'].result == 'success' && needs['generate-matrix'].result == 'success' && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.allow_jetson_dispatch && (inputs.checkout_repository == '' || inputs.checkout_repository == github.repository) && ((inputs.jobs == '' && inputs.targets == '') || contains(fromJSON(needs.generate-matrix.outputs.selected_jobs), 'jetson-nvmap-gpu')))) }}
if: ${{ always() && needs['base-image-publication'].result == 'success' && needs['base-image-publication'].outputs.managed_image_revision != '' && needs['generate-matrix'].result == 'success' && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.allow_jetson_dispatch && (inputs.checkout_repository == '' || inputs.checkout_repository == github.repository) && ((inputs.jobs == '' && inputs.targets == '') || contains(fromJSON(needs.generate-matrix.outputs.selected_jobs), 'jetson-nvmap-gpu')))) }}
concurrency:
group: jetson-nvmap-gpu-dispatch
cancel-in-progress: false
Expand Down Expand Up @@ -5438,6 +5470,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 +5602,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
2 changes: 2 additions & 0 deletions .github/workflows/pr-self-hosted.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ jobs:
env:
EXPECTED_SHA: ${{ steps.changed.outputs.base_sha }}
GITHUB_TOKEN: ${{ github.token }}
PUBLICATION_HISTORY_ALLOW_NON_HEAD: "1"
REQUIRE_MANAGED_IMAGE_PUBLICATION: "1"
SELECT_NEAREST_SUCCESSFUL_PUBLICATION: "1"
shell: bash
run: |
set -euo pipefail
Expand Down
5 changes: 4 additions & 1 deletion scripts/checks/run-managed-image-direct-e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,10 @@ function managedConfig(agent: ShippedManagedImageAgent): string {
}

function waitForAgentCommand(containerId: string): void {
const deadline = Date.now() + 120_000;
// The image-owned hold allows up to 600 seconds for managed startup
// completion. Keep this observer from abandoning a still-running container
// before that bounded product wait can finish.
const deadline = Date.now() + 600_000;
while (Date.now() < deadline) {
const ready = docker(
[
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
Loading
Loading