Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 2 additions & 4 deletions src/lib/onboard/machine/core-flow-phases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import type { WebSearchConfig } from "../../inference/web-search";
import type { OnboardFlowContext } from "./flow-context";
import { assertProviderSelectedContext, type OnboardFlowContext } from "./flow-context";
import { runCoreOnboardFlowSequence } from "./flow-slices";
import {
handleProviderInferenceState,
Expand Down Expand Up @@ -97,9 +97,7 @@ export function createCoreOnboardFlowPhases<
const sandboxPhase: OnboardSequencePhase<Context> = {
state: "sandbox",
async run(context) {
if (!context.model || !context.provider || !context.sandboxGpuConfig) {
throw new Error("Onboarding state is incomplete before sandbox setup.");
}
assertProviderSelectedContext(context, "sandbox setup");
const sandboxStateResult = await handleSandboxState({
resume: context.resume,
fresh: context.fresh,
Expand Down
17 changes: 4 additions & 13 deletions src/lib/onboard/machine/final-flow-phases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import type { WebSearchConfig } from "../../inference/web-search";
import type { OnboardFlowContext } from "./flow-context";
import { assertSandboxCreatedContext, type OnboardFlowContext } from "./flow-context";
import {
createAgentSetupPhase,
createFinalizationPhase,
Expand Down Expand Up @@ -38,15 +38,6 @@ export interface FinalOnboardFlowPhaseOptions<
>["deps"];
}

function requireFinalContext<Context extends OnboardFlowContext>(
context: Context,
stepName: string,
): asserts context is Context & { sandboxName: string; model: string; provider: string } {
if (!context.sandboxName || !context.model || !context.provider) {
throw new Error(`Onboarding state is incomplete before ${stepName}.`);
}
}

export function createFinalOnboardFlowPhases<
Context extends OnboardFlowContext,
VerifyChain = unknown,
Expand All @@ -57,7 +48,7 @@ export function createFinalOnboardFlowPhases<
const createBranchPhase =
options.branchState === "agent_setup" ? createAgentSetupPhase : createOpenclawSetupPhase;
const branchSetupPhase = createBranchPhase<Context>(async (context) => {
requireFinalContext(context, "agent setup");
assertSandboxCreatedContext(context, "agent setup");
const agentSetupResult = await handleAgentSetupState({
agent: context.agent,
sandboxName: context.sandboxName,
Expand All @@ -76,7 +67,7 @@ export function createFinalOnboardFlowPhases<
});

const policiesPhase = createPoliciesPhase<Context>(async (context) => {
requireFinalContext(context, "policies");
assertSandboxCreatedContext(context, "policies");
const policiesResult = await handlePoliciesState({
resume: context.resume,
sandboxName: context.sandboxName,
Expand All @@ -101,7 +92,7 @@ export function createFinalOnboardFlowPhases<
});

const finalizationPhase = createFinalizationPhase<Context>(async (context) => {
requireFinalContext(context, "finalization");
assertSandboxCreatedContext(context, "finalization");
const finalizationResult = await handleFinalizationState({
sandboxName: context.sandboxName,
model: context.model,
Expand Down
43 changes: 41 additions & 2 deletions src/lib/onboard/machine/flow-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
import { describe, expect, it } from "vitest";

import { createSession } from "../../state/onboard-session";
import { advanceTo } from "./result";
import {
assertProviderSelectedContext,
assertSandboxCreatedContext,
mergeOnboardFlowContext,
onboardFlowPhaseResult,
type OnboardFlowContext,
onboardFlowPhaseResult,
} from "./flow-context";
import { advanceTo } from "./result";

function baseContext(): OnboardFlowContext<null, { type: string }, { mode: string }> {
return {
Expand Down Expand Up @@ -61,4 +63,41 @@ describe("onboard flow context helpers", () => {
expect(result.context.provider).toBe("nvidia-prod");
expect(result.result).toMatchObject({ next: "gateway", transitionKind: "advance" });
});

it("asserts provider-selected context before sandbox setup", () => {
const context = mergeOnboardFlowContext(baseContext(), {
provider: "nvidia-prod",
model: "model",
});

expect(() => assertProviderSelectedContext(context, "sandbox setup")).not.toThrow();
});

it("rejects missing provider-selected context fields", () => {
expect(() => assertProviderSelectedContext(baseContext(), "sandbox setup")).toThrow(
/Onboarding state is incomplete before sandbox setup\./,
);
});

it("asserts sandbox-created context before final phases", () => {
const context = mergeOnboardFlowContext(baseContext(), {
sandboxName: "my-assistant",
provider: "nvidia-prod",
model: "model",
sandboxGpuConfig: null,
});

expect(() => assertSandboxCreatedContext(context, "policies")).not.toThrow();
});

it("rejects missing sandbox name before final phases", () => {
const context = mergeOnboardFlowContext(baseContext(), {
provider: "nvidia-prod",
model: "model",
});

expect(() => assertSandboxCreatedContext(context, "policies")).toThrow(
/Onboarding state is incomplete before policies\./,
);
});
});
33 changes: 33 additions & 0 deletions src/lib/onboard/machine/flow-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,44 @@ export interface OnboardFlowContext<Agent = unknown, Gpu = unknown, SandboxGpuCo
gpuPassthrough: boolean;
}

export type ProviderSelectedOnboardFlowContext<Context extends OnboardFlowContext> = Context & {
model: string;
provider: string;
sandboxGpuConfig: NonNullable<Context["sandboxGpuConfig"]>;
};

export type SandboxCreatedOnboardFlowContext<Context extends OnboardFlowContext> = Context & {
sandboxName: string;
model: string;
provider: string;
};

export type FinalOnboardFlowContext<Context extends OnboardFlowContext> =
SandboxCreatedOnboardFlowContext<Context>;

export interface OnboardFlowPhaseResult<Context extends OnboardFlowContext = OnboardFlowContext> {
context: Context;
result: OnboardStateHandlerResult;
}

export function assertProviderSelectedContext<Context extends OnboardFlowContext>(
context: Context,
stepName: string,
): asserts context is ProviderSelectedOnboardFlowContext<Context> {
if (!context.model || !context.provider || !context.sandboxGpuConfig) {
throw new Error(`Onboarding state is incomplete before ${stepName}.`);
}
}

export function assertSandboxCreatedContext<Context extends OnboardFlowContext>(
context: Context,
stepName: string,
): asserts context is SandboxCreatedOnboardFlowContext<Context> {
if (!context.sandboxName || !context.model || !context.provider) {
throw new Error(`Onboarding state is incomplete before ${stepName}.`);
}
}

export function mergeOnboardFlowContext<Context extends OnboardFlowContext>(
context: Context,
patch: Partial<Context>,
Expand Down