mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-09 07:10:34 +01:00
`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>
44 lines
1.7 KiB
TypeScript
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;
|
|
}
|
|
}
|