Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
c398452
fix(security): bind sandbox mutation authority
apurvvkumaria Aug 26, 2026
f9286b6
test(onboard): remove disconnected rollback assertions
apurvvkumaria Aug 26, 2026
9bf87da
fix(security): serialize rebuild authority publication
apurvvkumaria Aug 26, 2026
ba6a21f
fix(onboard): reject unsafe deferred provider plans
apurvvkumaria Aug 26, 2026
d5e9cf7
merge: resolve conflicts with main
github-actions[bot] Aug 26, 2026
a3572fb
Merge branch 'main' into codex/9833-advisor-identity-bound-mutations
cv Aug 26, 2026
32c74ca
test(onboard): align APF provider refusal
apurvvkumaria Aug 26, 2026
7965569
merge: resolve conflicts with main
github-actions[bot] Aug 26, 2026
374ebe4
merge: sync main into #10396
apurvvkumaria Aug 26, 2026
e3f7cd2
merge: sync main into #10396
apurvvkumaria Aug 26, 2026
12e4729
merge(main): reconcile #10396 with current main
apurvvkumaria Aug 26, 2026
cd15b07
fix(onboard): limit APF creation to providerless plans
apurvvkumaria Aug 26, 2026
ec88d7c
fix(onboard): refuse interceptor provider effects early
apurvvkumaria Aug 26, 2026
cd4fa24
merge: sync main into #10396
apurvvkumaria Aug 26, 2026
e70815f
merge: sync main into #10396
apurvvkumaria Aug 27, 2026
1686ccb
merge: sync main into #10396
apurvvkumaria Aug 27, 2026
665012e
fix(onboard): preserve non-tty cancellation recovery
apurvvkumaria Aug 27, 2026
6da68ad
test(onboard): cover cancellation recovery
apurvvkumaria Aug 27, 2026
5696a0a
fix(onboard): report missing identity recovery
apurvvkumaria Aug 27, 2026
568ead2
fix(onboard): reject deferred provider intent early
apurvvkumaria Aug 27, 2026
a68d8df
merge: synchronize main
apurvvkumaria Aug 27, 2026
e5794a1
merge: synchronize main
apurvvkumaria Aug 27, 2026
a8861a8
merge: synchronize main
apurvvkumaria Aug 27, 2026
2c87fe5
fix(onboard): retain managed image authority
apurvvkumaria Aug 27, 2026
acd47a2
merge: synchronize current main
apurvvkumaria Aug 27, 2026
9c96748
fix(onboard): reject staged provider intent early
apurvvkumaria Aug 27, 2026
6e892b6
merge: synchronize current main
apurvvkumaria Aug 27, 2026
06256df
fix(onboard): surface retained sandbox recovery
apurvvkumaria Aug 27, 2026
cd0d674
docs(onboard): complete cancellation recovery guidance
apurvvkumaria Aug 27, 2026
7ee4dc2
merge: resolve conflicts with main
github-actions[bot] Aug 27, 2026
e861eaf
fix(onboard): block cancelled sandbox reentry
apurvvkumaria Aug 27, 2026
8e3f82b
merge: synchronize published branch
apurvvkumaria Aug 27, 2026
ebb500d
merge: synchronize current main
apurvvkumaria Aug 27, 2026
ac6717e
fix(onboard): preserve deferred create integration
apurvvkumaria Aug 27, 2026
518c7ce
fix(onboard): persist recovery without sandbox identity
apurvvkumaria Aug 27, 2026
56fa746
test(onboard): align created sandbox identity fixtures
apurvvkumaria Aug 27, 2026
8b8903b
fix(onboard): retry recovery persistence on exit
apurvvkumaria Aug 27, 2026
62b1021
fix(onboard): reject provider plans before credential reads
apurvvkumaria Aug 27, 2026
6462409
fix(onboard): preserve cancellation recovery guidance
apurvvkumaria Aug 27, 2026
c7eea16
fix(onboard): persist retained create recovery
apurvvkumaria Aug 27, 2026
b30e100
fix(onboard): record post-create recovery
apurvvkumaria Aug 27, 2026
96a2f4b
fix(onboard): retain recovery through finalization
apurvvkumaria Aug 27, 2026
32fe3cc
merge: resolve main conflicts in PR #10396
sandl99 Aug 27, 2026
7cfed07
fix(onboard): preserve retained recovery records
apurvvkumaria Aug 27, 2026
13b15e4
merge: synchronize PR #10396 branch
apurvvkumaria Aug 27, 2026
1641b15
docs(onboard): scope retained recovery guidance
apurvvkumaria Aug 27, 2026
c508d66
fix(onboard): retain recovery persistence retries
apurvvkumaria Aug 27, 2026
2aeff9c
fix(security): close retained recovery state race
sandl99 Aug 27, 2026
10cfe13
fix(onboard): reject provider effects before create resolution
apurvvkumaria Aug 27, 2026
ffe9fa5
fix(onboard): retain recovery after create failures
apurvvkumaria Aug 27, 2026
a09264d
fix(onboard): keep retained recovery records unresolved
apurvvkumaria Aug 27, 2026
06e6070
fix(onboard): bind retained recovery evidence
apurvvkumaria Aug 27, 2026
9275324
fix(onboard): serialize retained recovery state
apurvvkumaria Aug 27, 2026
0966370
merge: resolve conflicts with main
github-actions[bot] Aug 27, 2026
3ae8f06
fix(onboard): reject unsupported interceptor agents early
apurvvkumaria Aug 27, 2026
14ff3b8
fix(onboard): refuse unsupported agent routes early
apurvvkumaria Aug 27, 2026
d625e9d
fix(onboard): retain recovery on credential repair failure
apurvvkumaria Aug 27, 2026
dbae39f
docs(onboard): clarify fresh recovery selection
apurvvkumaria Aug 27, 2026
4625e45
fix(onboard): bind retained recovery evidence
apurvvkumaria Aug 27, 2026
eefb255
fix(onboard): use locked entry decision
apurvvkumaria Aug 27, 2026
310bfe7
fix(state): pin onboarding state writes
apurvvkumaria Aug 27, 2026
ddbea24
Merge branch 'main' into codex/9833-advisor-identity-bound-mutations
cjagwani Aug 27, 2026
66adb50
merge: resolve conflicts with main
github-actions[bot] Aug 27, 2026
c41c59d
merge: reconcile current main
apurvvkumaria Aug 27, 2026
0c2574c
merge: include current main
apurvvkumaria Aug 27, 2026
15ba460
merge: reconcile concurrent branch update
apurvvkumaria Aug 27, 2026
1346f90
test(onboard): keep verified recovery providerless
apurvvkumaria Aug 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,16 @@ Use `$$nemoclaw <sandbox-name> rebuild` when you want NemoClaw to recreate the s
#### `--apf-interceptor`

Use this option to request a policyless sandbox creation for an APF-interceptor flow.
This option currently supports providerless sandbox creation only.
APF onboarding with an inference provider and model is not yet supported.
APF onboarding with an OpenShell provider for a Model Context Protocol (MCP) server is also not yet supported.
OpenShell cannot bind provider attachment to the new sandbox's verified immutable ID.
If the prepared plan contains any provider, NemoClaw exits before it:

- Creates the sandbox.
- Registers or changes credentials.
- Creates, updates, or deletes providers.

The option requires these conditions:

- Start a new onboarding session.
Expand All @@ -488,8 +498,8 @@ Do not destroy that sandbox by name.
Retain the reported sandbox name, create-attempt label, and durable identity fingerprint for comparison only.
If OpenShell did not return the fingerprint, recovery remains blocked until an administrator resolves the create-attempt label to one exact sandbox.
Ask an OpenShell administrator to obtain the exact live durable ID, verify it against the fingerprint, and use an identity-bound removal procedure.
After the administrator confirms removal, start a fresh onboarding attempt.
`--resume` and `--recreate-sandbox` cannot recover the incomplete attempt.
This onboarding mode does not support `--resume` or `--recreate-sandbox`, regardless of whether sandbox creation began.
After the administrator confirms identity-bound removal, repeat the original command with `--fresh` and a new name.
</Warning>

#### `--tool-disclosure <progressive|direct>`
Expand Down Expand Up @@ -886,8 +896,21 @@ Pairing and `TELEGRAM_ALLOWED_IDS` still govern direct messages.

</AgentOnly>

If you cancel a brand-new onboarding run at the policy preset step, NemoClaw rolls back the sandbox, registry entry, and onboarding session instead of leaving a default sandbox with unfinished policy state.
Existing live sandboxes are not deleted by this cancel rollback path.
If you cancel a brand-new onboarding run at the policy-tier selector or either policy-preset selector after sandbox creation, NemoClaw preserves the incomplete sandbox, registry entry, and onboarding session for identity-bound recovery.
NemoClaw reports the durable sandbox identity fingerprint when it is available.
It does not run OpenShell's mutable-name deletion command because the name may now identify a replacement sandbox.
Do not delete the sandbox by mutable name.
Shared inference providers are gateway configuration, not sandbox cleanup targets.
Sandbox-scoped provider registrations or gateway-bound credentials may remain when the durable recovery record lists them.
Ask an OpenShell administrator to inspect the exact sandbox identity and remove only sandbox-scoped resources whose ownership is confirmed for the retained sandbox.
A credential environment name in the recovery record does not prove that its value was exposed.
Rotate a credential only when identity-bound inspection proves that it was exposed or attached to a retained sandbox-scoped resource.
NemoClaw stores the recovery record independently from the active onboarding session.
A fresh run with a different name can proceed without clearing that record, but automatic resume, explicit `--resume`, reuse, recreation, and fresh onboarding with the retained name remain blocked.
NemoClaw has no supported operation in this release to clear the recovery record, so the retained name remains unavailable even after external recovery or removal.
Preserve the record as evidence.
Start fresh onboarding with `--fresh` and another available sandbox name.
Select the required provider, model, agent, policy, and environment inputs again because `--fresh` does not retain them.

If you run onboarding again with the same sandbox name and choose a different inference provider or model, NemoClaw detects the drift and recreates the sandbox so the running agent config matches your selection.
In interactive mode, the wizard asks for confirmation before delete and recreate.
Expand Down
64 changes: 32 additions & 32 deletions src/lib/onboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ const { resolveSandboxImageTagFromCreateOutput } =
require("./domain/sandbox/image-tag") as typeof import("./domain/sandbox/image-tag");
const nim: typeof import("./inference/nim") = require("./inference/nim");
const onboardSession: typeof import("./state/onboard-session") = require("./state/onboard-session");
const { markCancellationRecovery: recordRecovery } = onboardSession;
const portableRetirementAuthority: typeof import("./onboard/portable-retirement-authority") = require("./onboard/portable-retirement-authority");
const {
registerIncompleteOnboardExitHandlerForSession,
Expand Down Expand Up @@ -458,6 +459,7 @@ const {
}: typeof import("./onboard/cancel-rollback") = require("./onboard/cancel-rollback");
const {
createCoreOnboardFlowPhases,
isCoreFlowCompleteBeforeFinalization,
prepareCoreOnboardFlowContext,
prepareFinalOnboardFlowContext,
runCoreOnboardFlowSlice,
Expand Down Expand Up @@ -2634,9 +2636,7 @@ const onboardRuntimeBoundary = new OnboardRuntimeBoundary({
toSessionUpdates(updates as Parameters<typeof toSessionUpdates>[0]),
maybeForceE2eStepFailure,
});

const sandboxCancelRollback = installSandboxCancelRollback({ cliName: cliName() }); // #4614, #9833

const sandboxCancelRollback = installSandboxCancelRollback({ recordRecovery }); // #4614
const {
arePolicyPresetsApplied,
computeSetupPresetSuggestions,
Expand Down Expand Up @@ -2749,44 +2749,30 @@ async function runOnboard(opts: OnboardOptions = {}): Promise<void> {
);
setOnboardBrandingAgent(opts.agent || process.env.NEMOCLAW_AGENT || null);
AUTO_YES = opts.autoYes === true || process.env.NEMOCLAW_YES === "1";
const entryOptions = onboardEntryOptions.resolveDefaultRunEntryOptions(
opts,
onboardSession.loadSession()?.status ?? null,
validateName,
);
const { fresh, nonInteractive, cannotPrompt, resume } = entryOptions;
const { requestedFromDockerfile, requestedSandboxName } = entryOptions;
NON_INTERACTIVE = nonInteractive;
const validatePolicyTierBeforeRuntime =
isNonInteractive() && !resume && opts.experimentalProfile !== "portable";
if (validatePolicyTierBeforeRuntime) validatePolicyTierEnvEarly();
const resolveEntryOptions = () =>
onboardEntryOptions.resolveDefaultRunEntryOptionsFromState(opts, validateName, onboardSession);
const initialEntryOptions = resolveEntryOptions();
NON_INTERACTIVE = initialEntryOptions.nonInteractive;
RECREATE_SANDBOX = opts.recreateSandbox || process.env.NEMOCLAW_RECREATE_SANDBOX === "1";
_preflightDashboardPort =
opts.controlUiPort ?? (process.env.NEMOCLAW_DASHBOARD_PORT != null ? DASHBOARD_PORT : null);
onboardRuntimeBoundary.reset();
const baseImageResolutionContext = baseImageResolutionFlow.createBaseImageResolutionContext({
fresh,
initialHint: opts.baseImageResolutionHint,
initialPreResolvedMetadata: opts.preResolvedBaseImageMetadata,
});
const portableRetirementEntry = portableRetirementAuthority.beginPortableOnboardRetirementEntry({
alreadyHeld: opts.onboardLockAlreadyHeld === true,
command: `nemoclaw onboard${resume ? " --resume" : ""}${fresh ? " --fresh" : ""}${isNonInteractive() ? " --non-interactive" : ""}${requestedFromDockerfile ? ` --from ${requestedFromDockerfile}` : ""}`,
command: `nemoclaw onboard${initialEntryOptions.resume ? " --resume" : ""}${initialEntryOptions.fresh ? " --fresh" : ""}${initialEntryOptions.nonInteractive ? " --non-interactive" : ""}${initialEntryOptions.requestedFromDockerfile ? ` --from ${initialEntryOptions.requestedFromDockerfile}` : ""}`,
displayName: cliDisplayName(),
homeDir: process.env.HOME || os.homedir(),
loadRegistry: registry.load,
registryFile: registry.REGISTRY_FILE,
sessionFile: onboardSession.SESSION_FILE,
withLifecycleLock: sandboxMutationLock.withMcpLifecycleLock,
});

let portableEnvScope:
| import("./onboard/session-bootstrap").PortableOnboardEnvironmentScope
| null = null;
const restorePortableEnvScope = () => portableEnvScope?.restore();
// Secure removal remains gated on successful migration of every staged legacy credential.
let stagedLegacyKeys: string[] = [];

let onboardTrace: ReturnType<typeof onboardTracing.startOnboardTrace> = {
collector: null,
span: null,
Expand All @@ -2796,6 +2782,18 @@ async function runOnboard(opts: OnboardOptions = {}): Promise<void> {
preserveIncompleteSession = false;
try {
await portableRetirementEntry.run(async () => {
const entryOptions = resolveEntryOptions();
const { fresh, nonInteractive, cannotPrompt, resume } = entryOptions;
const { requestedFromDockerfile, requestedSandboxName } = entryOptions;
NON_INTERACTIVE = nonInteractive;
const validatePolicyTierBeforeRuntime =
isNonInteractive() && !resume && opts.experimentalProfile !== "portable";
if (validatePolicyTierBeforeRuntime) validatePolicyTierEnvEarly();
const baseImageResolutionContext = baseImageResolutionFlow.createBaseImageResolutionContext({
fresh,
initialHint: opts.baseImageResolutionHint,
initialPreResolvedMetadata: opts.preResolvedBaseImageMetadata,
});
const lockedRuntime = await resumeRuntime.prepare(
opts,
resume,
Expand Down Expand Up @@ -2965,13 +2963,7 @@ async function runOnboard(opts: OnboardOptions = {}): Promise<void> {
onboardSessionBootstrap.reportReadOnlyHostMounts(effectiveHostMounts, note);
const explicitSandboxGpuFlag = resolveSandboxGpuFlagFromOptions(opts);
const recordedGpuPassthroughBeforePreflight = session?.gpuPassthrough === true;
type InitialOnboardFlowContext =
import("./onboard/machine/initial-flow-composition").InitialOnboardFlowContext<
typeof agent,
ReturnType<typeof nim.detectGpu>,
ReturnType<typeof resolveSandboxGpuConfig>
>;
const initialFlowContext: InitialOnboardFlowContext = {
const initialFlowContext = {
resume,
fresh,
session,
Expand All @@ -2992,12 +2984,13 @@ async function runOnboard(opts: OnboardOptions = {}): Promise<void> {
webSearchConfig: session?.webSearchConfig || null,
webSearchSupported: false,
selectedMessagingChannels,
gpu: null,
sandboxGpuConfig: null,
gpu: null as ReturnType<typeof nim.detectGpu> | null,
sandboxGpuConfig: null as ReturnType<typeof resolveSandboxGpuConfig> | null,
gpuPassthrough: false,
resumeHasResolvedGpuIntent: false,
requestedGpuPassthrough: opts.gpu === true,
};
type InitialOnboardFlowContext = typeof initialFlowContext;
const policyAuthorityBindings =
sandboxCreateOrchestration.createOnboardPolicyAuthorityBindings(
sandboxCreateOrchestrationRuntime,
Expand Down Expand Up @@ -3123,6 +3116,7 @@ async function runOnboard(opts: OnboardOptions = {}): Promise<void> {
},
providerInference: {
gatewayName: GATEWAY_NAME,
inspectSandboxForCreate,
forceProviderSelection: forceProviderSelectionForAgentChange,
...authoritativeRebuildTarget.rebuildProviderFlowOptions(opts, coreFlowContext),
endpointProvenance,
Expand Down Expand Up @@ -3317,6 +3311,13 @@ async function runOnboard(opts: OnboardOptions = {}): Promise<void> {
resume,
recordRepairEvent,
});
if (isCoreFlowCompleteBeforeFinalization(coreFlowResult)) {
sandboxCancelRollback.disarm();
await portableRetirementEntry.supersede(lockedRuntime.checkpointProfile);
completed = true;
process.exitCode = 0;
return;
}
setupInferenceFactory.selectGatewayForFollowupOrExit(GATEWAY_NAME, runOpenshell);
const finalFlowContext = prepareFinalOnboardFlowContext(coreFlowResult);
let liveFinalFlowContext: InitialOnboardFlowContext = finalFlowContext;
Expand Down Expand Up @@ -3574,7 +3575,6 @@ module.exports = {
recoverGatewayRuntime,
buildChain,
buildControlUiUrls,

startGateway,
startDockerDriverGateway,
findAvailableDashboardPort,
Expand Down
Loading
Loading