Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions apps/console/src/pages/system/ApprovalsInboxPage.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -686,11 +686,14 @@ export function ApprovalsInboxPage() {
// so the hint never contradicts the buttons — it already reflects position/
// team approver resolution, which the client identity heuristic below can't.
// The heuristic stays as a fallback for a backend that predates `viewer`.
// No actor-override branch: the admin "act as" escape hatch retired with the
// composer (cloud#861 enterprise act-as is the successor).
// framework#3424: a platform/tenant admin may OVERRIDE a stuck request (one
// routed to an unstaffed position) even holding no slot — `viewer.can_override`
// mirrors the server's decision authz, so the hint and the approve/reject/
// reassign buttons (which OR in `can_override`) stay consistent. This is a
// privileged recovery path, not the retired per-request "act as" composer.
const canApproveReject = useMemo(() => {
if (!selected || selected.status !== 'pending') return false;
if (selected.viewer) return selected.viewer.can_act;
if (selected.viewer) return selected.viewer.can_act || !!selected.viewer.can_override;
const pending = new Set(selected.pending_approvers || []);
return identities.some(id => pending.has(id));
}, [selected, identities]);
Expand Down
8 changes: 8 additions & 0 deletions apps/console/src/services/approvalsApi.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -83,6 +83,14 @@ export interface ApprovalRequestRow {
can_act: boolean;
/** The caller submitted this request. */
is_submitter: boolean;
/**
* framework#3424 — the caller is a platform/tenant admin who may OVERRIDE a
* stuck pending request (approve / reject / reassign it) despite holding no
* approver slot: the recovery path for an approval routed to an unstaffed
* position, which would otherwise lock the record forever. Optional so a
* response from an older backend (no `can_override`) reads as `false`.
*/
can_override?: boolean;
};
/** ADR-0044 revision round on this (run, node): absent/1 = first round. */
round?: number;
Expand Down
Loading