Skip to content

fix(policies): cross-reference gateway state in policy-list to detect desync#2089

Merged
ericksoa merged 3 commits intoNVIDIA:mainfrom
yimoj:fix/2010-policy-state-inconsistency
Apr 20, 2026
Merged

fix(policies): cross-reference gateway state in policy-list to detect desync#2089
ericksoa merged 3 commits intoNVIDIA:mainfrom
yimoj:fix/2010-policy-state-inconsistency

Conversation

@yimoj
Copy link
Copy Markdown
Contributor

@yimoj yimoj commented Apr 20, 2026

Summary

policy-list previously read only from CLI-side registry metadata (~/.nemoclaw/sandboxes.json), not from the gateway's actual loaded policy. After rebuild, this caused presets to show as ○ (not applied) while the gateway still enforced them. This PR adds gateway cross-referencing so policy-list shows the true state of both sources.

Related Issue

Closes #2010

Changes

  • src/lib/policies.ts — Add getGatewayPresets(sandboxName) which queries the gateway via openshell policy get --full, parses the YAML response, and matches network_policies keys against known preset definitions. Returns null when gateway is unreachable vs [] when reachable but empty.
  • src/nemoclaw.ts — Update sandboxPolicyList() to compare registry and gateway state, showing discrepancy markers:
  • test/repro-2010.test.ts — 9 tests covering:
    • getGatewayPresets matching logic via subprocess (telegram-only, multi-preset, npm exclusion, unreachable gateway, empty policy)
    • sandboxPolicyList CLI output via subprocess (all 4 marker/suffix states including gateway-unreachable fallback)

Type of Change

  • Code change for a new feature, bug fix, or refactor.
  • Code change with doc updates.
  • Doc only. Prose changes without code sample modifications.
  • Doc only. Includes code sample changes.

Testing

  • npx prek run --all-files passes (or equivalently make check).
  • npm test passes.
  • make docs builds without warnings. (for doc-only changes)

71 tests pass (62 existing policy tests + 9 new). TypeScript typecheck, ESLint, and Prettier all clean.

Checklist

General

Signed-off-by: Yimo Jiang yimoj@nvidia.com

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 20, 2026

📝 Walkthrough

Walkthrough

Added getGatewayPresets(sandboxName) to read the gateway's loaded policy and updated sandboxPolicyList() to compare registry-applied presets with gateway-reported presets, showing discrepancy markers and a gateway-unreachable fallback. Added tests that reproduce registry/gateway divergence scenarios.

Changes

Cohort / File(s) Summary
Gateway State Integration
src/lib/policies.ts
New exported getGatewayPresets(sandboxName) runs policy get --full against the sandbox gateway, parses the loaded YAML via parseCurrentPolicy, and returns: null on command failure/unparseable output, [] when YAML contains no usable network_policies, or an array of preset names whose network_policies keys are all present in the gateway policy.
Policy List Display Enhancement
src/nemoclaw.ts
sandboxPolicyList() now queries both getAppliedPresets() (local registry) and getGatewayPresets() (gateway), selects per-preset markers by comparing the two sources, appends explanatory suffixes for mismatches, and falls back to registry-only behavior with a warning when gateway returns null.
Repro Test Suite
test/repro-2010.test.ts
Added Vitest suite that spawns subprocesses to exercise getGatewayPresets() using synthesized gateway YAML (including gateway-unreachable and empty-network_policies cases) and validates CLI rendering for gateway-only, registry-only, synced, and unreachable scenarios with mocked registry/policies functions.

Sequence Diagram

sequenceDiagram
    participant User as User / CLI
    participant Nemoclaw as nemoclaw.ts
    participant Registry as policies (local registry)
    participant GatewayProc as Gateway process

    User->>Nemoclaw: sandboxPolicyList(sandboxName)
    Nemoclaw->>Registry: getAppliedPresets(sandboxName)
    Registry-->>Nemoclaw: [local preset list]
    Nemoclaw->>GatewayProc: getGatewayPresets(sandboxName) (runs `policy get --full`)
    alt Gateway Reachable & YAML parseable
        GatewayProc-->>Nemoclaw: [gateway preset list]
        rect rgba(100, 200, 100, 0.5)
        Nemoclaw->>Nemoclaw: compare local vs gateway → determine markers/suffixes
        end
    else Gateway Unreachable or parse failure
        GatewayProc-->>Nemoclaw: null
        rect rgba(200, 100, 100, 0.5)
        Nemoclaw->>User: Warning: showing local state only
        Nemoclaw->>Nemoclaw: use registry-only markers
        end
    end
    Nemoclaw->>User: Render preset list with markers and mismatch suffixes
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped to check the gateway's light,

comparing keys by moonlit night.
If things don't match, I twitch my nose,
and mark the gaps where mismatch shows.
A little sync, a joyful thump — policies align, then I will jump!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% 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
Title check ✅ Passed The title accurately summarizes the main change: adding gateway state cross-referencing to the policy-list command to detect desynchronization between CLI registry and gateway enforcement.
Linked Issues check ✅ Passed The PR fully addresses issue #2010 by implementing gateway state querying via getGatewayPresets(), comparing it with registry state, displaying discrepancy markers, and providing a fallback when the gateway is unreachable.
Out of Scope Changes check ✅ Passed All changes directly support the objective to reflect gateway state in policy-list output and detect desync; no unrelated modifications detected in policies.ts, nemoclaw.ts, or the new test file.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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.

@yimoj yimoj force-pushed the fix/2010-policy-state-inconsistency branch from 1720c42 to a88d15a Compare April 20, 2026 09:21
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)
test/repro-2010.test.js (1)

106-133: Consider using vi.spyOn for more idiomatic Vitest mocking.

The direct property mutation with try/finally restore works but vi.spyOn with mockImplementation would integrate better with Vitest's cleanup and provide clearer intent:

vi.spyOn(policies, "getAppliedPresets").mockReturnValue([]);
vi.spyOn(policies, "getGatewayPresets").mockReturnValue(["telegram"]);

Vitest automatically restores spies after each test when configured appropriately. Additionally, the logSpy at line 106 is created but only used to suppress output—if assertions on log calls aren't needed, vi.spyOn(console, "log").mockImplementation(() => {}) can be placed in a beforeEach hook.

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

In `@test/repro-2010.test.js` around lines 106 - 133, Replace direct mutation of
policies.getAppliedPresets and policies.getGatewayPresets with Vitest spies: use
vi.spyOn(policies, "getAppliedPresets").mockReturnValue([]) and
vi.spyOn(policies, "getGatewayPresets").mockReturnValue(["telegram"]) so the
test integrates with Vitest's spy lifecycle instead of manually saving/restoring
origGetApplied/origGetGateway; also replace the manual logSpy creation/restore
by using vi.spyOn(console, "log").mockImplementation(() => {}) in a per-test
setup (beforeEach) or keep a local spy but use vi.spyOn to ensure automatic
cleanup.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/repro-2010.test.js`:
- Around line 106-133: Replace direct mutation of policies.getAppliedPresets and
policies.getGatewayPresets with Vitest spies: use vi.spyOn(policies,
"getAppliedPresets").mockReturnValue([]) and vi.spyOn(policies,
"getGatewayPresets").mockReturnValue(["telegram"]) so the test integrates with
Vitest's spy lifecycle instead of manually saving/restoring
origGetApplied/origGetGateway; also replace the manual logSpy creation/restore
by using vi.spyOn(console, "log").mockImplementation(() => {}) in a per-test
setup (beforeEach) or keep a local spy but use vi.spyOn to ensure automatic
cleanup.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8f44cacc-c3e3-47bd-be5f-893f873dcf07

📥 Commits

Reviewing files that changed from the base of the PR and between 67db244 and 1720c42.

📒 Files selected for processing (3)
  • src/lib/policies.ts
  • src/nemoclaw.ts
  • test/repro-2010.test.js

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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/repro-2010.test.ts`:
- Around line 105-194: These tests currently only assert mocked return values
instead of exercising the real rendering logic in sandboxPolicyList; update each
test to either (A) call sandboxPolicyList() after setting the
policies.getAppliedPresets and policies.getGatewayPresets mocks and assert the
function's output contains the expected marker (●/○) and suffix text for
"telegram", or (B) extract the marker/suffix selection into a small helper
(e.g., determinePresetMarker(presetName, registryPresets, gatewayPresets)) and
replace the in-test boolean logic with direct unit tests of that helper that
assert the exact marker and suffix; reference policies.getAppliedPresets,
policies.getGatewayPresets, and sandboxPolicyList (or the new
determinePresetMarker) when locating where to change the tests.
- Around line 42-100: The tests currently reimplement parsing/key-matching
instead of exercising the exported function; update them to call
policies.getGatewayPresets() directly (stub any gateway response used by
getGatewayPresets, e.g., replace the call that fetches/parses the gateway YAML
or inject buildGatewayYaml(...) as the stubbed response) and assert its returned
array (or empty string) equals the expected presets for the given gateway;
reference policies.getGatewayPresets, buildGatewayYaml, and policies.loadPreset
when locating the test logic to replace so you remove the inline parsing/key
comparisons and instead assert the direct output of getGatewayPresets().
🪄 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: Pro Plus

Run ID: 53d2c3df-0aaa-4191-8c95-510d48cb7708

📥 Commits

Reviewing files that changed from the base of the PR and between 1720c42 and a88d15a.

📒 Files selected for processing (3)
  • src/lib/policies.ts
  • src/nemoclaw.ts
  • test/repro-2010.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/nemoclaw.ts
  • src/lib/policies.ts

Comment thread test/repro-2010.test.ts Outdated
Comment thread test/repro-2010.test.ts Outdated
yimoj added 3 commits April 20, 2026 17:36
… desync

policy-list previously read only from CLI-side registry metadata, not
from the gateway actual loaded policy. After rebuild, this caused
presets to show as not-applied while the gateway still enforced them.

Add getGatewayPresets() which queries the gateway via openshell policy
get, parses the response, and matches network_policies keys against
known preset definitions. Update sandboxPolicyList() to compare both
sources and display discrepancy markers when they disagree.

Returns null (gateway unreachable) vs [] (reachable, no matches) so
the UI falls back to registry-only display with a warning when the
gateway cannot be queried.

Closes NVIDIA#2010

Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
…feedback

- Add type annotations to fix implicit-any and null-check TS errors
- Replace in-process mocking with subprocess tests (CJS destructured
  imports make vi.spyOn ineffective for closed-over runCapture)
- getGatewayPresets tests now exercise the actual matching logic via
  exported helpers (parseCurrentPolicy, extractPresetEntries, listPresets)
- sandboxPolicyList tests now assert real CLI output via subprocess,
  verifying markers (●/○) and discrepancy suffixes end-to-end
- Run prettier to match project formatting
@yimoj yimoj force-pushed the fix/2010-policy-state-inconsistency branch from a88d15a to f7f2cb2 Compare April 20, 2026 09:37
@wscurran wscurran added bug Something isn't working fix labels Apr 20, 2026
@wscurran wscurran added the NemoClaw CLI Use this label to identify issues with the NemoClaw command-line interface (CLI). label Apr 20, 2026
Copy link
Copy Markdown
Contributor

@ericksoa ericksoa left a comment

Choose a reason for hiding this comment

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

LGTM. Clean approach to the registry/gateway desync problem.

  • null vs [] semantic for unreachable vs empty is the right design
  • All 4 display states (synced/desynced x both directions) plus unreachable fallback covered
  • CLI subprocess tests exercise the real rendering logic end-to-end
  • CI all green

One note: the getGatewayPresets matching-logic tests reimplement the algorithm in subprocess rather than calling the real function (due to CJS closure capture of runCapture). This is a known trade-off and acceptable — CodeRabbit flagged the same. The CLI output tests provide the real end-to-end coverage.

@ericksoa ericksoa merged commit 0007bd8 into NVIDIA:main Apr 20, 2026
16 checks passed
ericksoa pushed a commit that referenced this pull request Apr 21, 2026
## Summary

Catches up the user-facing reference and troubleshooting docs with the
CLI and policy behavior changes that landed in v0.0.21. Drafted via the
`nemoclaw-contributor-update-docs` skill against commits in
`v0.0.20..v0.0.21`, filtered through `docs/.docs-skip`.

## Changes

- **`docs/reference/commands.md`**
- `nemoclaw list`: session indicator (●) for connected sandboxes
(#2117).
- `nemoclaw <name> connect`: active-session note; auto-recovery from SSH
identity drift after a host reboot (#2117, #2064).
- `nemoclaw <name> status`: three-state Inference line (`healthy` /
`unreachable` / `not probed`) covering both local and remote providers;
new `Connected` line (#2002, #2117).
- `nemoclaw <name> destroy` and `rebuild`: active-session warning with
second confirm; rebuild reapplies policy presets to the recreated
sandbox (#2117, #2026).
- `nemoclaw <name> policy-add` and `policy-remove`: positional preset
argument and non-interactive flow via
`--yes`/`--force`/`NEMOCLAW_NON_INTERACTIVE=1` (#2070).
- `nemoclaw <name> policy-list`: registry-vs-gateway desync detection
(#2089).
- **`docs/reference/troubleshooting.md`**
- `Reconnect after a host reboot`: now reflects automatic stale
`known_hosts` pruning on `connect` (#2064).
- `Running multiple sandboxes simultaneously`: onboard's forward-port
collision guard (#2086).
- New section: `openclaw config set` or `unset` is blocked inside the
sandbox (#2081).
- **`docs/network-policy/customize-network-policy.md`**: non-interactive
`policy-add`/`policy-remove` form; preset preservation across rebuild
(#2070, #2026).
- **`docs/inference/use-local-inference.md`**: NIM section now covers
the NGC API key prompt with masked input and `docker login nvcr.io
--password-stdin` behavior (#2043).
- **Generated skills regenerated** to pick up the source changes
(`.agents/skills/nemoclaw-user-reference/references/{commands,troubleshooting}.md`,
plus minor heading-flow deltas elsewhere). The pre-commit `Regenerate
agent skills from docs` hook ran and confirmed source ↔ generated
parity.

Commits skipped per `docs/.docs-skip` or no doc impact: `bbbaa0fb`
(skip-features), `7cb482cb` (skip-features), `8dee23fd` (skip-terms),
plus the usual CI / test / refactor / install-plumbing churn.

## Type of Change

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

## Verification

- [x] `npx prek run --all-files` passes for the modified files (the one
failing test, `test/cli.test.ts > unknown command exits 1`, also fails
on `origin/main` and is unrelated to these markdown-only changes)
- [ ] `npm test` passes — skipped; same pre-existing CLI-dispatch test
failure unrelated to docs
- [ ] Tests added or updated for new or changed behavior — n/a, doc-only
- [x] No secrets, API keys, or credentials committed
- [x] Docs updated for user-facing behavior changes
- [ ] `make docs` builds without warnings (doc changes only) — not run
locally
- [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)
— n/a, no new pages

## AI Disclosure

- [x] AI-assisted — tool: Claude Code

---
Signed-off-by: Miyoung Choi <miyoungc@nvidia.com>

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

## Summary by CodeRabbit

* **New Features**
  * Multi-session SSH connections with concurrent session support.
* Three-state inference health reporting (healthy/unreachable/not
probed) across all providers.
  * Automatic SSH host key rotation detection and recovery.
  * Non-interactive policy preset management via positional arguments.
  * Session indicators in sandbox list view.

* **Bug Fixes**
  * Protected destructive operations with active-session warnings.
  * Policy presets now preserved during sandbox rebuilds.

* **Documentation**
  * NGC registry authentication requirements for container images.
  * Multi-sandbox onboarding and reconnection guidance.
  * Troubleshooting updates for port conflicts and SSH issues.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.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 fix NemoClaw CLI Use this label to identify issues with the NemoClaw command-line interface (CLI).

Projects

None yet

3 participants