Files
PaperClipAI/server/src/__tests__/instance-settings-service.test.ts
T
DottaandPaperclip 5e4cc01073 feat(workspaces): keep remote files off the webserver disk (#18403)
Keep native sandbox workspace files independent of the webserver disk. Use encrypted saved versions and Postgres ownership so a replacement server can recover files and continue work. Enable the path by default for new eligible workspaces when durable storage is configured.

Preserve explicit opt-outs and existing host workspaces. Include safe snapshot repair, Files access, import, migration and retention. Bound saved-Files resources, preserve imported Git recovery, reject unfinished Git operations, publish immutable concurrent captures, and verify permanent object-version deletion.

Validated 471 targeted local controls, build/typechecks, all 40 CI partitions, fresh Greptile 5/5 and security review. The PR records original failed attempts and scoped retries.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-31 14:54:01 -05:00

498 lines
22 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { INSTANCE_FEATURE_CATALOG, instanceExperimentalSettingsSchema, type InstanceExperimentalSettings } from "@paperclipai/shared";
import {
applyExperimentalSettingsPatch,
applyCloudCatalogDefaults,
applyManagedExperimentalOverlay,
normalizeExperimentalSettings,
resolveWorktreeRunExecutionActivationState,
} from "../services/instance-settings.js";
import type { ManagedInstanceConfig } from "../services/managed-config.js";
describe("instance settings service", () => {
it("defaults remote workspace enrollment on while preserving explicit operator choices", () => {
expect(instanceExperimentalSettingsSchema.parse({}).remoteWorkspaceStorageEnabled).toBe(true);
expect(INSTANCE_FEATURE_CATALOG.remoteWorkspaceStorageEnabled).toMatchObject({ cloudDefault: true, selfHostedDefault: true });
for (const stored of [undefined, null, {}, { enableApps: true }, { remoteWorkspaceStorageEnabled: "invalid" }]) {
expect(normalizeExperimentalSettings(stored).remoteWorkspaceStorageEnabled).toBe(true);
}
const disabled = applyExperimentalSettingsPatch({}, { remoteWorkspaceStorageEnabled: false });
expect(normalizeExperimentalSettings(JSON.parse(JSON.stringify(disabled))).remoteWorkspaceStorageEnabled).toBe(false);
expect(normalizeExperimentalSettings({ remoteWorkspaceStorageEnabled: false, enableCases: "invalid" }).remoteWorkspaceStorageEnabled).toBe(false);
expect(applyExperimentalSettingsPatch(disabled, { enableStatusCards: true }).remoteWorkspaceStorageEnabled).toBe(false);
expect(applyExperimentalSettingsPatch(disabled, { remoteWorkspaceStorageEnabled: true }).remoteWorkspaceStorageEnabled).toBe(true);
});
it.each([
{ stored: {}, managed: undefined, expected: true },
{ stored: { remoteWorkspaceStorageEnabled: false }, managed: undefined, expected: false },
{ stored: { remoteWorkspaceStorageEnabled: false }, managed: true, expected: true },
{ stored: { remoteWorkspaceStorageEnabled: true }, managed: false, expected: false },
])("respects Cloud enrollment defaults and managed choices: $expected", ({ stored, managed, expected }) => {
const config: ManagedInstanceConfig = {
v: 1, mode: "cloud", catalogVersion: "test", plugins: { autoInstall: [] }, environments: [],
features: managed === undefined ? {} : { remoteWorkspaceStorageEnabled: managed },
};
const normalized = normalizeExperimentalSettings(stored);
const result = applyManagedExperimentalOverlay(applyCloudCatalogDefaults(normalized, stored, config), config);
expect(result.experimental.remoteWorkspaceStorageEnabled).toBe(expected);
expect(normalized.remoteWorkspaceStorageEnabled).toBe("remoteWorkspaceStorageEnabled" in stored ? stored.remoteWorkspaceStorageEnabled : true);
expect(result.managedKeys.remoteWorkspaceStorageEnabled).toEqual(managed === undefined
? undefined : { managed: true, managedBy: "paperclip-cloud" });
});
it("keeps Notebook off for legacy or invalid settings and round-trips explicit operator choices", () => {
for (const stored of [undefined, null, {}, { enableApps: true },
{ enablePaperclipNotebook: "true" }, { enablePaperclipNotebook: 1 },
{ enablePaperclipNotebook: true, enableCases: "invalid" }]) {
expect(normalizeExperimentalSettings(stored).enablePaperclipNotebook).toBe(false);
}
const enabled = applyExperimentalSettingsPatch({}, { enablePaperclipNotebook: true });
const restored = normalizeExperimentalSettings(JSON.parse(JSON.stringify(enabled)));
expect(restored).toMatchObject({ enablePaperclipNotebook: true, enableMemoryConnectors: false });
const disabled = applyExperimentalSettingsPatch(restored, { enablePaperclipNotebook: false });
expect(normalizeExperimentalSettings(JSON.parse(JSON.stringify(disabled))).enablePaperclipNotebook).toBe(false);
});
it("keeps OpenAI Dot opt-in and persists its setting independently of its prerequisites", () => {
expect(normalizeExperimentalSettings({}).enableOpenAiDot).toBe(false);
const enabled = applyExperimentalSettingsPatch({}, { enableOpenAiDot: true });
expect(normalizeExperimentalSettings(JSON.parse(JSON.stringify(enabled)))).toMatchObject({
enableOpenAiDot: true, enablePublicMcp: false,
});
expect(applyExperimentalSettingsPatch(enabled, { enableOpenAiDot: false }).enableOpenAiDot).toBe(false);
});
it("keeps chat connectors opt-in across legacy storage and patches without disabling Apps", () => {
for (const stored of [undefined, {}, { enableApps: true }, { enableConferenceRoomChat: true }]) {
expect(normalizeExperimentalSettings(stored).enableChatConnectors).toBe(false);
}
const enabled = applyExperimentalSettingsPatch({}, { enableChatConnectors: true });
expect(normalizeExperimentalSettings(JSON.parse(JSON.stringify(enabled))).enableChatConnectors).toBe(true);
const disabled = applyExperimentalSettingsPatch(enabled, { enableChatConnectors: false });
expect(disabled).toMatchObject({ enableApps: true, enableChatConnectors: false });
});
it("keeps GitHub review bots independent across legacy normalization and persistence", () => {
expect(normalizeExperimentalSettings({ enableChatConnectors: true })).toMatchObject({ enableChatConnectors: true, enableGitHubReviewBots: false });
const saved = applyExperimentalSettingsPatch({}, { enableGitHubReviewBots: true });
expect(normalizeExperimentalSettings(JSON.parse(JSON.stringify(saved)))).toMatchObject({ enableChatConnectors: false, enableGitHubReviewBots: true });
});
it("ignores retired experimental flags without resetting current settings", () => {
expect(normalizeExperimentalSettings({
enableEnvironments: true,
enableNativeRunner: false,
enableAiConnectionRouters: false,
enableManagedSandboxOnly: false,
enableIsolatedWorkspaces: true,
enableIssuePlanDecompositions: true,
enableExperimentalFileViewer: true,
enableBuiltInAgents: true,
enableGoalsSidebarLink: true,
enableServerInfoDebugView: true,
enablePaperclipDeveloperMode: true,
autoRestartDevServerWhenIdle: true,
enableWorkspaceBranchReconcileForward: true,
enableWorkspaceDirtyQuarantineRepair: false,
enableNewestFirstIssueThread: true,
enablePipelines: true,
})).toEqual({
enableEnvironments: true,
enableNativeRunner: false,
remoteWorkspaceStorageEnabled: true,
enableAiConnectionRouters: false,
enableManagedSandboxOnly: false,
enableIsolatedWorkspaces: true,
enableIsolatedWorkspacesByDefault: false,
enableStreamlinedLeftNavigation: true,
enableStreamlinedUi: true,
enableApps: true,
enableMcpAggregators: true,
enableAgentChat: false,
enablePublicMcp: false,
enableOpenAiDot: false,
enableMuse: false,
enableGrokBot: false,
enableCombinedInboxTasks: false,
enableArtifactsV1: false,
enableChatConnectors: false,
enableGitHubReviewBots: false,
enableMemoryConnectors: false,
enablePaperclipNotebook: false,
enableConferenceRoomChat: false,
enableClassicTaskInterface: false,
enableExternalObjects: false,
enableSmokeLab: false,
enableCases: false,
enableIssuePlanDecompositions: true,
enableExperimentalFileViewer: true,
enableBuiltInAgents: true,
enableBetaSkills: false,
enableSummaries: false,
enableStatusCards: false,
enableDecisions: false,
enableFastResponses: false,
enableGoalsSidebarLink: true,
enableServerInfoDebugView: true,
enablePaperclipDeveloperMode: true,
enableSimplifiedEnglishInteractions: false,
enableFirstTaskPlanProposal: false,
autoRestartDevServerWhenIdle: true,
enableWorkspaceBranchReconcileForward: true,
enableWorkspaceDirtyQuarantineRepair: false,
enableOwnerInstanceAdmin: false,
enableSandboxDuplexBridge: false,
enableRunnerPreviewIngress: false,
enableWorktreeRunExecution: false,
worktreeRunExecutionActivatedAt: null,
worktreeRunExecutionActivationInstanceId: null,
});
});
it("defaults streamlined UI on without inheriting the retired navigation preference", () => {
expect(normalizeExperimentalSettings(undefined).enableStreamlinedUi).toBe(true);
expect(normalizeExperimentalSettings({}).enableStreamlinedUi).toBe(true);
expect(
normalizeExperimentalSettings({ enableStreamlinedLeftNavigation: false }).enableStreamlinedUi,
).toBe(true);
expect(normalizeExperimentalSettings({ enableStreamlinedUi: false }).enableStreamlinedUi).toBe(false);
});
it("keeps Apps on for empty, legacy, and explicitly disabled stored settings", () => {
expect(normalizeExperimentalSettings(undefined).enableApps).toBe(true);
expect(normalizeExperimentalSettings({}).enableApps).toBe(true);
expect(normalizeExperimentalSettings({ enableCases: true }).enableApps).toBe(true);
expect(normalizeExperimentalSettings({ enableApps: false }).enableApps).toBe(true);
});
it("retains the deprecated ingress key for stored-settings compatibility", () => {
expect(
normalizeExperimentalSettings({ enableRunnerPreviewIngress: true })
.enableRunnerPreviewIngress,
).toBe(true);
});
it("defaults enableConferenceRoomChat to false for empty and legacy stored settings", () => {
expect(normalizeExperimentalSettings(undefined).enableConferenceRoomChat).toBe(false);
expect(normalizeExperimentalSettings({}).enableConferenceRoomChat).toBe(false);
// Rows persisted before the flag existed (PAP-137) must normalize to off.
expect(
normalizeExperimentalSettings({ enableStreamlinedLeftNavigation: true }).enableConferenceRoomChat,
).toBe(false);
});
it("defaults enableClassicTaskInterface to false for empty and legacy stored settings", () => {
expect(normalizeExperimentalSettings(undefined).enableClassicTaskInterface).toBe(false);
expect(normalizeExperimentalSettings({}).enableClassicTaskInterface).toBe(false);
// The retired enableTaskChatRedesign key must not bleed into the new flag:
// an install that had the chat redesign ON opted into chat-style, which is
// now the default — not into the classic view.
expect(
normalizeExperimentalSettings({ enableTaskChatRedesign: true }).enableClassicTaskInterface,
).toBe(false);
expect(
normalizeExperimentalSettings({ enableClassicTaskInterface: true }).enableClassicTaskInterface,
).toBe(true);
});
it("defaults enableSimplifiedEnglishInteractions to false for empty and legacy stored settings", () => {
expect(normalizeExperimentalSettings(undefined).enableSimplifiedEnglishInteractions).toBe(false);
expect(normalizeExperimentalSettings({}).enableSimplifiedEnglishInteractions).toBe(false);
expect(
normalizeExperimentalSettings({ enableStreamlinedLeftNavigation: true })
.enableSimplifiedEnglishInteractions,
).toBe(false);
expect(
normalizeExperimentalSettings({ enableSimplifiedEnglishInteractions: true })
.enableSimplifiedEnglishInteractions,
).toBe(true);
});
it("defaults enableSmokeLab to false for empty and legacy stored settings", () => {
expect(normalizeExperimentalSettings(undefined).enableSmokeLab).toBe(false);
expect(normalizeExperimentalSettings({}).enableSmokeLab).toBe(false);
expect(
normalizeExperimentalSettings({ enableExternalObjects: true }).enableSmokeLab,
).toBe(false);
});
it("defaults enableServerInfoDebugView to false for empty and legacy stored settings", () => {
expect(normalizeExperimentalSettings(undefined).enableServerInfoDebugView).toBe(false);
expect(normalizeExperimentalSettings({}).enableServerInfoDebugView).toBe(false);
expect(
normalizeExperimentalSettings({ autoRestartDevServerWhenIdle: true }).enableServerInfoDebugView,
).toBe(false);
});
it("defaults enablePaperclipDeveloperMode to false for empty and legacy settings", () => {
expect(normalizeExperimentalSettings(undefined).enablePaperclipDeveloperMode).toBe(false);
expect(normalizeExperimentalSettings({}).enablePaperclipDeveloperMode).toBe(false);
expect(
normalizeExperimentalSettings({ enableServerInfoDebugView: true })
.enablePaperclipDeveloperMode,
).toBe(false);
});
it("defaults enableGoalsSidebarLink to false for empty and legacy stored settings", () => {
expect(normalizeExperimentalSettings(undefined).enableGoalsSidebarLink).toBe(false);
expect(normalizeExperimentalSettings({}).enableGoalsSidebarLink).toBe(false);
expect(
normalizeExperimentalSettings({ enableStreamlinedLeftNavigation: true }).enableGoalsSidebarLink,
).toBe(false);
});
it("defaults enableDecisions to false for empty and legacy stored settings", () => {
expect(normalizeExperimentalSettings(undefined).enableDecisions).toBe(false);
expect(normalizeExperimentalSettings({}).enableDecisions).toBe(false);
expect(
normalizeExperimentalSettings({ enableStreamlinedLeftNavigation: true }).enableDecisions,
).toBe(false);
});
it("defaults workspace branch repair settings to true for empty and legacy stored settings", () => {
expect(normalizeExperimentalSettings(undefined).enableWorkspaceBranchReconcileForward).toBe(true);
expect(normalizeExperimentalSettings({}).enableWorkspaceBranchReconcileForward).toBe(true);
expect(
normalizeExperimentalSettings({ enableExperimentalFileViewer: true })
.enableWorkspaceBranchReconcileForward,
).toBe(true);
expect(normalizeExperimentalSettings(undefined).enableWorkspaceDirtyQuarantineRepair).toBe(true);
expect(normalizeExperimentalSettings({}).enableWorkspaceDirtyQuarantineRepair).toBe(true);
expect(
normalizeExperimentalSettings({ enableWorkspaceBranchReconcileForward: false })
.enableWorkspaceDirtyQuarantineRepair,
).toBe(true);
});
it("round-trips an enableConferenceRoomChat patch through the update merge", () => {
// updateExperimental merges `{ ...normalize(current), ...patch }` and
// re-normalizes; emulate that to prove the flag survives the roundtrip
// without disturbing other settings.
const current = normalizeExperimentalSettings({});
const enabled = normalizeExperimentalSettings({ ...current, enableConferenceRoomChat: true });
expect(enabled.enableConferenceRoomChat).toBe(true);
expect(enabled.enableStreamlinedLeftNavigation).toBe(true);
const disabled = normalizeExperimentalSettings({ ...enabled, enableConferenceRoomChat: false });
expect(disabled).toEqual(current);
});
it("rejects non-boolean enableConferenceRoomChat values back to the default", () => {
expect(
normalizeExperimentalSettings({ enableConferenceRoomChat: "yes" }).enableConferenceRoomChat,
).toBe(false);
});
it("defaults enableBuiltInAgents to false for empty and legacy stored settings", () => {
expect(normalizeExperimentalSettings(undefined).enableBuiltInAgents).toBe(false);
expect(normalizeExperimentalSettings({}).enableBuiltInAgents).toBe(false);
expect(normalizeExperimentalSettings({ enableExternalObjects: true }).enableBuiltInAgents).toBe(false);
});
it("preserves enableBetaSkills and defaults it off for legacy stored settings", () => {
expect(normalizeExperimentalSettings(undefined).enableBetaSkills).toBe(false);
expect(normalizeExperimentalSettings({}).enableBetaSkills).toBe(false);
expect(normalizeExperimentalSettings({ enableBetaSkills: true }).enableBetaSkills).toBe(true);
});
it("sets worktree run execution activation fields on a false to true transition", () => {
const activatedAt = new Date("2026-07-10T12:00:00.000Z");
const next = applyExperimentalSettingsPatch(
{ enableWorktreeRunExecution: false },
{ enableWorktreeRunExecution: true },
{
now: () => activatedAt,
runtimeEnv: {
PAPERCLIP_IN_WORKTREE: "true",
PAPERCLIP_INSTANCE_ID: "worktree-instance",
},
},
);
expect(next.enableWorktreeRunExecution).toBe(true);
expect(next.worktreeRunExecutionActivatedAt).toBe("2026-07-10T12:00:00.000Z");
expect(next.worktreeRunExecutionActivationInstanceId).toBe("worktree-instance");
});
it("clears worktree run execution activation fields on a true to false transition", () => {
const next = applyExperimentalSettingsPatch(
{
enableWorktreeRunExecution: true,
worktreeRunExecutionActivatedAt: "2026-07-10T12:00:00.000Z",
worktreeRunExecutionActivationInstanceId: "worktree-instance",
},
{ enableWorktreeRunExecution: false },
{
runtimeEnv: {
PAPERCLIP_IN_WORKTREE: "true",
PAPERCLIP_INSTANCE_ID: "worktree-instance",
},
},
);
expect(next.enableWorktreeRunExecution).toBe(false);
expect(next.worktreeRunExecutionActivatedAt).toBeNull();
expect(next.worktreeRunExecutionActivationInstanceId).toBeNull();
});
it("refreshes the activation cutoff when worktree run execution is re-toggled", () => {
const firstActivation = applyExperimentalSettingsPatch(
{ enableWorktreeRunExecution: false },
{ enableWorktreeRunExecution: true },
{
now: () => new Date("2026-07-10T12:00:00.000Z"),
runtimeEnv: {
PAPERCLIP_IN_WORKTREE: "true",
PAPERCLIP_INSTANCE_ID: "worktree-instance",
},
},
);
const disabled = applyExperimentalSettingsPatch(
firstActivation,
{ enableWorktreeRunExecution: false },
{
runtimeEnv: {
PAPERCLIP_IN_WORKTREE: "true",
PAPERCLIP_INSTANCE_ID: "worktree-instance",
},
},
);
const secondActivation = applyExperimentalSettingsPatch(
disabled,
{ enableWorktreeRunExecution: true },
{
now: () => new Date("2026-07-10T12:05:00.000Z"),
runtimeEnv: {
PAPERCLIP_IN_WORKTREE: "true",
PAPERCLIP_INSTANCE_ID: "worktree-instance",
},
},
);
expect(secondActivation.worktreeRunExecutionActivatedAt).toBe("2026-07-10T12:05:00.000Z");
expect(secondActivation.worktreeRunExecutionActivatedAt).not.toBe(
firstActivation.worktreeRunExecutionActivatedAt,
);
});
it("strips client-supplied activation fields before applying experimental patches", () => {
const next = applyExperimentalSettingsPatch(
{ enableWorktreeRunExecution: false },
{
enableWorktreeRunExecution: false,
worktreeRunExecutionActivatedAt: "2026-07-10T12:00:00.000Z",
worktreeRunExecutionActivationInstanceId: "copied-instance",
},
{
runtimeEnv: {
PAPERCLIP_IN_WORKTREE: "true",
PAPERCLIP_INSTANCE_ID: "worktree-instance",
},
},
);
expect(next.worktreeRunExecutionActivatedAt).toBeNull();
expect(next.worktreeRunExecutionActivationInstanceId).toBeNull();
});
it("resolves worktree run execution as armed only when the cutoff matches the current instance", async () => {
const experimental = normalizeExperimentalSettings({
enableWorktreeRunExecution: true,
worktreeRunExecutionActivatedAt: "2026-07-10T12:00:00.000Z",
worktreeRunExecutionActivationInstanceId: "worktree-instance",
});
await expect(
resolveWorktreeRunExecutionActivationState({
getExperimental: async () => experimental,
runtimeEnv: {
PAPERCLIP_IN_WORKTREE: "true",
PAPERCLIP_INSTANCE_ID: "worktree-instance",
},
}),
).resolves.toEqual({
armed: true,
cutoff: "2026-07-10T12:00:00.000Z",
activationInstanceId: "worktree-instance",
reason: null,
});
});
it("fails closed when worktree run execution is missing a cutoff", async () => {
const experimental = normalizeExperimentalSettings({
enableWorktreeRunExecution: true,
worktreeRunExecutionActivationInstanceId: "worktree-instance",
});
await expect(
resolveWorktreeRunExecutionActivationState({
getExperimental: async () => experimental,
runtimeEnv: {
PAPERCLIP_IN_WORKTREE: "true",
PAPERCLIP_INSTANCE_ID: "worktree-instance",
},
}),
).resolves.toMatchObject({
armed: false,
cutoff: null,
reason: "missing_cutoff",
});
});
it("fails closed when worktree run execution was activated by another instance", async () => {
const experimental = normalizeExperimentalSettings({
enableWorktreeRunExecution: true,
worktreeRunExecutionActivatedAt: "2026-07-10T12:00:00.000Z",
worktreeRunExecutionActivationInstanceId: "source-instance",
});
await expect(
resolveWorktreeRunExecutionActivationState({
getExperimental: async () => experimental,
runtimeEnv: {
PAPERCLIP_IN_WORKTREE: "true",
PAPERCLIP_INSTANCE_ID: "target-instance",
},
}),
).resolves.toMatchObject({
armed: false,
cutoff: null,
activationInstanceId: "source-instance",
reason: "instance_id_mismatch",
});
});
it("fails closed on settings read errors and avoids reads outside worktree runtimes", async () => {
await expect(
resolveWorktreeRunExecutionActivationState({
getExperimental: async () => {
throw new Error("settings unavailable");
},
runtimeEnv: {
PAPERCLIP_IN_WORKTREE: "true",
PAPERCLIP_INSTANCE_ID: "worktree-instance",
},
}),
).resolves.toMatchObject({
armed: false,
cutoff: null,
reason: "settings_read_error",
});
const getExperimental = vi.fn<() => Promise<InstanceExperimentalSettings>>();
await expect(
resolveWorktreeRunExecutionActivationState({
getExperimental,
runtimeEnv: {
PAPERCLIP_IN_WORKTREE: "false",
PAPERCLIP_INSTANCE_ID: "worktree-instance",
},
}),
).resolves.toMatchObject({
armed: false,
cutoff: null,
reason: "not_worktree_runtime",
});
expect(getExperimental).not.toHaveBeenCalled();
});
});