From eccba68be4ae1f562bff387a0cf900cfb9691ffb Mon Sep 17 00:00:00 2001 From: Dotta Date: Sun, 26 Apr 2026 16:41:59 -0500 Subject: [PATCH] test: cover liveness recovery owner skips --- ...eartbeat-issue-liveness-escalation.test.ts | 69 ++++++++++++++++++- server/src/__tests__/issue-liveness.test.ts | 32 +++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/server/src/__tests__/heartbeat-issue-liveness-escalation.test.ts b/server/src/__tests__/heartbeat-issue-liveness-escalation.test.ts index 0a33094db60..e194d89a0ef 100644 --- a/server/src/__tests__/heartbeat-issue-liveness-escalation.test.ts +++ b/server/src/__tests__/heartbeat-issue-liveness-escalation.test.ts @@ -4,7 +4,9 @@ import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest import { activityLog, agents, + budgetPolicies, companies, + costEvents, createDb, executionWorkspaces, heartbeatRuns, @@ -191,7 +193,7 @@ describeEmbeddedPostgres("heartbeat issue graph liveness escalation", () => { type: "blocks", }); - return { companyId, managerId, blockedIssueId, blockerIssueId }; + return { companyId, managerId, coderId, blockedIssueId, blockerIssueId }; } it("keeps liveness findings advisory when auto recovery is disabled", async () => { @@ -342,6 +344,71 @@ describeEmbeddedPostgres("heartbeat issue graph liveness escalation", () => { expect(events.some((event) => event.action === "issue.blockers.updated")).toBe(true); }); + it("skips budget-blocked direct owners and assigns recovery to the manager fallback", async () => { + await enableAutoRecovery(); + const { companyId, managerId, coderId, blockedIssueId, blockerIssueId } = await seedBlockedChain(); + const issueTimestamp = new Date(Date.now() - 25 * 60 * 60 * 1000); + await db + .update(issues) + .set({ + status: "in_review", + assigneeAgentId: coderId, + updatedAt: issueTimestamp, + }) + .where(eq(issues.id, blockerIssueId)); + await db.insert(budgetPolicies).values({ + companyId, + scopeType: "agent", + scopeId: coderId, + metric: "billed_cents", + windowKind: "calendar_month_utc", + amount: 1, + hardStopEnabled: true, + isActive: true, + }); + await db.insert(costEvents).values({ + companyId, + agentId: coderId, + issueId: blockerIssueId, + provider: "test", + biller: "test", + billingType: "tokens", + model: "test-model", + costCents: 1, + occurredAt: new Date(), + }); + + const result = await heartbeatService(db).reconcileIssueGraphLiveness(); + + expect(result.escalationsCreated).toBe(1); + const escalations = await db + .select() + .from(issues) + .where(and(eq(issues.companyId, companyId), eq(issues.originKind, "harness_liveness_escalation"))); + expect(escalations).toHaveLength(1); + expect(escalations[0]).toMatchObject({ + parentId: blockerIssueId, + assigneeAgentId: managerId, + originId: [ + "harness_liveness", + companyId, + blockedIssueId, + "in_review_without_action_path", + blockerIssueId, + ].join(":"), + }); + + const events = await db.select().from(activityLog).where(eq(activityLog.companyId, companyId)); + const createdEvent = events.find((event) => event.action === "issue.harness_liveness_escalation_created"); + expect(createdEvent?.details).toMatchObject({ + ownerSelection: { + selectedAgentId: managerId, + selectedReason: "assignee_reporting_chain", + budgetBlockedCandidateAgentIds: [coderId], + }, + }); + }); + it("parents recovery under the leaf blocker without inheriting dependent or blocker execution state for manager-owned recovery", async () => { await enableAutoRecovery(); await instanceSettingsService(db).updateExperimental({ enableIsolatedWorkspaces: true }); diff --git a/server/src/__tests__/issue-liveness.test.ts b/server/src/__tests__/issue-liveness.test.ts index 592244fa109..b8eb4a239ef 100644 --- a/server/src/__tests__/issue-liveness.test.ts +++ b/server/src/__tests__/issue-liveness.test.ts @@ -286,6 +286,38 @@ describe("issue graph liveness classifier", () => { }); }); + it("skips paused stalled review assignees when choosing recovery owner candidates", () => { + const reviewIssueId = "review-1"; + + const findings = classifyIssueGraphLiveness({ + issues: [ + issue({ + id: reviewIssueId, + identifier: "PAP-2279", + title: "Screenshot acceptance review", + status: "in_review", + assigneeAgentId: coderId, + executionState: null, + }), + ], + relations: [], + agents: [agent({ status: "paused" }), manager], + }); + + expect(findings).toHaveLength(1); + expect(findings[0]).toMatchObject({ + state: "in_review_without_action_path", + recommendedOwnerAgentId: managerId, + }); + expect(findings[0]?.recommendedOwnerCandidates).toEqual([ + { + agentId: managerId, + reason: "assignee_reporting_chain", + sourceIssueId: reviewIssueId, + }, + ]); + }); + it("does not flag healthy in_review issues with an explicit action path", () => { const reviewIssueId = "review-1"; const baseReviewIssue = issue({