|
| 1 | +--- |
| 2 | +"@objectstack/plugin-approvals": patch |
| 3 | +"@objectstack/spec": patch |
| 4 | +--- |
| 5 | + |
| 6 | +fix(approvals): admin override for a request routed to an unstaffed approver (#3424) |
| 7 | + |
| 8 | +An `approval` node routed to a `position` (or `team`/`department`) with **no |
| 9 | +holders** resolved to only the unresolvable `position:<name>` literal in |
| 10 | +`pending_approvers` — no concrete user was in the slate. Every normal |
| 11 | +`decide` / `reassign` / `recall` then returned `FORBIDDEN` (not a pending |
| 12 | +approver) and, with `lockRecord`, the target record stayed `RECORD_LOCKED` |
| 13 | +forever: a data-availability dead-end with no in-product recovery (the only exit |
| 14 | +was editing the DB by hand). Very easy to hit in fresh/demo orgs (positions |
| 15 | +seeded, holders not) and whenever a role is vacated in production. |
| 16 | + |
| 17 | +A **platform or tenant admin** — the same posture the engine's superuser bypass |
| 18 | +already trusts — may now act on any *pending* request to release it: **approve, |
| 19 | +reject, reassign** it to a real approver, or **recall** it. The override finalizes |
| 20 | +the request (which releases the record lock, keyed on a pending request); a |
| 21 | +tenant admin's authority is org-scoped, a platform admin's is not, and the |
| 22 | +decision is audited under the admin's own id. An admin approval is authoritative, |
| 23 | +finalizing the node even under `unanimous` / `quorum` / `per_group` rather than |
| 24 | +counting as one vote among the (empty) slate. |
| 25 | + |
| 26 | +- `sys_approval_request.viewer` gains `can_override` (server-computed): true for a |
| 27 | + privileged admin on a pending request. The `approve` / `reject` / `reassign` |
| 28 | + declared actions OR it into their `visible` gate, so the console surfaces the |
| 29 | + recovery path without a hand-wired button. Existing approver/submitter gating is |
| 30 | + unchanged. |
| 31 | +- `openNodeRequest` now logs a loud warning when a node resolves to **no concrete |
| 32 | + approver**, so the misconfiguration is visible instead of silently locking the |
| 33 | + record. The literal-fallback behavior (kept for 15.x slot back-compat) is |
| 34 | + otherwise unchanged. |
0 commit comments