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.
The four-concept identity split
Section titled “The four-concept identity split”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.
The User record
Section titled “The User record”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.
Two different things called “role”
Section titled “Two different things called “role””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:
- 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.
- 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.
Per-user settings — four subsystems
Section titled “Per-user settings — four subsystems”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.
2. Notification preferences
Section titled “2. Notification preferences”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.
3. Recently viewed (the MRU backing)
Section titled “3. Recently viewed (the MRU backing)”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.
4. Saved searches
Section titled “4. Saved searches”User-owned and shareable — your saved filters travel with you, not with the browser.
Parity against Salesforce
Section titled “Parity against Salesforce”| 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.