mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 02:26:12 +02:00
Handle mixed legacy blocker workspace ops
This commit is contained in:
1 parent
19a16db1d2
commit
6ed19734a6
2 files changed
+134
-5
No files matched your search
@@ -3103,6 +3103,139 @@ describeEmbeddedPostgres("issueService blockers and dependency wake readiness",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps a legacy blocker finalize barrier when a later sibling has run-attributed workspace ops", async () => {
|
||||
const companyId = randomUUID();
|
||||
const assigneeAgentId = randomUUID();
|
||||
const projectId = randomUUID();
|
||||
const projectWorkspaceId = randomUUID();
|
||||
const executionWorkspaceId = randomUUID();
|
||||
const siblingRunId = randomUUID();
|
||||
|
||||
await db.insert(companies).values({
|
||||
id: companyId,
|
||||
name: "Paperclip",
|
||||
issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
|
||||
requireBoardApprovalForNewAgents: false,
|
||||
});
|
||||
await db.insert(agents).values({
|
||||
id: assigneeAgentId,
|
||||
companyId,
|
||||
name: "QA",
|
||||
role: "qa",
|
||||
status: "active",
|
||||
adapterType: "claude_local",
|
||||
adapterConfig: {},
|
||||
runtimeConfig: {},
|
||||
permissions: {},
|
||||
});
|
||||
await db.insert(projects).values({
|
||||
id: projectId,
|
||||
companyId,
|
||||
name: "Shared workspace project",
|
||||
status: "in_progress",
|
||||
});
|
||||
await db.insert(projectWorkspaces).values({
|
||||
id: projectWorkspaceId,
|
||||
companyId,
|
||||
projectId,
|
||||
name: "Shared workspace",
|
||||
sourceType: "local_path",
|
||||
visibility: "default",
|
||||
isPrimary: true,
|
||||
});
|
||||
await db.insert(executionWorkspaces).values({
|
||||
id: executionWorkspaceId,
|
||||
companyId,
|
||||
projectId,
|
||||
projectWorkspaceId,
|
||||
mode: "isolated_workspace",
|
||||
strategyType: "git_worktree",
|
||||
name: "Shared exec workspace",
|
||||
status: "active",
|
||||
providerType: "git_worktree",
|
||||
});
|
||||
|
||||
const blockerId = randomUUID();
|
||||
const siblingId = randomUUID();
|
||||
const dependentId = randomUUID();
|
||||
await db.insert(issues).values([
|
||||
{
|
||||
id: blockerId,
|
||||
companyId,
|
||||
projectId,
|
||||
title: "Legacy predecessor",
|
||||
status: "done",
|
||||
priority: "medium",
|
||||
executionWorkspaceId,
|
||||
},
|
||||
{
|
||||
id: siblingId,
|
||||
companyId,
|
||||
projectId,
|
||||
title: "Later sibling",
|
||||
status: "in_progress",
|
||||
priority: "medium",
|
||||
executionWorkspaceId,
|
||||
},
|
||||
{
|
||||
id: dependentId,
|
||||
companyId,
|
||||
projectId,
|
||||
title: "Dependent",
|
||||
status: "blocked",
|
||||
priority: "medium",
|
||||
assigneeAgentId,
|
||||
},
|
||||
]);
|
||||
await db.insert(heartbeatRuns).values({
|
||||
id: siblingRunId,
|
||||
companyId,
|
||||
agentId: assigneeAgentId,
|
||||
invocationSource: "assignment",
|
||||
triggerDetail: "system",
|
||||
status: "running",
|
||||
contextSnapshot: { issueId: siblingId, taskId: siblingId },
|
||||
});
|
||||
await db.insert(workspaceOperations).values([
|
||||
{
|
||||
companyId,
|
||||
executionWorkspaceId,
|
||||
phase: "worktree_prepare",
|
||||
status: "succeeded",
|
||||
startedAt: new Date("2026-05-23T22:00:00.000Z"),
|
||||
},
|
||||
{
|
||||
companyId,
|
||||
executionWorkspaceId,
|
||||
heartbeatRunId: siblingRunId,
|
||||
phase: "worktree_prepare",
|
||||
status: "succeeded",
|
||||
startedAt: new Date("2026-05-23T22:15:00.000Z"),
|
||||
},
|
||||
]);
|
||||
await svc.update(dependentId, { blockedByIssueIds: [blockerId] });
|
||||
|
||||
await expect(svc.getDependencyReadiness(dependentId)).resolves.toMatchObject({
|
||||
isDependencyReady: false,
|
||||
pendingFinalizeBlockerIssueIds: [blockerId],
|
||||
unresolvedBlockerIssueIds: [blockerId],
|
||||
});
|
||||
|
||||
await db.insert(workspaceOperations).values({
|
||||
companyId,
|
||||
executionWorkspaceId,
|
||||
phase: "workspace_finalize",
|
||||
status: "succeeded",
|
||||
startedAt: new Date("2026-05-23T22:20:00.000Z"),
|
||||
});
|
||||
|
||||
await expect(svc.getDependencyReadiness(dependentId)).resolves.toMatchObject({
|
||||
isDependencyReady: true,
|
||||
pendingFinalizeBlockerIssueIds: [],
|
||||
unresolvedBlockerIssueIds: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps a reused-workspace blocker ready when a later sibling operation is unfinalized", async () => {
|
||||
const companyId = randomUUID();
|
||||
const assigneeAgentId = randomUUID();
|
||||
|
||||
@@ -697,7 +697,6 @@ async function listPendingFinalizeBlockerIssueIds(
|
||||
|
||||
const latestByBlockerIssueId = new Map<string, { phase: string; status: string; startedAt: Date }>();
|
||||
const latestLegacyByWorkspaceId = new Map<string, { phase: string; status: string; startedAt: Date }>();
|
||||
const workspacesWithRunAttributedOps = new Set<string>();
|
||||
for (const row of rows) {
|
||||
if (!row.executionWorkspaceId) continue;
|
||||
const blockerIds = blockerIdsByWorkspaceId.get(row.executionWorkspaceId);
|
||||
@@ -716,7 +715,6 @@ async function listPendingFinalizeBlockerIssueIds(
|
||||
}
|
||||
continue;
|
||||
}
|
||||
workspacesWithRunAttributedOps.add(row.executionWorkspaceId);
|
||||
for (const blockerIssueId of runIssueIds) {
|
||||
if (!blockerIds.has(blockerIssueId)) continue;
|
||||
const current = latestByBlockerIssueId.get(blockerIssueId);
|
||||
@@ -733,9 +731,7 @@ async function listPendingFinalizeBlockerIssueIds(
|
||||
for (const target of targets) {
|
||||
const workspaceId = target.executionWorkspaceId;
|
||||
const latest = latestByBlockerIssueId.get(target.blockerIssueId) ??
|
||||
(workspaceId && !workspacesWithRunAttributedOps.has(workspaceId)
|
||||
? latestLegacyByWorkspaceId.get(workspaceId)
|
||||
: null);
|
||||
(workspaceId ? latestLegacyByWorkspaceId.get(workspaceId) : null);
|
||||
if (!latest) continue; // no blocker-specific workspace ops recorded -> no finalize barrier
|
||||
if (latest.phase === "workspace_finalize" && latest.status === "succeeded") continue;
|
||||
pending.add(target.blockerIssueId);
|
||||
|
||||
Reference in new issue
Block a user