Concepts: Users and roles
Users and roles model identity and authorization within a tenant. A user is the person an application acts as — the human a request runs on behalf of, not the application that makes the call (those live under Applications). A role is a named bundle of scopes. The binding lives in dh_user_roles.
The console exposes both as the Users and Roles pages.
Users
A user record (dh_users) holds the durable identity within a tenant.
| Field | Purpose |
|---|---|
id | UUID, immutable. |
display_name | Free text. Self-editable without users:write. |
email | Lookup key for human users. |
external_id | Optional handle linking the user to a customer-side identity. |
kind | person (impersonable) or system (not). Finer labels live in metadata.subtype. |
status | active or archived. Soft-revocation. |
extra_scopes | Per-user scopes that augment role scopes. Cannot be self-granted. |
The console fetches the current operator's identity via GET /v1/users/me, which also returns the tenant, roles, and effective_scopes in one round trip.
Roles
A role (dh_roles) is a named scope bundle.
| Field | Purpose |
|---|---|
slug | URL handle, immutable. |
name | Human-friendly name. |
scopes | The list of scopes granted to anyone assigned this role. |
is_default | When true, the role cannot be deleted but can still be edited. |
Three default roles ship with every tenant:
adminholdsacaso_os:admin, the super-scope.memberholdscontext:write.viewerholdscontext:read.
Effective scopes
A user's effective scope set is the union of:
- An implicit
subject:<user_id>scope so policies can self-reference. - The scopes of every role assigned to the user.
- The user's
extra_scopes.
Adding or removing a role triggers a Redis pubsub event on acaso:key:invalidated so cached entries on other replicas drop within roughly 60 seconds. The console reflects the change on the user's detail page within the same window.
Self-edit rules
- Any user can edit their own
display_nameandmetadatawithout holdingusers:write. - A user cannot self-grant
extra_scopes. The API returns 403 and the console surfaces the error inline. - A user cannot delete themselves.
Audit attribution
Every write through the API records an audit row with actor_key_id set to the session key. Session keys inherit on_behalf_of_subject_id from their parent, so the audit log can resolve every action back to a single human.
Tip: Sharing keys between humans defeats audit attribution. Mint per-human keys.