Summary
AddPlatformUser and RemovePlatformUser are asymmetric in their relation granularity:
AddPlatformUser is relation-specific — the request carries a relation field (admin or member), so you can grant exactly one platform relation.
RemovePlatformUser is relation-agnostic — RemovePlatformUserRequest has no relation field, so the handler strips both admin and member (full platform removal).
As a result there is no API way to remove only one platform relation — e.g. to demote a platform admin down to member, or to revoke member while leaving admin. Removal is all-or-nothing.
Current behavior (for reference)
AddPlatformUser(id, relation) → Sudo(id, relation) (user or service account).
RemovePlatformUser(id) → loops ["admin", "member"] calling UnSudo(id, relation) (user or service account).
The underlying service primitives are already symmetric and relation-specific: Sudo(ctx, id, relationName) ↔ UnSudo(ctx, id, relationName). Only the RemovePlatformUser handler is coarse-grained, because the proto request lacks a relation field.
Proposed fix
- Add an optional
relation field to RemovePlatformUserRequest in raystack/proton (raystack/frontier/v1beta1).
- In the
RemovePlatformUser handler: when relation is set, validate via IsPlatformRelation and call UnSudo(id, relation) for that relation only; when unset, keep the current behavior (remove both) for backward compatibility.
No service-layer change is needed — UnSudo(ctx, id, relationName) already supports relation-specific removal.
Notes
- Requires a proto change in
raystack/proton, which is why it was left out of the original PR.
- Context: this gap surfaced while implementing opt-in authoritative superuser reconciliation.
Summary
AddPlatformUserandRemovePlatformUserare asymmetric in their relation granularity:AddPlatformUseris relation-specific — the request carries arelationfield (adminormember), so you can grant exactly one platform relation.RemovePlatformUseris relation-agnostic —RemovePlatformUserRequesthas norelationfield, so the handler strips bothadminandmember(full platform removal).As a result there is no API way to remove only one platform relation — e.g. to demote a platform admin down to
member, or to revokememberwhile leavingadmin. Removal is all-or-nothing.Current behavior (for reference)
AddPlatformUser(id, relation)→Sudo(id, relation)(user or service account).RemovePlatformUser(id)→ loops["admin", "member"]callingUnSudo(id, relation)(user or service account).The underlying service primitives are already symmetric and relation-specific:
Sudo(ctx, id, relationName)↔UnSudo(ctx, id, relationName). Only theRemovePlatformUserhandler is coarse-grained, because the proto request lacks arelationfield.Proposed fix
relationfield toRemovePlatformUserRequestinraystack/proton(raystack/frontier/v1beta1).RemovePlatformUserhandler: whenrelationis set, validate viaIsPlatformRelationand callUnSudo(id, relation)for that relation only; when unset, keep the current behavior (remove both) for backward compatibility.No service-layer change is needed —
UnSudo(ctx, id, relationName)already supports relation-specific removal.Notes
raystack/proton, which is why it was left out of the original PR.