fix(gateway): fail fast exec approvals when no approvers are reachable

Co-authored-by: fanxian831-netizen <262880470+fanxian831-netizen@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-02-22 22:13:40 +01:00
parent 73fab7e445
commit d24f5c1e3a
8 changed files with 168 additions and 47 deletions

View File

@@ -86,18 +86,7 @@ export class ExecApprovalManager {
promise,
};
entry.timer = setTimeout(() => {
// Update snapshot fields before resolving (mirror resolve()'s bookkeeping)
record.resolvedAtMs = Date.now();
record.decision = undefined;
record.resolvedBy = null;
resolvePromise(null);
// Keep entry briefly for in-flight awaitDecision calls
setTimeout(() => {
// Compare against captured entry instance, not re-fetched from map
if (this.pending.get(record.id) === entry) {
this.pending.delete(record.id);
}
}, RESOLVED_ENTRY_GRACE_MS);
this.expire(record.id);
}, timeoutMs);
this.pending.set(record.id, entry);
return promise;
@@ -138,6 +127,27 @@ export class ExecApprovalManager {
return true;
}
expire(recordId: string, resolvedBy?: string | null): boolean {
const pending = this.pending.get(recordId);
if (!pending) {
return false;
}
if (pending.record.resolvedAtMs !== undefined) {
return false;
}
clearTimeout(pending.timer);
pending.record.resolvedAtMs = Date.now();
pending.record.decision = undefined;
pending.record.resolvedBy = resolvedBy ?? null;
pending.resolve(null);
setTimeout(() => {
if (this.pending.get(recordId) === pending) {
this.pending.delete(recordId);
}
}, RESOLVED_ENTRY_GRACE_MS);
return true;
}
getSnapshot(recordId: string): ExecApprovalRecord | null {
const entry = this.pending.get(recordId);
return entry?.record ?? null;