Skip to content

refactor(runtime): add provider lifecycle and mutation parity - #7990

Merged
senthilr-nv merged 29 commits into
feat/buildless-managed-contract-hardeningfrom
feat/runtime-provider-lifecycle-parity
Jul 31, 2026
Merged

refactor(runtime): add provider lifecycle and mutation parity#7990
senthilr-nv merged 29 commits into
feat/buildless-managed-contract-hardeningfrom
feat/runtime-provider-lifecycle-parity

Conversation

@ericksoa

@ericksoa ericksoa commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Introduces the driver-neutral runtime-provider lifecycle and mutation contract used by the incremental buildless/runtime stack, and closes the destructive-cleanup authority boundary identified during exact-head review. Destroy, rebuild, and snapshot force-restore must now prove provider and workload cleanup authority through a side-effect-free provider plan before deleting or stopping anything.

Production selection remains limited to the existing Docker and Kubernetes providers. This slice does not activate another runtime or expand supported lifecycle platforms.

Related Issue

Part of #7744

Changes

  • Add one versioned, immutable provider bundle registry covering plan, capability, preflight, gateway, workload, lifecycle, mutation-authority, bootstrap, snapshot, recovery, cleanup, and container-engine surfaces.
  • Route sandbox registration, start, provider-owned post-start verification, stop, inference-set authority, live destroy preparation/deletion, and owned-workload cleanup through the selected bundle.
  • Add planOwnedWorkloadCleanup to the cleanup contract and require every supported provider to prove cleanup intent without side effects before a destructive action.
  • Apply one complete authority check before destructive side effects in normal destroy, rebuild, and snapshot force-restore.
  • Recheck rebuild authority at the exact delete edge after MCP preparation; on failure, restore MCP attachment, relock shields, retain ownership state, and skip deletion.
  • Keep actual cleanup independently fail closed, preserving a residual post-delete guard for raw-writer or TOCTOU changes outside NemoClaw's lifecycle lock.
  • Preserve shared managed images and rows with no owned image without turning a missing or legacy receipt into a deletion candidate.
  • Replace nonexistent "repair the receipt" guidance with the real nemoclaw <sandbox> doctor --json diagnostic path. Operators must restore trusted ownership metadata or resolve the runtime conflict and must not rewrite a receipt to match a mutable sandbox name.
  • Make the Kubernetes compatibility boundary explicit: the shipped Kubernetes gateway path's legacy per-sandbox image remains owned by the host Docker engine until a separately registered CRI-native provider exists.
  • Preserve the existing Kubernetes lifecycle gate. Its bundle remains lifecycle.supported: false, and this PR does not claim Kubernetes lifecycle activation.
  • Add a socket-free MXC-style contract provider and exercise OpenClaw, Hermes, and LangChain Deep Agents Code without Podman- or MXC-specific central switches.
  • Fail closed for unknown provider identities, unsupported mutation surfaces, malformed ownership receipts, and unresolved cleanup authority.

Direct connect, status, logs, authenticated reconciliation, and durable crash recovery remain owned by later slices. No future provider is production-selectable or advertised by this PR.

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 provider contract remains inert for future providers, production selection is unchanged, and no supported CLI/runtime behavior is advertised or activated.
  • 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-head review covers registry identity binding, provider-owned lifecycle verification, mutation authority, all-agent MXC-style action proof, pre-delete and exact-edge cleanup authority, rollback, and source architecture.
  • 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 45-file diff (+4,073/-508) tightens an inert internal provider and destructive-authority contract and replaces misleading failure text with an existing diagnostic command. It does not activate or advertise a new provider, platform, or runtime.
  • Agent: Codex Desktop

DGX Station Hardware Evidence

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

Verification

  • Exact locally validated head/base: 75730cf09bf1a1aa901cc3b275052250f8e7d85d / 4788d287b8672be1b44999e78e094b2221303bd1
  • Review budget: 45 files, +4,073/-508; five files above the soft file guide to apply and prove one complete cross-cutting destructive-authority boundary, while remaining within the 2–5k line guide.
  • Stable exact-slice patch ID: eef1fddf8138d6e8a3ef4efb443aa2adc9f74fe3.
  • PR description includes a Signed-off-by: line and every new commit contains an SSH signature and DCO trailer.
  • npm run validate:pr passed on the exact clean head.
  • 320 focused changed-surface provider, lifecycle, snapshot, and image-cleanup tests passed on the exact head; CLI typecheck, repository architecture, source-shape and test-size budgets, Biome, secret scanning, and git diff --check passed.
  • Unknown-provider and mismatched-receipt tests prove snapshot force-restore performs no NIM stop, OpenShell delete, provider deletion, shields cleanup, replacement creation, or registry registration.
  • Rebuild tests prove authority is checked before MCP preparation and again at the exact delete edge; a changed receipt restores MCP attachment, relocks shields, and performs no sandbox delete.
  • Cleanup-contract tests prove planning is side-effect free, registration rejects providers without it, and actual cleanup revalidates authority before mutation.
  • Error-path tests prove ownership state is retained, the command exits nonzero, doctor --json is named, unsafe receipt rewriting is rejected, and no false success is emitted.
  • Kubernetes cleanup and lifecycle findings are dispositioned against the shipped compatibility contract: legacy images remain host-Docker-owned, while Kubernetes lifecycle remains explicitly unsupported and fail closed.
  • CodeRabbit's inline findings, including inert planner-state and boolean-removal fixtures, are resolved; exact-head incremental review is active.
  • Applicable broad gate passed — exact-head required CI, advisors, CodeRabbit, multiarch, and protected E2E are the broad remote gates.

Stack

  • Base: PR3.5c test(e2e): add cross-runtime execution foundation #7988 branch feat/buildless-runtime-e2e-foundation at 4788d287b8672be1b44999e78e094b2221303bd1.
  • This slice: PR3.6 branch feat/runtime-provider-lifecycle-parity at 75730cf09bf1a1aa901cc3b275052250f8e7d85d.
  • Later slices own snapshot/clone/rebuild/restore parity, transactional bootstrap, authenticated reconciliation, durable recovery, and final all-agent multiarch activation.
  • Buildless support remains disabled until every supported agent and required qualification gate passes.

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

Summary by CodeRabbit

  • New Features
    • Added provider-neutral runtime support for Docker and Kubernetes environments.
    • Sandbox start, stop, recovery, diagnostics, inference updates, snapshots, and rebuilds now use the selected runtime provider.
    • Added workload ownership records to improve managed-image tracking and portability.
  • Bug Fixes
    • Destructive cleanup now fails safely when ownership or runtime authority cannot be verified.
    • Added recovery guidance while preserving registry and session state after blocked cleanup.
    • Improved diagnostics for unsupported or unregistered runtime providers.
  • Tests
    • Expanded coverage for lifecycle operations, cleanup safeguards, workload validation, and provider portability.

ericksoa added 14 commits July 30, 2026 23:11
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
(cherry picked from commit f99197b)
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
(cherry picked from commit 7436f35)
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
(cherry picked from commit 98f17dbb8409f4320763c4ad653bcda6b554e601)
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
@ericksoa ericksoa added 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 labels Jul 31, 2026
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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

The pull request adds provider-neutral runtime contracts, Docker and Kubernetes bundles, validated workload receipts, and provider-driven sandbox lifecycle, diagnosis, cleanup, rebuild, snapshot restore, and inference-set authority checks.

Changes

Runtime provider migration

Layer / File(s) Summary
Provider contracts and registry validation
src/lib/onboard/runtime-provider/*
Adds provider contracts, bundle validation, immutable registries, provider selection, authority checks, runtime receipts, and Docker/Kubernetes bundles.
Workload receipts and durable registry state
src/lib/state/registry/*, src/lib/onboard/workload/runtime.ts
Adds typed workload receipts with validation, cloning, persistence, and provider-based capability resolution.
Sandbox registration and onboarding integration
src/lib/onboard/sandbox-registration.ts, src/lib/onboard/*test.ts
Validates workload ownership and provider authority before registry writes.
Sandbox lifecycle, diagnosis, and cleanup
src/lib/actions/sandbox/*, src/lib/tunnel/*, test/image-cleanup.test.ts
Delegates lifecycle, diagnosis, gateway shutdown, destruction, rebuild, snapshot restore, and image cleanup to runtime providers.
Mutation authority and architectural validation
src/lib/actions/inference-set*, test/helpers/runtime-provider-bundle.ts, test/runtime-provider-source-shape.test.ts
Adds pre-lock and in-lock inference authority checks, in-memory provider coverage, and provider-neutral source-boundary tests.

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

Possibly related PRs

  • NVIDIA/NemoClaw#7772: The workload capability logic replaces the managed-image profile registry introduced by this PR.
  • NVIDIA/NemoClaw#7369: Both changes modify rebuild deletion sequencing and preserve recovery state when cleanup is not confirmed.
  • NVIDIA/NemoClaw#7879: Both changes modify sandbox startup, but this PR delegates startup to runtime-provider lifecycle bundles.

Suggested labels: area: architecture, area: sandbox, refactor

Suggested reviewers: laitingsheng, sandl99, cv

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.12% 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
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 summarizes the main change: adding provider lifecycle handling and mutation authority parity through a runtime refactor.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/runtime-provider-lifecycle-parity

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

@ericksoa

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-code-quality

github-code-quality Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in commit 75730cf in the feat/runtime-provide... branch remains at 96%, unchanged from commit 94c0b07 in the feat/buildless-manag... branch.

TypeScript / code-coverage/cli

The overall coverage in commit 75730cf in the feat/runtime-provide... branch remains at 81%, unchanged from commit 7eb0369 in the feat/buildless-manag... branch.

Show a code coverage summary of the most impacted files.
File feat/buildless-manag... 7eb0369 feat/runtime-provide... 75730cf +/-
src/lib/onboard/docker-cdi.ts 80% 70% -10%
src/lib/onboard...mage-runtime.ts 52% 50% -2%
src/lib/onboard...box-prebuild.ts 74% 92% +18%
src/lib/onboard...-transaction.ts 0% 78% +78%
src/lib/onboard...p/root-apply.ts 0% 84% +84%
src/lib/onboard...shared-state.ts 0% 87% +87%
src/lib/state/r...try/workload.ts 0% 88% +88%
src/lib/onboard...vider/docker.ts 0% 92% +92%
src/lib/onboard...r-root-apply.ts 0% 94% +94%
src/lib/onboard...der/registry.ts 0% 97% +97%

Updated July 31, 2026 15:12 UTC

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.

Actionable comments posted: 4

🧹 Nitpick comments (11)
test/runtime-provider-source-shape.test.ts (1)

24-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Key the guarded sources by path instead of array position.

nonSnapshotActions, centralConsumers[4], and providerContract[1] depend on the literal order of the arrays above. If a contributor inserts or reorders a path, slice(0, 6) silently stops covering stop.ts and the provider-neutrality guard keeps passing with reduced coverage. Read the sources into a keyed record so each assertion names its file.

♻️ Proposed keyed lookup
-    const centralConsumers = [
-      readFileSync(join(repoRoot, "src/lib/actions/inference-set.ts"), "utf8"),
-      readFileSync(join(repoRoot, "src/lib/actions/sandbox/destroy-execution.ts"), "utf8"),
-      readFileSync(join(repoRoot, "src/lib/actions/sandbox/destroy.ts"), "utf8"),
-      readFileSync(join(repoRoot, "src/lib/actions/sandbox/runtime/lifecycle-runtime.ts"), "utf8"),
-      readFileSync(join(repoRoot, "src/lib/actions/sandbox/start.ts"), "utf8"),
-      readFileSync(join(repoRoot, "src/lib/actions/sandbox/stop.ts"), "utf8"),
-      readFileSync(join(repoRoot, "src/lib/onboard/compute/plan.ts"), "utf8"),
-      readFileSync(join(repoRoot, "src/lib/onboard/sandbox-registration.ts"), "utf8"),
-      readFileSync(join(repoRoot, "src/lib/onboard/workload/runtime.ts"), "utf8"),
-    ];
-    const nonSnapshotActions = centralConsumers.slice(0, 6);
-    const providerContract = [
-      readFileSync(join(repoRoot, "src/lib/onboard/runtime-provider/contract.ts"), "utf8"),
-      readFileSync(join(repoRoot, "src/lib/onboard/runtime-provider/current.ts"), "utf8"),
-      readFileSync(join(repoRoot, "src/lib/onboard/runtime-provider/docker.ts"), "utf8"),
-      readFileSync(join(repoRoot, "src/lib/onboard/runtime-provider/registry.ts"), "utf8"),
-    ];
+    const read = (relativePath: string) => readFileSync(join(repoRoot, relativePath), "utf8");
+    const driverNeutralActions = {
+      "actions/inference-set.ts": read("src/lib/actions/inference-set.ts"),
+      "actions/sandbox/destroy-execution.ts": read("src/lib/actions/sandbox/destroy-execution.ts"),
+      "actions/sandbox/destroy.ts": read("src/lib/actions/sandbox/destroy.ts"),
+      "actions/sandbox/runtime/lifecycle-runtime.ts": read(
+        "src/lib/actions/sandbox/runtime/lifecycle-runtime.ts",
+      ),
+      "actions/sandbox/start.ts": read("src/lib/actions/sandbox/start.ts"),
+      "actions/sandbox/stop.ts": read("src/lib/actions/sandbox/stop.ts"),
+    };
+    const onboardConsumers = {
+      "onboard/compute/plan.ts": read("src/lib/onboard/compute/plan.ts"),
+      "onboard/sandbox-registration.ts": read("src/lib/onboard/sandbox-registration.ts"),
+      "onboard/workload/runtime.ts": read("src/lib/onboard/workload/runtime.ts"),
+    };
+    const providerContract = {
+      contract: read("src/lib/onboard/runtime-provider/contract.ts"),
+      current: read("src/lib/onboard/runtime-provider/current.ts"),
+      docker: read("src/lib/onboard/runtime-provider/docker.ts"),
+      registry: read("src/lib/onboard/runtime-provider/registry.ts"),
+    };
 
-    for (const source of nonSnapshotActions) {
+    for (const source of Object.values(driverNeutralActions)) {
       expect(source).not.toMatch(/\b(?:docker|podman)\b/iu);
       expect(source).not.toMatch(/(?:adapters\/docker|docker-driver-sandbox-recovery)/u);
     }
-    for (const source of centralConsumers) {
+    for (const source of [
+      ...Object.values(driverNeutralActions),
+      ...Object.values(onboardConsumers),
+    ]) {
       expect(source).not.toMatch(/\b(?:openshellDriver|driverName)\s*={2,3}\s*["'][^"']+["']/u);
       expect(source).not.toMatch(/switch\s*\([^)]*\b(?:openshellDriver|driverName)\b[^)]*\)/u);
     }
-    expect(centralConsumers[4]).toMatch(/resolved\.lifecycle\.verifyStarted\(/u);
-    expect(providerContract.join("\n")).not.toMatch(/managed-bootstrap/u);
-    expect(providerContract[1]).not.toMatch(/\b(?:podman|mxc)\b/iu);
+    expect(driverNeutralActions["actions/sandbox/start.ts"]).toMatch(
+      /resolved\.lifecycle\.verifyStarted\(/u,
+    );
+    expect(Object.values(providerContract).join("\n")).not.toMatch(/managed-bootstrap/u);
+    expect(providerContract.current).not.toMatch(/\b(?:podman|mxc)\b/iu);
🤖 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/runtime-provider-source-shape.test.ts` around lines 24 - 42, Replace
positional source selection in the test with a path-keyed record for the
runtime-provider files and central consumers. Update the assertions using
nonSnapshotActions, centralConsumers[4], and providerContract[1] to retrieve
sources by their explicit file keys, preserving coverage for stop.ts regardless
of array order or insertions.
src/lib/actions/sandbox/runtime/lifecycle-runtime.ts (1)

21-30: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Carry the resolved sandbox on the ok: true branch.

SandboxLifecycleProviderResolution does not relate ok: true to a non-null sandbox. The caller must therefore re-assert the value it already passed in. src/lib/actions/sandbox/stop.ts line 64 uses sandbox: sandbox! for exactly this reason. A future edit that changes the null check in this function would not be caught at that call site, because the assertion suppresses the error.

♻️ Proposed resolution shape
 export type SandboxLifecycleProviderResolution =
   | {
       readonly ok: true;
+      readonly sandbox: SandboxEntry;
       readonly bundle: RuntimeProviderBundle;
       readonly lifecycle: Extract<RuntimeProviderBundle["lifecycle"], { readonly supported: true }>;
     }

Then return it at line 83:

-  return { ok: true, bundle, lifecycle: bundle.lifecycle };
+  return { ok: true, sandbox, bundle, lifecycle: bundle.lifecycle };

src/lib/actions/sandbox/stop.ts then uses sandbox: resolved.sandbox and drops the !.

🤖 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/actions/sandbox/runtime/lifecycle-runtime.ts` around lines 21 - 30,
Update SandboxLifecycleProviderResolution so its ok: true branch carries a
non-null sandbox value, return that sandbox from the resolution function after
its null check, and update the caller in stop.ts to use resolved.sandbox instead
of sandbox!.
src/lib/tunnel/sandbox-gateway-stop.test.ts (1)

130-133: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assert the script constant instead of a substring of it.

expect.stringContaining("find_gateway_pids") couples the assertion to a shell function name inside GATEWAY_STOP_SCRIPT. A rename of that internal function breaks the test without any behavior change. Compare against the exported constant.

♻️ Proposed assertion
       expect.objectContaining({
-        input: expect.stringContaining("find_gateway_pids"),
+        input: GATEWAY_STOP_SCRIPT,
         timeout: 20000,
       }),

Import the constant from src/lib/tunnel/gateway-stop-script.ts.

As per path instructions: "Prefer observable outcomes through the public boundary over source-text, private-shape, or mock-call assertions."

🤖 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/tunnel/sandbox-gateway-stop.test.ts` around lines 130 - 133, Update
the test assertion around the gateway stop input to compare against the exported
GATEWAY_STOP_SCRIPT constant rather than using expect.stringContaining with the
internal find_gateway_pids function name. Import and reuse that constant from
gateway-stop-script.ts while preserving the existing timeout assertion.

Source: Path instructions

src/lib/tunnel/sandbox-gateway-stop.ts (1)

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

Reuse the contract transport type instead of restating the union.

src/lib/onboard/runtime-provider/contract.ts line 181 declares channelStopTransport: "docker-kubectl-first" | "openshell" on the supported lifecycle surface. src/lib/actions/sandbox/stop.ts line 77 forwards that exact value into this dependency. Two independent declarations of one value can drift. If the contract gains a third transport, this file compiles and silently applies the kubectl-first branch.

Export a named type from the contract and reference it here.

♻️ Proposed shared type

In src/lib/onboard/runtime-provider/contract.ts:

+export type RuntimeProviderChannelStopTransport = "docker-kubectl-first" | "openshell";

Then in this file:

-  channelStopTransport?: "docker-kubectl-first" | "openshell";
+  channelStopTransport?: RuntimeProviderChannelStopTransport;
🤖 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/tunnel/sandbox-gateway-stop.ts` at line 27, Export a named type for
the channelStopTransport value from the runtime-provider contract, then update
the channelStopTransport declaration in the sandbox gateway stop module to
reference that shared type instead of repeating the string union. Preserve the
existing optional property shape and values.
src/lib/actions/sandbox/destroy-flow.test.ts (1)

107-118: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the provider-authority reason, not only exit(1).

The title claims the refusal comes from unknown runtime authority. The body proves only that destruction exited with code 1, skipped sandbox delete, and kept the registry row. Other refusal paths in this flow also exit with 1, for example the force/MCP path at line 194. A regression that rejects "unknown-runtime" for an unrelated reason keeps this test green.

destroy-execution.ts surfaces the RuntimeProviderSelectionError message as deleteOutput, so the reason is observable at the boundary.

💚 Proposed additional assertion
     expect(harness.removeSandboxSpy).not.toHaveBeenCalled();
+    const errorOutput = harness.errorSpy.mock.calls.map((call) => String(call[0])).join("\n");
+    expect(errorOutput).toContain("unknown-runtime");
+    expect(errorOutput).toContain("is not registered for this operation");
   });

As per path instructions: "Flag copied production algorithms, broad mocks that bypass the behavior under test, and conditionals that make a test pass without exercising its claim."

🤖 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/actions/sandbox/destroy-flow.test.ts` around lines 107 - 118,
Strengthen the test “preserves provider and registry ownership when runtime
authority is unknown” by asserting that the rejected error or surfaced
deleteOutput contains the unknown-runtime provider-authority reason, not just
“process.exit(1)”. Keep the existing assertions that no sandbox delete occurs
and the registry row remains, using the RuntimeProviderSelectionError message
exposed by destroy-execution.ts.

Source: Path instructions

src/lib/onboard/runtime-provider/registry.ts (1)

240-360: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Split validateSupportedSurfaceSchemas into per-surface validators.

The function validates twelve surfaces plus two cross-surface agreement rules in one body. Each new surface adds another branch to the same function, and a reader must scan the whole body to find the rule for one surface.

Extract one small validator per surface, then keep the cross-surface agreement checks (lines 345-359) in this function. validateWorkloadProfile already shows the pattern.

As per coding guidelines: "Prefix intentionally unused variables with _ and keep function complexity low."

🤖 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/runtime-provider/registry.ts` around lines 240 - 360, Split
validateSupportedSurfaceSchemas into dedicated validators for each surface,
moving the existing plan, capabilities, preflightDoctor, gateway, workload,
lifecycle, mutationAuthority, bootstrap, snapshot, recovery, cleanup, and
containerEngine checks into small named functions. Have the main function invoke
those validators and retain only the two cross-surface agreement checks there.
Keep validateWorkloadProfile’s existing pattern, and prefix any intentionally
unused parameters with an underscore while keeping each validator simple.

Source: Coding guidelines

src/lib/onboard/runtime-provider/contract.ts (1)

134-136: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Align detachProviders arity with the caller.

The contract declares detachProviders(sandboxName: string). The known caller in src/lib/actions/sandbox/destroy-execution.ts supplies a zero-argument closure that already captures sandboxName:

const detachProviders = (): DetachSandboxProvidersResult =>
  runSandboxProviderPreDeleteCleanup(sandboxName, { runOpenshell, redact });

TypeScript accepts this, so a provider that calls operations.detachProviders(otherName) gets cleanup for the captured name instead. Either drop the parameter from the contract or make the caller forward the argument. A zero-argument shape matches the current single consumer.

♻️ Proposed contract change
 export interface RuntimeProviderCleanupOperations {
-  readonly detachProviders: (sandboxName: string) => RuntimeProviderProviderDetachResult;
+  readonly detachProviders: () => RuntimeProviderProviderDetachResult;
 }
🤖 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/runtime-provider/contract.ts` around lines 134 - 136, Update
RuntimeProviderCleanupOperations.detachProviders to a zero-argument function
matching the closure created by the destroy execution caller, which already
captures sandboxName. Remove the sandboxName parameter from the contract and
preserve the existing cleanup behavior.
src/lib/onboard/compute/plan.ts (1)

4-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use explicit type-only imports in the three runtime-provider consumers. access.ts exports these contracts with export type, and all three consumers use them only as types. The current CommonJS configuration elides these imports, so this is not a current build failure. Add type modifiers to make the import contract explicit and prevent future compiler changes from introducing errors.

🤖 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/compute/plan.ts` around lines 4 - 12, Update the
runtime-provider imports in src/lib/onboard/compute/plan.ts lines 4-12,
src/lib/onboard/workload/runtime.ts lines 6-12, and
src/lib/actions/sandbox/runtime/lifecycle-runtime.ts lines 4-11 to mark every
imported type-only contract with an explicit type modifier, while leaving
runtime imports such as resolveRuntimeProviderBundle,
resolveCurrentRuntimeProviderBundle, runtimeProviderContainerEngineIdentity, and
RuntimeProviderGatewayLauncher unchanged.
src/lib/onboard/runtime-provider/docker.ts (1)

357-437: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider moving the Kubernetes bundle out of docker.ts.

createKubernetesRuntimeProviderBundle lives in a module named docker.ts, accepts DockerRuntimeProviderDependencies, and reuses inspectDockerHost and removeOwnedDockerWorkload. The shared host inspection also reports the label "Docker daemon" for the Kubernetes provider, which will surface as a Docker-named doctor row for Kubernetes sandboxes. A separate kubernetes.ts module plus a shared container-engine helper module would make provider ownership explicit and keep the doctor label provider-accurate. This is a structural cleanup, not a behavior fix, so it can be deferred to the next slice if the current label is intended.

🤖 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/runtime-provider/docker.ts` around lines 357 - 437, Defer
this structural cleanup unless the current Docker-labeled doctor row is not
acceptable: otherwise move createKubernetesRuntimeProviderBundle out of
docker.ts into a dedicated kubernetes.ts module, extract shared host-inspection
and workload-cleanup logic into a container-engine helper, and update the
Kubernetes dependencies and doctor labeling so it no longer reports “Docker
daemon” under Kubernetes.
src/lib/actions/sandbox/destroy-execution.ts (1)

234-240: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Align detachProviders with its contract.

RuntimeProviderCleanupOperations accepts sandboxName, but destroy-execution.ts ignores it and always uses the captured name. Accept the argument and pass it to runSandboxProviderPreDeleteCleanup, or remove the parameter from the contract and its callers.

🤖 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/actions/sandbox/destroy-execution.ts` around lines 234 - 240, Update
the detachProviders callback in the destroy execution flow to accept the
sandboxName argument required by RuntimeProviderCleanupOperations, and pass that
argument to runSandboxProviderPreDeleteCleanup instead of always using the
captured name. Preserve the existing runOpenshell and redact options and all
detachOutcome branching.
test/image-cleanup.test.ts (1)

109-137: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add coverage for the authority-unproven blocking path.

Add tests for both cleanup helpers. Assert that they return false or null and do not remove the registry entry when cleanup returns { status: "skipped", reason: "authority-unproven" }.

🤖 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/image-cleanup.test.ts` around lines 109 - 137, Add test coverage for the
authority-unproven cleanup path in removeSandboxImage and
removeSandboxRegistryEntry. Mock cleanup to return { status: "skipped", reason:
"authority-unproven" }, then assert removeSandboxImage returns the skipped
result without invoking image removal and removeSandboxRegistryEntry returns
false or null without invoking the registry-removal callback.
🤖 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/actions/sandbox/destroy.ts`:
- Around line 270-273: The authority-unproven image-cleanup outcome is currently
silent and reported as success. In src/lib/actions/sandbox/destroy.ts lines
270-273, propagate the skipped result through destroySandboxUnlocked, warn with
a recovery hint when removed is false around line 461, and suppress the success
message around line 500; in lines 244-250, log the provider id and caught
RuntimeProviderSelectionError message through deps.warn before returning the
skipped result. In test/image-cleanup.test.ts lines 109-137, add coverage
asserting authority-unproven makes removeSandboxRegistryEntry return false
without calling removeSandbox.

In `@src/lib/onboard/runtime-provider/runtime-provider-contract.test.ts`:
- Around line 582-587: Update the runOpenshell mock to remove the argument-based
if conditional and record the sandbox deletion event with a direct linear
expression before returning the existing response object.

In `@src/lib/onboard/workload/runtime.ts`:
- Around line 28-44: Remove the unused projectRuntimeProviderWorkloadProfiles
function, CURRENT_MANAGED_IMAGE_RUNTIME_PROFILES constant, and
ManagedImageRuntimeProfile-related type aliases. In cloneRuntimeSupport, replace
ManagedImageRuntimeSupport with RuntimeProviderManagedImageSupport while
preserving the existing cloning behavior.

In `@src/lib/state/registry/workload.ts`:
- Around line 21-22: Update the constants used by workload registry profile
validation to import and reuse MANAGED_STARTUP_PROFILE_MAX_BYTES and
MANAGED_STARTUP_PROFILE_MAX_ENCODED_BYTES from managed-startup/profile.ts,
removing the local MAX_PROFILE_BYTES and MAX_PROFILE_ENCODED_BYTES definitions.

---

Nitpick comments:
In `@src/lib/actions/sandbox/destroy-execution.ts`:
- Around line 234-240: Update the detachProviders callback in the destroy
execution flow to accept the sandboxName argument required by
RuntimeProviderCleanupOperations, and pass that argument to
runSandboxProviderPreDeleteCleanup instead of always using the captured name.
Preserve the existing runOpenshell and redact options and all detachOutcome
branching.

In `@src/lib/actions/sandbox/destroy-flow.test.ts`:
- Around line 107-118: Strengthen the test “preserves provider and registry
ownership when runtime authority is unknown” by asserting that the rejected
error or surfaced deleteOutput contains the unknown-runtime provider-authority
reason, not just “process.exit(1)”. Keep the existing assertions that no sandbox
delete occurs and the registry row remains, using the
RuntimeProviderSelectionError message exposed by destroy-execution.ts.

In `@src/lib/actions/sandbox/runtime/lifecycle-runtime.ts`:
- Around line 21-30: Update SandboxLifecycleProviderResolution so its ok: true
branch carries a non-null sandbox value, return that sandbox from the resolution
function after its null check, and update the caller in stop.ts to use
resolved.sandbox instead of sandbox!.

In `@src/lib/onboard/compute/plan.ts`:
- Around line 4-12: Update the runtime-provider imports in
src/lib/onboard/compute/plan.ts lines 4-12, src/lib/onboard/workload/runtime.ts
lines 6-12, and src/lib/actions/sandbox/runtime/lifecycle-runtime.ts lines 4-11
to mark every imported type-only contract with an explicit type modifier, while
leaving runtime imports such as resolveRuntimeProviderBundle,
resolveCurrentRuntimeProviderBundle, runtimeProviderContainerEngineIdentity, and
RuntimeProviderGatewayLauncher unchanged.

In `@src/lib/onboard/runtime-provider/contract.ts`:
- Around line 134-136: Update RuntimeProviderCleanupOperations.detachProviders
to a zero-argument function matching the closure created by the destroy
execution caller, which already captures sandboxName. Remove the sandboxName
parameter from the contract and preserve the existing cleanup behavior.

In `@src/lib/onboard/runtime-provider/docker.ts`:
- Around line 357-437: Defer this structural cleanup unless the current
Docker-labeled doctor row is not acceptable: otherwise move
createKubernetesRuntimeProviderBundle out of docker.ts into a dedicated
kubernetes.ts module, extract shared host-inspection and workload-cleanup logic
into a container-engine helper, and update the Kubernetes dependencies and
doctor labeling so it no longer reports “Docker daemon” under Kubernetes.

In `@src/lib/onboard/runtime-provider/registry.ts`:
- Around line 240-360: Split validateSupportedSurfaceSchemas into dedicated
validators for each surface, moving the existing plan, capabilities,
preflightDoctor, gateway, workload, lifecycle, mutationAuthority, bootstrap,
snapshot, recovery, cleanup, and containerEngine checks into small named
functions. Have the main function invoke those validators and retain only the
two cross-surface agreement checks there. Keep validateWorkloadProfile’s
existing pattern, and prefix any intentionally unused parameters with an
underscore while keeping each validator simple.

In `@src/lib/tunnel/sandbox-gateway-stop.test.ts`:
- Around line 130-133: Update the test assertion around the gateway stop input
to compare against the exported GATEWAY_STOP_SCRIPT constant rather than using
expect.stringContaining with the internal find_gateway_pids function name.
Import and reuse that constant from gateway-stop-script.ts while preserving the
existing timeout assertion.

In `@src/lib/tunnel/sandbox-gateway-stop.ts`:
- Line 27: Export a named type for the channelStopTransport value from the
runtime-provider contract, then update the channelStopTransport declaration in
the sandbox gateway stop module to reference that shared type instead of
repeating the string union. Preserve the existing optional property shape and
values.

In `@test/image-cleanup.test.ts`:
- Around line 109-137: Add test coverage for the authority-unproven cleanup path
in removeSandboxImage and removeSandboxRegistryEntry. Mock cleanup to return {
status: "skipped", reason: "authority-unproven" }, then assert
removeSandboxImage returns the skipped result without invoking image removal and
removeSandboxRegistryEntry returns false or null without invoking the
registry-removal callback.

In `@test/runtime-provider-source-shape.test.ts`:
- Around line 24-42: Replace positional source selection in the test with a
path-keyed record for the runtime-provider files and central consumers. Update
the assertions using nonSnapshotActions, centralConsumers[4], and
providerContract[1] to retrieve sources by their explicit file keys, preserving
coverage for stop.ts regardless of array order or insertions.
🪄 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: 48e5b907-df46-403d-b417-9609cccb5309

📥 Commits

Reviewing files that changed from the base of the PR and between abf45b3 and 534773b.

📒 Files selected for processing (37)
  • ci/source-architecture-budget.json
  • ci/source-shape-test-budget.json
  • src/lib/actions/inference-set-failure-handling.test.ts
  • src/lib/actions/inference-set-provider.ts
  • src/lib/actions/inference-set.ts
  • src/lib/actions/sandbox/destroy-execution.ts
  • src/lib/actions/sandbox/destroy-flow.test.ts
  • src/lib/actions/sandbox/destroy.ts
  • src/lib/actions/sandbox/doctor-system-checks.ts
  • src/lib/actions/sandbox/doctor.ts
  • src/lib/actions/sandbox/runtime/lifecycle-runtime.ts
  • src/lib/actions/sandbox/start.test.ts
  • src/lib/actions/sandbox/start.ts
  • src/lib/actions/sandbox/stop.test.ts
  • src/lib/actions/sandbox/stop.ts
  • src/lib/onboard/compute/plan.ts
  • src/lib/onboard/runtime-provider/access.ts
  • src/lib/onboard/runtime-provider/contract.ts
  • src/lib/onboard/runtime-provider/current.ts
  • src/lib/onboard/runtime-provider/docker.ts
  • src/lib/onboard/runtime-provider/registry.ts
  • src/lib/onboard/runtime-provider/runtime-provider-contract.test.ts
  • src/lib/onboard/sandbox-registration.test.ts
  • src/lib/onboard/sandbox-registration.ts
  • src/lib/onboard/sandbox-workload-preparation.test.ts
  • src/lib/onboard/sandbox-workload-runtime.test.ts
  • src/lib/onboard/workload/runtime.ts
  • src/lib/state/registry.ts
  • src/lib/state/registry/persistence.ts
  • src/lib/state/registry/types.ts
  • src/lib/state/registry/workload.ts
  • src/lib/tunnel/sandbox-gateway-stop.test.ts
  • src/lib/tunnel/sandbox-gateway-stop.ts
  • test/helpers/destroy-flow-test-harness.ts
  • test/helpers/runtime-provider-bundle.ts
  • test/image-cleanup.test.ts
  • test/runtime-provider-source-shape.test.ts

Comment thread src/lib/actions/sandbox/destroy.ts
Comment thread src/lib/onboard/runtime-provider/runtime-provider-contract.test.ts Outdated
Comment thread src/lib/onboard/workload/runtime.ts Outdated
Comment thread src/lib/state/registry/workload.ts Outdated
@ericksoa

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — No blocking findings reported

Advisor assessment: No blocking advisor findings reported
Next action: No advisor follow-up needed.
Findings: 0 blockers · 0 warnings · 0 suggestions

Model lanes

  • GPT-5.6 Terra (primary): Completed · high confidence · 0 blockers · 0 warnings · 0 suggestions
  • Nemotron 3 Ultra (second opinion): Completed · high confidence · 0 blockers · 0 warnings · 0 suggestions
  • Model comparison: normalized findings match; normalized E2E selections differ; severity counts match.

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: cloud-inference, cloud-onboard, security-posture, channels-add-remove, channels-stop-start, inference-routing, network-policy, onboard-repair, onboard-resume, rebuild-openclaw, state-backup-restore

1 optional E2E recommendation
  • ubuntu-repo-cloud-openclaw

Workflow run details

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

ericksoa added 3 commits July 31, 2026 01:31
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 31, 2026

Copy link
Copy Markdown
Contributor

@ericksoa I will review the changes on head d73f13a08, with attention to the doctor --json unknown-provider regression and its fail-closed diagnostic behavior.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Exact-head follow-up at 8f8bcd5 addresses the remaining advisor findings: the cleanup-authority recovery boundary is documented with its source, repair constraint, regression proof, and removal condition; a repaired matching workload receipt now proves registry/session retirement; and table-driven unknown-provider start/stop tests prove failure before Docker, channel, dashboard, or gateway side effects. Focused suites and npm run validate:pr pass on this head.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='8f8bcd5a18ae270538015e7f3772adf3a39d52fe'
git rev-parse --verify "${target}^{commit}"
git show --no-ext-diff --format='commit %H%nparent %P%nsubject %s' --no-patch "$target"
git diff --no-ext-diff --name-status "${target}^" "$target"
git diff --no-ext-diff --stat "${target}^" "$target"

echo '--- TypeScript outlines for changed source/test files ---'
git diff --no-ext-diff --name-only "${target}^" "$target" \
  | grep -E '\.(ts|tsx)$' \
  | while IFS= read -r file; do
      echo "### $file"
      ast-grep outline "$file" --items all --type function,class,interface,type 2>/dev/null || true
    done

Length of output: 1875


🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='8f8bcd5a18ae270538015e7f3772adf3a39d52fe'
parent="${target}^"

echo '--- Full follow-up diff ---'
git diff --no-ext-diff --unified=80 "$parent" "$target" -- \
  src/lib/actions/sandbox/destroy.ts \
  src/lib/actions/sandbox/destroy-flow.test.ts \
  src/lib/actions/sandbox/start.test.ts \
  src/lib/actions/sandbox/stop.test.ts \
  test/helpers/destroy-flow-test-harness.ts

echo '--- Current line-numbered changed files ---'
for file in \
  src/lib/actions/sandbox/destroy.ts \
  src/lib/actions/sandbox/destroy-flow.test.ts \
  src/lib/actions/sandbox/start.test.ts \
  src/lib/actions/sandbox/stop.test.ts \
  test/helpers/destroy-flow-test-harness.ts
do
  echo "### $file"
  cat -n "$file"
done

Length of output: 146153


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- Implementation outlines ---'
for file in \
  src/lib/actions/sandbox/start.ts \
  src/lib/actions/sandbox/stop.ts \
  src/lib/actions/sandbox/runtime/lifecycle-runtime.ts \
  src/lib/onboard/runtime-provider/docker.ts \
  src/lib/onboard/runtime-provider/registry.ts \
  src/lib/actions/sandbox/destroy-execution.ts \
  src/lib/state/registry.ts
do
  echo "### $file"
  ast-grep outline "$file" --items all 2>/dev/null || true
done

echo '--- Provider-resolution and cleanup implementations ---'
rg -n -A90 -B15 \
  'function (startSandbox|stopSandbox|resolveSandboxLifecycleProvider|removeOwnedWorkload|removeSandboxWithReceipt)|const (startSandbox|stopSandbox|removeOwnedWorkload)' \
  src/lib/actions/sandbox/start.ts \
  src/lib/actions/sandbox/stop.ts \
  src/lib/actions/sandbox/runtime/lifecycle-runtime.ts \
  src/lib/onboard/runtime-provider/docker.ts \
  src/lib/state/registry.ts

echo '--- Lifecycle and cleanup call sites ---'
rg -n -A8 -B8 \
  '\b(resolveSandboxLifecycleProvider|removeSandboxRegistryEntryOutcome|removeOwnedWorkload|requireRuntimeProviderBundleForSandbox)\s*\(' \
  src/lib/actions/sandbox \
  src/lib/onboard/runtime-provider \
  src/lib/state/registry.ts

Length of output: 47470


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- Docker provider: lifecycle and owned-workload cleanup ---'
sed -n '105,275p' src/lib/onboard/runtime-provider/docker.ts

echo '--- Destroy execution: authority checks before live deletion ---'
sed -n '188,360p' src/lib/actions/sandbox/destroy-execution.ts

echo '--- Related lower-level cleanup tests ---'
rg -n -A55 -B12 \
  '(authority-unproven|legacy-dockerfile|removeSandboxImage|removeSandboxRegistryEntry|shared)' \
  test/image-cleanup.test.ts \
  src/lib/actions/sandbox/destroy-flow.test.ts

echo '--- Exact follow-up patch whitespace check ---'
git diff --check d73f13a08688df4a61e00426d2206e1d7175d678 8f8bcd5a18ae270538015e7f3772adf3a39d52fe

Length of output: 45948


@ericksoa No new actionable findings in 8f8bcd5a18ae270538015e7f3772adf3a39d52fe.

The start and stop actions resolve the persisted provider before the tested lifecycle side effects.

The repaired legacy Dockerfile receipt matches the recorded image tag. The provider then removes the image, removes the registry entry, and retires the onboarding session.

An unproven receipt still blocks registry and session retirement.

The follow-up patch has no whitespace errors.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

ericksoa added 2 commits July 31, 2026 02:39
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
@ericksoa

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Exact head bf2fd19 closes both retained-ownership callers: snapshot restore and rebuild now exit nonzero with repair guidance instead of reporting deletion success when provider-owned cleanup cannot retire the registry row. The mismatched legacy workload path and rebuild receipt-denial path have regression proofs. The Nemotron null/vm note is dispositioned as the pre-existing Docker compatibility contract; any writer able to set that local authority field can already set docker directly, while unknown registered identities still fail closed. Its destroy-race note is bounded by the outer per-sandbox lifecycle lock across the whole destroy operation, with the later provider check intentionally remaining fail closed. 228 focused tests and npm run validate:pr pass on this exact head.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

base='8f8bcd5a18ae270538015e7f3772adf3a39d52fe'
head='bf2fd190113428cd9c452fdda1476a0a5c8eb439'

git rev-parse --verify "${base}^{commit}"
git rev-parse --verify "${head}^{commit}"

echo '--- commits in reviewed range ---'
git log --no-ext-diff --format='%H %s' "${base}..${head}"

echo '--- changed files ---'
git diff --no-ext-diff --name-status "$base" "$head"

echo '--- change statistics ---'
git diff --no-ext-diff --stat "$base" "$head"

echo '--- TypeScript outlines for changed files ---'
git diff --no-ext-diff --name-only "$base" "$head" \
  | grep -E '\.(ts|tsx)$' \
  | while IFS= read -r file; do
      echo "### $file"
      ast-grep outline "$file" --items all --type function,class,interface,type 2>/dev/null || true
    done

echo '--- relevant diff ---'
git diff --no-ext-diff --unified=90 "$base" "$head" -- \
  src/lib/actions/sandbox \
  src/lib/onboard \
  src/lib/state \
  test \
  ci

echo '--- whitespace check ---'
git diff --no-ext-diff --check "$base" "$head"

Length of output: 49109


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- registry removal contracts ---'
ast-grep outline src/lib/actions/sandbox/destroy.ts --items all --type function --match 'removeSandboxRegistryEntry|removeSandboxRegistryEntryWithReceipt'
rg -n -A100 -B20 \
  'export function (removeSandboxRegistryEntry|removeSandboxRegistryEntryWithReceipt)\s*\(' \
  src/lib/actions/sandbox/destroy.ts

echo '--- rebuild destroy phase and direct callers ---'
ast-grep outline src/lib/actions/sandbox/rebuild-destroy-phase.ts --items all --type function --match 'runRebuildDestroyPhase|waitForRebuildDeleteAbsence'
rg -n -A55 -B15 \
  '\brunRebuildDestroyPhase\s*\(' \
  src/lib/actions/sandbox

echo '--- snapshot removal helper and restore error handling ---'
ast-grep outline src/lib/actions/sandbox/snapshot.ts --items all --type function,class --match 'requireSnapshotDestinationRegistryRemoval|deleteSandboxForRestore|snapshot|restore'
rg -n -A65 -B20 \
  '(function (deleteSandboxForRestore|requireSnapshotDestinationRegistryRemoval)|deleteSandboxForRestore\s*\(|SnapshotCommandError|snapshotExit\s*\()' \
  src/lib/actions/sandbox/snapshot.ts

echo '--- tests that cover snapshot restore destination deletion ---'
rg -n -A35 -B15 \
  '(deleteSandboxForRestore|snapshot restore|Destination.*deleted|SnapshotCommandError|requireSnapshotDestinationRegistryRemoval)' \
  src/lib/actions/sandbox --glob '*test.ts' \
  test --glob '*test.ts'

Length of output: 203629


@ericksoa No new actionable findings in bf2fd190113428cd9c452fdda1476a0a5c8eb439.

Snapshot restore now throws SnapshotCommandError with exit code 1 before it reports destination deletion success when registry removal is blocked.

Rebuild now calls bail(..., 1) and exits before recreation when receipt-based registry removal returns null.

The regression coverage verifies retained registry ownership, repair guidance, backup preservation, and false-success suppression.

The reviewed diff has no whitespace errors.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Require a side-effect-free provider cleanup plan before destructive sandbox actions.

Validate provider and workload ownership before destroy, rebuild, or force-restore deletes.

Preserve ownership state and give fail-closed guidance when authority is ambiguous.

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

Copy link
Copy Markdown
Contributor Author

Advisor remediation on exact head 4316a18daa2827442fe9786a8c943a1b29938bce (base 4788d287b8672be1b44999e78e094b2221303bd1):

  • Terra PRA-2 is fixed. Destroy, rebuild, and snapshot force-restore now call the shared destructive-cleanup authority boundary before side effects. It requires the registered provider; destroy, provider-cleanup, and workload-cleanup authority; supported cleanup; and a side-effect-free planOwnedWorkloadCleanup result. A destructive plan must also accept the durable workload receipt, and actual cleanup revalidates the plan.
  • Snapshot force-restore refuses unknown providers and mismatched legacy receipts before NIM stop, OpenShell deletion, provider deletion, shields cleanup, replacement creation, or registry registration. Table-driven regressions prove those calls remain untouched.
  • Rebuild checks authority before MCP preparation and again at the exact delete edge. If authority changes during preparation, it restores MCP attachment, relocks shields, preserves ownership and backup state, and performs no sandbox deletion.
  • Terra PRA-1 is fixed. The residual post-delete failure has a complete SOURCE_OF_TRUTH record covering the invalid state, source boundary, non-atomic/raw-writer constraint, regression proof, and provider-native transaction removal condition.
  • The Nemotron Kubernetes cleanup warning is an existing compatibility boundary, not CRI cleanup: the shipped Kubernetes gateway path builds and retains its legacy per-sandbox image in the host Docker engine. The bundle now states that ownership explicitly; a future CRI-native implementation must be separately registered rather than added as a central Kubernetes/Podman switch.
  • The Kubernetes lifecycle warning is also existing behavior. Before this contract, direct lifecycle rejected every recorded driver outside the local Docker/VM compatibility path. The Kubernetes bundle remains lifecycle.supported: false; this PR does not claim Kubernetes lifecycle activation.
  • Recovery guidance no longer invents an unsafe repair operation. Every affected path names nemoclaw <sandbox> doctor --json, directs the operator to restore trusted ownership metadata or resolve the runtime conflict, and explicitly says not to rewrite a receipt to match a mutable name. Authenticated automated reconciliation remains owned by the later recovery slice.
  • Exact-head validation: 139 focused tests; npm run validate:pr; CLI typecheck; architecture, source-shape, and test-size checks; Biome; secret scan; and git diff --check.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

base='4788d287b8672be1b44999e78e094b2221303bd1'
head='4316a18daa2827442fe9786a8c943a1b29938bce'

git rev-parse --verify "${base}^{commit}"
git rev-parse --verify "${head}^{commit}"

echo '--- commits ---'
git log --no-ext-diff --format='%H %s' "${base}..${head}"

echo '--- changed files ---'
git diff --no-ext-diff --name-status "$base" "$head"

echo '--- change statistics ---'
git diff --no-ext-diff --stat "$base" "$head"

echo '--- TypeScript outlines for changed files ---'
git diff --no-ext-diff --name-only "$base" "$head" |
  grep -E '\.(ts|tsx)$' |
  while IFS= read -r file; do
    echo "### $file"
    ast-grep outline "$file" --items all --type function,class,interface,type 2>/dev/null || true
  done

echo '--- authority, destroy, rebuild, and snapshot diff ---'
git diff --no-ext-diff --unified=60 "$base" "$head" -- \
  src/lib/actions/sandbox \
  src/lib/onboard/runtime-provider \
  test \
  ci

echo '--- whitespace ---'
git diff --no-ext-diff --check "$base" "$head"

Length of output: 93054


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- relevant outlines ---'
for file in \
  src/lib/actions/sandbox/destroy.ts \
  src/lib/actions/sandbox/destroy-execution.ts \
  src/lib/actions/sandbox/snapshot.ts \
  src/lib/actions/sandbox/rebuild-destroy-phase.ts \
  src/lib/onboard/runtime-provider/registry.ts \
  src/lib/actions/sandbox/destroy-flow.test.ts \
  src/lib/actions/sandbox/rebuild-destroy-phase.test.ts
do
  echo "### $file"
  ast-grep outline "$file" --items all --type function,class,interface,type 2>/dev/null || true
done

echo '--- destructive authority definitions and call sites ---'
rg -n -A75 -B20 \
  '\b(requireRuntimeProviderDestructiveCleanupAuthority|requireSandboxDestructiveCleanupAuthority)\s*\(' \
  src/lib/actions/sandbox \
  src/lib/onboard/runtime-provider \
  test

echo '--- snapshot destructive sequencing ---'
rg -n -A140 -B30 \
  '(function (deleteSandboxForRestore|restoreSnapshot|requireSnapshotDestinationRegistryRemoval)|\b(deleteSandboxForRestore|requireSandboxDestructiveCleanupAuthority)\s*\()' \
  src/lib/actions/sandbox/snapshot.ts

echo '--- rebuild destructive sequencing ---'
rg -n -A150 -B30 \
  '(function (runRebuildDestroyPhase|deleteSandboxForRebuild)|\b(requireSandboxDestructiveCleanupAuthority|prepareMcpBridgesForDestroy|restoreMcpBridgesAfterDestroyAbort)\s*\()' \
  src/lib/actions/sandbox/rebuild-destroy-phase.ts \
  src/lib/actions/sandbox/rebuild-destroy-phase.test.ts

echo '--- snapshot and destroy regression cases ---'
rg -n -A100 -B20 \
  '(unknown provider|mismatched legacy|authority.*change|before.*side effect|destructive cleanup|doctor --json)' \
  src/lib/actions/sandbox --glob '*test.ts' \
  test --glob '*test.ts'

Length of output: 202226


🏁 Script executed:

#!/bin/bash
set -euo pipefail

show_matches() {
  local file="$1"
  local pattern="$2"
  echo "### $file :: $pattern"
  rg -n -P "$pattern" "$file" | while IFS=: read -r line _; do
    start=$(( line > 35 ? line - 35 : 1 ))
    end=$(( line + 125 ))
    sed -n "${start},${end}p" "$file" | nl -ba -v "$start"
    echo '---'
  done
}

show_matches src/lib/actions/sandbox/destroy.ts \
  '\b(requireSandboxDestructiveCleanupAuthority|removeSandboxRegistryEntryWithReceipt|destroySandbox)\s*\('

show_matches src/lib/actions/sandbox/snapshot.ts \
  '\b(requireSandboxDestructiveCleanupAuthority|deleteSandboxForRestore|runSandboxSnapshot)\s*\('

show_matches src/lib/actions/sandbox/rebuild-destroy-phase.ts \
  '\b(requireSandboxDestructiveCleanupAuthority|runRebuildDestroyPhase|prepareMcpBridgesForDestroy|restoreMcpBridgesAfterDestroyAbort)\s*\('

show_matches src/lib/actions/sandbox/rebuild-destroy-phase.test.ts \
  '(authority|receipt|restoreMcp|relock|delete)'

echo '--- all imports and direct authority calls, constrained to relevant sources ---'
rg -n -C4 \
  '\b(requireRuntimeProviderDestructiveCleanupAuthority|requireSandboxDestructiveCleanupAuthority)\b' \
  src/lib/actions/sandbox/destroy.ts \
  src/lib/actions/sandbox/snapshot.ts \
  src/lib/actions/sandbox/rebuild-destroy-phase.ts \
  src/lib/onboard/runtime-provider/registry.ts

Length of output: 1842


🏁 Script executed:

#!/bin/bash
set -euo pipefail

show_matches() {
  local file="$1"
  local pattern="$2"
  echo "### $file :: $pattern"
  rg -n -P "$pattern" "$file" | while IFS=: read -r line _; do
    start=$(( line > 35 ? line - 35 : 1 ))
    end=$(( line + 125 ))
    awk -v start="$start" -v end="$end" \
      'NR >= start && NR <= end { printf "%6d  %s\n", NR, $0 }' "$file"
    echo '---'
  done
}

show_matches src/lib/actions/sandbox/destroy.ts \
  '\b(requireSandboxDestructiveCleanupAuthority|removeSandboxRegistryEntryWithReceipt|destroySandbox)\s*\('

show_matches src/lib/actions/sandbox/snapshot.ts \
  '\b(requireSandboxDestructiveCleanupAuthority|deleteSandboxForRestore|runSandboxSnapshot)\s*\('

show_matches src/lib/actions/sandbox/rebuild-destroy-phase.ts \
  '\b(requireSandboxDestructiveCleanupAuthority|runRebuildDestroyPhase|prepareMcpBridgesForDestroy|restoreMcpBridgesAfterDestroyAbort)\s*\('

show_matches src/lib/actions/sandbox/rebuild-destroy-phase.test.ts \
  '(authority|receipt|restoreMcp|relock|delete)'

echo '--- all imports and direct authority calls, constrained to relevant sources ---'
rg -n -C4 \
  '\b(requireRuntimeProviderDestructiveCleanupAuthority|requireSandboxDestructiveCleanupAuthority)\b' \
  src/lib/actions/sandbox/destroy.ts \
  src/lib/actions/sandbox/snapshot.ts \
  src/lib/actions/sandbox/rebuild-destroy-phase.ts \
  src/lib/onboard/runtime-provider/registry.ts

Length of output: 119036


🏁 Script executed:

#!/bin/bash
set -euo pipefail

print_range() {
  local file="$1"
  local start="$2"
  local end="$3"
  echo "### $file lines $start-$end"
  awk -v start="$start" -v end="$end" \
    'NR >= start && NR <= end { printf "%6d  %s\n", NR, $0 }' "$file"
}

print_range src/lib/actions/sandbox/destroy.ts 235 325
print_range src/lib/actions/sandbox/snapshot.ts 450 560
print_range src/lib/actions/sandbox/rebuild-destroy-phase.ts 95 300
print_range src/lib/actions/sandbox/rebuild-destroy-phase.test.ts 620 815
print_range src/lib/actions/sandbox/destroy-flow.test.ts 1 220

echo '--- check for destructive calls before the snapshot authority call ---'
awk 'NR >= 430 && NR <= 520 { printf "%6d  %s\n", NR, $0 }' \
  src/lib/actions/sandbox/snapshot.ts |
  grep -nE '(requireSandboxDestructiveCleanupAuthority|stopNim|runOpenshell|cleanupShields|streamSandboxCreate|registerSandbox)' || true

echo '--- check for destructive calls around rebuild authority revalidation ---'
awk 'NR >= 95 && NR <= 300 { printf "%6d  %s\n", NR, $0 }' \
  src/lib/actions/sandbox/rebuild-destroy-phase.ts |
  grep -nE '(requireSandboxDestructiveCleanupAuthority|prepareMcp|restoreMcp|relock|runOpenshell|stopNim)' || true

Length of output: 40943


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- rebuild: MCP preparation through delete edge ---'
awk 'NR >= 300 && NR <= 450 { printf "%6d  %s\n", NR, $0 }' \
  src/lib/actions/sandbox/rebuild-destroy-phase.ts

echo '--- rebuild: changed-authority regression cases ---'
rg -n -A110 -B20 \
  '(authority changes|authority.*preparation|cleanup authority|revalidateBeforeDelete|assertDeleteEdgeUnchanged|reattachMcp)' \
  src/lib/actions/sandbox/rebuild-destroy-phase.test.ts

Length of output: 46896


@ericksoa No new actionable findings in 4316a18daa2827442fe9786a8c943a1b29938bce.

The shared destructive-cleanup authority boundary checks registered provider identity, required mutation authority, supported cleanup, and workload ownership before destructive cleanup.

Snapshot force-restore checks authority before NIM stop and OpenShell deletion. The regression cases verify that unknown providers and mismatched receipts cause no destructive side effects.

Rebuild checks authority before MCP preparation. It checks authority again at the delete edge. If the second check fails, it restores MCP state, relocks shields, and stops before sandbox deletion.

Destroy preserves registry and session ownership when cleanup authority is unproven. It permits retirement after a matching repaired receipt.

The reviewed range has no whitespace errors.


✏️ Learnings added
Learnt from: ericksoa
URL: https://github.com/NVIDIA/NemoClaw/pull/7990

Timestamp: 2026-07-31T10:30:18.243Z
Learning: In the TypeScript runtime-provider lifecycle, destructive sandbox operations must use `requireRuntimeProviderDestructiveCleanupAuthority` before side effects. The boundary requires a registered provider, `destroy`, `provider-cleanup`, and `workload-cleanup` mutation authority, supported cleanup, and a side-effect-free workload cleanup plan. If authority or durable workload ownership cannot be proven, the operation must preserve registry ownership and direct the operator to `nemoclaw <sandbox> doctor --json`; it must not instruct the operator to rewrite a receipt to match a mutable sandbox name.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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 (5)
src/lib/actions/sandbox/rebuild-resume-snapshot.test.ts (1)

186-194: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Type the receipt entry instead of casting to never.

{ name: "alpha", agent: null } satisfies SandboxEntry. The as never cast suppresses future shape drift in SandboxRemovalReceipt, so a contract change will not fail this test.

♻️ Proposed change
-        entry: {
-          name: "alpha",
-          agent: null,
-        } as never,
+        entry: { name: "alpha", agent: null } satisfies SandboxEntry,
🤖 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/actions/sandbox/rebuild-resume-snapshot.test.ts` around lines 186 -
194, Update the mock receipt in the test around
removeSandboxRegistryEntryWithReceipt to type entry as the expected SandboxEntry
shape instead of casting { name: "alpha", agent: null } to never, preserving
compile-time detection of future SandboxRemovalReceipt contract changes.
src/lib/actions/sandbox/rebuild-destroy-phase.test.ts (2)

409-411: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid asserting the exact registry read count.

toHaveBeenCalledTimes(3) locks the number of registry.getSandbox reads in runRebuildDestroyPhase. Any added read fails this test without a behavior change. Keep the ordering assertion and drop the exact count, or assert a lower bound.

As per path instructions: "Prefer observable outcomes through the public boundary over source-text, private-shape, or mock-call assertions."

🤖 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/actions/sandbox/rebuild-destroy-phase.test.ts` around lines 409 -
411, Remove the exact getSandbox call-count assertion from the test around
runRebuildDestroyPhase, since it over-specifies internal reads. Preserve the
prepareMcpForRebuild ordering assertion and, if needed, replace the count check
with a lower-bound assertion while continuing to validate the observable
ordering behavior.

Source: Path instructions


265-274: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Both assertions couple to the number of registry.getSandbox reads. runRebuildDestroyPhase reads the registry at several points, so ordinal-keyed mocks and exact call counts break or silently weaken when a read is added.

  • src/lib/actions/sandbox/rebuild-destroy-phase.test.ts#L265-L274: return the drifted entry based on a flag set when prepareMcpForRebuild resolves, instead of mockReturnValueOnce twice.
  • src/lib/actions/sandbox/rebuild-destroy-phase.test.ts#L409-L411: drop toHaveBeenCalledTimes(3) and keep only the invocation-order assertion.

As per path instructions: "Prefer observable outcomes through the public boundary over source-text, private-shape, or mock-call assertions."

🤖 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/actions/sandbox/rebuild-destroy-phase.test.ts` around lines 265 -
274, Decouple both assertions from the number of registry reads in
rebuild-destroy-phase.test.ts: at lines 265-274, have getSandbox return the
drifted entry based on a flag set when prepareMcpForRebuild resolves instead of
using two mockReturnValueOnce calls; at lines 409-411, remove the exact
toHaveBeenCalledTimes(3) assertion and retain only the invocation-order
assertion.

Source: Path instructions

src/lib/actions/sandbox/snapshot.ts (1)

481-492: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Redact the authority failure detail before printing it.

src/lib/actions/sandbox/rebuild-destroy-phase.ts line 135 wraps the same class of cleanup-authority detail in redactFull(detail). Apply the same treatment here so both destructive paths print the detail under one redaction rule.

♻️ Proposed change
-    const detail = error instanceof Error ? error.message : String(error);
+    const detail = redactFull(error instanceof Error ? error.message : String(error));
🤖 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/actions/sandbox/snapshot.ts` around lines 481 - 492, Update the
cleanup-authority failure handling around
requireSandboxDestructiveCleanupAuthority in the snapshot deletion path to pass
the derived detail through redactFull before including it in console.error. Keep
the existing error normalization, message context, and retry guidance unchanged,
matching the treatment in rebuild-destroy-phase.ts.
src/lib/actions/sandbox/rebuild-destroy-phase.ts (1)

133-137: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hardcoded nemoclaw command name in two recovery messages. Both messages build doctor guidance from a literal instead of the shared CLI_NAME constant used by src/lib/actions/sandbox/snapshot.ts and src/lib/actions/sandbox/destroy.ts.

  • src/lib/actions/sandbox/rebuild-destroy-phase.ts#L133-L137: replace 'nemoclaw ${sandboxName} doctor --json' with '${CLI_NAME} ${sandboxName} doctor --json' and import CLI_NAME from ../../cli/branding.
  • src/lib/actions/sandbox/rebuild-destroy-phase.ts#L523-L527: replace the same literal in the registry-retention message with ${CLI_NAME}.
🤖 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/actions/sandbox/rebuild-destroy-phase.ts` around lines 133 - 137,
Replace the hardcoded nemoclaw command in both recovery messages in
rebuild-destroy-phase.ts (lines 133-137 and 523-527) with CLI_NAME, and import
CLI_NAME from ../../cli/branding. Preserve the existing sandboxName doctor
command formatting and message content at both sites.
🤖 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/actions/sandbox/snapshot-restore-test-fixture.ts`:
- Line 314: Update the removeSandboxRegistryEntry mock in the snapshot restore
test fixture to return true, matching the production function’s boolean contract
and the success path expected by requireSnapshotDestinationRegistryRemoval.

In `@src/lib/onboard/runtime-provider/runtime-provider-contract.test.ts`:
- Around line 352-369: Update the test around planOwnedWorkloadCleanup to
snapshot the relevant sandbox and runtime state before invoking the planner,
then assert both snapshots are unchanged afterward. Keep the existing
authority-unproven plan assertion, and verify the planner remains
side-effect-free while validating the cleanup request.

---

Nitpick comments:
In `@src/lib/actions/sandbox/rebuild-destroy-phase.test.ts`:
- Around line 409-411: Remove the exact getSandbox call-count assertion from the
test around runRebuildDestroyPhase, since it over-specifies internal reads.
Preserve the prepareMcpForRebuild ordering assertion and, if needed, replace the
count check with a lower-bound assertion while continuing to validate the
observable ordering behavior.
- Around line 265-274: Decouple both assertions from the number of registry
reads in rebuild-destroy-phase.test.ts: at lines 265-274, have getSandbox return
the drifted entry based on a flag set when prepareMcpForRebuild resolves instead
of using two mockReturnValueOnce calls; at lines 409-411, remove the exact
toHaveBeenCalledTimes(3) assertion and retain only the invocation-order
assertion.

In `@src/lib/actions/sandbox/rebuild-destroy-phase.ts`:
- Around line 133-137: Replace the hardcoded nemoclaw command in both recovery
messages in rebuild-destroy-phase.ts (lines 133-137 and 523-527) with CLI_NAME,
and import CLI_NAME from ../../cli/branding. Preserve the existing sandboxName
doctor command formatting and message content at both sites.

In `@src/lib/actions/sandbox/rebuild-resume-snapshot.test.ts`:
- Around line 186-194: Update the mock receipt in the test around
removeSandboxRegistryEntryWithReceipt to type entry as the expected SandboxEntry
shape instead of casting { name: "alpha", agent: null } to never, preserving
compile-time detection of future SandboxRemovalReceipt contract changes.

In `@src/lib/actions/sandbox/snapshot.ts`:
- Around line 481-492: Update the cleanup-authority failure handling around
requireSandboxDestructiveCleanupAuthority in the snapshot deletion path to pass
the derived detail through redactFull before including it in console.error. Keep
the existing error normalization, message context, and retry guidance unchanged,
matching the treatment in rebuild-destroy-phase.ts.
🪄 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: 126e8cd9-ecb6-4fcc-9257-f49a85af78ed

📥 Commits

Reviewing files that changed from the base of the PR and between 8f8bcd5 and 4316a18.

📒 Files selected for processing (16)
  • src/lib/actions/sandbox/destroy-execution.ts
  • src/lib/actions/sandbox/destroy-flow.test.ts
  • src/lib/actions/sandbox/destroy.ts
  • src/lib/actions/sandbox/rebuild-destroy-phase.test.ts
  • src/lib/actions/sandbox/rebuild-destroy-phase.ts
  • src/lib/actions/sandbox/rebuild-resume-snapshot.test.ts
  • src/lib/actions/sandbox/snapshot-restore-lifecycle.test.ts
  • src/lib/actions/sandbox/snapshot-restore-test-fixture.ts
  • src/lib/actions/sandbox/snapshot.ts
  • src/lib/onboard/runtime-provider/access.ts
  • src/lib/onboard/runtime-provider/contract.ts
  • src/lib/onboard/runtime-provider/docker.ts
  • src/lib/onboard/runtime-provider/registry.ts
  • src/lib/onboard/runtime-provider/runtime-provider-contract.test.ts
  • test/helpers/runtime-provider-bundle.ts
  • test/image-cleanup.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • test/helpers/runtime-provider-bundle.ts
  • src/lib/onboard/runtime-provider/access.ts
  • src/lib/actions/sandbox/destroy-flow.test.ts
  • test/image-cleanup.test.ts
  • src/lib/actions/sandbox/destroy.ts
  • src/lib/actions/sandbox/destroy-execution.ts

Comment thread src/lib/actions/sandbox/snapshot-restore-test-fixture.ts Outdated
Comment thread src/lib/onboard/runtime-provider/runtime-provider-contract.test.ts
ericksoa added 2 commits July 31, 2026 03:42
Keep the force-restore fixture aligned with the required cleanup-authority boundary.

Its success path now exercises the new guard before deletion.

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Snapshot provider and sandbox state around cleanup planning.

Keep snapshot registry-removal mocks faithful to the production boolean contract.

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Base automatically changed from feat/buildless-runtime-e2e-foundation to feat/buildless-managed-contract-hardening July 31, 2026 15:00
@senthilr-nv
senthilr-nv merged commit b758dc7 into feat/buildless-managed-contract-hardening Jul 31, 2026
100 of 103 checks passed
@senthilr-nv
senthilr-nv deleted the feat/runtime-provider-lifecycle-parity branch July 31, 2026 20:05
ericksoa added a commit that referenced this pull request Aug 1, 2026
<!-- markdownlint-disable MD041 -->
## Summary

Adds the inert, provider-neutral managed-workload rebuild transaction
for the incremental buildless stack. The exact old workload and registry
row remain authoritative through replacement preparation, readiness,
state restore, and provider rebind. Only one exact compare-and-swap
publishes the replacement, and old-runtime retirement happens afterward
through provider-owned opaque handles.

This slice does not wire a production rebuild caller or activate
buildless onboarding. Snapshot/backup and durable recovery ownership
remain tracked in #7744 and are required before activation.

## Related Issue

Part of #7744

## Changes

- Capture a deep-frozen rebuild plan bound to the exact provider,
shipped agent, platform, prior managed receipt, full durable-row
revision, lifecycle generation, and live identity fingerprint.
- Pre-render and validate the exact replacement image, startup profile,
receipt, and safe metadata before provider mutation.
- Define provider-neutral prepare, create, readiness, restore,
provider-rebind, rollback, abort-preparation, and retire-previous phases
using opaque exact handles rather than sandbox-name deletion.
- Keep partial prepare/create cleanup transaction-idempotent and run
abort cleanup even when post-prepare registry revalidation throws.
- Publish only through exact old-authority CAS; reconcile ambiguous
persistence against either the exact replacement or exact old row.
- Preserve the staged replacement and return an immutable recovery task
when publication is indeterminate, avoiding rollback of a replacement
that may already be durable.
- Retire the exact old runtime only after publication; return a
durable-owner recovery task if retirement remains pending.
- Bind replacement contracts and startup profiles to OpenClaw, Hermes,
or DCode authority and reject provider, agent, platform, receipt,
generation, or identity drift.
- Reject malformed provider artifacts at every transition, stop before
later phases, and prove exact transaction abort or exact staged-handle
rollback.
- Document the shared backup boundary and the durable recovery ownership
tracked in #7744 before activation.

## Type of Change

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

## Quality Gates

- [x] Tests added or updated for changed behavior
- [ ] Existing tests cover changed behavior — justification:
- [ ] Tests not applicable — justification:
- [ ] Docs updated for user-facing behavior changes
- [x] Docs not applicable — justification: The transaction is inert with
no production caller or support claim; the internal README records
ownership boundaries for later slices.
- [x] Sensitive paths changed (security, policy, credentials, preflight,
onboarding, inference, runner, sandbox, or messaging)
- [x] Sensitive-path review completed or maintainer-approved waiver
recorded — reviewer/approval link/justification: Exact-head audit covers
immutable authority, pre-mutation validation, CAS ambiguity, abort
cleanup, exact-handle rollback, and deferred recovery ownership.
- [ ] 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: The reviewed 23-file, `+4,272/-0` slice remains
byte-identical after the append-only current-main refresh to
`0de2789608a86e580d787991e81c03c5f0b14dbf` through
`e97ecce48c7fcc1dfb398e1cfae8c81a859b7dcd`; stable patch ID remains
`dd1c4a899fd9a62954a00d4e2e61da445a306e03`. The only documentation path
is `src/lib/onboard/managed-workload/rebuild/README.md`. It accurately
states that the transaction is dormant, has no CLI command or
production-action importer, and does not activate buildless rebuilds. It
assigns ambiguous publication and pending retirement to durable
recovery, links recovery and snapshot/backup ownership to the live
accepted epic #7744, and requires normalized backup manifests, restore
validation, durable reconciliation, and protected qualification for
OpenClaw, Hermes, and LangChain Deep Agents Code before activation.
Production-import and command/action diff scans found no activation
caller. Markdownlint passed with zero issues on the exact refreshed
head. The append-only parent refresh to `362a70cda` preserves the exact
reviewed slice diff and changes no reviewed documentation.
- Agent: Codex Desktop
<!-- docs-review-head-sha: 362a70c -->
<!-- docs-review-agents-blob-sha: 3dd7c24 -->

## DGX Station Hardware Evidence

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

## Verification

- Exact locally validated head/base:
`e97ecce48c7fcc1dfb398e1cfae8c81a859b7dcd` /
`0de2789608a86e580d787991e81c03c5f0b14dbf`
- Review budget: 23 files, `+4,272/-0`.
- Stable exact-slice patch ID:
`dd1c4a899fd9a62954a00d4e2e61da445a306e03`.
- [x] The six implementation/review commits and both maintainer refresh
commits are SSH-signed and contain DCO trailers; GitHub-generated
conflict-resolution merge commits preserve append-only branch history.
- [x] `npm run validate:pr` passed on the exact clean head with Node
22.16.0.
- [x] 132 focused rebuild transaction, workload authority, registry CAS,
and source-boundary tests passed again on the exact refreshed head; CLI
typecheck and repository checks also passed; changed test files add zero
`if` statements.
- [x] `npm run build:cli`, CLI typecheck through `validate:pr`,
exact-base pre-commit, commitlint, and pre-push gates passed.
- [x] Failure tests prove prepare/create ambiguity aborts exact
transaction resources, staged failures roll back only exact staging
authority, and indeterminate CAS never rolls back.
- [x] Agent-binding tests reject cross-agent image/profile drift for all
shipped managed-image agents.
- [x] No snapshot manifest dependency, production rebuild callsite,
runtime selection change, or public activation exists in this slice.
- [ ] Applicable broad gate passed — exact-head required CI, advisors,
CodeRabbit, multiarch, and protected E2E are the broad remote gates.

## Stack

- Base: live `main` at `0de2789608a86e580d787991e81c03c5f0b14dbf`; PR3.1
through PR3.6 content is already landed, with #7976, #7988, and #7990
carried once through the final #7973 aggregate tree.
- This slice: PR3.7 branch `feat/managed-workload-rebuild-parity` at
`e97ecce48c7fcc1dfb398e1cfae8c81a859b7dcd`.
- Epic #7744 tracks shared snapshot, backup, restore, and durable
recovery ownership before activation.
- Buildless support remains disabled until OpenClaw, Hermes, and DCode
plus required multiarch and protected qualification pass together.

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

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

* **New Features**
* Added managed workload rebuild workflows with staged replacement,
validation, rollback, recovery, and atomic commit handling.
* Added authority validation for managed workloads, including receipt,
image, platform, and startup configuration checks.
  * Added safe cloning and deep-freezing for supported immutable data.
* Added safeguards against stale, conflicting, or incomplete workload
state during rebuilds.

* **Documentation**
  * Documented rebuild recovery behavior and activation requirements.

* **Tests**
* Added comprehensive coverage for rebuild transactions, authority
validation, rollback, persistence reconciliation, and immutable data
handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

3 participants