Skip to main content

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 PublicUserProfile records (shareable with orgs)
  • Joins teams via Member records
  • Private profile (UserProfile) is separate from public profiles

Organization

  • Has many OrgWorkspace records
  • Connected to users via team memberships (not direct FK)

Workspace (polymorphic base)

  • OrgWorkspace — org-owned; has orgId, organization FK, publicProfile FK
  • UserWorkspace — personal; has userId, user FK
  • Never instantiate the base Workspace class 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 private OrganizationProfile + addresses; auto-updated when private profile changes
  • synced = false: independently managed; private org updates do NOT affect it
  • Has its own OrgPublicProfileAddress records (not shared with OrganizationAddress)

Team

  • Belongs to a workspace
  • Has many Member records
  • Slug is unique within its workspace

Member

  • Links a User to a Team with a MemberRole
  • Has status (INVITED | ACTIVE | INACTIVE | SUSPENDED) — INVITED means the Keycloak account was pre-created but the user has not completed onboarding
  • sentDate, expiresAt, reminderSent are nullable fields used only when status = INVITED
  • Creator of a team is automatically added as OWNER
  • There is no separate invitation entity — invitation state is tracked directly on the Member record

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
  • ApplicationInput intentionally omits status — managed by workflow engine only

Client

  • External applicant assigned directly to an org (orgId FK → Organization)
  • Linked to a User via userId
  • Has status (INVITED | ACTIVE | INACTIVE | SUSPENDED) — INVITED means the Keycloak account was pre-created but the user has not completed onboarding
  • sentDate, expiresAt, reminderSent are nullable fields used only when status = INVITED
  • Queried with orgId in GraphQL: clients(orgId: ID) (filterable by status)
  • There is no separate invitation entity — invitation state is tracked directly on the Client record
  • Different from Applicant — a Client is the person; an Applicant is their role on a specific application

Applicant

  • Belongs to an Application; has type (PRIMARY → FIFTH) and role (VIEWER/COMMENTER/EDITOR)
  • Optionally links to a PublicUserProfile via profileId
  • Optionally links to a Client via clientId
  • 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 DocumentDefinition at application creation time
  • Changing the source DocumentDefinition does NOT update existing slots
  • May be null for custom/ad-hoc documents

Key Invariants

  1. Every OrgWorkspace must have exactly one OrgPublicProfile — create it immediately via OrgPublicProfileService.sync() when creating the workspace
  2. Never instantiate base Workspace — always use OrgWorkspace or UserWorkspace
  3. currentApplicationStatus is read-only — set currentApplicationStatusId; never assign through ApplicationInput
  4. Document slots are snapshots — they do not update when the source DocumentDefinition changes
  5. Clients only see EXTERNAL comments — INTERNAL comments are strictly staff-only
  6. Slugs are scoped — workspace slug: unique per user; team slug: unique per workspace

See also