Files
PaperClipAI/server/src/services/reviewed-chat-binding-diagnostics.ts
T
Devin FoleyandClaude Opus 5.5 7edbc1fc07 fix(server): keep a successor's issue lock when a finalizing native run releases its checkout (#19156)
`release_checkout` now clears `checkoutRunId` and the execution lock only when they belong to the committing run, so a successor that claimed the issue while its predecessor was finalizing keeps its claim and passes reviewed-chat attestation.

`attestReviewedExternalChatRun` reports the first failed check as a closed label. Setup failures store it in `resultJson.reviewedChatBinding.denialReason`, and Sentry run-failure diagnostics export it as `execution.reviewedChatBindingDenialReason` (no IDs or text). The error message is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:49:03 -08:00

44 lines
1.7 KiB
TypeScript

export const REVIEWED_CHAT_BINDING_NOT_AUTHORIZED = "reviewed_chat_execution_binding_not_authorized";
/**
* 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;
}
}