Handle mixed legacy blocker workspace ops

This commit is contained in:
Dotta committed 2026-06-09 23:05:44 +00:00
1 parent 19a16db1d2
commit 6ed19734a6
2 files changed
+134 -5

No files matched your search

+133
View File
@@ -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();
+1 -5
View File
@@ -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);