Files
PaperClipAI/server/src/__tests__/execution-workspace-policy.test.ts
T
Nicky LeachandPaperclip 63b0752a9a Preserve inherited workspace settings through task preparation
Keep unspecified task modes inherited during creation and import. Retain declared provider strategies for operator branches, and permit pinning or unpinning the same effective storage owner with active workspaces.

Verified 58 targeted policy, database, and heartbeat regressions and the server TypeScript check. Full suites run in GitHub CI.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-22 16:43:41 -07:00

782 lines
28 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
issueExecutionWorkspaceSettingsSchema,
projectExecutionWorkspacePolicySchema,
} from "@paperclipai/shared";
import {
applyDefaultIsolatedExecutionWorkspacePolicy,
buildExecutionWorkspaceAdapterConfig,
defaultIssueExecutionWorkspaceSettingsForProject,
gateProjectExecutionWorkspacePolicy,
isUnrunnableWorktreeCombo,
issueExecutionWorkspaceModeForPersistedWorkspace,
parseIssueExecutionWorkspaceSettings,
parseIssueAssigneeAdapterOverrides,
parseProjectExecutionWorkspacePolicy,
ManagedSandboxUnavailableError,
resolveEffectiveWorkspaceStrategyType,
resolveExecutionWorkspaceEnvironmentId,
resolvePinnedIssueWorkspaceStrategyType,
resolveExecutionWorkspaceMode,
resolveSharedWorkspaceConcurrency,
selectEnvironmentExecutionWorkspaceSettings,
} from "../services/execution-workspace-policy.ts";
describe("execution workspace policy helpers", () => {
it("ignores operator-owned homeMode in stored task overrides while retaining task settings", () => {
expect(parseIssueAssigneeAdapterOverrides({ adapterConfig: { homeMode: "preconfigured", model: "task-model" } }))
.toEqual({ adapterConfig: { model: "task-model" }, useProjectWorkspace: null });
});
it("defaults new issue settings from enabled project policy", () => {
expect(
defaultIssueExecutionWorkspaceSettingsForProject({
enabled: true,
defaultMode: "isolated_workspace",
}),
).toEqual({ mode: "isolated_workspace" });
expect(
defaultIssueExecutionWorkspaceSettingsForProject({
enabled: true,
defaultMode: "shared_workspace",
}),
).toEqual({ mode: "shared_workspace" });
expect(defaultIssueExecutionWorkspaceSettingsForProject(null)).toBeNull();
expect(defaultIssueExecutionWorkspaceSettingsForProject({ enabled: true })).toBeNull();
});
it.each(["shared_workspace", "operator_branch"] as const)("keeps declared provider strategy for %s", mode => {
const config = buildExecutionWorkspaceAdapterConfig({ agentConfig: {}, issueSettings: null, mode, legacyUseProjectWorkspace: null,
projectPolicy: { enabled: true, storageProviderPluginId: "10000000-0000-4000-8000-000000000001", defaultMode: mode,
workspaceStrategy: { type: "git_worktree", baseRef: "release" } } });
expect(config.workspaceStrategy).toEqual({ type: "git_worktree", baseRef: "release" });
expect(resolveEffectiveWorkspaceStrategyType(mode, config)).toBe("git_worktree");
});
it("prefers explicit issue mode over project policy and legacy overrides", () => {
expect(
resolveExecutionWorkspaceMode({
projectPolicy: { enabled: true, defaultMode: "shared_workspace" },
issueSettings: { mode: "isolated_workspace" },
legacyUseProjectWorkspace: false,
}),
).toBe("isolated_workspace");
});
it("resolves shared-workspace concurrency from issue override, project policy, then auto", () => {
expect(
resolveSharedWorkspaceConcurrency({
projectPolicy: { enabled: true, sharedWorkspaceConcurrency: "serialize" },
issueSettings: { sharedWorkspaceConcurrency: "allow" },
}),
).toBe("allow");
expect(
resolveSharedWorkspaceConcurrency({
projectPolicy: { enabled: true, sharedWorkspaceConcurrency: "serialize" },
issueSettings: null,
}),
).toBe("serialize");
expect(
resolveSharedWorkspaceConcurrency({
projectPolicy: { enabled: false, sharedWorkspaceConcurrency: "serialize" },
issueSettings: null,
}),
).toBe("auto");
expect(resolveSharedWorkspaceConcurrency({ projectPolicy: null, issueSettings: null })).toBe("auto");
});
it("validates the shared-workspace concurrency enum on project and issue settings", () => {
expect(projectExecutionWorkspacePolicySchema.parse({
enabled: true,
sharedWorkspaceConcurrency: "auto",
}).sharedWorkspaceConcurrency).toBe("auto");
expect(issueExecutionWorkspaceSettingsSchema.parse({
sharedWorkspaceConcurrency: "allow",
}).sharedWorkspaceConcurrency).toBe("allow");
expect(projectExecutionWorkspacePolicySchema.safeParse({
enabled: true,
sharedWorkspaceConcurrency: "parallel",
}).success).toBe(false);
});
it("accepts an existing-branch pin only with isolated mode and a git_worktree strategy", () => {
expect(issueExecutionWorkspaceSettingsSchema.parse({
mode: "isolated_workspace",
workspaceStrategy: {
type: "git_worktree",
existingBranch: "PAP-14380-salvage-pap-9514",
},
}).workspaceStrategy?.existingBranch).toBe("PAP-14380-salvage-pap-9514");
// Fail closed at the contract layer: an exact-branch pin outside an
// isolated git worktree could silently land in the shared checkout.
expect(issueExecutionWorkspaceSettingsSchema.safeParse({
workspaceStrategy: { type: "git_worktree", existingBranch: "some-branch" },
}).success).toBe(false);
expect(issueExecutionWorkspaceSettingsSchema.safeParse({
mode: "shared_workspace",
workspaceStrategy: { type: "git_worktree", existingBranch: "some-branch" },
}).success).toBe(false);
expect(issueExecutionWorkspaceSettingsSchema.safeParse({
mode: "isolated_workspace",
workspaceStrategy: { type: "project_primary", existingBranch: "some-branch" },
}).success).toBe(false);
expect(issueExecutionWorkspaceSettingsSchema.safeParse({
mode: "isolated_workspace",
workspaceStrategy: {
type: "git_worktree",
existingBranch: "some-branch",
branchTemplate: "{{issue.identifier}}-{{slug}}",
},
}).success).toBe(false);
for (const invalidBranch of ["-leading-dash", "a..b", "has space", "ends/", "back\\slash", "a.lock", "../escape"]) {
expect(issueExecutionWorkspaceSettingsSchema.safeParse({
mode: "isolated_workspace",
workspaceStrategy: { type: "git_worktree", existingBranch: invalidBranch },
}).success).toBe(false);
}
});
it("carries the existing-branch pin through issue settings parsing", () => {
expect(
parseIssueExecutionWorkspaceSettings({
mode: "isolated_workspace",
workspaceStrategy: { type: "git_worktree", existingBranch: " PAP-14754-run-redaction " },
})?.workspaceStrategy,
).toEqual({ type: "git_worktree", existingBranch: "PAP-14754-run-redaction" });
});
it("centralizes unrunnable isolated worktree detection", () => {
expect(
isUnrunnableWorktreeCombo({
issue: {
projectId: null,
projectWorkspaceId: null,
executionWorkspaceId: null,
executionWorkspacePreference: null,
},
resolvedMode: "isolated_workspace",
resolvedStrategy: "git_worktree",
}),
).toBe(true);
expect(
isUnrunnableWorktreeCombo({
issue: {
projectId: "project-1",
projectWorkspaceId: null,
executionWorkspaceId: null,
executionWorkspacePreference: null,
},
resolvedMode: "isolated_workspace",
resolvedStrategy: "git_worktree",
}),
).toBe(false);
expect(
isUnrunnableWorktreeCombo({
issue: {
projectId: null,
projectWorkspaceId: null,
executionWorkspaceId: "workspace-1",
executionWorkspacePreference: "reuse_existing",
},
resolvedMode: "isolated_workspace",
resolvedStrategy: "git_worktree",
}),
).toBe(false);
expect(
isUnrunnableWorktreeCombo({
issue: {
projectId: null,
projectWorkspaceId: null,
executionWorkspaceId: null,
executionWorkspacePreference: null,
},
resolvedMode: "shared_workspace",
resolvedStrategy: "git_worktree",
}),
).toBe(false);
expect(
isUnrunnableWorktreeCombo({
issue: {
projectId: null,
projectWorkspaceId: null,
executionWorkspaceId: null,
executionWorkspacePreference: null,
},
resolvedMode: "agent_default",
resolvedStrategy: "git_worktree",
}),
).toBe(false);
expect(
isUnrunnableWorktreeCombo({
issue: {
projectId: null,
projectWorkspaceId: null,
executionWorkspaceId: null,
executionWorkspacePreference: null,
},
resolvedMode: "operator_branch",
resolvedStrategy: "git_worktree",
}),
).toBe(true);
expect(
isUnrunnableWorktreeCombo({
issue: {
projectId: null,
projectWorkspaceId: null,
executionWorkspaceId: null,
executionWorkspacePreference: null,
},
resolvedMode: "isolated_workspace",
resolvedStrategy: "git_worktree",
hasResolvablePriorSessionWorkspace: true,
}),
).toBe(false);
});
it("mirrors runtime default (project_primary) when pinned settings omit strategy type", () => {
// Mode-only pin without explicit workspaceStrategy.type → same project_primary default as runtime.
expect(
resolvePinnedIssueWorkspaceStrategyType({
mode: "isolated_workspace",
issueSettings: { mode: "isolated_workspace" },
}),
).toBe("project_primary");
// Explicit strategy type is always respected.
expect(
resolvePinnedIssueWorkspaceStrategyType({
mode: "isolated_workspace",
issueSettings: {
mode: "isolated_workspace",
workspaceStrategy: { type: "git_worktree" },
},
}),
).toBe("git_worktree");
expect(
resolvePinnedIssueWorkspaceStrategyType({
mode: "isolated_workspace",
issueSettings: {
mode: "isolated_workspace",
workspaceStrategy: { type: "project_primary" },
},
}),
).toBe("project_primary");
});
it("falls back to project policy before legacy project-workspace compatibility flag", () => {
expect(
resolveExecutionWorkspaceMode({
projectPolicy: { enabled: true, defaultMode: "isolated_workspace" },
issueSettings: null,
legacyUseProjectWorkspace: false,
}),
).toBe("isolated_workspace");
expect(
resolveExecutionWorkspaceMode({
projectPolicy: null,
issueSettings: null,
legacyUseProjectWorkspace: false,
}),
).toBe("agent_default");
});
it("applies project policy strategy and runtime defaults when isolation is enabled", () => {
const result = buildExecutionWorkspaceAdapterConfig({
agentConfig: {
workspaceStrategy: { type: "project_primary" },
},
projectPolicy: {
enabled: true,
defaultMode: "isolated_workspace",
workspaceStrategy: {
type: "git_worktree",
baseRef: "origin/main",
provisionCommand: "bash ./scripts/provision-worktree.sh",
runtimeProvisionCommand: "bash ./scripts/provision-runtime.sh",
},
workspaceRuntime: {
services: [{ name: "web", command: "pnpm dev" }],
},
},
issueSettings: null,
mode: "isolated_workspace",
legacyUseProjectWorkspace: null,
});
expect(result.workspaceStrategy).toEqual({
type: "git_worktree",
baseRef: "origin/main",
provisionCommand: "bash ./scripts/provision-worktree.sh",
runtimeProvisionCommand: "bash ./scripts/provision-runtime.sh",
});
expect(result.workspaceRuntime).toEqual({
services: [{ name: "web", command: "pnpm dev" }],
});
});
describe("partial issue workspace strategies", () => {
const projectStrategy = {
type: "git_worktree" as const,
baseRef: "origin/main",
branchTemplate: "{{issue.identifier}}-{{slug}}",
worktreeParentDir: ".paperclip/worktrees",
provisionCommand: "true",
runtimeProvisionCommand: "npm run setup:runtime",
teardownCommand: "npm run teardown",
};
function resolveStrategy(
strategy: Record<string, unknown>,
enabled = true,
) {
return buildExecutionWorkspaceAdapterConfig({
agentConfig: { workspaceStrategy: { type: "git_worktree", provisionCommand: "agent-setup" } },
projectPolicy: parseProjectExecutionWorkspacePolicy({
enabled,
defaultMode: "isolated_workspace",
workspaceStrategy: projectStrategy,
}),
issueSettings: parseIssueExecutionWorkspaceSettings({
mode: "isolated_workspace",
workspaceStrategy: strategy,
}),
mode: "isolated_workspace",
legacyUseProjectWorkspace: null,
}).workspaceStrategy;
}
it("retains project hooks when an issue changes only its base branch", () => {
expect(resolveStrategy({ type: "git_worktree", baseRef: "origin/release" })).toEqual({
...projectStrategy,
baseRef: "origin/release",
});
});
it.each(["npm run issue-setup", "", null])("honors an explicit provisioning override of %j", (provisionCommand) => {
expect(resolveStrategy({ type: "git_worktree", provisionCommand })).toEqual({
...projectStrategy,
provisionCommand,
});
});
it("preserves explicit null clears through persisted JSON parsing", () => {
const strategy = {
type: "git_worktree",
baseRef: null,
branchTemplate: null,
worktreeParentDir: null,
provisionCommand: null,
runtimeProvisionCommand: null,
teardownCommand: null,
};
expect(resolveStrategy(strategy)).toEqual(strategy);
});
it.each(["cloud_sandbox", "adapter_managed", "project_primary"])("does not carry project hooks into %s", (type) => {
expect(resolveStrategy({ type })).toEqual({ type });
});
it("does not inherit a disabled project strategy", () => {
expect(resolveStrategy({ type: "git_worktree", baseRef: "origin/release" }, false)).toEqual({
type: "git_worktree",
baseRef: "origin/release",
});
expect(resolveStrategy({}, false)).toEqual({
type: "git_worktree",
provisionCommand: "agent-setup",
});
});
it("keeps project hooks for an exact branch pin without inheriting a branch template", () => {
const resolved = resolveStrategy({ type: "git_worktree", existingBranch: "fix/existing" });
expect(resolved).toEqual({
...projectStrategy,
branchTemplate: undefined,
existingBranch: "fix/existing",
});
expect(issueExecutionWorkspaceSettingsSchema.safeParse({
mode: "isolated_workspace",
workspaceStrategy: resolved,
}).success).toBe(true);
});
it("does not mutate the project or issue strategy", () => {
const issueStrategy = { type: "git_worktree" as const, baseRef: "origin/release" };
const result = buildExecutionWorkspaceAdapterConfig({
agentConfig: {},
projectPolicy: { enabled: true, workspaceStrategy: Object.freeze({ ...projectStrategy }) },
issueSettings: { workspaceStrategy: Object.freeze(issueStrategy) },
mode: "isolated_workspace",
legacyUseProjectWorkspace: null,
});
expect(result.workspaceStrategy).not.toBe(issueStrategy);
expect(issueStrategy).toEqual({ type: "git_worktree", baseRef: "origin/release" });
expect(projectStrategy.baseRef).toBe("origin/main");
});
});
it("preserves project authorization policy for trust-preset resolution", () => {
expect(parseProjectExecutionWorkspacePolicy({
enabled: true,
authorizationPolicy: {
trustBoundary: {
mode: "low_trust_review",
projectIds: ["33333333-3333-4333-8333-333333333333"],
},
},
})?.authorizationPolicy).toEqual({
trustBoundary: {
mode: "low_trust_review",
projectIds: ["33333333-3333-4333-8333-333333333333"],
},
});
});
it("clears managed workspace strategy when issue opts out to project primary or agent default", () => {
const baseConfig = {
workspaceStrategy: { type: "git_worktree", branchTemplate: "{{issue.identifier}}" },
workspaceRuntime: { services: [{ name: "web" }] },
};
expect(
buildExecutionWorkspaceAdapterConfig({
agentConfig: baseConfig,
projectPolicy: { enabled: true, defaultMode: "isolated_workspace" },
issueSettings: { mode: "shared_workspace" },
mode: "shared_workspace",
legacyUseProjectWorkspace: null,
}).workspaceStrategy,
).toBeUndefined();
const agentDefault = buildExecutionWorkspaceAdapterConfig({
agentConfig: baseConfig,
projectPolicy: null,
issueSettings: { mode: "agent_default" },
mode: "agent_default",
legacyUseProjectWorkspace: null,
});
expect(agentDefault.workspaceStrategy).toBeUndefined();
expect(agentDefault.workspaceRuntime).toBeUndefined();
});
it("parses persisted JSON payloads into typed project and issue workspace settings", () => {
expect(
parseProjectExecutionWorkspacePolicy({
enabled: true,
sharedWorkspaceConcurrency: "serialize",
defaultMode: "isolated",
workspaceStrategy: {
type: "git_worktree",
worktreeParentDir: ".paperclip/worktrees",
provisionCommand: "bash ./scripts/provision-worktree.sh",
runtimeProvisionCommand: "bash ./scripts/provision-runtime.sh",
teardownCommand: "bash ./scripts/teardown-worktree.sh",
},
}),
).toEqual({
enabled: true,
sharedWorkspaceConcurrency: "serialize",
defaultMode: "isolated_workspace",
workspaceStrategy: {
type: "git_worktree",
worktreeParentDir: ".paperclip/worktrees",
provisionCommand: "bash ./scripts/provision-worktree.sh",
runtimeProvisionCommand: "bash ./scripts/provision-runtime.sh",
teardownCommand: "bash ./scripts/teardown-worktree.sh",
},
});
expect(
parseIssueExecutionWorkspaceSettings({
mode: "project_primary",
environmentId: "11111111-1111-4111-8111-111111111111",
}),
).toEqual({
mode: "shared_workspace",
});
expect(
parseIssueExecutionWorkspaceSettings(
{
mode: "project_primary",
environmentId: "11111111-1111-4111-8111-111111111111",
},
{ includeEnvironmentId: true },
),
).toEqual({
mode: "shared_workspace",
environmentId: "11111111-1111-4111-8111-111111111111",
});
expect(
parseIssueExecutionWorkspaceSettings({
mode: "isolated_workspace",
sharedWorkspaceConcurrency: "allow",
networkEgress: {
allowFqdns: ["github.com", "pypi.org"],
allowCidrs: ["203.0.113.0/24"],
},
}),
).toEqual({
mode: "isolated_workspace",
sharedWorkspaceConcurrency: "allow",
networkEgress: {
allowFqdns: ["github.com", "pypi.org"],
allowCidrs: ["203.0.113.0/24"],
},
});
});
it("keeps egress grants independent from isolated workspace mode", () => {
const parsedSettings = {
mode: "isolated_workspace" as const,
workspaceRuntime: { image: "example/image" },
networkEgress: {
allowFqdns: ["github.com"],
allowCidrs: ["203.0.113.0/24"],
},
};
expect(selectEnvironmentExecutionWorkspaceSettings(parsedSettings, false)).toEqual({
networkEgress: parsedSettings.networkEgress,
});
expect(selectEnvironmentExecutionWorkspaceSettings(parsedSettings, true)).toEqual(parsedSettings);
expect(selectEnvironmentExecutionWorkspaceSettings({ mode: "isolated_workspace" }, false)).toBeNull();
});
it("prefers the agent default environment", () => {
expect(
resolveExecutionWorkspaceEnvironmentId({
agentDefaultEnvironmentId: "agent-env",
instanceDefaultEnvironmentId: "instance-env",
localDefaultEnvironmentId: "local-env",
}),
).toEqual({
environmentId: "agent-env",
source: "agent",
});
});
it("falls back to the instance default environment when the agent has none", () => {
expect(
resolveExecutionWorkspaceEnvironmentId({
agentDefaultEnvironmentId: null,
instanceDefaultEnvironmentId: "instance-env",
localDefaultEnvironmentId: "local-env",
}),
).toEqual({
environmentId: "instance-env",
source: "instance",
});
});
it("falls back to the built-in local environment when neither agent nor instance selects one", () => {
expect(
resolveExecutionWorkspaceEnvironmentId({
agentDefaultEnvironmentId: null,
instanceDefaultEnvironmentId: null,
localDefaultEnvironmentId: "local-env",
}),
).toEqual({
environmentId: "local-env",
source: "default",
});
});
it("redirects local-landing selections to the managed sandbox under managed-sandbox-only", () => {
// The default fallback and an explicit local selection both land on the
// managed environment; a non-local selection stays untouched.
expect(
resolveExecutionWorkspaceEnvironmentId({
agentDefaultEnvironmentId: null,
instanceDefaultEnvironmentId: null,
localDefaultEnvironmentId: "local-env",
managedSandboxOnly: true,
managedSandboxEnvironmentId: "managed-env",
}),
).toEqual({ environmentId: "managed-env", source: "managed" });
expect(
resolveExecutionWorkspaceEnvironmentId({
agentDefaultEnvironmentId: "local-env",
instanceDefaultEnvironmentId: null,
localDefaultEnvironmentId: "local-env",
managedSandboxOnly: true,
managedSandboxEnvironmentId: "managed-env",
}),
).toEqual({ environmentId: "managed-env", source: "managed" });
expect(
resolveExecutionWorkspaceEnvironmentId({
agentDefaultEnvironmentId: "ssh-env",
instanceDefaultEnvironmentId: null,
localDefaultEnvironmentId: "local-env",
managedSandboxOnly: true,
managedSandboxEnvironmentId: "managed-env",
}),
).toEqual({ environmentId: "ssh-env", source: "agent" });
});
it("fails closed — never local — when managed-sandbox-only has no managed environment", () => {
expect(() =>
resolveExecutionWorkspaceEnvironmentId({
agentDefaultEnvironmentId: null,
instanceDefaultEnvironmentId: null,
localDefaultEnvironmentId: "local-env",
managedSandboxOnly: true,
managedSandboxEnvironmentId: null,
}),
).toThrow(ManagedSandboxUnavailableError);
});
it("maps persisted execution workspace modes back to issue settings", () => {
expect(issueExecutionWorkspaceModeForPersistedWorkspace("isolated_workspace")).toBe("isolated_workspace");
expect(issueExecutionWorkspaceModeForPersistedWorkspace("operator_branch")).toBe("operator_branch");
expect(issueExecutionWorkspaceModeForPersistedWorkspace("shared_workspace")).toBe("shared_workspace");
expect(issueExecutionWorkspaceModeForPersistedWorkspace("adapter_managed")).toBe("agent_default");
expect(issueExecutionWorkspaceModeForPersistedWorkspace("cloud_sandbox")).toBe("agent_default");
expect(issueExecutionWorkspaceModeForPersistedWorkspace(null)).toBe("agent_default");
expect(issueExecutionWorkspaceModeForPersistedWorkspace(undefined)).toBe("agent_default");
});
it("disables project execution workspace policy when the instance flag is off", () => {
expect(
gateProjectExecutionWorkspacePolicy(
{ enabled: true, defaultMode: "isolated_workspace" },
false,
),
).toBeNull();
expect(
gateProjectExecutionWorkspacePolicy(
{ enabled: true, defaultMode: "isolated_workspace" },
true,
),
).toEqual({ enabled: true, defaultMode: "isolated_workspace" });
});
});
describe("operator default isolated execution workspaces", () => {
const withDefault = (
projectPolicy: Parameters<
typeof applyDefaultIsolatedExecutionWorkspacePolicy
>[0]["projectPolicy"],
hasProjectWorkspace = true,
defaultIsolatedWorkspacesEnabled = true,
) =>
applyDefaultIsolatedExecutionWorkspacePolicy({
projectPolicy,
defaultIsolatedWorkspacesEnabled,
hasProjectWorkspace,
});
it("substitutes an isolated policy for a project that stores none", () => {
expect(withDefault(null)).toEqual({
enabled: true,
defaultMode: "isolated_workspace",
});
});
it("leaves everything alone while the operator default is off", () => {
expect(withDefault(null, true, false)).toBeNull();
});
it("keeps a task that has no project on its existing behavior", () => {
// Isolation needs a repository to cut a worktree from. A project-less task
// (agent chat, for example) must not be pulled into worktree mode.
expect(withDefault(null, false)).toBeNull();
});
it("keeps a project without a configured workspace on its existing behavior", () => {
const projectPolicy = withDefault(null, false);
expect(projectPolicy).toBeNull();
expect(resolveExecutionWorkspaceMode({
projectPolicy,
issueSettings: null,
legacyUseProjectWorkspace: null,
})).toBe("shared_workspace");
expect(withDefault({ enabled: true, defaultMode: "isolated_workspace" }, false))
.toEqual({ enabled: true, defaultMode: "isolated_workspace" });
});
it("never overrides a policy the project already stores", () => {
expect(withDefault({ enabled: true, defaultMode: "shared_workspace" })).toEqual({
enabled: true,
defaultMode: "shared_workspace",
});
// `enabled: false` is a tenant decision to stay on the shared checkout,
// not an absent policy to fill in.
expect(withDefault({ enabled: false })).toEqual({ enabled: false });
});
it("resolves an unpolicied project's tasks to an isolated workspace", () => {
expect(
resolveExecutionWorkspaceMode({
projectPolicy: withDefault(null),
issueSettings: null,
legacyUseProjectWorkspace: null,
}),
).toBe("isolated_workspace");
});
it("still lets an explicit issue setting win over the operator default", () => {
expect(
resolveExecutionWorkspaceMode({
projectPolicy: withDefault(null),
issueSettings: { mode: "shared_workspace" },
legacyUseProjectWorkspace: null,
}),
).toBe("shared_workspace");
});
it("keeps mode and strategy coherent for the substituted policy", () => {
// Substituting a policy (rather than moving the terminal fallback) is what
// makes `hasWorkspaceControl` true, so the default git_worktree strategy is
// supplied instead of leaving isolated mode on a project_primary strategy.
const projectPolicy = withDefault(null);
const mode = resolveExecutionWorkspaceMode({
projectPolicy,
issueSettings: null,
legacyUseProjectWorkspace: null,
});
const config = buildExecutionWorkspaceAdapterConfig({
agentConfig: {},
projectPolicy,
issueSettings: null,
mode,
legacyUseProjectWorkspace: null,
});
expect(resolveEffectiveWorkspaceStrategyType(mode, config)).toBe("git_worktree");
});
it("does not strand a project-less task as an unrunnable worktree", () => {
const projectPolicy = withDefault(null, false);
const mode = resolveExecutionWorkspaceMode({
projectPolicy,
issueSettings: null,
legacyUseProjectWorkspace: null,
});
const config = buildExecutionWorkspaceAdapterConfig({
agentConfig: {},
projectPolicy,
issueSettings: null,
mode,
legacyUseProjectWorkspace: null,
});
expect(
isUnrunnableWorktreeCombo({
issue: {
projectId: null,
projectWorkspaceId: null,
executionWorkspaceId: null,
executionWorkspacePreference: null,
},
resolvedMode: mode,
resolvedStrategy: resolveEffectiveWorkspaceStrategyType(mode, config),
}),
).toBe(false);
});
});
describe("task-scoped low-trust environment", () => {
it("uses the explicit phone-task sandbox while preserving ordinary agent defaults", () => {
const defaults = {agentDefaultEnvironmentId: "agent-env", instanceDefaultEnvironmentId: "instance-env", localDefaultEnvironmentId: "local-env"};
expect(resolveExecutionWorkspaceEnvironmentId({...defaults, lowTrustIssueEnvironmentId: "phone-sandbox"})).toEqual({environmentId: "phone-sandbox", source: "issue"});
expect(resolveExecutionWorkspaceEnvironmentId(defaults)).toEqual({environmentId: "agent-env", source: "agent"});
});
});