



















@@ -11,8 +11,19 @@ import type { GatewayClient, GatewayRequestContext, RespondFn } from "./types.js
11111212export const APPROVAL_NOT_FOUND_DETAILS = {
1313reason: ErrorCodes.APPROVAL_NOT_FOUND,
14+remediation: "Re-request the action; pending approvals are cleared after expiry or restart.",
1415} as const;
151617+const APPROVAL_ALREADY_RESOLVED_DETAILS = {
18+reason: "APPROVAL_ALREADY_RESOLVED",
19+} as const;
20+21+function resolveRecordedApprovalDecision<TPayload>(
22+record: ExecApprovalRecord<TPayload>,
23+): ExecApprovalDecision | undefined {
24+return record.decision ?? record.consumedDecision;
25+}
26+1627type PendingApprovalLookupError =
1728| "missing"
1829| {
@@ -97,6 +108,37 @@ export function resolvePendingApprovalRecord<TPayload>(params: {
97108return { ok: true, approvalId: resolvedId.id, snapshot };
98109}
99110111+function resolveResolvedApprovalRecord<TPayload>(params: {
112+manager: ExecApprovalManager<TPayload>;
113+inputId: string;
114+exposeAmbiguousPrefixError?: boolean;
115+}):
116+| {
117+ok: true;
118+approvalId: string;
119+snapshot: ExecApprovalRecord<TPayload>;
120+}
121+| {
122+ok: false;
123+response: PendingApprovalLookupError;
124+} {
125+const resolvedId = params.manager.lookupApprovalId(params.inputId, { includeResolved: true });
126+if (resolvedId.kind !== "exact" && resolvedId.kind !== "prefix") {
127+return {
128+ok: false,
129+response: resolvePendingApprovalLookupError({
130+ resolvedId,
131+exposeAmbiguousPrefixError: params.exposeAmbiguousPrefixError,
132+}),
133+};
134+}
135+const snapshot = params.manager.getSnapshot(resolvedId.id);
136+if (!snapshot || snapshot.resolvedAtMs === undefined) {
137+return { ok: false, response: "missing" };
138+}
139+return { ok: true, approvalId: resolvedId.id, snapshot };
140+}
141+100142export function respondPendingApprovalLookupError(params: {
101143respond: RespondFn;
102144response: PendingApprovalLookupError;
@@ -259,6 +301,25 @@ export async function handleApprovalResolve<TPayload, TResolvedEvent extends obj
259301exposeAmbiguousPrefixError: params.exposeAmbiguousPrefixError,
260302});
261303if (!resolved.ok) {
304+const resolvedRepeat = resolveResolvedApprovalRecord({
305+manager: params.manager,
306+inputId: params.inputId,
307+exposeAmbiguousPrefixError: params.exposeAmbiguousPrefixError,
308+});
309+if (resolvedRepeat.ok) {
310+if (resolveRecordedApprovalDecision(resolvedRepeat.snapshot) === params.decision) {
311+params.respond(true, { ok: true }, undefined);
312+return;
313+}
314+params.respond(
315+false,
316+undefined,
317+errorShape(ErrorCodes.INVALID_REQUEST, "approval already resolved", {
318+details: APPROVAL_ALREADY_RESOLVED_DETAILS,
319+}),
320+);
321+return;
322+}
262323respondPendingApprovalLookupError({ respond: params.respond, response: resolved.response });
263324return;
264325}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。