Skip to content

fix(policies): preset application for versionless policies (Fixes #35)#101

Merged
kjw3 merged 3 commits intoNVIDIA:mainfrom
deepujain:fix/35-versionless-policies
Mar 27, 2026
Merged

fix(policies): preset application for versionless policies (Fixes #35)#101
kjw3 merged 3 commits intoNVIDIA:mainfrom
deepujain:fix/35-versionless-policies

Conversation

@deepujain
Copy link
Copy Markdown
Contributor

@deepujain deepujain commented Mar 17, 2026

Summary

Preset application now correctly handles existing policies that have content but no version: header (versionless policies). The merged policy always includes a version header so OpenShell accepts it.

Fixes #35.

Changes

  • bin/lib/policies.js
    • Extracted merge logic into a pure function mergePresetIntoPolicy(currentPolicy, presetEntries) so it can be unit-tested.
    • When the current policy has content but no version header, the merged result is prefixed with version: 1\n.
    • When the current policy has a network_policies: section but no version (e.g. from an older or external source), the merged result is given a version header.
    • Exported extractPresetEntries, parseCurrentPolicy, and mergePresetIntoPolicy for tests.
  • test/policies.test.js
    • Added mergePresetIntoPolicy describe block with 4 regression tests: versionless content-only policy, versionless policy with network_policies, existing version preserved, empty current policy.

Testing

  • npm test — 43 tests pass (39 existing + 4 new).

Summary by CodeRabbit

  • Bug Fixes

    • Improved policy merge behavior: missing version headers are normalized (defaults to "version: 1"), existing keys and explicit versions are preserved, and preset network entries are consistently appended even when policies are empty or previously lacked a network section.
  • Tests

    • Added coverage for merge scenarios: empty policies, versionless policies, existing network policies, and explicit version preservation.

@wscurran wscurran added bug Something isn't working enhancement: feature Use this label to identify requests for new capabilities in NemoClaw. labels Mar 19, 2026
@wscurran
Copy link
Copy Markdown
Contributor

I've examined the changes and see that this PR addresses a bug with versionless policies and extracts the merge logic into a testable function, which should improve the overall policy handling.

@wscurran wscurran added the priority: medium Issue that should be addressed in upcoming releases label Mar 19, 2026
@deepujain deepujain force-pushed the fix/35-versionless-policies branch from 4a0ac43 to be97a12 Compare March 20, 2026 01:01
@deepujain
Copy link
Copy Markdown
Contributor Author

Rebased onto latest main to resolve conflicts in bin/lib/policies.js and test/policies.test.js. Upstream added buildPolicySetCommand and buildPolicyGetCommand helpers; kept both alongside mergePresetIntoPolicy. All policy tests pass (buildPolicySetCommand, buildPolicyGetCommand, mergePresetIntoPolicy).

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added exported helper mergePresetIntoPolicy(currentPolicy, presetEntries) to merge network_policies into existing YAML and ensure a version: header when missing. Refactored applyPreset to delegate merging to this helper and adjusted its behavior. Added tests covering multiple merge scenarios.

Changes

Cohort / File(s) Summary
Policy logic
bin/lib/policies.js
Added and exported mergePresetIntoPolicy(currentPolicy, presetEntries) which injects/merges network_policies: and prepends version: 1 when the merged output lacks a version: header. Refactored applyPreset to load/validate preset content, fetch current policy (runCapture(..., { ignoreError: true })), parse it, and delegate YAML composition to mergePresetIntoPolicy. Removed prior inline merging/version-insertion branches and adjusted error/return behavior when no preset entries are present.
Tests
test/policies.test.js
Added describe("mergePresetIntoPolicy") tests covering: non-empty policy without version: (prepends version: 1 and adds presets), policy with existing network_policies but no version: (preserves existing entries and appends presets), policy with existing version: (preserved), and empty policy input (creates version: 1 and network_policies: with presets).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through YAML lines at night,
I tucked in presets neat and tight,
When version: vanished from sight,
I placed version: 1 just right,
Policies now bound and light.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: fixing preset application for versionless policies, and directly references the linked issue #35.
Linked Issues check ✅ Passed The PR fully addresses all requirements from #35: handles versionless policies with content [#35], adds mergePresetIntoPolicy function for proper YAML merging with version headers, includes regression tests for versionless policies, and all tests pass via npm test.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing preset application for versionless policies: extraction of merge logic into mergePresetIntoPolicy, refactoring of applyPreset, and comprehensive test coverage for the new function.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
bin/lib/policies.js (1)

166-172: Consider logging or commenting on the swallowed exception.

The empty catch {} block silently swallows any error from runCapture. While this is intentional (treating policy-get failures as "no existing policy"), an inline comment explaining the intent or a debug log would improve clarity for future maintainers.

📝 Suggested improvement
   let rawPolicy = "";
   try {
     rawPolicy = runCapture(
       buildPolicyGetCommand(sandboxName),
       { ignoreError: true }
     );
-  } catch {}
+  } catch {
+    // Policy may not exist yet — treat as empty policy
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/policies.js` around lines 166 - 172, The empty catch around the
runCapture call that sets rawPolicy silently swallows errors; add either a brief
inline comment clarifying this is intentional (treating policy-get failures as
"no existing policy") or log the caught error at debug/trace level using the
same logger used elsewhere, referencing the
runCapture(buildPolicyGetCommand(sandboxName)) call and the rawPolicy variable
so maintainers understand the intent and can diagnose unexpected failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@bin/lib/policies.js`:
- Around line 166-172: The empty catch around the runCapture call that sets
rawPolicy silently swallows errors; add either a brief inline comment clarifying
this is intentional (treating policy-get failures as "no existing policy") or
log the caught error at debug/trace level using the same logger used elsewhere,
referencing the runCapture(buildPolicyGetCommand(sandboxName)) call and the
rawPolicy variable so maintainers understand the intent and can diagnose
unexpected failures.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 615337ac-38a7-4bf9-8028-0dbca6657634

📥 Commits

Reviewing files that changed from the base of the PR and between dbfd78c and be97a12.

📒 Files selected for processing (2)
  • bin/lib/policies.js
  • test/policies.test.js

@deepujain deepujain force-pushed the fix/35-versionless-policies branch from be97a12 to 0feaea6 Compare March 20, 2026 01:17
@deepujain
Copy link
Copy Markdown
Contributor Author

Addressed CodeRabbit review comment

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
bin/lib/policies.js (1)

101-111: ⚠️ Potential issue | 🟡 Minor

Match network_policies as a YAML key, not a substring.

Line 101 and Line 110 use broad string checks; these can mis-detect comments/scalars and route merge logic incorrectly. Prefer top-level-key detection to avoid malformed output.

Suggested hardening
-  if (currentPolicy.includes("network_policies:")) {
+  if (/^network_policies\s*:/m.test(currentPolicy)) {
...
-      if (line.trim() === "network_policies:" || line.trim().startsWith("network_policies:")) {
+      if (/^network_policies\s*:/.test(line)) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/policies.js` around lines 101 - 111, currentPolicy is being detected
with broad string checks which can match substrings in comments or scalars;
replace those checks with top-level YAML key detection by using a regex that
matches a line that starts with optional indentation then the literal
"network_policies:" as a key (e.g. /^\s*network_policies:\s*(#.*)?$/ or similar)
and use that same test inside the loop instead of line.trim().startsWith or
currentPolicy.includes; update the logic around inNetworkPolicies, isTopLevel
and the line-match condition so you only enter/exit the network_policies block
when a true top-level key is seen (respecting indentation via isTopLevel) to
avoid false positives.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/lib/policies.js`:
- Around line 167-174: The catch block around
runCapture(buildPolicyGetCommand(sandboxName)) is too broad and masks real
errors; update the handler so that runCapture errors are inspected and only
treated as "policy missing" when the command indicates a not-found condition
(e.g., specific exit code or stderr message), while other errors are rethrown or
logged and cause failure. Locate the call to runCapture and
buildPolicyGetCommand and change the catch to check the thrown error's
properties (error.code, error.exitCode, error.stderr/message) and only set
rawPolicy to empty for the not-found case; for all other errors rethrow or call
processLogger.error and exit.

---

Outside diff comments:
In `@bin/lib/policies.js`:
- Around line 101-111: currentPolicy is being detected with broad string checks
which can match substrings in comments or scalars; replace those checks with
top-level YAML key detection by using a regex that matches a line that starts
with optional indentation then the literal "network_policies:" as a key (e.g.
/^\s*network_policies:\s*(#.*)?$/ or similar) and use that same test inside the
loop instead of line.trim().startsWith or currentPolicy.includes; update the
logic around inNetworkPolicies, isTopLevel and the line-match condition so you
only enter/exit the network_policies block when a true top-level key is seen
(respecting indentation via isTopLevel) to avoid false positives.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4a6afca8-ecc3-4978-9843-cce971a01164

📥 Commits

Reviewing files that changed from the base of the PR and between be97a12 and 0feaea6.

📒 Files selected for processing (2)
  • bin/lib/policies.js
  • test/policies.test.js
✅ Files skipped from review due to trivial changes (1)
  • test/policies.test.js

Comment thread bin/lib/policies.js Outdated
@deepujain deepujain force-pushed the fix/35-versionless-policies branch from 0feaea6 to e2755e6 Compare March 20, 2026 01:31
@deepujain
Copy link
Copy Markdown
Contributor Author

Addressed second round of CodeRabbit review

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
bin/lib/policies.js (2)

96-98: Edge case discards currentPolicy when presetEntries is falsy.

If currentPolicy has content but presetEntries is falsy, this returns a fresh template and silently loses the existing policy. The caller guards against this (lines 161-164), but as a "pure function for testing," it should handle this case defensively.

Suggested defensive fix
 function mergePresetIntoPolicy(currentPolicy, presetEntries) {
-  if (!currentPolicy || !presetEntries) {
-    return "version: 1\n\nnetwork_policies:\n" + (presetEntries || "");
+  if (!presetEntries) {
+    // Nothing to merge — return current policy unchanged (or empty template)
+    return currentPolicy || "version: 1\n\nnetwork_policies:\n";
+  }
+  if (!currentPolicy) {
+    return "version: 1\n\nnetwork_policies:\n" + presetEntries;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/policies.js` around lines 96 - 98, The current conditional returns a
fresh template whenever either currentPolicy or presetEntries is falsy,
discarding a valid currentPolicy; update the logic in the function that contains
currentPolicy and presetEntries so it only returns the template when both are
missing, otherwise preserve and return currentPolicy when present, or return the
template plus presetEntries when only presetEntries exists—i.e., change the
guard around currentPolicy/presetEntries to handle three cases (both missing ->
template, currentPolicy present -> return currentPolicy, only presetEntries ->
template + presetEntries) and retain the existing return formatting using the
same string construction.

131-139: Potential duplicate version: keys when version exists but not at the document start.

The includes("version:") check at line 131 detects version anywhere in the document, but the final guard at line 137 only checks if the merged result starts with version:. If the original policy has a non-leading version: field, both checks can pass/fail in opposite ways, resulting in two version: keys at the root level.

This is an edge case since well-formed policies have version: at the top, but worth noting for robustness.

Suggested fix: unify detection logic
   } else {
-    const withVersion = currentPolicy.includes("version:")
-      ? currentPolicy
-      : "version: 1\n" + currentPolicy;
-    merged = withVersion + "\n\nnetwork_policies:\n" + presetEntries;
+    merged = currentPolicy + "\n\nnetwork_policies:\n" + presetEntries;
   }
 
   if (!merged.trimStart().startsWith("version:")) {
     merged = "version: 1\n" + merged;
   }

This delegates all version-prefixing to the final guard (lines 137-139), ensuring consistent behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/policies.js` around lines 131 - 139, The code currently uses
currentPolicy.includes("version:") to decide whether to prepend a version, then
later checks merged.trimStart().startsWith("version:") which can produce
duplicate top-level version keys if "version:" appears mid-document; update the
logic so detection is unified: stop using includes("version:") (or change it to
a startsWith-style check) and build merged from currentPolicy (and
presetEntries) without conditionally prepending there, then rely solely on the
final guard that uses merged.trimStart().startsWith("version:") to prepend
"version: 1\n" when missing; adjust the variables referenced (currentPolicy,
withVersion, merged, presetEntries) accordingly and remove the redundant branch
that used includes("version:").
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@bin/lib/policies.js`:
- Around line 96-98: The current conditional returns a fresh template whenever
either currentPolicy or presetEntries is falsy, discarding a valid
currentPolicy; update the logic in the function that contains currentPolicy and
presetEntries so it only returns the template when both are missing, otherwise
preserve and return currentPolicy when present, or return the template plus
presetEntries when only presetEntries exists—i.e., change the guard around
currentPolicy/presetEntries to handle three cases (both missing -> template,
currentPolicy present -> return currentPolicy, only presetEntries -> template +
presetEntries) and retain the existing return formatting using the same string
construction.
- Around line 131-139: The code currently uses
currentPolicy.includes("version:") to decide whether to prepend a version, then
later checks merged.trimStart().startsWith("version:") which can produce
duplicate top-level version keys if "version:" appears mid-document; update the
logic so detection is unified: stop using includes("version:") (or change it to
a startsWith-style check) and build merged from currentPolicy (and
presetEntries) without conditionally prepending there, then rely solely on the
final guard that uses merged.trimStart().startsWith("version:") to prepend
"version: 1\n" when missing; adjust the variables referenced (currentPolicy,
withVersion, merged, presetEntries) accordingly and remove the redundant branch
that used includes("version:").

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7a2ecb62-6468-4c67-9893-bed5a0f00a4c

📥 Commits

Reviewing files that changed from the base of the PR and between 0feaea6 and e2755e6.

📒 Files selected for processing (2)
  • bin/lib/policies.js
  • test/policies.test.js
✅ Files skipped from review due to trivial changes (1)
  • test/policies.test.js

@deepujain deepujain force-pushed the fix/35-versionless-policies branch from e2755e6 to 227ff0e Compare March 21, 2026 22:42
@deepujain
Copy link
Copy Markdown
Contributor Author

Rebased onto latest main. Resolved merge conflict

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
bin/lib/policies.js (1)

138-138: Minor: potential extra blank lines when appending new section.

If currentPolicy already ends with one or more newlines, the concatenation produces extra blank lines. This is cosmetic and won't break functionality.

♻️ Optional: normalize trailing newlines
-    merged = currentPolicy + "\n\nnetwork_policies:\n" + presetEntries;
+    merged = currentPolicy.trimEnd() + "\n\nnetwork_policies:\n" + presetEntries;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/policies.js` at line 138, The concatenation that builds merged uses
currentPolicy + "\n\nnetwork_policies:\n" + presetEntries and can create extra
blank lines if currentPolicy ends with newlines; trim or normalize trailing
newlines from currentPolicy (e.g., call a trimEnd/newline-normalizing helper)
before constructing merged so you always insert exactly one blank line before
"network_policies:"; update the merged assignment that references currentPolicy
and presetEntries to use the normalized currentPolicy.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@bin/lib/policies.js`:
- Line 138: The concatenation that builds merged uses currentPolicy +
"\n\nnetwork_policies:\n" + presetEntries and can create extra blank lines if
currentPolicy ends with newlines; trim or normalize trailing newlines from
currentPolicy (e.g., call a trimEnd/newline-normalizing helper) before
constructing merged so you always insert exactly one blank line before
"network_policies:"; update the merged assignment that references currentPolicy
and presetEntries to use the normalized currentPolicy.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 612384b9-9936-4eb8-8bb8-f6f619f8718d

📥 Commits

Reviewing files that changed from the base of the PR and between e2755e6 and 227ff0e.

📒 Files selected for processing (2)
  • bin/lib/policies.js
  • test/policies.test.js
✅ Files skipped from review due to trivial changes (1)
  • test/policies.test.js

@deepujain deepujain force-pushed the fix/35-versionless-policies branch from 227ff0e to fc4e389 Compare March 24, 2026 02:23
@deepujain
Copy link
Copy Markdown
Contributor Author

Rebased on latest main, addressed the trailing-newline nitpick (trimEnd before concatenation), and converted the mergePresetIntoPolicy tests from assert to vitest expect. All tests pass. Should be good to go!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
bin/lib/policies.js (1)

141-143: Edge case: leading comments could cause duplicate version headers.

If currentPolicy has content before the version: line (e.g., a leading comment), trimStart() removes only whitespace, so "# comment\nversion: 1\n..." still fails the check and gets version: 1\n prepended—resulting in duplicate version: keys.

Consider using a regex that searches for version: anywhere at the start of a line:

💡 Suggested fix
-  if (!merged.trimStart().startsWith("version:")) {
+  if (!/^version\s*:/m.test(merged)) {
     merged = "version: 1\n" + merged;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/policies.js` around lines 141 - 143, The current check uses
merged.trimStart().startsWith("version:") which fails when there are leading
comments or other non-whitespace characters before a version header (causing
duplicate version: lines); update the check around the merged variable (where
currentPolicy is combined) to test for a version header at the start of any line
using a multiline-aware regex (e.g., match /^\s*version:/m) or by scanning lines
for one that starts with "version:" and only prepend "version: 1\n" when no such
header is found; adjust the if condition that currently prepends the header so
it relies on this regex/line-scan check instead of
trimStart().startsWith("version:").
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@bin/lib/policies.js`:
- Around line 141-143: The current check uses
merged.trimStart().startsWith("version:") which fails when there are leading
comments or other non-whitespace characters before a version header (causing
duplicate version: lines); update the check around the merged variable (where
currentPolicy is combined) to test for a version header at the start of any line
using a multiline-aware regex (e.g., match /^\s*version:/m) or by scanning lines
for one that starts with "version:" and only prepend "version: 1\n" when no such
header is found; adjust the if condition that currently prepends the header so
it relies on this regex/line-scan check instead of
trimStart().startsWith("version:").

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f5e6b7fc-53f5-4aa0-bd94-1b5a5a493c1d

📥 Commits

Reviewing files that changed from the base of the PR and between 227ff0e and fc4e389.

📒 Files selected for processing (2)
  • bin/lib/policies.js
  • test/policies.test.js
✅ Files skipped from review due to trivial changes (1)
  • test/policies.test.js

@deepujain deepujain force-pushed the fix/35-versionless-policies branch from fc4e389 to e723872 Compare March 25, 2026 22:07
Copy link
Copy Markdown
Contributor

@senthilr-nv senthilr-nv left a comment

Choose a reason for hiding this comment

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

Tested on DGX Spark (ARM64, Ubuntu 24.04) against current main (c051cbb).

Bug reproduced on main: When the existing policy has network_policies: but no version: field, the merge path (lines 133–166 of applyPreset) produces YAML without version:, and openshell policy set rejects it with missing field 'version'. This matches the report in #991.

Fix verified: mergePresetIntoPolicy() correctly adds version: 1 as a safety net after all merge paths. Tested four scenarios:

  • Versionless policy with network_policies:version: 1 prepended ✅
  • Empty/no policy — version: 1 added ✅
  • Existing version: 2 preserved, not overwritten ✅
  • Policy with content but no network_policies: and no version: — both added ✅

Full test suite passes: 497 tests green (33 suites, 2 skipped).

This also fixes #991.

@kjw3 kjw3 merged commit 5f692e5 into NVIDIA:main Mar 27, 2026
8 checks passed
mafueee pushed a commit to mafueee/NemoClaw that referenced this pull request Mar 28, 2026
…IDIA#102)

Closes NVIDIA#101

Add pytest-xdist for parallel e2e test execution with configurable
concurrency. Default to 5 workers; override via E2E_PARALLEL env var
(accepts a number or 'auto' for CPU-count matching). Make session-scoped
mock inference route fixtures worker-safe by incorporating the xdist
worker_id into route names and routing hints.

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
TSavo pushed a commit to wopr-network/nemoclaw that referenced this pull request Mar 28, 2026
* fix: improve gateway lifecycle recovery (NVIDIA#953)

* fix: improve gateway lifecycle recovery

* docs: fix readme markdown list spacing

* fix: tighten gateway lifecycle review follow-ups

* fix: simplify tokenized control ui output

* fix: restore chat route in control ui urls

* refactor: simplify ansi stripping in onboard

* fix: shorten control ui url output

* fix: move control ui below cli next steps

* fix: swap hard/soft ulimit settings in start script (NVIDIA#951)

Fixes NVIDIA#949

Co-authored-by: KJ <kejones@nvidia.com>

* chore: add cyclomatic complexity lint rule (NVIDIA#875)

* chore: add cyclomatic complexity rule (ratchet from 95)

Add ESLint complexity rule to bin/ and scripts/ to prevent new
functions from accumulating excessive branching. Starting threshold
is 95 (current worst offender: setupNim in onboard.js). Ratchet
plan: 95 → 40 → 25 → 15.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: ratchet complexity to 20, suppress existing violations

Suppress 6 functions that exceed the threshold with eslint-disable
comments so we can start enforcing at 20 instead of 95:

- setupNim (95), setupPolicies (41), setupInference (22) in onboard.js
- deploy (22), main IIFE (27) in nemoclaw.js
- applyPreset (24) in policies.js

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: suppress complexity for 3 missed functions

preflight (23), getReconciledSandboxGatewayState (25), sandboxStatus (27)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add host-side config and state file locations to README (NVIDIA#903)

Signed-off-by: peteryuqin <peter.yuqin@gmail.com>

* chore: add tsconfig.cli.json, root execa, TS coverage ratchet (NVIDIA#913)

* chore: add tsconfig.cli.json, root execa, TS coverage ratchet

Foundation for the CLI TypeScript migration (PR 0 of the shell
consolidation plan). No runtime changes — config, tooling, and
dependency only.

- tsconfig.cli.json: strict TS type-checking for bin/ and scripts/
  (noEmit, module: preserve — tsx handles the runtime)
- scripts/check-coverage-ratchet.ts: pure TS replacement for the
  bash+python coverage ratchet script (same logic, same tolerance)
- execa ^9.6.1 added to root devDependencies (used by PR 1+)
- pr.yaml: coverage ratchet step now runs the TS version via tsx
- .pre-commit-config.yaml: SPDX headers cover scripts/*.ts,
  new tsc-check-cli pre-push hook
- CONTRIBUTING.md: document typecheck:cli task and CLI pre-push hook
- Delete scripts/check-coverage-ratchet.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Apply suggestion from @brandonpelfrey

* chore: address PR feedback — use types_or, add tsx devDep

- Use `types_or: [ts, tsx]` instead of file glob for tsc-check-cli
  hook per @brandonpelfrey's suggestion.
- Add `tsx` to devDependencies so CI doesn't re-fetch it on every run
  per CodeRabbit's suggestion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): ignore GitHub "Apply suggestion" commits in commitlint

* fix(ci): lint only PR title since repo is squash-merge only

Reverts the commitlint ignores rule from the previous commit and
instead removes the per-commit lint step entirely.

Individual commit messages are discarded at merge time — only the
squash-merged PR title lands in main and drives changelog generation.
Drop the per-commit lint, keep the PR title check, and remove the
now-unnecessary fetch-depth: 0.

* Revert "fix(ci): lint only PR title since repo is squash-merge only"

This reverts commit 1257a47.

* Revert "fix(ci): ignore GitHub "Apply suggestion" commits in commitlint"

This reverts commit c395657.

* docs: fix markdownlint MD032 in README (blank line before list)

* refactor: make coverage ratchet script idiomatic TypeScript

- Wrap in main() with process.exitCode instead of scattered process.exit()
- Replace mutable flags with .map()/.some() over typed MetricResult[]
- Separate pure logic (checkMetrics) from formatting (formatReport)
- Throw with { cause } chaining instead of exit-in-helpers
- Derive CoverageThresholds from METRICS tuple (single source of truth)
- Exhaustive switch on CheckStatus discriminated union

* refactor: remove duplication in coverage ratchet script

- Drop STATUS_LABELS map; inline labels in exhaustive switch
- Extract common 'metric coverage is N%' preamble in formatResult
- Simplify ratchetedThresholds: use results directly (already in
  METRICS order) instead of re-scanning with .find() per metric
- Compute 'failed' once in main, pass into formatReport to avoid
  duplicate .some() scan

* refactor: simplify coverage ratchet with FP patterns

- Extract classify() as a named pure function (replaces nested ternary)
- loadJSON takes repo-relative paths, eliminating THRESHOLD_PATH and
  SUMMARY_PATH constants (DRY the join-with-REPO_ROOT pattern)
- Drop CoverageMetric/CoverageSummary interfaces (only pct is read);
  use structural type at the call site instead
- Inline ratchetedThresholds (one-liner, used once)
- formatReport derives fail/improved from results instead of taking
  a pre-computed boolean (let functions derive from data, don't
  thread derived state)
- sections.join("\n\n") replaces manual empty-string pushing
- Shorter type names (Thresholds, Status, Result) — no ambiguity
  in a single-purpose script

* refactor: strip coverage ratchet to failure-only output

prek hides output from commands that exit 0, so ok/improved
reporting was dead code. Remove Status, Result, classify,
formatResult, formatReport, and the ratcheted-thresholds
suggestion block. The script now just filters for regressions
and prints actionable errors on failure.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Pelfrey <bpelfrey@nvidia.com>

* fix: use CONNECT tunnel for WebSocket endpoints in Discord/Slack presets (NVIDIA#438)

* fix: use CONNECT tunnel for WebSocket endpoints in Discord/Slack presets

The egress proxy's HTTP idle timeout (~2 min) kills long-lived WebSocket
connections when endpoints are configured with protocol:rest + tls:terminate.
Switch WebSocket endpoints to access:full (CONNECT tunnel) which bypasses
HTTP-level timeouts entirely.

Discord:
- gateway.discord.gg → access:full (WebSocket gateway)
- Add PUT/PATCH/DELETE methods for discord.com (message editing, reactions)
- Add media.discordapp.net for attachment access

Slack:
- Add wss-primary.slack.com and wss-backup.slack.com → access:full
  (Socket Mode WebSocket endpoints)

Partially addresses NVIDIA#409 — the policy-level fix enables WebSocket
connections to survive. The hardcoded 2-min timeout in openshell-sandbox
still affects any protocol:rest endpoints with long-lived connections.

Related: NVIDIA#361 (WhatsApp Web, same root cause)

* fix: correct comment wording for media endpoint and YAML formatting

* fix: standardize Node.js minimum version to 22.16 (NVIDIA#840)

* fix: remove unused RECOMMENDED_NODE_MAJOR from scripts/install.sh

Shellcheck flagged it as unused after the min/recommended merge.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: enforce full semver >=22.16.0 in installer scripts

The runtime checks only compared the major Node.js version, allowing
22.0–22.15 to pass despite package.json requiring >=22.16.0. Use the
version_gte() helper for full semver comparison in both installers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: harden version_gte and align fallback message

Guard version_gte() against prerelease suffixes (e.g. "22.16.0-rc.1")
that would crash bash arithmetic. Also update the manual-install
fallback message to reference MIN_NODE_VERSION instead of hardcoded "22".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update test stubs for Node.js 22.16 minimum and add Node 20 rejection test

- Bump node stub in 'succeeds with acceptable Node.js' from v20.0.0 to v22.16.0
- Bump node stub in buildCurlPipeEnv from v22.14.0 to v22.16.0
- Add new test asserting Node.js 20 is rejected by ensure_supported_runtime

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: harden installer and onboard resiliency (NVIDIA#961)

* fix: harden installer and onboard resiliency

* fix: address installer and debug review follow-ups

* fix: harden onboard resume across later setup steps

* test: simplify payload extraction in onboard tests

* test: keep onboard payload extraction target-compatible

* chore: align onboard session lint with complexity rule

* fix: harden onboard session safety and lock handling

* fix: tighten onboard session redaction and metadata handling

* fix(security): strip credentials from migration snapshots and enforce blueprint digest (NVIDIA#769)

Reconciles NVIDIA#156 and NVIDIA#743 into a single comprehensive solution:

- Filter auth-profiles.json at copy time via cpSync filter (from NVIDIA#743)
- Recursive stripCredentials() with pattern-based field detection for
  deep config sanitization (from NVIDIA#156: CREDENTIAL_FIELDS set +
  CREDENTIAL_FIELD_PATTERN regex)
- Remove gateway config section (contains auth tokens) from sandbox
  openclaw.json
- Blueprint digest verification (SHA-256): recorded at snapshot time,
  validated on restore, empty/missing digest is a hard failure
- computeFileDigest() throws when blueprint file is missing instead of
  silently returning null
- Sanitize both snapshot-level and sandbox-bundle openclaw.json copies
- Backward compatible: old snapshots without blueprintDigest skip
  validation
- Bump SNAPSHOT_VERSION 2 → 3

Supersedes NVIDIA#156 and NVIDIA#743.

* fix(sandbox): export proxy env vars with full NO_PROXY and persist across reconnects (NVIDIA#1025)

* fix(sandbox): export proxy env vars with full NO_PROXY and persist across reconnects

OpenShell injects NO_PROXY=127.0.0.1,localhost,::1 into the sandbox, missing
inference.local and the gateway IP (10.200.0.1). This causes LLM inference
requests to route through the egress proxy instead of going direct, and the
proxy gateway IP itself gets proxied.

Add proxy configuration block to nemoclaw-start.sh that:
- Exports HTTP_PROXY, HTTPS_PROXY, and NO_PROXY with inference.local and
  the gateway IP included
- Persists via /etc/profile.d/nemoclaw-proxy.sh (root) or ~/.profile
  (non-root fallback) so values survive OpenShell reconnect injection
- Supports NEMOCLAW_PROXY_HOST / NEMOCLAW_PROXY_PORT overrides

The non-root fallback ensures the fix works in environments like Brev where
containers run without root privileges.

Tested on DGX Spark (ARM64) and Brev VM (x86_64). Verified NO_PROXY contains
inference.local and 10.200.0.1 inside the live sandbox after connect.

Ref: NVIDIA#626, NVIDIA#704
Ref: NVIDIA#704 (comment)

* fix(sandbox): write proxy config to ~/.bashrc for interactive reconnect sessions

OpenShell's `sandbox connect` spawns `/bin/bash -i` (interactive, non-login),
which sources ~/.bashrc — not ~/.profile or /etc/profile.d/*.  The previous
approach wrote to ~/.profile and /etc/profile.d/, neither of which is sourced
by `bash -i`, so the narrow OpenShell-injected NO_PROXY persisted in live
interactive sessions.

Changes:
- Write proxy snippet to ~/.bashrc (primary) and ~/.profile (login fallback)
- Export both uppercase and lowercase proxy variants (NO_PROXY + no_proxy,
  HTTP_PROXY + http_proxy, etc.) — Node.js undici prefers lowercase no_proxy
  over uppercase NO_PROXY when both are set
- Add idempotency guard to prevent duplicate blocks on container restart
- Update tests: verify .bashrc writing, idempotency, bash -i override
  behavior, and lowercase variant correctness

Tested on DGX Spark (ARM64) and Brev VM (x86_64) with full destroy +
re-onboard + live `env | grep proxy` verification inside the sandbox shell
via `openshell sandbox connect`.

Ref: NVIDIA#626

* fix(sandbox): replace stale proxy values on restart with begin/end markers

Use begin/end markers in .bashrc/.profile proxy snippet so
_write_proxy_snippet replaces the block when PROXY_HOST/PORT change
instead of silently keeping stale values. Adds test coverage for the
replacement path.

Addresses CodeRabbit review feedback on idempotency gap.

* fix(sandbox): resolve sandbox user home dynamically when running as root

When the entrypoint runs as root, $HOME is /root — the proxy snippet
was written to /root/.bashrc instead of the sandbox user's home.
Use getent passwd to look up the sandbox user's home when running as
UID 0; fall back to /sandbox if the user entry is missing.

Addresses CodeRabbit review feedback on _SANDBOX_HOME resolution.

---------

Co-authored-by: Carlos Villela <cvillela@nvidia.com>

* fix(policies): preset application for versionless policies (Fixes NVIDIA#35) (NVIDIA#101)

* fix(policies): allow preset application for versionless policies (Fixes NVIDIA#35)

Fixes NVIDIA#35

Signed-off-by: Deepak Jain <deepujain@gmail.com>

* fix: remove stale complexity suppression in policies

---------

Signed-off-by: Deepak Jain <deepujain@gmail.com>
Co-authored-by: Kevin Jones <kejones@nvidia.com>

* fix: restore routed inference and connect UX (NVIDIA#1037)

* fix: restore routed inference and connect UX

* fix: simplify detected local inference hint

* fix: remove stale local inference hint

* test: relax connect forward assertion

---------

Signed-off-by: peteryuqin <peter.yuqin@gmail.com>
Signed-off-by: Deepak Jain <deepujain@gmail.com>
Co-authored-by: KJ <kejones@nvidia.com>
Co-authored-by: Emily Wilkins <80470879+epwilkins@users.noreply.github.com>
Co-authored-by: Carlos Villela <cvillela@nvidia.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Peter <peter.yuqin@gmail.com>
Co-authored-by: Brandon Pelfrey <bpelfrey@nvidia.com>
Co-authored-by: Benedikt Schackenberg <6381261+BenediktSchackenberg@users.noreply.github.com>
Co-authored-by: Lucas Wang <lucas_wang@lucas-futures.com>
Co-authored-by: senthilr-nv <senthilr@nvidia.com>
Co-authored-by: Deepak Jain <deepujain@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
ksapru pushed a commit to ksapru/NemoClaw that referenced this pull request Mar 30, 2026
…DIA#35) (NVIDIA#101)

* fix(policies): allow preset application for versionless policies (Fixes NVIDIA#35)

Fixes NVIDIA#35

Signed-off-by: Deepak Jain <deepujain@gmail.com>

* fix: remove stale complexity suppression in policies

---------

Signed-off-by: Deepak Jain <deepujain@gmail.com>
Co-authored-by: Kevin Jones <kejones@nvidia.com>
laitingsheng pushed a commit that referenced this pull request Apr 2, 2026
#101)

* fix(policies): allow preset application for versionless policies (Fixes #35)

Fixes #35

Signed-off-by: Deepak Jain <deepujain@gmail.com>

* fix: remove stale complexity suppression in policies

---------

Signed-off-by: Deepak Jain <deepujain@gmail.com>
Co-authored-by: Kevin Jones <kejones@nvidia.com>
lakamsani pushed a commit to lakamsani/NemoClaw that referenced this pull request Apr 4, 2026
…DIA#35) (NVIDIA#101)

* fix(policies): allow preset application for versionless policies (Fixes NVIDIA#35)

Fixes NVIDIA#35

Signed-off-by: Deepak Jain <deepujain@gmail.com>

* fix: remove stale complexity suppression in policies

---------

Signed-off-by: Deepak Jain <deepujain@gmail.com>
Co-authored-by: Kevin Jones <kejones@nvidia.com>
gemini2026 pushed a commit to gemini2026/NemoClaw that referenced this pull request Apr 14, 2026
…DIA#35) (NVIDIA#101)

* fix(policies): allow preset application for versionless policies (Fixes NVIDIA#35)

Fixes NVIDIA#35

Signed-off-by: Deepak Jain <deepujain@gmail.com>

* fix: remove stale complexity suppression in policies

---------

Signed-off-by: Deepak Jain <deepujain@gmail.com>
Co-authored-by: Kevin Jones <kejones@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement: feature Use this label to identify requests for new capabilities in NemoClaw. priority: medium Issue that should be addressed in upcoming releases

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants