Role requests
Table of Contents
1. Summary
A person gains a role by asking for it. The ask is stored as an approval
request of kind iam.role_grant, with the role ids in IAM's own detail tables
and the reason on the request. A tenant administrator answers from the queue if
they hold the kind's decide permission, iam::roles:assign, and nobody answers
their own request. An approval is not a grant: the IAM service reconciles
approved requests into role assignments exactly once. Both people are told
through in-application notifications.
2. Detail
2.1. A request is a record, not a waiting workflow
Approval requests holds the decision: the workflow engine cannot wait on a person, so a request is its own record with a state, a raiser, and an answer. The role request is the first kind, and a kind names the permission that decides it.
2.2. Asking
A member calls iam.v1.ops.ask_for_roles with the role ids and a reason. IAM
forwards inbox.v1.ops.raise_approval with kind_code iam.role_grant,
reusing the caller's token so the request is the person's, then writes
ores_iam_role_grant_requests_tbl and
ores_iam_role_grant_request_roles_tbl. The ask is refused when a role is
unknown, already held, or already asked for in a request that still waits. If
the detail write fails, IAM forwards a withdrawal so the request does not wait
naming no roles.
The roles a person may ask for are the tenant's roles minus what they already hold, minus the service roles and the role that grants everything. A flag on the role row would state this directly; today the screens filter, and the ask operation refuses what it should.
2.3. The queue and the answer
inbox.v1.ops.list_approval_queue returns the open requests the caller holds
the decide permission for, and leaves out the ones they raised. The order is
oldest first. inbox.v1.ops.decide_approval takes the request id, the
version the row was read at, a decision code, and a comment. A refusal, a hold
and a reversal each need a comment, because they are what the person reads.
An approval needs one only when the kind asks for it. The version is the whole
concurrency control: a second decision on a request that moved is refused.
Four eyes is a rule of the store, not of the screen. The decisions table refuses a decision by the person who asked, and the queue read already leaves their own requests out.
2.4. From an approval to a grant
The IAM service does not grant at the moment of the decision. Its
role_grant_applier reconciles: ores_iam_unapplied_role_grants_fn returns the
approved request roles an account does not hold, the applier grants them as the
person who approved, and marks the detail row applied. The query is idempotent,
so a crash and a retry converge, and a role taken away after it was applied is
not granted again. The grant reaches the member at their next sign-in.
2.5. States
A request is waiting, held, approved, refused, withdrawn or
expired. A refusal is final, and an approval is reversed rather than edited.
The role request kind does not allow a hold, and it carries no expiry.
2.6. Telling the two people
The approval handler raises inbox.approval_waiting to the holders of the
kind's decide permission, less the asker, when a request is raised, and
inbox.approval_decided to the asker when its state changes. Each notification
carries a kind, the values its sentence names, and a link to the request.
2.7. What a screen cannot read
The self read, inbox.v1.ops.list_my_approval_requests, returns the request rows and
not the roles they ask for. The roles live in IAM's detail tables, whose read
codes a plain member does not hold, so a member's own request shows the kind and
the reason. A joined read would close the gap; the store serves no batched read
on role_grant_request_roles yet.