From b6c3b7c775e0ea5ebd9a6f597a781a1c957d1308 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Wed, 5 Aug 2026 13:06:28 -0700 Subject: [PATCH 1/5] refactor(onboard): isolate policy application Signed-off-by: Carlos Villela --- ci/source-architecture-budget.json | 2 +- src/lib/onboard.ts | 142 ++++++++-------------------- src/lib/onboard/policy-selection.ts | 118 ++++++++++++++++++++++- 3 files changed, 152 insertions(+), 110 deletions(-) diff --git a/ci/source-architecture-budget.json b/ci/source-architecture-budget.json index fbeabf1555b..d818b4b5604 100644 --- a/ci/source-architecture-budget.json +++ b/ci/source-architecture-budget.json @@ -47,7 +47,7 @@ "src/lib/actions/uninstall/run-plan.ts": 26, "src/lib/inference/onboard-probes.ts": 21, "src/lib/inference/vllm.ts": 21, - "src/lib/onboard.ts": 219, + "src/lib/onboard.ts": 212, "src/lib/onboard/machine/handlers/sandbox.ts": 21, "src/lib/sandbox/config.ts": 22, "src/lib/shields/index.ts": 23 diff --git a/src/lib/onboard.ts b/src/lib/onboard.ts index 4a4edc19b98..48d110433ca 100644 --- a/src/lib/onboard.ts +++ b/src/lib/onboard.ts @@ -126,9 +126,6 @@ const { isLinuxDockerDriverGatewayEnabled } = dockerDriverPlatform; const { reconcileGatewayGpuReuseForGpuIntent, }: typeof import("./onboard/gateway-gpu-passthrough") = require("./onboard/gateway-gpu-passthrough"); -const { - syncPresetSelection, -}: typeof import("./onboard/policy-preset-sync") = require("./onboard/policy-preset-sync"); const { maybeForceE2eStepFailure, }: typeof import("./onboard/e2e-failure-injection") = require("./onboard/e2e-failure-injection"); @@ -494,10 +491,7 @@ const { }: typeof import("./onboard/machine/initial-flow-composition") = require("./onboard/machine/initial-flow-composition"); const { skippedStepMessage }: typeof import("./onboard/skipped-step-message") = require("./onboard/skipped-step-message"); -const policies: typeof import("./policy") = require("./policy"); const policyPresetCarry: typeof import("./onboard/policy-preset-persistence") = require("./onboard/policy-preset-persistence"); -const tiers: typeof import("./policy/tiers") = require("./policy/tiers"); -const policyTierEnv: typeof import("./onboard/policy-tier-env") = require("./onboard/policy-tier-env"); const { ensureUsageNoticeConsent } = require("./onboard/usage-notice"); const { findAvailableDashboardPort, @@ -588,7 +582,6 @@ import { setupHermesToolGateways, stringSetsEqual, } from "./onboard/hermes-managed-tools"; -import { mergePolicyMessagingChannels } from "./onboard/messaging-policy-presets"; import { filterEnabledChannelsByAgent } from "./onboard/messaging-state"; import { getValidatedMessagingTokenByEnvKey } from "./onboard/messaging-token"; import * as ollamaFlow from "./onboard/ollama-probe-failure"; @@ -599,15 +592,7 @@ import type { OpenShellInstallDeps, OpenShellInstallResult, } from "./onboard/openshell-install"; -import { getSuggestedPolicyPresets } from "./onboard/policy-presets"; -import { - computeSetupPresetSuggestions as computeSetupPresetSuggestionsImpl, - preparePolicyPresetResumeSelection, - type SetupPolicySelectionOptions, - type SetupPresetSuggestionOptions, - setupPoliciesWithSelection as setupPoliciesWithSelectionImpl, -} from "./onboard/policy-selection"; -import { createPolicySelectionPromptHelpers } from "./onboard/policy-selection-prompts"; +import { createOnboardPolicyApplication } from "./onboard/policy-selection"; import { printLowMemoryWarning, printMessagingProviderMissing, @@ -3712,89 +3697,6 @@ const setupOpenclaw = createOpenclawSetup({ cleanupTempDir, }); -// ── Step 7: Policy presets ─────────────────────────────────────── - -function arePolicyPresetsApplied(sandboxName: string, selectedPresets: string[] = []): boolean { - if (!Array.isArray(selectedPresets) || selectedPresets.length === 0) return false; - const applied = new Set(policies.getAppliedPresets(sandboxName)); - return selectedPresets.every((preset) => applied.has(preset)); -} - -function getPolicySelectionPromptHelpers(): ReturnType { - return createPolicySelectionPromptHelpers({ - tiers, - policyTierEnv, - isNonInteractive, - note, - prompt, - selectFromNumberedMenuOrExit, - makeOnboardCancelExit, - sandboxCancelRollback, - useColor: USE_COLOR, - }); -} - -async function selectPolicyTier(): Promise { - return getPolicySelectionPromptHelpers().selectPolicyTier(); -} - -async function selectTierPresetsAndAccess( - tierName: string, - allPresets: Array<{ name: string; description?: string }>, - initialSelected?: string[], -): Promise> { - return getPolicySelectionPromptHelpers().selectTierPresetsAndAccess( - tierName, - allPresets, - initialSelected, - ); -} - -async function presetsCheckboxSelector( - allPresets: Array<{ name: string; description: string }>, - initialSelected: string[], -): Promise { - return getPolicySelectionPromptHelpers().presetsCheckboxSelector(allPresets, initialSelected); -} - -const computeSetupPresetSuggestions = ( - tierName: string, - options: SetupPresetSuggestionOptions = {}, -): string[] => - computeSetupPresetSuggestionsImpl( - { policies, tiers, localInferenceProviders: [...LOCAL_INFERENCE_PROVIDERS, "llama-cpp-local"] }, - tierName, - options, - ); -async function setupPoliciesWithSelection( - sandboxName: string, - options: SetupPolicySelectionOptions = {}, -) { - return sandboxMutationLock.withSandboxMutationLock(sandboxName, () => - setupPoliciesWithSelectionImpl( - { - policies, - tiers, - localInferenceProviders: [...LOCAL_INFERENCE_PROVIDERS, "llama-cpp-local"], - step, - note, - isNonInteractive, - waitForSandboxReady, - waitForSandboxControlPlaneReady: finalizationHandlerDeps.waitForSandboxControlPlaneReady, - syncPresetSelection, - selectPolicyTier, - setPolicyTier: (s, t) => registry.updateSandbox(s, { policyTier: t }), - getRecordedPolicyTier: (s) => registry.getSandbox(s)?.policyTier ?? null, - selectTierPresetsAndAccess, - parsePolicyPresetEnv, - env: process.env, - }, - sandboxName, - options, - ), - ); -} - const { buildChain, buildControlUiUrls, @@ -3834,6 +3736,39 @@ const sandboxCancelRollback = installSandboxCancelRollback({ clearOnboardSession: onboardSession.clearSession, }); // #4614 +const { + arePolicyPresetsApplied, + computeSetupPresetSuggestions, + filterSetupPolicyPresets, + getSuggestedPolicyPresets, + mergePolicyMessagingChannels, + preparePolicyPresetResumeSelection, + presetsCheckboxSelector, + resolveSandboxBaselinePolicy, + selectPolicyTier, + selectTierPresetsAndAccess, + setupPoliciesWithSelection, + validatePolicyTierEnvEarly, +} = createOnboardPolicyApplication({ + localInferenceProviders: [...LOCAL_INFERENCE_PROVIDERS, "llama-cpp-local"], + step, + note, + isNonInteractive, + prompt, + selectFromNumberedMenuOrExit, + makeOnboardCancelExit, + sandboxCancelRollback, + useColor: USE_COLOR, + withSandboxMutationLock: sandboxMutationLock.withSandboxMutationLock, + waitForSandboxReady, + waitForSandboxControlPlaneReady: finalizationHandlerDeps.waitForSandboxControlPlaneReady, + setPolicyTier: (sandboxName, tierName) => + registry.updateSandbox(sandboxName, { policyTier: tierName }), + getRecordedPolicyTier: (sandboxName) => registry.getSandbox(sandboxName)?.policyTier ?? null, + parsePolicyPresetEnv, + env: process.env, +}); + const startRecordedStep = onboardRuntimeBoundary.startRecordedStep.bind(onboardRuntimeBoundary); const recordStepComplete = onboardRuntimeBoundary.recordStepComplete.bind(onboardRuntimeBoundary); const recordStepSkipped = onboardRuntimeBoundary.recordStepSkipped.bind(onboardRuntimeBoundary); @@ -3864,7 +3799,7 @@ async function preflightAuthoritativeRebuildTarget( await authoritativeRebuildTarget.preflightAuthoritativeRebuildTarget( { ...opts, controlUiPort: opts.controlUiPort ?? null }, { - resolveBaselinePolicy: (sandboxName) => policies.resolveSandboxBaselinePolicy(sandboxName), + resolveBaselinePolicy: resolveSandboxBaselinePolicy, runFatalRuntimePreflight: () => fatalRuntimePreflight.runFatalOnboardRuntimePreflight( { @@ -3943,7 +3878,7 @@ async function runOnboard(opts: OnboardOptions = {}): Promise { initialHint: opts.baseImageResolutionHint, initialPreResolvedMetadata: opts.preResolvedBaseImageMetadata, }); - if (isNonInteractive()) policyTierEnv.validatePolicyTierEnvEarly(); + if (isNonInteractive()) validatePolicyTierEnvEarly(); const noticeAccepted = await ensureUsageNoticeConsent({ nonInteractive: isNonInteractive(), acceptedByFlag: opts.acceptThirdPartySoftware === true, @@ -4467,8 +4402,7 @@ async function runOnboard(opts: OnboardOptions = {}): Promise { mergePolicyMessagingChannels, // biome-ignore format: keep src/lib/onboard.ts net-neutral for growth guardrail. verifyCompatibleEndpointSandboxSmoke: (options) => verifyCompatibleEndpointSandboxSmoke({ ...options, runOpenshell: runCoreGatewayOpenshell, redact }), - preparePolicyPresetResumeSelection: (name, options) => - preparePolicyPresetResumeSelection({ policies }, name, options), + preparePolicyPresetResumeSelection, arePolicyPresetsApplied, skippedStepMessage, recordStateSkipped, @@ -4675,7 +4609,7 @@ module.exports = { getSuggestedPolicyPresets, computeSetupPresetSuggestions, mergeRequiredHermesToolGatewayPolicyPresets, - filterSetupPolicyPresets: policies.filterSetupPolicyPresets, + filterSetupPolicyPresets, LOCAL_INFERENCE_PROVIDERS, presetsCheckboxSelector, selectPolicyTier, diff --git a/src/lib/onboard/policy-selection.ts b/src/lib/onboard/policy-selection.ts index ca3fb31c723..728c760c867 100644 --- a/src/lib/onboard/policy-selection.ts +++ b/src/lib/onboard/policy-selection.ts @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import { type WebSearchConfig, webSearchProviderForConfig } from "../inference/web-search"; +import * as policies from "../policy"; +import * as tiers from "../policy/tiers"; import { filterSetupPolicyPresetNamesForAgent, filterSetupPolicyPresetsForAgent, @@ -11,7 +13,10 @@ import { allHermesToolGatewayPolicyPresets, HERMES_TOOL_GATEWAY_PRESET_NAMES, } from "./hermes-managed-tools"; -import { allMessagingChannelPolicyPresets } from "./messaging-policy-presets"; +import { + allMessagingChannelPolicyPresets, + mergePolicyMessagingChannels, +} from "./messaging-policy-presets"; import { isInactiveObservabilityPolicyPreset, OBSERVABILITY_OTLP_LOCAL_POLICY_PRESET, @@ -23,6 +28,17 @@ import { isStaleBuiltinWebSearchPolicyPreset, mergeRequiredSetupPolicyPresets, } from "./policy-preset-reconciliation"; +import { syncPresetSelection } from "./policy-preset-sync"; +import { getSuggestedPolicyPresets } from "./policy-presets"; +import { + type PreparedPolicyResumeSelection, + preparePolicyPresetResumeSelection, +} from "./policy-resume-selection"; +import { + createPolicySelectionPromptHelpers, + type PolicySelectionPromptDeps, +} from "./policy-selection-prompts"; +import * as policyTierEnv from "./policy-tier-env"; import { agentRequiredPresetAdditions, emitSuppressedAgentRequiredPresetsNote, @@ -37,6 +53,21 @@ export { } from "./policy-preset-reconciliation"; export { suppressedAgentRequiredPresets } from "./policy-tier-suppression"; +export type OnboardPolicyApplicationDeps = Omit< + PolicySelectionPromptDeps, + "tiers" | "policyTierEnv" +> & { + step: (number: number, total: number, title: string) => void; + localInferenceProviders: readonly string[]; + withSandboxMutationLock: typeof import("../state/mcp-lifecycle-lock").withSandboxMutationLock; + waitForSandboxReady(sandboxName: string): boolean; + waitForSandboxControlPlaneReady(sandboxName: string): boolean; + setPolicyTier(sandboxName: string, tierName: string): void; + getRecordedPolicyTier(sandboxName: string): string | null | undefined; + parsePolicyPresetEnv(raw: string): string[]; + env: NodeJS.ProcessEnv; +}; + type Preset = { name: string; access?: string }; type SupportOptions = { webSearchSupported?: boolean | null; agent?: string | null }; type PoliciesApi = { @@ -115,6 +146,86 @@ export type SetupPolicySelectionDeps = { env?: NodeJS.ProcessEnv; }; +export function createOnboardPolicyApplication(deps: OnboardPolicyApplicationDeps) { + const promptHelpers = () => + createPolicySelectionPromptHelpers({ + ...deps, + tiers, + policyTierEnv, + }); + const selectPolicyTier = () => promptHelpers().selectPolicyTier(); + const selectTierPresetsAndAccess = ( + tierName: string, + allPresets: Array<{ name: string; description?: string }>, + initialSelected?: string[], + ) => promptHelpers().selectTierPresetsAndAccess(tierName, allPresets, initialSelected); + const presetsCheckboxSelector = ( + allPresets: Array<{ name: string; description: string }>, + initialSelected: string[], + ) => promptHelpers().presetsCheckboxSelector(allPresets, initialSelected); + const setupDeps: SetupPolicySelectionDeps = { + policies, + tiers, + localInferenceProviders: deps.localInferenceProviders, + step: deps.step, + note: deps.note, + isNonInteractive: deps.isNonInteractive, + waitForSandboxReady: deps.waitForSandboxReady, + waitForSandboxControlPlaneReady: deps.waitForSandboxControlPlaneReady, + syncPresetSelection, + selectPolicyTier, + setPolicyTier: deps.setPolicyTier, + getRecordedPolicyTier: deps.getRecordedPolicyTier, + selectTierPresetsAndAccess, + parsePolicyPresetEnv: deps.parsePolicyPresetEnv, + env: deps.env, + }; + + return { + arePolicyPresetsApplied(sandboxName: string, selectedPresets: string[] = []): boolean { + if (!Array.isArray(selectedPresets) || selectedPresets.length === 0) return false; + const applied = new Set(policies.getAppliedPresets(sandboxName)); + return selectedPresets.every((preset) => applied.has(preset)); + }, + computeSetupPresetSuggestions( + tierName: string, + options: SetupPresetSuggestionOptions = {}, + ): string[] { + return computeSetupPresetSuggestions( + { + policies, + tiers, + localInferenceProviders: deps.localInferenceProviders, + }, + tierName, + options, + ); + }, + filterSetupPolicyPresets: policies.filterSetupPolicyPresets, + getSuggestedPolicyPresets, + mergePolicyMessagingChannels, + preparePolicyPresetResumeSelection( + sandboxName: string, + options: Parameters[2], + ): PreparedPolicyResumeSelection { + return preparePolicyPresetResumeSelection({ policies }, sandboxName, options); + }, + presetsCheckboxSelector, + resolveSandboxBaselinePolicy: policies.resolveSandboxBaselinePolicy, + selectPolicyTier, + selectTierPresetsAndAccess, + setupPoliciesWithSelection( + sandboxName: string, + options: SetupPolicySelectionOptions = {}, + ): Promise { + return deps.withSandboxMutationLock(sandboxName, () => + setupPoliciesWithSelection(setupDeps, sandboxName, options), + ); + }, + validatePolicyTierEnvEarly: policyTierEnv.validatePolicyTierEnvEarly, + }; +} + export function computeSetupPresetSuggestions( deps: { policies: PoliciesApi; @@ -210,10 +321,7 @@ export function computeSetupPresetSuggestions( return suggestions; } -export { - preparePolicyPresetResumeSelection, - type PreparedPolicyResumeSelection, -} from "./policy-resume-selection"; +export { type PreparedPolicyResumeSelection, preparePolicyPresetResumeSelection }; export async function setupPoliciesWithSelection( deps: SetupPolicySelectionDeps, From 55eb69187637acdaa38d4d371dc5d555fdcdc11a Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Wed, 5 Aug 2026 16:42:01 -0700 Subject: [PATCH 2/5] test(onboard): protect policy application lock Signed-off-by: Carlos Villela --- .../policy-selection-application.test.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/lib/onboard/policy-selection-application.test.ts diff --git a/src/lib/onboard/policy-selection-application.test.ts b/src/lib/onboard/policy-selection-application.test.ts new file mode 100644 index 00000000000..5f810151317 --- /dev/null +++ b/src/lib/onboard/policy-selection-application.test.ts @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { describe, expect, it, vi } from "vitest"; +import { selectFromNumberedMenuOrExit } from "./prompt-helpers"; +import { + createOnboardPolicyApplication, + type OnboardPolicyApplicationDeps, +} from "./policy-selection"; + +describe("onboarding policy application", () => { + it("runs policy setup through the sandbox mutation lock", async () => { + const lockResult = ["npm"]; + const withSandboxMutationLock = vi.fn(async () => lockResult); + const application = createOnboardPolicyApplication({ + localInferenceProviders: [], + step: vi.fn(), + note: vi.fn(), + isNonInteractive: vi.fn(() => true), + prompt: vi.fn(async () => ""), + selectFromNumberedMenuOrExit, + makeOnboardCancelExit: (rollback, cleanup) => () => { + cleanup(); + rollback.markCancelled(); + }, + sandboxCancelRollback: { markCancelled: vi.fn() }, + useColor: false, + withSandboxMutationLock: + withSandboxMutationLock as unknown as OnboardPolicyApplicationDeps["withSandboxMutationLock"], + waitForSandboxReady: vi.fn(() => true), + waitForSandboxControlPlaneReady: vi.fn(() => true), + setPolicyTier: vi.fn(), + getRecordedPolicyTier: vi.fn(() => null), + parsePolicyPresetEnv: vi.fn(() => []), + env: {}, + }); + + await expect( + application.setupPoliciesWithSelection("alpha", { selectedPresets: ["npm"] }), + ).resolves.toEqual(lockResult); + expect(withSandboxMutationLock).toHaveBeenCalledOnce(); + expect(withSandboxMutationLock).toHaveBeenCalledWith("alpha", expect.any(Function)); + }); +}); From a1ddcd9623c689c3c320939966ff15cebba8b68d Mon Sep 17 00:00:00 2001 From: Prekshi Vyas Date: Wed, 5 Aug 2026 18:19:19 -0700 Subject: [PATCH 3/5] test(onboard): exercise policy lock callback Signed-off-by: Prekshi Vyas --- .../policy-selection-application.test.ts | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/src/lib/onboard/policy-selection-application.test.ts b/src/lib/onboard/policy-selection-application.test.ts index 5f810151317..f87fb1fea4b 100644 --- a/src/lib/onboard/policy-selection-application.test.ts +++ b/src/lib/onboard/policy-selection-application.test.ts @@ -2,16 +2,45 @@ // SPDX-License-Identifier: Apache-2.0 import { describe, expect, it, vi } from "vitest"; -import { selectFromNumberedMenuOrExit } from "./prompt-helpers"; import { createOnboardPolicyApplication, type OnboardPolicyApplicationDeps, } from "./policy-selection"; +import { selectFromNumberedMenuOrExit } from "./prompt-helpers"; + +const { seedInitialPolicyContext, syncPresetSelection } = vi.hoisted(() => ({ + seedInitialPolicyContext: vi.fn(), + syncPresetSelection: vi.fn(), +})); + +vi.mock("../policy", () => ({ + clampSetupPolicyPresetNames: vi.fn((names: string[]) => names), + customPresetOwnsNetworkPolicyKey: vi.fn(() => false), + filterSetupPolicyPresets: vi.fn(), + getAppliedPresets: vi.fn(() => []), + listCustomPresets: vi.fn(() => []), + listSetupPolicyPresets: vi.fn(() => [{ name: "npm" }]), + resolveSandboxBaselinePolicy: vi.fn(), + setupPolicyPresetSupported: vi.fn(() => true), +})); +vi.mock("./policy-context-seed", () => ({ seedInitialPolicyContext })); +vi.mock("./policy-preset-sync", () => ({ syncPresetSelection })); describe("onboarding policy application", () => { it("runs policy setup through the sandbox mutation lock", async () => { - const lockResult = ["npm"]; - const withSandboxMutationLock = vi.fn(async () => lockResult); + const events: string[] = []; + const withSandboxMutationLock: OnboardPolicyApplicationDeps["withSandboxMutationLock"] = vi.fn( + async (_sandboxName, action) => { + events.push("lock entered"); + try { + return await action(); + } finally { + events.push("lock released"); + } + }, + ); + syncPresetSelection.mockImplementation(() => events.push("policies synchronized")); + seedInitialPolicyContext.mockImplementation(() => events.push("policy context seeded")); const application = createOnboardPolicyApplication({ localInferenceProviders: [], step: vi.fn(), @@ -25,8 +54,7 @@ describe("onboarding policy application", () => { }, sandboxCancelRollback: { markCancelled: vi.fn() }, useColor: false, - withSandboxMutationLock: - withSandboxMutationLock as unknown as OnboardPolicyApplicationDeps["withSandboxMutationLock"], + withSandboxMutationLock, waitForSandboxReady: vi.fn(() => true), waitForSandboxControlPlaneReady: vi.fn(() => true), setPolicyTier: vi.fn(), @@ -37,8 +65,15 @@ describe("onboarding policy application", () => { await expect( application.setupPoliciesWithSelection("alpha", { selectedPresets: ["npm"] }), - ).resolves.toEqual(lockResult); + ).resolves.toEqual(["npm"]); expect(withSandboxMutationLock).toHaveBeenCalledOnce(); expect(withSandboxMutationLock).toHaveBeenCalledWith("alpha", expect.any(Function)); + expect(syncPresetSelection).toHaveBeenCalledWith("alpha", [], ["npm"]); + expect(events).toEqual([ + "lock entered", + "policies synchronized", + "policy context seeded", + "lock released", + ]); }); }); From 4904af8badaa7e4030580f73dcccad17417aa227 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Fri, 7 Aug 2026 10:34:18 -0700 Subject: [PATCH 4/5] test(onboard): cover production policy wiring --- .../policy-selection-application.test.ts | 2 +- .../onboard-policy-application-wiring.test.ts | 155 ++++++++++++++++++ 2 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 test/onboard-policy-application-wiring.test.ts diff --git a/src/lib/onboard/policy-selection-application.test.ts b/src/lib/onboard/policy-selection-application.test.ts index f87fb1fea4b..6e63e94722f 100644 --- a/src/lib/onboard/policy-selection-application.test.ts +++ b/src/lib/onboard/policy-selection-application.test.ts @@ -27,7 +27,7 @@ vi.mock("./policy-context-seed", () => ({ seedInitialPolicyContext })); vi.mock("./policy-preset-sync", () => ({ syncPresetSelection })); describe("onboarding policy application", () => { - it("runs policy setup through the sandbox mutation lock", async () => { + it("runs policy application while holding the sandbox mutation lock", async () => { const events: string[] = []; const withSandboxMutationLock: OnboardPolicyApplicationDeps["withSandboxMutationLock"] = vi.fn( async (_sandboxName, action) => { diff --git a/test/onboard-policy-application-wiring.test.ts b/test/onboard-policy-application-wiring.test.ts new file mode 100644 index 00000000000..80aecda31d1 --- /dev/null +++ b/test/onboard-policy-application-wiring.test.ts @@ -0,0 +1,155 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { createRequire } from "node:module"; +import { describe, expect, it, vi } from "vitest"; +import type { OnboardPolicyApplicationDeps } from "../src/lib/onboard/policy-selection.js"; + +const require = createRequire(import.meta.url); + +type PolicySelectionModule = typeof import("../src/lib/onboard/policy-selection.js"); +type PolicyApplication = ReturnType; + +function replaceCachedExports(modulePath: string, exports: unknown): void { + const cached = require.cache[modulePath]; + if (!cached) throw new Error(`Expected ${modulePath} to be loaded`); + cached.exports = exports; +} + +function restoreRequireCache(prior: Map): void { + for (const modulePath of Object.keys(require.cache)) { + if (!prior.has(modulePath)) delete require.cache[modulePath]; + } + for (const [modulePath, cached] of prior) require.cache[modulePath] = cached; +} + +describe("onboarding policy application production wiring", () => { + it("wires resume policy application to the sandbox registry, readiness checks, and sandbox mutation lock (#7695)", async () => { + const priorCache = new Map( + Object.entries(require.cache).filter( + (entry): entry is [string, NodeModule] => entry[1] !== undefined, + ), + ); + const events: string[] = []; + const getSandbox = vi.fn(() => { + events.push("registry tier read"); + return { policyTier: "restricted" }; + }); + const updateSandbox = vi.fn(); + const waitForSandboxReady = vi.fn(() => { + events.push("sandbox ready"); + return true; + }); + const waitForSandboxControlPlaneReady = vi.fn(() => { + events.push("control plane ready"); + return true; + }); + const withSandboxMutationLock: OnboardPolicyApplicationDeps["withSandboxMutationLock"] = vi.fn( + async (_sandboxName, action) => { + events.push("lock entered"); + try { + return await action(); + } finally { + events.push("lock released"); + } + }, + ); + const syncPresetSelection = vi.fn(() => events.push("policies synchronized")); + const seedInitialPolicyContext = vi.fn(() => events.push("policy context seeded")); + let capturedDeps: OnboardPolicyApplicationDeps | undefined; + let application: PolicyApplication | undefined; + + const onboardPath = require.resolve("../src/lib/onboard.js"); + const policyPath = require.resolve("../src/lib/policy/index.js"); + const syncPath = require.resolve("../src/lib/onboard/policy-preset-sync.js"); + const seedPath = require.resolve("../src/lib/onboard/policy-context-seed.js"); + const policySelectionPath = require.resolve("../src/lib/onboard/policy-selection.js"); + const registryPath = require.resolve("../src/lib/state/registry.js"); + const lockPath = require.resolve("../src/lib/state/mcp-lifecycle-lock.js"); + const readinessPath = require.resolve("../src/lib/onboard/sandbox-readiness-tracing.js"); + const finalFlowPath = require.resolve("../src/lib/onboard/machine/final-flow-composition.js"); + + try { + const policy = require(policyPath) as Record; + replaceCachedExports(policyPath, { + ...policy, + clampSetupPolicyPresetNames: vi.fn((names: string[]) => [...names]), + customPresetOwnsNetworkPolicyKey: vi.fn(() => false), + getAppliedPresets: vi.fn(() => []), + listCustomPresets: vi.fn(() => []), + listSetupPolicyPresets: vi.fn(() => [{ name: "npm" }]), + setupPolicyPresetSupported: vi.fn(() => true), + }); + + require(syncPath); + replaceCachedExports(syncPath, { syncPresetSelection }); + require(seedPath); + replaceCachedExports(seedPath, { seedInitialPolicyContext }); + + delete require.cache[policySelectionPath]; + const policySelection = require(policySelectionPath) as PolicySelectionModule; + replaceCachedExports(policySelectionPath, { + ...policySelection, + createOnboardPolicyApplication: (deps: OnboardPolicyApplicationDeps) => { + capturedDeps = deps; + application = policySelection.createOnboardPolicyApplication(deps); + return application; + }, + }); + + const registry = require(registryPath) as Record; + replaceCachedExports(registryPath, { ...registry, getSandbox, updateSandbox }); + const lock = require(lockPath) as Record; + replaceCachedExports(lockPath, { ...lock, withSandboxMutationLock }); + const readiness = require(readinessPath) as Record; + replaceCachedExports(readinessPath, { + ...readiness, + createSandboxReadyWaiter: vi.fn(() => waitForSandboxReady), + }); + const finalFlow = require(finalFlowPath) as { + finalizationHandlerDeps: Record; + [key: string]: unknown; + }; + replaceCachedExports(finalFlowPath, { + ...finalFlow, + finalizationHandlerDeps: { + ...finalFlow.finalizationHandlerDeps, + waitForSandboxControlPlaneReady, + }, + }); + + delete require.cache[onboardPath]; + require(onboardPath); + + if (!capturedDeps || !application) { + throw new Error("Expected onboard.ts to create the policy application"); + } + expect(capturedDeps.withSandboxMutationLock).toBe(withSandboxMutationLock); + expect(capturedDeps.waitForSandboxReady).toBe(waitForSandboxReady); + expect(capturedDeps.waitForSandboxControlPlaneReady).toBe(waitForSandboxControlPlaneReady); + + capturedDeps.setPolicyTier("beta", "balanced"); + expect(updateSandbox).toHaveBeenCalledWith("beta", { policyTier: "balanced" }); + + await expect( + application.setupPoliciesWithSelection("alpha", { selectedPresets: ["npm"] }), + ).resolves.toEqual(["npm"]); + expect(getSandbox).toHaveBeenCalledWith("alpha"); + expect(waitForSandboxReady).toHaveBeenCalledTimes(2); + expect(waitForSandboxControlPlaneReady).toHaveBeenCalledOnce(); + expect(syncPresetSelection).toHaveBeenCalledWith("alpha", [], ["npm"]); + expect(events).toEqual([ + "lock entered", + "registry tier read", + "sandbox ready", + "policies synchronized", + "sandbox ready", + "control plane ready", + "policy context seeded", + "lock released", + ]); + } finally { + restoreRequireCache(priorCache); + } + }); +}); From 8848ca9541efe3125c158c151e81dd55c8d032bf Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Fri, 7 Aug 2026 10:44:12 -0700 Subject: [PATCH 5/5] test(onboard): keep policy wiring test linear --- test/onboard-policy-application-wiring.test.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/test/onboard-policy-application-wiring.test.ts b/test/onboard-policy-application-wiring.test.ts index 80aecda31d1..f9a7c66fdba 100644 --- a/test/onboard-policy-application-wiring.test.ts +++ b/test/onboard-policy-application-wiring.test.ts @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import assert from "node:assert/strict"; + import { createRequire } from "node:module"; import { describe, expect, it, vi } from "vitest"; import type { OnboardPolicyApplicationDeps } from "../src/lib/onboard/policy-selection.js"; @@ -12,14 +14,15 @@ type PolicyApplication = ReturnType): void { - for (const modulePath of Object.keys(require.cache)) { - if (!prior.has(modulePath)) delete require.cache[modulePath]; - } + const addedModulePaths = Object.keys(require.cache).filter( + (modulePath) => !prior.has(modulePath), + ); + for (const modulePath of addedModulePaths) delete require.cache[modulePath]; for (const [modulePath, cached] of prior) require.cache[modulePath] = cached; } @@ -121,9 +124,8 @@ describe("onboarding policy application production wiring", () => { delete require.cache[onboardPath]; require(onboardPath); - if (!capturedDeps || !application) { - throw new Error("Expected onboard.ts to create the policy application"); - } + assert.ok(capturedDeps, "Expected onboard.ts to capture policy application dependencies"); + assert.ok(application, "Expected onboard.ts to create the policy application"); expect(capturedDeps.withSandboxMutationLock).toBe(withSandboxMutationLock); expect(capturedDeps.waitForSandboxReady).toBe(waitForSandboxReady); expect(capturedDeps.waitForSandboxControlPlaneReady).toBe(waitForSandboxControlPlaneReady);