Skip to content

Commit dcb90e8

Browse files
os-zhuangclaude
andauthored
fix(approvals): surface the admin override for a stuck request in the inbox (#3424) (#2810)
Backend #3424 lets a platform/tenant admin act on a pending approval routed to an unstaffed position (otherwise undecidable, locking the record forever) via a new server-computed `viewer.can_override`. Mirror it in the console so the recovery path is visible in-product: - Add `can_override` to the `viewer` type (optional — an older backend without it reads as false). - `canApproveReject` ORs in `viewer.can_override`, so the reply box shows and the "why disabled" hint no longer contradicts the approve/reject/reassign buttons (which the backend's declared-action `visible` CEL already ORs `can_override` into). Normal approver/submitter gating is unchanged. Claude-Session: https://claude.ai/code/session_01VmQPXXbgomoqrXtoxr3CS2 Co-authored-by: Claude <noreply@anthropic.com>
1 parent b821287 commit dcb90e8

2 files changed

Lines changed: 14 additions & 3 deletions

File tree

apps/console/src/pages/system/ApprovalsInboxPage.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -686,11 +686,14 @@ export function ApprovalsInboxPage() {
686686
// so the hint never contradicts the buttons — it already reflects position/
687687
// team approver resolution, which the client identity heuristic below can't.
688688
// The heuristic stays as a fallback for a backend that predates `viewer`.
689-
// No actor-override branch: the admin "act as" escape hatch retired with the
690-
// composer (cloud#861 enterprise act-as is the successor).
689+
// framework#3424: a platform/tenant admin may OVERRIDE a stuck request (one
690+
// routed to an unstaffed position) even holding no slot — `viewer.can_override`
691+
// mirrors the server's decision authz, so the hint and the approve/reject/
692+
// reassign buttons (which OR in `can_override`) stay consistent. This is a
693+
// privileged recovery path, not the retired per-request "act as" composer.
691694
const canApproveReject = useMemo(() => {
692695
if (!selected || selected.status !== 'pending') return false;
693-
if (selected.viewer) return selected.viewer.can_act;
696+
if (selected.viewer) return selected.viewer.can_act || !!selected.viewer.can_override;
694697
const pending = new Set(selected.pending_approvers || []);
695698
return identities.some(id => pending.has(id));
696699
}, [selected, identities]);

apps/console/src/services/approvalsApi.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ export interface ApprovalRequestRow {
8383
can_act: boolean;
8484
/** The caller submitted this request. */
8585
is_submitter: boolean;
86+
/**
87+
* framework#3424 — the caller is a platform/tenant admin who may OVERRIDE a
88+
* stuck pending request (approve / reject / reassign it) despite holding no
89+
* approver slot: the recovery path for an approval routed to an unstaffed
90+
* position, which would otherwise lock the record forever. Optional so a
91+
* response from an older backend (no `can_override`) reads as `false`.
92+
*/
93+
can_override?: boolean;
8694
};
8795
/** ADR-0044 revision round on this (run, node): absent/1 = first round. */
8896
round?: number;

0 commit comments

Comments
 (0)