Replaces every role currently bound to the Actor with the supplied set. The replacement is computed as a DIFF: only the roles being added are inserted and only the roles being dropped are deleted, so POST is idempotent — re-applying the same set is a DB no-op, and a role present in both sets keeps its original assignment timestamp and produces no audit event.
To ADD a role, POST the union of the current set and the new role; to drop one, POST the set without it (or use DELETE /actors/{actorId}/roles/{roleName}). A payload that shares roles with the current set is the normal case, not a conflict.
Validation: every role name must exist in the active catalog and must NOT carry the s2sOnly flag (defense-in-depth — S2S-internal scopes are rejected here even if a tenant-admin somehow obtains the assign scope).
When a replacement drops a role, the withdrawal takes effect at the Actor's next token mint — not immediately. An access token already issued to this Actor carries the withdrawn scope literally and stays valid until its natural expiry, bounded by JWT_EXPIRATION (default 900s). The client_credentials grant issues no refresh token (RFC 6749 §4.4.3), so the token cannot be extended — only re-minted from credentials, and a re-mint reads the role set fresh. Worst-case staleness is therefore one token lifetime; raising JWT_EXPIRATION widens that window linearly. To cut a machine identity off without waiting for expiry, suspend the Actor instead — that works through the effective-status gate, which is consulted on the Actor's next request rather than at its next mint.
| Time | Status | User Agent | |
|---|---|---|---|
Retrieving recent requests… | |||