mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-10 20:46:28 +01:00
fix(server): keep issue environment parsing opt-in
Keep normal issue workspace settings normalization from persisting legacy environmentId values while allowing accepted-plan pre-realization and cleanup paths to inspect existing legacy environment intent. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
4 files changed
+99
-4
No files matched your search
@@ -155,10 +155,23 @@ describe("execution workspace policy helpers", () => {
|
||||
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",
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers the agent default environment", () => {
|
||||
|
||||
@@ -3084,6 +3084,78 @@ describeEmbeddedPostgres("issueService.create workspace inheritance", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("createChild preserves strategy-only workspace intent without realizing the parent workspace", async () => {
|
||||
const companyId = randomUUID();
|
||||
const projectId = randomUUID();
|
||||
const parentIssueId = randomUUID();
|
||||
const projectWorkspaceId = randomUUID();
|
||||
const environmentId = randomUUID();
|
||||
|
||||
await db.insert(companies).values({
|
||||
id: companyId,
|
||||
name: "Paperclip",
|
||||
issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
|
||||
requireBoardApprovalForNewAgents: false,
|
||||
});
|
||||
await instanceSettingsService(db).updateExperimental({ enableIsolatedWorkspaces: true });
|
||||
|
||||
await db.insert(projects).values({
|
||||
id: projectId,
|
||||
companyId,
|
||||
name: "Workspace project",
|
||||
status: "in_progress",
|
||||
});
|
||||
|
||||
await db.insert(projectWorkspaces).values({
|
||||
id: projectWorkspaceId,
|
||||
companyId,
|
||||
projectId,
|
||||
name: "Primary workspace",
|
||||
isPrimary: true,
|
||||
});
|
||||
|
||||
await db.insert(issues).values({
|
||||
id: parentIssueId,
|
||||
companyId,
|
||||
projectId,
|
||||
projectWorkspaceId,
|
||||
title: "Accepted plan parent",
|
||||
status: "in_progress",
|
||||
priority: "medium",
|
||||
executionWorkspaceSettings: {
|
||||
mode: "isolated_workspace",
|
||||
environmentId,
|
||||
workspaceStrategy: {
|
||||
type: "git_worktree",
|
||||
baseRef: "origin/master",
|
||||
branchTemplate: "{{issue.identifier}}-{{slug}}",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { issue: child } = await svc.createChild(parentIssueId, {
|
||||
title: "Accepted plan child",
|
||||
status: "todo",
|
||||
priority: "medium",
|
||||
executionWorkspaceInheritanceMode: "strategy_only",
|
||||
});
|
||||
|
||||
expect(child.parentId).toBe(parentIssueId);
|
||||
expect(child.projectId).toBe(projectId);
|
||||
expect(child.projectWorkspaceId).toBe(projectWorkspaceId);
|
||||
expect(child.executionWorkspaceId).toBeNull();
|
||||
expect(child.executionWorkspacePreference).toBeNull();
|
||||
expect(child.executionWorkspaceSettings).toEqual({
|
||||
mode: "isolated_workspace",
|
||||
environmentId,
|
||||
workspaceStrategy: {
|
||||
type: "git_worktree",
|
||||
baseRef: "origin/master",
|
||||
branchTemplate: "{{issue.identifier}}-{{slug}}",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("clamps helper-created child requestDepth to the safe maximum", async () => {
|
||||
const companyId = randomUUID();
|
||||
const projectId = randomUUID();
|
||||
|
||||
@@ -88,7 +88,14 @@ export function gateProjectExecutionWorkspacePolicy(
|
||||
return projectPolicy;
|
||||
}
|
||||
|
||||
export function parseIssueExecutionWorkspaceSettings(raw: unknown): IssueExecutionWorkspaceSettings | null {
|
||||
type ParseIssueExecutionWorkspaceSettingsOptions = {
|
||||
includeEnvironmentId?: boolean;
|
||||
};
|
||||
|
||||
export function parseIssueExecutionWorkspaceSettings(
|
||||
raw: unknown,
|
||||
options: ParseIssueExecutionWorkspaceSettingsOptions = {},
|
||||
): IssueExecutionWorkspaceSettings | null {
|
||||
const parsed = parseObject(raw);
|
||||
if (Object.keys(parsed).length === 0) return null;
|
||||
const workspaceStrategy = parseExecutionWorkspaceStrategy(parsed.workspaceStrategy);
|
||||
@@ -112,7 +119,7 @@ export function parseIssueExecutionWorkspaceSettings(raw: unknown): IssueExecuti
|
||||
...(normalizedMode
|
||||
? { mode: normalizedMode as IssueExecutionWorkspaceSettings["mode"] }
|
||||
: {}),
|
||||
...(parsed.environmentId && typeof parsed.environmentId === "string"
|
||||
...(options.includeEnvironmentId && (typeof parsed.environmentId === "string" || parsed.environmentId === null)
|
||||
? { environmentId: parsed.environmentId }
|
||||
: {}),
|
||||
...(workspaceStrategy ? { workspaceStrategy } : {}),
|
||||
|
||||
@@ -241,7 +241,7 @@ function buildReusedExecutionWorkspaceConfigPatchFromIssueSettings(
|
||||
// Accepted-plan children are not realized yet, so carry only unresolved
|
||||
// workspace intent and let the first child run render/persist its own branch.
|
||||
function buildPreRealizationExecutionWorkspaceSettings(raw: unknown): Record<string, unknown> | null {
|
||||
const settings = parseIssueExecutionWorkspaceSettings(raw);
|
||||
const settings = parseIssueExecutionWorkspaceSettings(raw, { includeEnvironmentId: true });
|
||||
if (!settings) return null;
|
||||
const mode =
|
||||
settings.mode && settings.mode !== "inherit" && settings.mode !== "reuse_existing"
|
||||
@@ -6409,7 +6409,10 @@ export function issueService(db: Db) {
|
||||
|
||||
let cleared = 0;
|
||||
for (const row of rows) {
|
||||
const settings = parseIssueExecutionWorkspaceSettings(row.executionWorkspaceSettings);
|
||||
const settings = parseIssueExecutionWorkspaceSettings(
|
||||
row.executionWorkspaceSettings,
|
||||
{ includeEnvironmentId: true },
|
||||
);
|
||||
if (settings?.environmentId !== environmentId) continue;
|
||||
|
||||
await db
|
||||
|
||||
Reference in new issue
Block a user