Data Model
This page is the authoritative entity reference for AI agents and developers. It describes all major entities, their relationships, and key invariants.
Entity Relationship Overview
Key Entities
User
- Has one
UserWorkspace(personal, auto-created) - Has many
PublicUserProfilerecords (shareable with orgs) - Joins teams via
Memberrecords - Private profile (
UserProfile) is separate from public profiles
Organization
- Has many
OrgWorkspacerecords - Connected to users via team memberships (not direct FK)
Workspace (polymorphic base)
OrgWorkspace— org-owned; hasorgId,organizationFK,publicProfileFKUserWorkspace— personal; hasuserId,userFK- Never instantiate the base
Workspaceclass directly — always use the correct subclass - Stored using
InheritanceType.JOINED(joined table strategy)
OrgPublicProfile
- Always exactly one per OrgWorkspace — created automatically with synced mode when workspace is created
synced = true: mirrors privateOrganizationProfile+ addresses; auto-updated when private profile changessynced = false: independently managed; private org updates do NOT affect it- Has its own
OrgPublicProfileAddressrecords (not shared withOrganizationAddress)
Team
- Belongs to a workspace
- Has many
Memberrecords - Slug is unique within its workspace
Member
- Links a
Userto aTeamwith aMemberRole - Has
status(INVITED | ACTIVE | INACTIVE | SUSPENDED) — INVITED means the Keycloak account was pre-created but the user has not completed onboarding sentDate,expiresAt,reminderSentare nullable fields used only whenstatus = INVITED- Creator of a team is automatically added as OWNER
- There is no separate invitation entity — invitation state is tracked directly on the
Memberrecord
Application
- Created from an
ApplicationTemplate - Has
currentApplicationStatusId(UUID column, FK) — read-only JPA join, never set via input - Has
currentWorkflowStepId(nullable) - Has
clientApprovalConfirmedAt(Instant) — set when client approves; triggers workflow handler ApplicationInputintentionally omits status — managed by workflow engine only
Client
- External applicant assigned directly to an org (
orgIdFK →Organization) - Linked to a
UserviauserId - Has
status(INVITED | ACTIVE | INACTIVE | SUSPENDED) — INVITED means the Keycloak account was pre-created but the user has not completed onboarding sentDate,expiresAt,reminderSentare nullable fields used only whenstatus = INVITED- Queried with
orgIdin GraphQL:clients(orgId: ID)(filterable by status) - There is no separate invitation entity — invitation state is tracked directly on the
Clientrecord - Different from
Applicant— a Client is the person; an Applicant is their role on a specific application
Applicant
- Belongs to an
Application; hastype(PRIMARY → FIFTH) androle(VIEWER/COMMENTER/EDITOR) - Optionally links to a
PublicUserProfileviaprofileId - Optionally links to a
ClientviaclientId - Different from
Client— see Applicants
PublicUserProfile
- A user's shareable data card (firstName, lastName, DOB, phone, addresses, bio, email)
- One user can have multiple profiles
- Shared with orgs via
UserProfileSharing(consent-based) - Once shared, org staff can view and edit
ApplicationDocument
- An actual file upload against a document slot (
ApplicationDocumentDefinition) - Status:
PENDING | UPLOADED | APPROVED | REJECTED | NOT_APPLICABLE - Status is computed from uploads — NOT_APPLICABLE is the only manually-set status
ApplicationDocumentDefinition (document slot)
- A snapshot copied from
DocumentDefinitionat application creation time - Changing the source
DocumentDefinitiondoes NOT update existing slots - May be
nullfor custom/ad-hoc documents
Key Invariants
- Every
OrgWorkspacemust have exactly oneOrgPublicProfile— create it immediately viaOrgPublicProfileService.sync()when creating the workspace - Never instantiate base
Workspace— always useOrgWorkspaceorUserWorkspace currentApplicationStatusis read-only — setcurrentApplicationStatusId; never assign throughApplicationInput- Document slots are snapshots — they do not update when the source
DocumentDefinitionchanges - Clients only see EXTERNAL comments — INTERNAL comments are strictly staff-only
- Slugs are scoped — workspace slug: unique per user; team slug: unique per workspace
See also
- Backend Rules: Entities — JPA mapping details, inheritance rules, transient delegates
- Backend Rules: Package Structure — file organization
- Roles & Permissions
- Document System