From 7f460ba8f69dbee38ca4eaa245474f24c0a7c0a9 Mon Sep 17 00:00:00 2001 From: Dotta Date: Sun, 16 Aug 2026 21:39:04 +0000 Subject: [PATCH] test: assert cross-root feedback isolation Co-Authored-By: Paperclip --- .../heartbeat-process-recovery.test.ts | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/server/src/__tests__/heartbeat-process-recovery.test.ts b/server/src/__tests__/heartbeat-process-recovery.test.ts index 94e93534fee..ee922ab196f 100644 --- a/server/src/__tests__/heartbeat-process-recovery.test.ts +++ b/server/src/__tests__/heartbeat-process-recovery.test.ts @@ -2130,7 +2130,7 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { }); }); - it("coalesces a recovered feedback wake ahead of a newer queued comment without another handler", async () => { + it("defers a recovered feedback wake behind a newer queued comment from another root", async () => { let releaseAdapter: (() => void) | null = null; const adapterStarted = new Promise((resolve) => { mockAdapterExecute.mockImplementationOnce(async () => { @@ -2212,7 +2212,7 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { expect(await heartbeat.reapOrphanedRuns()).toEqual({ reaped: 1, runIds: [fixture.runId] }); await Promise.race([ adapterStarted, - new Promise((_, reject) => setTimeout(() => reject(new Error("coalesced retry did not start")), 3_000)), + new Promise((_, reject) => setTimeout(() => reject(new Error("newer comment run did not start")), 3_000)), ]); const runs = await db @@ -2220,14 +2220,32 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { .from(heartbeatRuns) .where(eq(heartbeatRuns.agentId, fixture.agentId)); expect(runs).toHaveLength(2); - const coalescedRun = runs.find((run) => run.id === secondRunId); - expect(coalescedRun?.contextSnapshot).toMatchObject({ - feedbackDeliveryRootWakeupRequestId: fixture.wakeupRequestId, - feedbackDeliveryRetryGeneration: 1, - wakeCommentIds: [first.commentId, secondCommentId], + const newerCommentRun = runs.find((run) => run.id === secondRunId); + expect(newerCommentRun?.contextSnapshot).toMatchObject({ + wakeCommentIds: [secondCommentId], commentId: secondCommentId, wakeCommentId: secondCommentId, }); + expect(newerCommentRun?.contextSnapshot).not.toHaveProperty("feedbackDeliveryRootWakeupRequestId"); + const deferredRetry = await db + .select() + .from(agentWakeupRequests) + .where(and( + eq(agentWakeupRequests.companyId, fixture.companyId), + eq(agentWakeupRequests.agentId, fixture.agentId), + eq(agentWakeupRequests.status, "deferred_issue_execution"), + )) + .then((rows) => rows[0] ?? null); + const deferredContext = ( + deferredRetry?.payload as Record | null + )?._paperclipWakeContext as Record | undefined; + expect(deferredContext).toMatchObject({ + feedbackDeliveryRootWakeupRequestId: fixture.wakeupRequestId, + feedbackDeliveryRetryGeneration: 1, + wakeCommentIds: [first.commentId], + commentId: first.commentId, + wakeCommentId: first.commentId, + }); const auditRows = await db .select() .from(activityLog) @@ -2238,18 +2256,19 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { expect(auditRows).toHaveLength(1); expect(auditRows[0]?.details).toMatchObject({ retryRunId: secondRunId, - disposition: "coalesced", + retryWakeupRequestId: deferredRetry?.id, + disposition: "deferred", sourceCommentIds: [first.commentId], }); - if (!releaseAdapter) throw new Error("coalesced feedback run did not reach the adapter"); + if (!releaseAdapter) throw new Error("newer comment run did not reach the adapter"); await db.insert(issueComments).values({ companyId: fixture.companyId, issueId: fixture.issueId, authorType: "agent", authorAgentId: fixture.agentId, createdByRunId: secondRunId, - body: "Applied both feedback comments in order.", + body: "Applied the newer feedback comment.", }); await db.update(issues).set({ status: "done" }).where(eq(issues.id, fixture.issueId)); releaseAdapter();