Skip to content

Identity & User model

Salesforce packs identity, authentication, role, locale, and personal preferences into one User SObject. CAOS deliberately splits those concerns: a stable identity referent, its secret, its live sessions, and its login history are four separate control-plane records, while the business User you see in a list view is an ordinary object with no secret on it. This page documents that model: the identity split, the two distinct things called “role,” and the four per-user settings subsystems that back a My Profile surface.

Everything here that is a tenant object also carries the universal standard fields.

Authentication lives in the control-plane caos schema, never in a tenant table, and is split into four records so that a secret or a session can never leak through a business query:

Concept Holds Key facts
Principal The stable identity referent id never reused; status ∈ invited / active / locked / deactivated (only active may authenticate); kind ∈ person / service; login_identifier (email, mutable). No delete path — a principal is deactivated, never removed, so audit and ownership references stay valid forever.
Credential The hashed secret scrypt-encoded, keyed to the principal, never a column on any record.
Session A live login SHA-256 token hash, an assurance level (single-factor / multi-factor / phishing-resistant), a policy generation, and 12-hour absolute / 1-hour idle lifetimes.
Login event Login history Every attempt — success or failure — with a reason code and source.

The owner of every record (owner_id) points at a principal, so ownership survives an email change or a re-issued credential.

Separate from the principal is the User object — an ordinary tenant object you can put on a tab, lay out, and secure with FLS. It holds name, email, status, and a principal reference tying the business row to its control-plane identity. It carries no credential: the thing on screen in a list view is safe to share, because the secret lives one layer down.

Why split it this way (beating Salesforce): in Salesforce the User row is simultaneously the security principal, the credential anchor, and the addressable business record, which is why user-related security bugs are so easy to write. Separating the referent, the secret, the session, and the display record means a query against the business User can never surface a hash or a token, and deactivation is a status flip that leaves every historical reference intact rather than a delete that orphans them.

This is the one place the vocabulary overloads, so it is worth stating plainly — CAOS has two constructs named “role,” and they do different jobs:

  1. Record-visibility role — a hierarchy tree (each role has an optional parent). It governs who can see whose records through the sharing model, and nothing else. A user gets zero or one of these. This is what the Setup Roles editor manages.
  2. Capability role — a metadata component that seeds permission sets. It is about what you can do (object/field/system permissions), granted by assigning permission sets or permission-set groups to the user.

Salesforce blurs these into UserRoleId (visibility) plus ProfileId / permission sets (capability). CAOS keeps visibility and capability as separate, additive systems: visibility comes from the role tree + sharing rules; capability comes only from permission sets and groups (there are no profiles). See Permissions & FLS and Roles & record sharing for each.

A My Profile surface is built over four independent, per-user settings subsystems. The surface itself is delivered by an installable package; the four settings subsystems below are kernel and back whatever surface a package renders over them.

1. Viewer settings — four independent locale axes

Section titled “1. Viewer settings — four independent locale axes”

language, locale, time_zone, and corporate_currency are four independent settings, each resolved on its own user → tenant → platform-floor ladder (floor = en / en-US / UTC / USD). They are never cross-derived — changing your language does not move your time zone. A new user’s values are prefilled once from the browser’s Accept-Language at account creation, then owned by the user. This is a superset of Salesforce’s LanguageLocaleKey / LocaleSidKey / TimeZoneSidKey, with an independent corporate-currency axis on top.

Three levels of control resolve most-specific-wins: an org channel policy (email / push / chat can be disabled org-wide; in-app can never be turned off), per-user preferences scoped by notification category or type and by channel, and quiet hours (a per-user window in the user’s own time zone). The default for a type is authored on the notification-type metadata.

A per-user, per-object bounded ring of the last 200 records touched, ordered by a monotonic touch sequence — the queryable backing for “Recently Viewed.” Reads route through record access, so a record a user loses access to falls out of their list. This store also backs the per-user LastViewedDate / LastReferencedDate fields on the Standard fields page.

User-owned and shareable — your saved filters travel with you, not with the browser.

Salesforce (User + personal settings) CAOS Notes
Username login_identifier on the principal Match
Alias (not modeled) No separate compact display handle
Name user.name (record) Match
Email user.email + principal identifier Match
IsActive principal status (active/…) Richer — four states, not a boolean
ProfileId (no profiles) Deliberately different — permission-set-only capability model
UserRoleId record-visibility caos.role (zero-or-one) Match (visibility axis)
LanguageLocaleKey viewer setting language Match
LocaleSidKey viewer setting locale Match
TimeZoneSidKey viewer setting time_zone Match
(no direct analogue) viewer setting corporate_currency Beyond SF — independent currency axis
EmailEncodingKey (not modeled) Deliberately omitted — legacy encoding field
Notification settings notification preferences + quiet hours Match / richer (org policy + quiet hours)
RecentlyViewed recent_item ring Match — per-user, access-filtered
LoginHistory / UserLogin login_event + session Match — split cleanly from the record

Salesforce sourcing: the User object standard fields (Username, Alias, ProfileId, UserRoleId, IsActive, TimeZoneSidKey, LocaleSidKey, LanguageLocaleKey, EmailEncodingKey) are per developer.salesforce.com’s Object Reference (User) and the standard required-field set for user creation.