Files
PaperClipAI/server/src/services/reviewed-chat-binding-diagnostics.ts
T
Devin FoleyandPaperclip bf12b2d8ad fix: retry reviewed chat setup after temporary contention (#19787)
Retry reviewed chat preparation with the original accepted input.

Bound retries for authorization proof waits that expire before provider
work. Preserve the original answer receipt and queue later wakes
separately. Keep plan confirmation semantics by assigning the interaction
marker only to question answer delivery and legacy question payloads.

Verification: 433 tests across four backend suites, all 17 project-chat
browser tests, and all server source/test typecheck layers passed. The
plan approval regression was reproduced on the previous head before the
fix. All applicable CI passed and exact-head Greptile Apex scored 5/5.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-11-07 02:14:42 -08:00

53 lines
1.9 KiB
TypeScript

export const REVIEWED_CHAT_BINDING_NOT_AUTHORIZED = "reviewed_chat_execution_binding_not_authorized";
export const REVIEWED_CHAT_BINDING_NOT_READY = "reviewed_chat_execution_binding_not_ready";
export class ReviewedChatBindingNotReadyError extends Error {
readonly code = REVIEWED_CHAT_BINDING_NOT_READY;
constructor() {
super(REVIEWED_CHAT_BINDING_NOT_READY);
}
}
/**
* The first reviewed-chat attestation check that denied a run. Closed labels
* only: the run result and its failure report never carry IDs or message text.
*/
const reasons = [
"binding_missing", "issue_status", "assignee_mismatch", "owner_mismatch",
"run_not_running", "native_issue_mismatch", "agent_not_invokable",
"context_mismatch", "question_response_missing",
"binding_denied", "destination_denied", "principal_denied",
] as const;
export type ReviewedChatBindingDenialReason = typeof reasons[number];
export function isReviewedChatBindingDenialReason(value: unknown): value is ReviewedChatBindingDenialReason {
return reasons.some((reason) => reason === value);
}
/** Keeps the established run error text while carrying the denied check. */
export class ReviewedChatBindingDeniedError extends Error {
readonly code = REVIEWED_CHAT_BINDING_NOT_AUTHORIZED;
constructor(readonly denialReason: ReviewedChatBindingDenialReason | null) {
super(REVIEWED_CHAT_BINDING_NOT_AUTHORIZED);
}
}
export function reviewedChatBindingDenialResultJson(error: unknown) {
return error instanceof ReviewedChatBindingDeniedError && error.denialReason
? { reviewedChatBinding: { denialReason: error.denialReason } }
: {};
}
export function readReviewedChatBindingDenialReason(value: unknown): ReviewedChatBindingDenialReason | null {
if (!value || typeof value !== "object") return null;
try {
const reason = (value as Record<string, unknown>).denialReason;
return isReviewedChatBindingDenialReason(reason) ? reason : null;
} catch {
return null;
}
}