Domain Model
Ten core nouns, each small on purpose — the complexity in this system is in how they relate, not in any single one’s field list — plus four supporting join/credential records that make the many-to-many relationships and multi-method auth actually work without a bigger table.
Overview
| Entity | What it represents |
|---|---|
| User | A person with an account on Substratal. One identity, used everywhere. |
| Organization | A domain/grouping object — a company, or a group within one — for Users. Decoupled from infrastructure placement; see Tenancy. Pulled into Phase 2. |
| Tenant | The infrastructure-placement and data-isolation boundary for one Application owner’s data — shared, isolated, or dedicated-region. See Decisions. |
| Application | A catalog entry for a developer’s app, built on this platform for its user/org/tenancy/settings/storage layer. |
| Role / Permission | A named bundle of capabilities, scoped to the platform or to one app. |
| Entitlement | The on/off record: does a User own an Application, and is it currently switched on. |
| Profile / AppProfile | Identity/display data — global, and per-app. |
| Setting / AppSettings | Configuration — global, and per-app, layered over app defaults. |
| Order | The commerce record an Entitlement traces back to. |
| Audit Event | An immutable log of who changed what access, when. |
Supporting entities
Each of these is a join or credential record behind one of the relationships above — real rows in the schema, just not independent concepts the way the ten above are:
| Entity | Joins | What it represents |
|---|---|---|
| UserIdentity | User ↔ login method | How a User actually authenticates — password or sso — since a User can hold more than one at once. See Decisions → Identity provider. |
| SSOConnection | Organization ↔ identity provider | One Organization’s federated-login configuration, referenced by its members’ sso-method UserIdentity rows. |
| OrganizationMembership | User ↔ Organization | Which Organizations a User belongs to, and their standing (org_admin/member) in each. |
| UserRoleAssignment | User ↔ Role | Which Roles a User holds, platform-wide or scoped to one Application. |
How they relate
erDiagram
USER ||--o{ ENTITLEMENT : holds
ORGANIZATION ||--o{ USER : "has members (many:many)"
ORGANIZATION ||--o{ ENTITLEMENT : "holds (org-wide)"
APPLICATION ||--o{ ENTITLEMENT : "granted via"
USER ||--o{ APPLICATION : owns
ORGANIZATION ||--o{ APPLICATION : "owns (alt.)"
USER ||--o| TENANT : "owns (alt.)"
ORGANIZATION ||--o| TENANT : "owns (alt.)"
TENANT ||--o{ APPLICATION : places
ENTITLEMENT ||--o| ORDER : "traces to"
USER ||--|| PROFILE : "has (global)"
USER ||--o{ APP_PROFILE : "has, per app"
APPLICATION ||--o{ APP_PROFILE : scopes
USER ||--|| SETTINGS : "has (global)"
USER ||--o{ APP_SETTINGS : "has, per app"
APPLICATION ||--o{ APP_SETTINGS : scopes
USER }o--o{ PLATFORM_ROLE : assigned
USER }o--o{ APP_ROLE : "assigned, per app"
APP_ROLE }o--|| APPLICATION : scopes
PLATFORM_ROLE ||--o{ PERMISSION : grants
APP_ROLE ||--o{ PERMISSION : grants
The relationship worth internalizing before reading further: Entitlement and Role are independent axes. Entitlement answers “can this user reach this app at all, right now.” Role answers “once inside, what can they do.” See Access Control for how the two combine on every request.
ID format
Every entity has an opaque, stable id, prefixed by type for readability (a Stripe-style convention): usr_, uid_, ssc_, org_, tnt_, app_, role_, ent_, ord_, evt_, whk_, key_. IDs are never reused and never encode meaning beyond the type prefix (Role is a deliberate exception — see Conventions).
Building the actual database, not just calling the API? Database Schema has the Postgres-level types, constraints, and indexes behind every entity above.