fix: combine partial feedback receipts

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-08-16 21:25:58 +00:00
1 parent d3366b1a8e
commit b49c032da2
4 files changed
+49 -10

No files matched your search

+15 -10
View File
@@ -238,6 +238,7 @@ import {
buildFeedbackDeliveryFingerprint,
buildFeedbackDeliveryRetryIdempotencyKey,
carriesExplicitFeedbackResume,
collectFeedbackDispositionHandledCommentIds,
isEligibleFeedbackDeliveryWake,
isSuccessfulFeedbackDeliveryRecoveryMatch,
remainingFeedbackDispositionCommentIds,
@@ -10211,7 +10212,7 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
run: typeof heartbeatRuns.$inferSelect,
delivery: FeedbackDeliveryContext,
) {
const comment = await db
const comments = await db
.select({ id: issueComments.id, metadata: issueComments.metadata })
.from(issueComments)
.where(and(
@@ -10221,7 +10222,7 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
isNull(issueComments.deletedAt),
))
.orderBy(desc(issueComments.createdAt), desc(issueComments.id))
.then((rows) => rows.find((row) => {
.then((rows) => rows.filter((row) => {
const disposition = row.metadata?.feedbackDisposition;
if (
disposition?.kind !== "feedback_delivery"
@@ -10231,17 +10232,21 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
deliveryCommentIds: delivery.commentIds,
handledCommentIds: disposition.handledCommentIds,
}) !== null;
}) ?? null);
if (!comment) return null;
const disposition = comment.metadata?.feedbackDisposition;
if (!disposition) return null;
}));
if (comments.length === 0) return null;
const handledCommentIds = collectFeedbackDispositionHandledCommentIds({
deliveryCommentIds: delivery.commentIds,
rootWakeupRequestId: delivery.rootWakeupRequestId,
dispositions: comments.map((comment) => comment.metadata?.feedbackDisposition),
});
// Only an explicit, persisted disposition can acknowledge feedback. Ordinary
// run comments and other same-run issue activity can be unrelated.
// run comments and other same-run issue activity can be unrelated. A run may
// post several partial receipts, so combine all valid receipts for this root.
return {
kind: "comment",
id: comment.id,
rootWakeupRequestId: disposition.rootWakeupRequestId,
sourceCommentIds: disposition.handledCommentIds,
id: comments[0]!.id,
rootWakeupRequestId: delivery.rootWakeupRequestId,
sourceCommentIds: handledCommentIds,
} as const;
}
@@ -5,6 +5,7 @@ import {
STRANDED_FEEDBACK_DELIVERY_BACKSTOP_SOURCE,
buildFeedbackDeliveryFingerprint,
buildFeedbackDeliveryRetryIdempotencyKey,
collectFeedbackDispositionHandledCommentIds,
decideStrandedFeedbackDeliveryBackstop,
hasCompleteFeedbackDispositionCoverage,
isSuccessfulFeedbackDeliveryRecoveryMatch,
@@ -203,6 +204,18 @@ describe("feedback disposition coverage", () => {
handledCommentIds,
})).toBeNull();
});
it("combines complementary partial receipts from one run", () => {
expect(collectFeedbackDispositionHandledCommentIds({
deliveryCommentIds: ["comment-1", "comment-2", "comment-3"],
rootWakeupRequestId: "wake-root",
dispositions: [
{ rootWakeupRequestId: "wake-root", handledCommentIds: ["comment-1"] },
{ rootWakeupRequestId: "wake-root", handledCommentIds: ["comment-3"] },
{ rootWakeupRequestId: "other-root", handledCommentIds: ["comment-2"] },
],
})).toEqual(["comment-1", "comment-3"]);
});
});
describe("manual feedback retry attempts", () => {
@@ -130,6 +130,26 @@ export function remainingFeedbackDispositionCommentIds(input: {
return [...deliveryIds].filter((commentId) => !handledIds.has(commentId));
}
export function collectFeedbackDispositionHandledCommentIds(input: {
deliveryCommentIds: string[];
rootWakeupRequestId: string;
dispositions: Array<{
rootWakeupRequestId: string;
handledCommentIds: string[];
} | null | undefined>;
}) {
const handledIds = new Set<string>();
for (const disposition of input.dispositions) {
if (!disposition || disposition.rootWakeupRequestId !== input.rootWakeupRequestId) continue;
if (remainingFeedbackDispositionCommentIds({
deliveryCommentIds: input.deliveryCommentIds,
handledCommentIds: disposition.handledCommentIds,
}) === null) continue;
for (const commentId of disposition.handledCommentIds) handledIds.add(commentId);
}
return input.deliveryCommentIds.filter((commentId) => handledIds.has(commentId));
}
export function isSuccessfulFeedbackDeliveryRecoveryMatch(input: {
handlingEvidence: {
rootWakeupRequestId: string;
+1
View File
@@ -40,6 +40,7 @@ export {
buildFeedbackDeliveryFingerprint,
buildFeedbackDeliveryRetryIdempotencyKey,
carriesExplicitFeedbackResume,
collectFeedbackDispositionHandledCommentIds,
decideStrandedFeedbackDeliveryBackstop,
hasCompleteFeedbackDispositionCoverage,
isEligibleFeedbackDeliveryWake,