Skip to content

feat(onboard): add managed startup profile schema - #7946

Merged
ericksoa merged 8 commits into
mainfrom
feat/buildless-startup-profile-v2
Jul 31, 2026
Merged

feat(onboard): add managed startup profile schema#7946
ericksoa merged 8 commits into
mainfrom
feat/buildless-startup-profile-v2

Conversation

@ericksoa

@ericksoa ericksoa commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Defines the dormant, versioned managed startup-profile contract for OpenClaw, Hermes, and LangChain Deep Agents Code. It adds bounded canonical serialization and transport for later image-owned startup work without changing a production onboarding caller or activating buildless support.

Related Issue

Part of #7744

Changes

  • Add schema version 1 for one engine-neutral startup profile across OpenClaw, Hermes, and LangChain Deep Agents Code.
  • Define fail-closed agent capabilities for inference APIs, dashboards, tools, messaging, tuning, proxy intent, device authentication, and agent-specific configuration.
  • Keep validation authority private and immutable while exposing a deeply frozen capability catalog for consumers.
  • Require OpenClaw's primary model reference to match its route provider and model, and reject inherited messaging discriminators rather than treating prototype data as profile input.
  • Classify every stock Dockerfile onboarding input as a startup affordance or a deliberate exclusion. This inventory prevents later image-owned startup work from silently dropping a required Dockerfile behavior.
  • Record the typed, per-agent runtime-input disposition for every image-consumed startup control, including controls that later application-environment work must forward or agent-gate.
  • Serialize validated profiles as canonical JSON, encode them as unpadded base64url, and fingerprint the canonical decoded form with SHA-256.
  • Bound decoded profiles to 64 KiB. Reject malformed encodings, non-canonical forms, excess structure, unsupported agent fields, raw credential values, and custom or inherited serialization hooks.
  • Reserve the startup-profile and corporate-CA environment variable names for later image-runtime construction.
  • Map stock Dockerfile changes to the profile contract test in focused Vitest watch mode.
  • Keep the new modules dormant. No production caller imports the profile or transport modules, no container engine is selected, and no user-visible runtime support is enabled.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Docs updated for user-facing behavior changes
  • Docs not applicable — justification: The profile and transport contracts have no production consumer. This PR changes no CLI, configuration, default, workflow, or supported runtime behavior.
  • Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging)
  • Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: Exact-diff review confirmed bounded canonical transport, fail-closed agent fields, raw-credential rejection, prototype-hook rejection, and no production activation. The focused test suite covers these constraints on the exact head.
  • Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue:

Documentation Writer Review

  • Documentation writer subagent reviewed the completed changes
  • Result: no-docs-needed
  • Evidence: The exact six-file diff (+3,205/-0) adds dormant startup-profile and transport contracts, contract tests, source-shape budget registration, and focused test watch triggers. Exact-tree searches found no production import or reference to the new modules or exports. This PR does not change a CLI, configuration, default, workflow, support claim, or other user-visible behavior.
  • Agent: Codex Desktop

DGX Station Hardware Evidence

  • Tested on DGX Station
  • Tested commit:
  • Station profile/scenario:
  • Result:
  • Supporting evidence:

Verification

  • Exact validated head/base: ea5bd8a8e617563133b1f8fc2843d6b7ba407511 / 0030ba4d3e50a0402a440776f83bace033044937
  • Signed commit receipt: 38329e80c82301bba2cec38fe13d7c68c42bc29d, 7f3859a32e9791ec40337785d72cad8674b37c96, 32561a916f0b60336761a460120259adf7281f6e, 087a9a58765bfc6e51fe3ae54c8b1f8ed007877f, e1845dd7a4ea3f1dca67aaa91544d234bbcc8bcf, 244e3e642fd687dd9ea88158fcd335b8ad485304, 24033160bb8a986ce32b6a6dd26ef1a709388d55, and ea5bd8a8e617563133b1f8fc2843d6b7ba407511 each contain an SSH signature and Signed-off-by: Aaron Erickson <aerickson@nvidia.com>; GitHub reports every pushed signature as valid and Verified.
  • PR description includes a Signed-off-by: line and every commit appears as Verified in GitHub
  • Normal pre-commit, commit-msg, and pre-push hooks passed, or npm run validate:pr passed after refreshing origin/main when hooks were skipped or unavailable
  • Targeted behavior tests pass for the current change set, or tests are marked not applicable above — command/result or justification: the managed-profile and transport suite passed 102/102 tests; five integration watch-trigger checks passed; CLI and plugin builds passed; source-shape and test-size budgets passed. This includes canonical serialization, base64url transport, fingerprint stability, all-agent round trips, malformed and oversized payloads, raw-credential rejection, inherited-data and prototype-hook rejection, immutable capability validation, OpenClaw model-reference consistency, Dockerfile affordance coverage, runtime-input disposition coverage, and focused watch mappings. npm run validate:pr then passed after the final current-main merge on the exact head.
  • Applicable broad gate passed — npm test for broad runtime/test-harness changes; npm run check for repo-wide validation/coverage changes — command/result: Not applicable because this slice adds an inert internal contract and focused test mapping. Exact-head required CI remains the broad gate.
  • Quality Gates section completed with required justifications or waivers
  • No secrets, API keys, or credentials committed
  • npm run docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Stack

  • Base branch: main at 0030ba4d3e50a0402a440776f83bace033044937 (including merged PR3.1 commit a8c7ab01ef8442e6edc3b071c758d680ecd29549)
  • This PR: feat/buildless-startup-profile-v2 at ea5bd8a8e617563133b1f8fc2843d6b7ba407511
  • Source record: feat(onboard): add managed startup profile schema #7944 remains closed after GitHub automatically closed it when PR3.1's stacked base branch was deleted. Its original head is preserved; this clean replacement carries only the narrowed PR3.2 implementation, review hardening, and explicit current-main sync merges.
  • Downstream replay receipt: PR3.3–PR3.5 were reconstructed sequentially onto this exact head before the remote update. Their clean review boundaries are preserved as separate signed, DCO-compliant branches; each slice passed focused tests, both builds, validate:pr, diff and review-budget checks, and an independent P1/P2 audit before push.
  • The next slice is not part of this review diff. Buildless support remains disabled until every supported agent and required qualification gate passes.

Signed-off-by: Aaron Erickson aerickson@nvidia.com

ericksoa added 3 commits July 30, 2026 14:50
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a versioned managed startup profile contract with strict validation, canonical encoding/decoding, fingerprinting, security hardening, agent-specific rules, comprehensive tests, and Dockerfile-triggered test watch coverage.

Changes

Managed startup profile

Layer / File(s) Summary
Profile contracts and inventories
src/lib/onboard/managed-startup/profile.ts, src/lib/onboard/managed-startup/transport.ts
Defines profile types, agent capabilities, Docker/runtime input mappings, cleanup obligations, exclusions, validation limits, and transport environment constants.
Structural and credential validation
src/lib/onboard/managed-startup/profile.ts
Adds bounded JSON traversal, strict object and field validation, credential and certificate screening, URL/proxy restrictions, and canonical profile rebuilding.
Agent validation and canonical transport
src/lib/onboard/managed-startup/profile.ts
Adds agent-specific and cross-field validation, canonical serialization, base64url encoding/decoding, and SHA-256 fingerprinting.
Contract and agent behavior tests
src/lib/onboard/managed-startup-profile.test.ts
Tests canonical round trips, fingerprints, capability and runtime inventories, cleanup obligations, schema rejection, credential screening, and agent-specific policies.
Hardening and Dockerfile watch coverage
src/lib/onboard/managed-startup-profile.test.ts, test/helpers/vitest-watch-triggers.ts, test/vitest-watch-triggers.test.ts, ci/source-shape-test-budget.json
Tests malformed and hostile inputs, prototype-pollution defenses, serializer handling, canonical decoding, and Dockerfile-triggered test mappings.

Estimated code review effort: 5 (Critical) | ~90 minutes

Possibly related PRs

  • NVIDIA/NemoClaw#7849: Overlaps with managed-startup profile schema coverage and Dockerfile watch-trigger tests for Hermes runtime migration.
  • NVIDIA/NemoClaw#7892: Overlaps with Hermes Docker/runtime input classification and dashboard-profile path tests.

Suggested labels: area: architecture

Suggested reviewers: cv, apurvvkumaria

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant validateManagedStartupProfile
  participant encodeManagedStartupProfile
  participant decodeManagedStartupProfile
  participant fingerprintManagedStartupProfile
  Caller->>validateManagedStartupProfile: submit profile
  validateManagedStartupProfile-->>Caller: canonical profile
  Caller->>encodeManagedStartupProfile: encode profile
  encodeManagedStartupProfile-->>Caller: base64url payload
  Caller->>decodeManagedStartupProfile: decode payload
  decodeManagedStartupProfile-->>Caller: validated profile
  Caller->>fingerprintManagedStartupProfile: fingerprint profile
  fingerprintManagedStartupProfile-->>Caller: SHA-256 digest
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: adding the managed startup profile schema.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/buildless-startup-profile-v2

Comment @coderabbitai help to get the list of available commands.

@github-code-quality

github-code-quality Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in commit ea5bd8a in the feat/buildless-start... branch remains at 96%, unchanged from commit 0030ba4 in the main branch.

TypeScript / code-coverage/cli

The overall coverage in commit ea5bd8a in the feat/buildless-start... branch remains at 81%, unchanged from commit 0030ba4 in the main branch.

Show a code coverage summary of the most impacted files.
File main 0030ba4 feat/buildless-start... ea5bd8a +/-
src/lib/inferen...ompatibility.ts 95% 94% -1%
src/lib/onboard...up/transport.ts 0% 0% 0%
src/lib/sandbox...rce-identity.ts 88% 88% 0%
src/lib/onboard...rtup/profile.ts 0% 93% +93%

Updated July 31, 2026 01:40 UTC

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — No blocking findings reported

Advisor assessment: No blocking advisor findings reported
Next action: Review the warnings below.
Findings: 0 blockers · 1 warning · 0 suggestions

Model lanes

  • GPT-5.6 Terra (primary): Completed · medium confidence · 0 blockers · 1 warning · 0 suggestions
  • Nemotron 3 Ultra (second opinion): Completed · high confidence · 0 blockers · 0 warnings · 0 suggestions
  • Model comparison: normalized findings differ; normalized E2E selections differ; Nemotron reported the same number of blockers, 1 fewer warning, the same number of suggestions.

Nemotron output stays in workflow artifacts and does not change the assessment above.

E2E guidance

Advisory only. E2E / PR Gate selects and runs jobs independently.

Recommended E2E: onboard-repair, onboard-resume, cloud-onboard

1 warning · 0 suggestions

Warnings

Warnings do not block.

PRA-1 Warning — Add lifecycle coverage for the managed-startup profile boundary

  • Location: src/lib/onboard/managed-startup/profile.ts:1843
  • Category: tests
  • Problem: The new profile transport has unit coverage for validation and canonicalization, but no checked-in integration test exercises managed-startup activation through failure, retry, status probing, and cleanup.
  • Impact: A later runtime consumer can leave stale resources or ports, report status that differs from the gateway or sandbox, or remove an unrelated sandbox without a regression test detecting it.
  • Recommendation: Add one lifecycle integration regression that injects a failed managed-startup activation, retries it, independently probes gateway and sandbox status, and verifies cleanup removes only owned resources while retaining an unrelated sandbox.
  • Verification: Inspect checked-in onboarding integration tests for a profile activation path that injects failure, retries, probes live state independently, and retains an unrelated sandbox.
  • Test coverage: A managed-startup activation failure-and-retry test that proves convergence without stale ports or resources, status agreement with independent gateway and sandbox probes, and owned-only cleanup.
  • Evidence: The risk plan lists `src/lib/onboard/managed-startup/profile.ts` and `transport.ts` in the lifecycle-state family. The required invariants are retry convergence, independently verified status agreement, and owned-only cleanup. `managed-startup-profile.test.ts` covers validation and serialization but does not activate a profile or exercise sandbox lifecycle operations.

Workflow run details

This automated review informs maintainers. Warnings and suggestions do not require a response. A maintainer decides whether to merge.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (6)
src/lib/onboard/managed-startup/profile.ts (3)

752-773: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer Array#sort over a hand-rolled insertion sort.

Default Array#sort() already orders by UTF-16 code units, matching the > comparison here, and avoids the O(n²) path plus per-write defineProperty. Inputs are Object.keys output or arrays already proven to hold only indexed data properties, so nothing is gained by re-implementing the algorithm.

♻️ Proposed refactor
-function sortStrings(values: string[]): string[] {
-  for (let index = 1; index < values.length; index += 1) {
-    const selected = values[index] as string;
-    let insertion = index;
-    while (insertion > 0 && (values[insertion - 1] as string) > selected) {
-      Object.defineProperty(values, String(insertion), {
-        configurable: true,
-        enumerable: true,
-        value: values[insertion - 1],
-        writable: true,
-      });
-      insertion -= 1;
-    }
-    Object.defineProperty(values, String(insertion), {
-      configurable: true,
-      enumerable: true,
-      value: selected,
-      writable: true,
-    });
-  }
-  return values;
-}
+function sortStrings(values: string[]): string[] {
+  return values.sort();
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/onboard/managed-startup/profile.ts` around lines 752 - 773, Replace
the hand-rolled insertion sort in sortStrings with the built-in Array#sort using
its default ordering, preserving the function’s in-place mutation and returned
array behavior.

685-691: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Descriptor dance is unnecessary over module-owned arrays.

SECRET_VALUE_PATTERNS (and the String#match result at Line 704) are internally produced and cannot be attacker-shaped, unlike the payload arrays handled by mapArrayByIndex. Plain iteration reads better here.

Based on learnings: avoid adding "defensive" validation around internal helper logic when there is no realistic failure mode; reserve it for system boundaries.

♻️ Proposed simplification
 function valueLooksLikeSecret(value: string): boolean {
-  for (let index = 0; index < SECRET_VALUE_PATTERNS.length; index += 1) {
-    const descriptor = Object.getOwnPropertyDescriptor(SECRET_VALUE_PATTERNS, String(index));
-    if (descriptor && "value" in descriptor && descriptor.value.test(value)) return true;
-  }
-  return false;
+  return SECRET_VALUE_PATTERNS.some((pattern) => pattern.test(value));
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/onboard/managed-startup/profile.ts` around lines 685 - 691, In
valueLooksLikeSecret, replace the indexed property-descriptor lookup with direct
iteration over SECRET_VALUE_PATTERNS and test each pattern against value. Apply
the same simplification to the String#match result around the referenced
matching logic, removing unnecessary descriptor-based validation while
preserving the existing matching behavior.

Source: Learnings


1173-1185: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive per-agent allowed values from the capability table instead of restating them.

MANAGED_STARTUP_PROFILE_CAPABILITIES is documented (Lines 326-330) as the authoritative negotiation table, and validateInference already reads inferenceApis from it (Lines 1398-1406). But validateWebSearch restates webSearchProviders, validateDashboard restates dashboardModes (Lines 1303-1307, 1336-1340), validateInference restates inputModalities (Line 1425), and validateTuning restates tuningFields (Lines 1527-1550). A table edit will silently leave these validators stale, and the capability test at src/lib/onboard/managed-startup-profile.test.ts:374 would still pass.

♻️ Example for webSearch
-  const provider = requireStringEnum<ManagedStartupWebSearchProvider>(
-    webSearch.provider,
-    new Set(agent === "openclaw" ? ["brave", "tavily"] : ["tavily"]),
-    "agentConfig.webSearch.provider",
-  );
+  const provider = requireStringEnum<ManagedStartupWebSearchProvider>(
+    webSearch.provider,
+    new Set<string>(MANAGED_STARTUP_PROFILE_CAPABILITIES[agent].webSearchProviders),
+    "agentConfig.webSearch.provider",
+  );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/onboard/managed-startup/profile.ts` around lines 1173 - 1185, Update
validateWebSearch and the other capability-backed validators—validateDashboard,
validateInference, and validateTuning—to derive each agent’s allowed values from
MANAGED_STARTUP_PROFILE_CAPABILITIES instead of duplicating literals or sets.
Reuse the existing capability-table lookup pattern used by validateInference for
inferenceApis, while preserving each validator’s current validation and error
behavior.
src/lib/onboard/managed-startup-profile.test.ts (3)

966-1012: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Object.prototype mutations are file-global side effects — worth asserting cleanup.

Both tests correctly delete the injected property in finally, but a nested pollution test that fails before finally (or a future it.concurrent) would silently contaminate the rest of the file. Adding a post-assertion that the property is gone makes the isolation self-checking.

♻️ Proposed tweak
     expect(caught).toBeInstanceOf(Error);
     expect((caught as Error).message).toMatch(/custom JSON serializer/);
     expect(serializerInvoked).toBe(false);
+    expect("toJSON" in Object.prototype).toBe(false);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/onboard/managed-startup-profile.test.ts` around lines 966 - 1012, Add
post-cleanup assertions to both Object.prototype pollution tests, verifying that
toJSON and bundleSha256 are absent after each finally block. Use an own-property
check so the tests self-verify isolation without changing the existing cleanup
or validation behavior.

250-260: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Strengthen or drop the model-leak assertion at Line 258.

encoded is base64url, so not.toContain(model) passes trivially and does not express a real transport property. If the intent is "the model is carried but opaque", assert on the decoded profile instead.

♻️ Proposed tweak
     expect(decodeManagedStartupProfile(encoded)).toEqual(validated);
-    expect(encoded).not.toContain(profile.inference.model);
+    expect(decodeManagedStartupProfile(encoded).inference.model).toBe(profile.inference.model);
     expect(fingerprintManagedStartupProfile(profile)).toMatch(/^[a-f0-9]{64}$/);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/onboard/managed-startup-profile.test.ts` around lines 250 - 260,
Replace the ineffective encoded-string model assertion in the managed startup
profile test with a meaningful decoded-profile assertion: verify that
decodeManagedStartupProfile(encoded) preserves profile.inference.model while
retaining the existing canonical round-trip and fingerprint checks. Remove the
trivial encoded not-to-contain expectation.

1029-1062: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Guard the Array.prototype descriptor restore.

mapDescriptor/sortDescriptor are cast with as PropertyDescriptor; if either lookup ever returned undefined, the finally block itself throws and leaves Array.prototype poisoned for every subsequent test in the worker. A non-null assertion at capture time (or an if (descriptor) guard) keeps the failure local.

♻️ Proposed tweak
-    const mapDescriptor = Object.getOwnPropertyDescriptor(Array.prototype, "map");
-    const sortDescriptor = Object.getOwnPropertyDescriptor(Array.prototype, "sort");
+    const mapDescriptor = Object.getOwnPropertyDescriptor(Array.prototype, "map");
+    const sortDescriptor = Object.getOwnPropertyDescriptor(Array.prototype, "sort");
+    if (!mapDescriptor || !sortDescriptor) throw new Error("missing Array.prototype descriptors");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/onboard/managed-startup-profile.test.ts` around lines 1029 - 1062,
Guard restoration of the Array.prototype descriptors in the test around
serializeManagedStartupProfile: avoid blindly casting potentially undefined
mapDescriptor or sortDescriptor values, and restore each property only when its
descriptor was successfully captured. Keep the existing cleanup in the finally
block and ensure a failed lookup cannot leave Array.prototype poisoned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/onboard/managed-startup/profile.ts`:
- Around line 1014-1017: Update configuredDashboardPort to use the URL scheme
when no explicit port is present: retain 18,789 for HTTP or other existing
cases, but return HTTPS’s default port 443 so standard HTTPS dashboard URLs pass
requirePort(..., 1024). Preserve explicit port handling unchanged.

In `@test/helpers/vitest-watch-triggers.ts`:
- Line 50: Update the Dockerfile alternative in the watch-trigger pattern used
by vitest configuration so it matches only the repository-root Dockerfile after
paths are normalized to repository-relative form. Preserve the explicit
agents/hermes and agents/langchain-deepagents-code Dockerfile matches, while
excluding unrelated nested paths such as agents/other/Dockerfile.

---

Nitpick comments:
In `@src/lib/onboard/managed-startup-profile.test.ts`:
- Around line 966-1012: Add post-cleanup assertions to both Object.prototype
pollution tests, verifying that toJSON and bundleSha256 are absent after each
finally block. Use an own-property check so the tests self-verify isolation
without changing the existing cleanup or validation behavior.
- Around line 250-260: Replace the ineffective encoded-string model assertion in
the managed startup profile test with a meaningful decoded-profile assertion:
verify that decodeManagedStartupProfile(encoded) preserves
profile.inference.model while retaining the existing canonical round-trip and
fingerprint checks. Remove the trivial encoded not-to-contain expectation.
- Around line 1029-1062: Guard restoration of the Array.prototype descriptors in
the test around serializeManagedStartupProfile: avoid blindly casting
potentially undefined mapDescriptor or sortDescriptor values, and restore each
property only when its descriptor was successfully captured. Keep the existing
cleanup in the finally block and ensure a failed lookup cannot leave
Array.prototype poisoned.

In `@src/lib/onboard/managed-startup/profile.ts`:
- Around line 752-773: Replace the hand-rolled insertion sort in sortStrings
with the built-in Array#sort using its default ordering, preserving the
function’s in-place mutation and returned array behavior.
- Around line 685-691: In valueLooksLikeSecret, replace the indexed
property-descriptor lookup with direct iteration over SECRET_VALUE_PATTERNS and
test each pattern against value. Apply the same simplification to the
String#match result around the referenced matching logic, removing unnecessary
descriptor-based validation while preserving the existing matching behavior.
- Around line 1173-1185: Update validateWebSearch and the other
capability-backed validators—validateDashboard, validateInference, and
validateTuning—to derive each agent’s allowed values from
MANAGED_STARTUP_PROFILE_CAPABILITIES instead of duplicating literals or sets.
Reuse the existing capability-table lookup pattern used by validateInference for
inferenceApis, while preserving each validator’s current validation and error
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: f56dc565-0e1b-4b07-8bac-7c8443044f9b

📥 Commits

Reviewing files that changed from the base of the PR and between a8c7ab0 and 32561a9.

📒 Files selected for processing (6)
  • ci/source-shape-test-budget.json
  • src/lib/onboard/managed-startup-profile.test.ts
  • src/lib/onboard/managed-startup/profile.ts
  • src/lib/onboard/managed-startup/transport.ts
  • test/helpers/vitest-watch-triggers.ts
  • test/vitest-watch-triggers.test.ts

Comment on lines +1014 to +1017
function configuredDashboardPort(value: string): number {
const explicit = new URL(value).port;
return explicit === "" ? 18_789 : Number(explicit);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for the established default dashboard port and any remote/443 dashboard handling.
rg -nP --type=ts -C3 '\b18[_]?789\b' | head -60
fd -e ts -i dashboard src/lib/onboard --exec rg -nP -C2 '\b(443|https:)\b' {} \; | head -60

Repository: NVIDIA/NemoClaw

Length of output: 153


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file=src/lib/onboard/managed-startup/profile.ts
wc -l "$file"
echo '--- around configuredDashboardPort ---'
sed -n '980,1045p' "$file"
echo '--- around requireHttpUrl / port handling ---'
sed -n '1288,1395p' "$file"
echo '--- symbol search ---'
rg -n "configuredDashboardPort|requireHttpUrl|requirePort|dashboard port|18789|443|https://" src/lib/onboard -S

Repository: NVIDIA/NemoClaw

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail
file=src/lib/onboard/managed-startup/profile.ts
wc -l "$file"
echo '--- around configuredDashboardPort ---'
sed -n '980,1045p' "$file"
echo '--- around requireHttpUrl / port handling ---'
sed -n '1288,1395p' "$file"
echo '--- symbol search ---'
rg -n "configuredDashboardPort|requireHttpUrl|requirePort|dashboard port|18789|443|https://" src/lib/onboard -S

Repository: NVIDIA/NemoClaw

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "configuredDashboardPort|requireHttpUrl|requirePort" src/lib/onboard/managed-startup/profile.ts
sed -n '1310,1385p' src/lib/onboard/managed-startup/profile.ts

Repository: NVIDIA/NemoClaw

Length of output: 3953


🏁 Script executed:

#!/bin/bash
set -euo pipefail

testfile=src/lib/onboard/managed-startup/profile.test.ts
wc -l "$testfile"
echo '--- dashboard-related tests ---'
rg -n "dashboard|18789|443|https://" "$testfile" -n -C 2

Repository: NVIDIA/NemoClaw

Length of output: 232


🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-files 'src/lib/onboard/*profile*.test.ts' 'src/lib/onboard/**/profile*.test.ts' 'src/lib/onboard/**/managed-startup*test.ts' | sort
echo '--- URL probe ---'
node - <<'JS'
for (const u of ['http://dash.example.com', 'https://dash.example.com', 'https://dash.example.com:443', 'http://dash.example.com:80']) {
  const url = new URL(u);
  console.log(u, '=> protocol=', url.protocol, 'port=', JSON.stringify(url.port), 'origin=', url.origin);
}
JS

Repository: NVIDIA/NemoClaw

Length of output: 666


🏁 Script executed:

#!/bin/bash
set -euo pipefail

testfile=src/lib/onboard/managed-startup-profile.test.ts
wc -l "$testfile"
echo '--- dashboard tests ---'
rg -n -C 3 "dashboard|18789|443|https://dash|OpenClaw|Hermes" "$testfile"
echo '--- requireHttpUrl / configuredDashboardPort context ---'
sed -n '930,1025p' src/lib/onboard/managed-startup/profile.ts
sed -n '1298,1378p' src/lib/onboard/managed-startup/profile.ts

Repository: NVIDIA/NemoClaw

Length of output: 13443


🏁 Script executed:

#!/bin/bash
set -euo pipefail

testfile=src/lib/onboard/managed-startup-profile.test.ts
wc -l "$testfile"
echo '--- dashboard-related cases ---'
rg -n -C 3 "dashboard|18789|443|https://dash|mode|publicPort|port" "$testfile"

Repository: NVIDIA/NemoClaw

Length of output: 12487


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 3 "dashboard\.url|dashboard\.port|publicPort|18789|reverse proxy|remote dashboard|loopback-forwarded|mode: \"remote\"" src/lib/onboard src/lib/README.md src/lib/onboard/machine/README.md

Repository: NVIDIA/NemoClaw

Length of output: 50372


Honor the URL scheme’s default port
configuredDashboardPort() treats any portless URL as 18,789, so https://dash.example.com only passes if :18789 is spelled out. Since requirePort(..., 1024) also rejects 443, a standard HTTPS reverse proxy can’t be represented. If HTTPS dashboards are in scope, make the fallback scheme-aware.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/onboard/managed-startup/profile.ts` around lines 1014 - 1017, Update
configuredDashboardPort to use the URL scheme when no explicit port is present:
retain 18,789 for HTTP or other existing cases, but return HTTPS’s default port
443 so standard HTTPS dashboard URLs pass requirePort(..., 1024). Preserve
explicit port handling unchanged.


export const vitestWatchTriggerPatterns: VitestWatchTriggerPattern[] = [
{
pattern: /(?:^|\/)(?:Dockerfile|agents\/(?:hermes|langchain-deepagents-code)\/Dockerfile)$/,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Restrict the Dockerfile match to the repository root.

The Dockerfile alternative currently matches unrelated nested paths such as agents/other/Dockerfile, causing unnecessary managed-startup-profile tests. Match repository-relative paths or otherwise anchor this alternative to the repository root.

Proposed direction
-    pattern: /(?:^|\/)(?:Dockerfile|agents\/(?:hermes|langchain-deepagents-code)\/Dockerfile)$/,
+    pattern: /^(?:Dockerfile|agents\/(?:hermes|langchain-deepagents-code)\/Dockerfile)$/,

Apply this after normalizing paths to repository-relative form.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/helpers/vitest-watch-triggers.ts` at line 50, Update the Dockerfile
alternative in the watch-trigger pattern used by vitest configuration so it
matches only the repository-root Dockerfile after paths are normalized to
repository-relative form. Preserve the explicit agents/hermes and
agents/langchain-deepagents-code Dockerfile matches, while excluding unrelated
nested paths such as agents/other/Dockerfile.

@ericksoa ericksoa added area: onboarding Onboarding FSM, provider setup, sandbox launch, or first-run flow feature PR adds or expands user-visible functionality platform: container Affects Docker, containerd, Podman, or images area: security Security controls, permissions, secrets, or hardening labels Jul 30, 2026
@ericksoa ericksoa self-assigned this Jul 30, 2026
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
ericksoa added 3 commits July 30, 2026 16:22
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/lib/onboard/managed-startup-profile.test.ts (1)

252-273: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Source-text scraping is CWD-dependent and fails opaquely on file moves.

path.join(process.cwd(), relativePath) assumes the Vitest working directory is the repo root, and any rename of the six scanned files (or scripts/nemoclaw-start.sh) surfaces as an ENOENT from readFileSync at module load rather than a meaningful contract failure. Resolving relative to import.meta.url and asserting readability up front makes the failure self-explanatory.

Also note the path instruction preference for observable outcomes over source-text assertions; the source-shape-contract marker at Line 474 suggests this is a sanctioned exception, so this is a robustness nit rather than an objection to the approach.

♻️ Resolve paths from the module location
+const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
 const STOCK_RUNTIME_INPUTS = new Set(
   RUNTIME_INPUT_SOURCE_FILES.flatMap((relativePath) => [
-    ...readFileSync(path.join(process.cwd(), relativePath), "utf8").matchAll(
+    ...readFileSync(path.join(REPO_ROOT, relativePath), "utf8").matchAll(
       QUOTED_RUNTIME_INPUT_RE,
     ),
   ]).map((match) => match[1] as string),
 );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/onboard/managed-startup-profile.test.ts` around lines 252 - 273,
Update the source-file loading around RUNTIME_INPUT_SOURCE_FILES and
OPENCLAW_AUTO_PAIR_CONSUMER_INPUTS to resolve paths relative to import.meta.url
rather than process.cwd(). Add an upfront readability/assertion check for every
scanned file, including scripts/nemoclaw-start.sh, so renames or missing files
produce a clear contract failure instead of an opaque readFileSync ENOENT.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/lib/onboard/managed-startup-profile.test.ts`:
- Around line 252-273: Update the source-file loading around
RUNTIME_INPUT_SOURCE_FILES and OPENCLAW_AUTO_PAIR_CONSUMER_INPUTS to resolve
paths relative to import.meta.url rather than process.cwd(). Add an upfront
readability/assertion check for every scanned file, including
scripts/nemoclaw-start.sh, so renames or missing files produce a clear contract
failure instead of an opaque readFileSync ENOENT.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 873140b8-cf25-48f2-8060-4f59c734a621

📥 Commits

Reviewing files that changed from the base of the PR and between 32561a9 and 244e3e6.

📒 Files selected for processing (3)
  • ci/source-shape-test-budget.json
  • src/lib/onboard/managed-startup-profile.test.ts
  • src/lib/onboard/managed-startup/profile.ts

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
sandl99 added a commit that referenced this pull request Jul 31, 2026
<!-- markdownlint-disable MD041 -->
## Summary

Adds the canonical July 30 release entry for `v0.0.99` before the
release tag is captured.
The entry covers all 37 merged PRs since `v0.0.98` and bounds
experimental or dormant work without presenting it as supported
behavior.

## Changes

- Adds `docs/changelog/2026-07-30.mdx` with the exact `## v0.0.99`
heading, parser-safe MDX SPDX comment, summary, detailed release
bullets, and published documentation routes.
- Records user-visible recovery, snapshot, shared-route, Hermes,
readiness, inference, image, documentation, and release E2E changes.
- States that the managed-image selection and startup-profile contracts
remain dormant and do not activate buildless onboarding.

Source summary:

- [#7972](#7972) ->
`docs/changelog/2026-07-30.mdx`: Records restored managed OpenClaw
configuration modes during recovery.
- [#7834](#7834) ->
`docs/changelog/2026-07-30.mdx`: Records clone-bound pairing
verification after snapshot restore.
- [#7975](#7975) ->
`docs/changelog/2026-07-30.mdx`: Records managed startup recovery
coverage.
- [#7960](#7960) ->
`docs/changelog/2026-07-30.mdx`: Records dormant startup-profile
coordination without activating a supported surface.
- [#7856](#7856) ->
`docs/changelog/2026-07-30.mdx`: Records persistence of the
credential-free OpenClaw startup command.
- [#7959](#7959) ->
`docs/changelog/2026-07-30.mdx`: Records dormant startup-profile
construction without changing onboarding.
- [#7946](#7946) ->
`docs/changelog/2026-07-30.mdx`: Records the internal startup-profile
schema and transport contract.
- [#7951](#7951) ->
`docs/changelog/2026-07-30.mdx`: Records platform-pull cleanup before
managed-image validation.
- [#7949](#7949) ->
`docs/changelog/2026-07-30.mdx`: Records rejection of retained Hermes
`uv` build cache metadata.
- [#7597](#7597) ->
`docs/changelog/2026-07-30.mdx`: Records separate command and agent
first-turn latency evidence.
- [#7931](#7931) ->
`docs/changelog/2026-07-30.mdx`: Records focused E2E replacement
evidence for retired selectors.
- [#7950](#7950) ->
`docs/changelog/2026-07-30.mdx`: Records exclusion of build-only
BuildKit telemetry from the Deep Agents Code probe.
- [#7665](#7665) ->
`docs/changelog/2026-07-30.mdx`: Records consolidated priority 2 E2E
coverage.
- [#7911](#7911) ->
`docs/changelog/2026-07-30.mdx`: Records the corrected NVIDIA DORI
installation pin.
- [#7934](#7934) ->
`docs/changelog/2026-07-30.mdx`: Records the staging image-family wait
before Brev Launchable deployment.
- [#7772](#7772) ->
`docs/changelog/2026-07-30.mdx`: Records dormant managed-image selection
contracts without activating buildless onboarding.
- [#7941](#7941) ->
`docs/changelog/2026-07-30.mdx`: Records corrected agent-specific
provider and policy guidance.
- [#7819](#7819) ->
`docs/changelog/2026-07-30.mdx`: Records removal of empty Deep Agents
Code provider-switch sections.
- [#7932](#7932) ->
`docs/changelog/2026-07-30.mdx`: Records independent
credential-generation E2E execution.
- [#7840](#7840) ->
`docs/changelog/2026-07-30.mdx`: Records shared-route preservation and
pre-delete peer validation during upgrades.
- [#7874](#7874) ->
`docs/changelog/2026-07-30.mdx`: Records the split between pre-tag
release entries and post-tag Announcements.
- [#7876](#7876) ->
`docs/changelog/2026-07-30.mdx`: Records the writable Hermes runtime
root within lockdown.
- [#7756](#7756) ->
`docs/changelog/2026-07-30.mdx`: Records validated multi-platform
managed-image publication.
- [#7914](#7914) ->
`docs/changelog/2026-07-30.mdx`: Records accepted `uv` version metadata
in Hermes image validation.
- [#7686](#7686) ->
`docs/changelog/2026-07-30.mdx`: Records the explicitly experimental
Microsoft Entra runtime identity reference.
- [#7869](#7869) ->
`docs/changelog/2026-07-30.mdx`: Records classified gateway relaunch
quarantine and rebuild guidance.
- [#7814](#7814) ->
`docs/changelog/2026-07-30.mdx`: Records state restore into replacement
sandboxes and SQLite write verification.
- [#7839](#7839) ->
`docs/changelog/2026-07-30.mdx`: Records quieter onboarding test
execution without a user-facing behavior claim.
- [#7854](#7854) ->
`docs/changelog/2026-07-30.mdx`: Records generalized agent-selection
guidance.
- [#7845](#7845) ->
`docs/changelog/2026-07-30.mdx`: Records isolated CDI test evidence
without a user-facing behavior claim.
- [#7843](#7843) ->
`docs/changelog/2026-07-30.mdx`: Records the corrected Omni sub-agent
model ID.
- [#7908](#7908) ->
`docs/changelog/2026-07-30.mdx`: Records reviewed Hermes and Deep Agents
Code dependency pins.
- [#7887](#7887) ->
`docs/changelog/2026-07-30.mdx`: Records rejection of a symlinked DGX
Station release marker.
- [#7747](#7747) ->
`docs/changelog/2026-07-30.mdx`: Records the internal compute-driver
separation without a user-facing behavior claim.
- [#7660](#7660) ->
`docs/changelog/2026-07-30.mdx`: Records atomic publication of rebuild
recovery manifests.
- [#7661](#7661) ->
`docs/changelog/2026-07-30.mdx`: Records bounded local inference
health-response retention.
- [#7654](#7654) ->
`docs/changelog/2026-07-30.mdx`: Records state preservation across
supervisor relaunch recovery.

## Type of Change

- [ ] Code change (feature, bug fix, or refactor)
- [ ] Code change with doc updates
- [x] Doc only (prose changes, no code sample modifications)
- [ ] Doc only (includes code sample changes)

## Quality Gates

- [ ] Tests added or updated for changed behavior
- [x] Existing tests cover changed behavior — justification:
`test/changelog-docs.test.ts` validates the dated changelog contract,
SPDX comment, version heading, and published routes.
- [ ] Tests not applicable — justification:
- [x] Docs updated for user-facing behavior changes
- [ ] Docs not applicable — justification:
- [ ] Sensitive paths changed (security, policy, credentials, preflight,
onboarding, inference, runner, sandbox, or messaging)
- [ ] Sensitive-path review completed or maintainer-approved waiver
recorded — reviewer/approval link/justification:
- [ ] Non-success, skipped, or missing CI check accepted by maintainer —
check name, approval link, and follow-up issue:

## Documentation Writer Review

- [x] Documentation writer subagent reviewed the completed changes
- Result: `docs-updated`
- Evidence: `docs/changelog/2026-07-30.mdx`; the documentation-only diff
passed review against `WRITING.md`, the controlled word list, and
`docs/CONTRIBUTING.md`. The review covered terminology, structure,
active voice, release meaning, product-scope boundaries, and link and
code presentation. Changelog tests passed 6/6, and the docs build
reported 0 errors with 2 pre-existing warnings.
- Agent: Codex CLI
<!-- docs-review-head-sha: 200940f -->
<!-- docs-review-agents-blob-sha: c052d60 -->

## DGX Station Hardware Evidence

- [ ] Tested on DGX Station
- Tested commit:
- Station profile/scenario:
- Result:
- Supporting evidence:

## Verification

- [x] PR description includes a `Signed-off-by:` line and every commit
appears as `Verified` in GitHub
- [x] Normal `pre-commit`, `commit-msg`, and `pre-push` hooks passed, or
`npm run validate:pr` passed after refreshing `origin/main` when hooks
were skipped or unavailable
- [x] Targeted behavior tests pass for the current change set, or tests
are marked not applicable above — command/result or justification: `npx
vitest run test/changelog-docs.test.ts` passed 6/6 tests.
- [ ] Applicable broad gate passed — `npm test` for broad
runtime/test-harness changes; `npm run check` for repo-wide
validation/coverage changes — command/result: Not applicable to this
documentation-only release entry.
- [x] Quality Gates section completed with required justifications or
waivers
- [x] No secrets, API keys, or credentials committed
- [ ] `npm run docs` builds without warnings (doc changes only) —
result: Build passed with 0 errors and 2 pre-existing warnings.
- [x] Doc pages follow the [style
guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md)
(doc changes only)
- [ ] New doc pages include SPDX header and frontmatter (new pages only)

---
Signed-off-by: San Dang <sdang@nvidia.com>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Added release notes for v0.0.99 covering snapshot restoration, sandbox
recovery, gateway route upgrades, and Hermes security updates.
* Documented experimental Microsoft Entra runtime identity support and
enhanced readiness checks.
* Added details on managed image validation, trusted CI image promotion,
and end-to-end release evidence.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: onboarding Onboarding FSM, provider setup, sandbox launch, or first-run flow area: security Security controls, permissions, secrets, or hardening feature PR adds or expands user-visible functionality platform: container Affects Docker, containerd, Podman, or images

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant