Address graph liveness review followups

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-05-16 21:30:19 -05:00
1 parent 372e9349de
commit 64f01e2cd9
4 files changed
+109 -65

No files matched your search

@@ -1019,64 +1019,109 @@ describeEmbeddedPostgres("heartbeat issue graph liveness escalation", () => {
expect(actions).toHaveLength(1);
});
it.each(["resolved", "cancelled"] as const)(
"does not create a fresh recovery object when the same-fingerprint action was already %s",
async (status) => {
await enableAutoRecovery();
const { companyId, managerId, blockedIssueId, blockerIssueId } = await seedBlockedChain();
const heartbeat = heartbeatService(db);
const incidentKey = [
"harness_liveness",
it("does not create a fresh recovery object when the same-fingerprint action was already resolved", async () => {
await enableAutoRecovery();
const { companyId, managerId, blockedIssueId, blockerIssueId } = await seedBlockedChain();
const heartbeat = heartbeatService(db);
const incidentKey = [
"harness_liveness",
companyId,
blockedIssueId,
"blocked_by_unassigned_issue",
blockerIssueId,
].join(":");
const resolvedAt = new Date();
await db.insert(issueRecoveryActions).values({
companyId,
sourceIssueId: blockerIssueId,
kind: "issue_graph_liveness",
status: "resolved",
ownerType: "agent",
ownerAgentId: managerId,
cause: "issue_graph_liveness",
fingerprint: [
"harness_liveness_leaf",
companyId,
blockedIssueId,
"blocked_by_unassigned_issue",
blockerIssueId,
].join(":");
const resolvedAt = new Date();
].join(":"),
evidence: { incidentKey },
nextAction: "Already handled",
outcome: "restored",
resolvedAt,
});
await db.insert(issueRecoveryActions).values({
const result = await heartbeat.reconcileIssueGraphLiveness();
expect(result.escalationsCreated).toBe(0);
expect(result.existingEscalations).toBe(0);
expect(result.resolvedUnchangedSourceRecoveryActions).toBe(1);
const openEscalations = await db
.select()
.from(issues)
.where(
and(
eq(issues.companyId, companyId),
eq(issues.originKind, "harness_liveness_escalation"),
eq(issues.originId, incidentKey),
),
);
expect(openEscalations).toHaveLength(0);
const actions = await db
.select()
.from(issueRecoveryActions)
.where(and(eq(issueRecoveryActions.companyId, companyId), eq(issueRecoveryActions.sourceIssueId, blockerIssueId)));
expect(actions).toHaveLength(1);
expect(actions[0]?.status).toBe("resolved");
});
it("creates a fresh recovery object when the same-fingerprint action was cancelled", async () => {
await enableAutoRecovery();
const { companyId, managerId, blockedIssueId, blockerIssueId } = await seedBlockedChain();
const heartbeat = heartbeatService(db);
const incidentKey = [
"harness_liveness",
companyId,
blockedIssueId,
"blocked_by_unassigned_issue",
blockerIssueId,
].join(":");
const resolvedAt = new Date();
await db.insert(issueRecoveryActions).values({
companyId,
sourceIssueId: blockerIssueId,
kind: "issue_graph_liveness",
status: "cancelled",
ownerType: "agent",
ownerAgentId: managerId,
cause: "issue_graph_liveness",
fingerprint: [
"harness_liveness_leaf",
companyId,
sourceIssueId: blockerIssueId,
kind: "issue_graph_liveness",
status,
ownerType: "agent",
ownerAgentId: managerId,
cause: "issue_graph_liveness",
fingerprint: [
"harness_liveness_leaf",
companyId,
"blocked_by_unassigned_issue",
blockerIssueId,
].join(":"),
evidence: { incidentKey },
nextAction: "Already handled",
outcome: "restored",
resolvedAt,
});
"blocked_by_unassigned_issue",
blockerIssueId,
].join(":"),
evidence: { incidentKey },
nextAction: "Cancelled by operator",
outcome: "cancelled",
resolvedAt,
});
const result = await heartbeat.reconcileIssueGraphLiveness();
const result = await heartbeat.reconcileIssueGraphLiveness();
expect(result.escalationsCreated).toBe(0);
expect(result.existingEscalations).toBe(0);
expect(result.resolvedUnchangedSourceRecoveryActions).toBe(1);
expect(result.escalationsCreated).toBe(1);
expect(result.existingEscalations).toBe(0);
expect(result.sourceRecoveryActionsOpened).toBe(1);
expect(result.resolvedUnchangedSourceRecoveryActions).toBe(0);
const openEscalations = await db
.select()
.from(issues)
.where(
and(
eq(issues.companyId, companyId),
eq(issues.originKind, "harness_liveness_escalation"),
eq(issues.originId, incidentKey),
),
);
expect(openEscalations).toHaveLength(0);
const actions = await db
.select()
.from(issueRecoveryActions)
.where(and(eq(issueRecoveryActions.companyId, companyId), eq(issueRecoveryActions.sourceIssueId, blockerIssueId)));
expect(actions).toHaveLength(1);
expect(actions[0]?.status).toBe(status);
},
);
const actions = await db
.select()
.from(issueRecoveryActions)
.where(and(eq(issueRecoveryActions.companyId, companyId), eq(issueRecoveryActions.sourceIssueId, blockerIssueId)));
expect(actions).toHaveLength(2);
expect(actions.map((action) => action.status).sort()).toEqual(["active", "cancelled"]);
});
});
+10 -12
View File
@@ -1557,18 +1557,16 @@ export function issueRoutes(
blocks?: IssueRelationIssueSummary[];
}>;
const issueIds = issuesWithRelations.map((issue) => issue.id);
const relationRecoveryCandidateIds = [
...new Set(issuesWithRelations.flatMap((issue) => [
...((issue.blockedBy ?? []).flatMap((summary) => [
summary.id,
...((summary.terminalBlockers ?? []).map((terminal) => terminal.id)),
])),
...((issue.blocks ?? []).flatMap((summary) => [
summary.id,
...((summary.terminalBlockers ?? []).map((terminal) => terminal.id)),
])),
])),
];
const relationRecoveryCandidateIdSet = new Set<string>();
const collectRelationRecoveryCandidateIds = (summary: IssueRelationIssueSummary) => {
relationRecoveryCandidateIdSet.add(summary.id);
summary.terminalBlockers?.forEach(collectRelationRecoveryCandidateIds);
};
issuesWithRelations.forEach((issue) => {
issue.blockedBy?.forEach(collectRelationRecoveryCandidateIds);
issue.blocks?.forEach(collectRelationRecoveryCandidateIds);
});
const relationRecoveryCandidateIds = [...relationRecoveryCandidateIdSet];
const [handoffStates, recoveryActionByIssue] = await Promise.all([
listSuccessfulRunHandoffStates(db, companyId, issueIds),
recoveryActionsSvc.listActiveForIssues(companyId, [...issueIds, ...relationRecoveryCandidateIds]),
+1 -1
View File
@@ -2511,7 +2511,7 @@ export function recoveryService(db: Db, deps: { enqueueWakeup: RecoveryWakeup })
eq(issueRecoveryActions.kind, "issue_graph_liveness"),
eq(issueRecoveryActions.cause, "issue_graph_liveness"),
eq(issueRecoveryActions.fingerprint, livenessRecoveryLeafFingerprint(finding)),
inArray(issueRecoveryActions.status, ["resolved", "cancelled"]),
eq(issueRecoveryActions.status, "resolved"),
),
)
.orderBy(desc(issueRecoveryActions.resolvedAt), desc(issueRecoveryActions.updatedAt))
+1
View File
@@ -3010,6 +3010,7 @@ export function IssueDetail() {
void resolveRecoveryAction.mutateAsync({ actionId, outcome: "blocked", sourceIssueStatus: "blocked" });
return;
case "delegated":
// The API also permits "blocked" for delegated recovery when a first-class blocker should keep the source parked.
void resolveRecoveryAction.mutateAsync({
actionId,
outcome: "delegated",