← All frames
Frame (c) · route /settings/access

Change role

Two variants. The first is the ordinary change. The second is the one that matters: the organization's last admin cannot be demoted.

(c1) Ordinary change
(c2) Last admin — refused

Contract. PUT /v1/security/tenants/{tenantId}/members/{userId}/roles (RoleAssignment200 Member). Replaces the member's assignment.

⚠ The last-admin refusal does not exist yet — this frame proposes it. There is no last-admin guard in backend/security/src/routes/authz.ts or in the contract today; the call would succeed and leave the org unadministrable. Approving this frame commissions: (1) a 409 LAST_ADMIN response on this operation, and (2) a client-side pre-check so the UI disables the option rather than letting the user discover the refusal by failing. The disabled control is an affordance, not the enforcement — the server must refuse independently, because a client-side guard is not a guard.

⚠ Role replacement is destructive today. The provider implements "replace" as remove-then-re-add, so a partial failure can leave a member with no roles. Until that is atomic, a failed save must re-fetch and show the member's true current role — never assume the prior value held.

Review · authz-admin approve or reject each flow ↓
access-adminpendingApproveReject