diff --git a/src/lib/onboard.ts b/src/lib/onboard.ts index b287a2b556..9b0ffd044d 100644 --- a/src/lib/onboard.ts +++ b/src/lib/onboard.ts @@ -6294,8 +6294,8 @@ const recordStepSkipped = onboardRuntimeBoundary.recordStepSkipped.bind(onboardR const recordStepFailed = onboardRuntimeBoundary.recordStepFailed.bind(onboardRuntimeBoundary); const recordStateSkipped = onboardRuntimeBoundary.recordStateSkipped.bind(onboardRuntimeBoundary); const recordRepairEvent = onboardRuntimeBoundary.recordRepairEvent.bind(onboardRuntimeBoundary); +const recordStateResult = onboardRuntimeBoundary.recordStateResult.bind(onboardRuntimeBoundary); const recordPostVerifyStarted = onboardRuntimeBoundary.recordPostVerifyStarted.bind(onboardRuntimeBoundary); -const recordSessionComplete = onboardRuntimeBoundary.recordSessionComplete.bind(onboardRuntimeBoundary); function skippedStepMessage( stepName: string, @@ -7004,7 +7004,7 @@ async function onboard(opts: OnboardOptions = {}): Promise { session = policiesResult.session; sandboxCancelRollback.disarm(); // #4614: policies confirmed, past the cancellable window - await handleFinalizationState({ + const finalizationResult = await handleFinalizationState({ sandboxName, model, provider, @@ -7020,7 +7020,6 @@ async function onboard(opts: OnboardOptions = {}): Promise { setDefaultSandbox: registry.setDefault, verifyWebSearchInsideSandbox, recordPostVerifyStarted, - recordSessionComplete, toSessionUpdates: (updates) => toSessionUpdates(updates as Parameters[0]), removeLegacyCredentialsFile, cleanupStaleHostFiles, @@ -7058,6 +7057,7 @@ async function onboard(opts: OnboardOptions = {}): Promise { log: (message) => console.log(message), }, }); + await recordStateResult(finalizationResult.stateResult); traceCompleted = true; } finally { releaseOnboardLock(); diff --git a/src/lib/onboard/machine/handlers/finalization.test.ts b/src/lib/onboard/machine/handlers/finalization.test.ts index aa6e6b2ad8..b6cfec8202 100644 --- a/src/lib/onboard/machine/handlers/finalization.test.ts +++ b/src/lib/onboard/machine/handlers/finalization.test.ts @@ -15,7 +15,6 @@ function createDeps(overrides: Partial 18789), postVerify: vi.fn(async () => createSession({ machine: { version: 1, state: "post_verify", stateEnteredAt: null, revision: 1 } })), - complete: vi.fn(async () => createSession({ status: "complete" })), removeLegacy: vi.fn(), cleanupHost: vi.fn(), recoverProcesses: vi.fn(), @@ -34,7 +33,6 @@ function createDeps(overrides: Partial) => updates as SessionUpdates, removeLegacyCredentialsFile: calls.removeLegacy, cleanupStaleHostFiles: calls.cleanupHost, @@ -76,10 +74,10 @@ describe("handleFinalizationState", () => { const result = await handleFinalizationState(baseOptions(deps)); - // Default is set at finalization (deferred from sandbox creation, #4614), before completion. + // Default is set at finalization (deferred from sandbox creation, #4614), before post-verify starts. expect(calls.setDefaultSandbox).toHaveBeenCalledWith("my-assistant"); expect(calls.setDefaultSandbox.mock.invocationCallOrder[0]).toBeLessThan( - calls.complete.mock.invocationCallOrder[0], + calls.postVerify.mock.invocationCallOrder[0], ); expect(calls.cleanupHost).toHaveBeenCalledOnce(); expect(calls.recoverProcesses).toHaveBeenCalledWith("my-assistant", { quiet: true }); @@ -88,12 +86,16 @@ describe("handleFinalizationState", () => { expect(calls.log).toHaveBeenCalledWith(" ✓ verified"); expect(calls.dashboard).toHaveBeenCalledWith("my-assistant", "model", "provider", null, null); expect(calls.postVerify).toHaveBeenCalledOnce(); - expect(calls.complete).toHaveBeenCalledWith({ - sandboxName: "my-assistant", - provider: "provider", - model: "model", - hermesAuthMethod: null, - hermesToolGateways: [], + expect(result.stateResult).toEqual({ + type: "complete", + updates: { + sandboxName: "my-assistant", + provider: "provider", + model: "model", + hermesAuthMethod: null, + hermesToolGateways: [], + }, + metadata: { state: "finalizing" }, }); expect(result.verificationDiagnostics).toEqual([" ✓ verified"]); }); @@ -105,9 +107,8 @@ describe("handleFinalizationState", () => { await handleFinalizationState({ ...baseOptions(deps), agent }); expect(calls.ensureAgentDashboard).toHaveBeenCalledWith("my-assistant", agent); - expect(calls.complete).toHaveBeenCalled(); expect(calls.ensureAgentDashboard.mock.invocationCallOrder[0]).toBeLessThan( - calls.complete.mock.invocationCallOrder[0], + calls.dashboard.mock.invocationCallOrder[0], ); expect(calls.dashboard).toHaveBeenCalledWith("my-assistant", "model", "provider", null, agent); }); @@ -122,7 +123,6 @@ describe("handleFinalizationState", () => { await expect(handleFinalizationState(baseOptions(deps))).rejects.toThrow("verification failed"); expect(calls.postVerify).toHaveBeenCalledOnce(); - expect(calls.complete).not.toHaveBeenCalled(); expect(calls.dashboard).not.toHaveBeenCalled(); // The sandbox reached finalization (policies confirmed), so it stays the default // even when post-policy verification flakes — only a pre-policy cancel rolls back. diff --git a/src/lib/onboard/machine/handlers/finalization.ts b/src/lib/onboard/machine/handlers/finalization.ts index 760cdf1561..a89d0920c9 100644 --- a/src/lib/onboard/machine/handlers/finalization.ts +++ b/src/lib/onboard/machine/handlers/finalization.ts @@ -1,7 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import type { Session, SessionUpdates } from "../../../state/onboard-session"; +import type { Session } from "../../../state/onboard-session"; +import { completeOnboardMachine, type OnboardStateCompleteResult } from "../result"; export interface FinalizationStateOptions { sandboxName: string; @@ -23,8 +24,7 @@ export interface FinalizationStateOptions; - recordSessionComplete(updates: SessionUpdates): Promise; - toSessionUpdates(updates: Record): SessionUpdates; + toSessionUpdates(updates: Record): NonNullable; removeLegacyCredentialsFile(): void; cleanupStaleHostFiles(): void; checkAndRecoverSandboxProcesses(sandboxName: string, options: { quiet: boolean }): void; @@ -52,7 +52,7 @@ export interface FinalizationStateOptions { + return this.getRuntime().applyResult(result); + } + async recordResumeConflict(conflict: ResumeConfigConflict): Promise { return this.getRuntime().emitResumeConflict(conflict); }