forked from farhoodlabs/paperclip
Cancel stale queued heartbeats when issue graph changes (PAP-2314) (#4534)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -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 });
|
||||
|
||||
Reference in New Issue
Block a user