Project ROUNDTABLE Docs
Data model

Users and roles

Identity records, government roles, interest profiles, and notification preferences

This group answers “who is acting?” A User can represent an industry contact or a government employee. The same table supports both audiences, while nullable commandId and organizationId connect the user to the side of the platform they belong to.

User

ColumnPrisma typeRequired?DefaultMeaning
idStringYescuid()Stable identifier used by sessions and foreign keys.
emailStringYesNone; uniqueLogin address. Authentication lowercases and trims credentials before lookup.
passwordHashStringYesNoneBcrypt hash; plaintext passwords are never stored.
nameStringYesNoneDisplay name shown in government and audit contexts.
roleRoleYesINDUSTRYRole-based access decision.
mustChangePasswordBooleanYesfalseForces an admin-created account through the password-change page before other pages and APIs can be used.
sessionVersionIntYes0Incremented when the account credential changes; JWTs with an older version are revoked on refresh.
commandIdString?NoNULLGovernment command assignment.
titleString?NoNULLGovernment title or position.
organizationIdString?NoNULLIndustry organization assignment.
createdAtDateTimeYesnow()Account creation time.
updatedAtDateTimeYes@updatedAtLast account-row update.

The model also exposes relation fields: command, organization, one interestProfile, many notificationSettings, many notifications, created engagements, engagement notes, organization dialogue notes, announcements, and reviewed submissions. Relation fields are typed Prisma conveniences, not duplicated scalar columns.

Role enum

ValueWho it representsPractical authority
INDUSTRYIndustry organization contactOwn organization, own submissions, public calls and announcements.
GOV_READONLYGovernment viewerGovernment read access where route policy allows; no publishing or administrative mutation.
GOV_POCGovernment point of contact (POC)Receives routing notifications and participates in government workflows allowed by the route.
GOV_COMMAND_LEADCommand-level engagement leadOwn-command scope; can perform command-level government work but does not inherit child commands.
GOV_ADMINEnterprise program administratorEnterprise visibility and administrative functions, but not command-tree reorganization.
GOV_SYSTEM_ADMINEnterprise system administratorEnterprise visibility plus command hierarchy creation, editing, deletion, and reparenting.

Role values are database enum values, not free-form strings. src/lib/auth.ts defines the government-role allowlist used by requireGovSession().

InterestProfile

ColumnPrisma typeRequired?DefaultMeaning
idStringYescuid()Profile identifier.
userIdStringYes; uniqueNoneOne-to-one link to the government user.
techTagsStringYesNoneLowercase comma-separated areas of interest.
keywordsStringYesNoneComma-separated free-text terms used by matching.
portfolioNodeIdString?NoNULLOptional primary portfolio node assignment.

The profile is intentionally small. A POC can be assigned to a portfolio node and still use free-form tags or keywords. routeSubmission checks both the matched node and overlapping profile terms before creating a notification.

NotificationSetting

ColumnPrisma typeRequired?DefaultMeaning
idStringYescuid()Preference-row identifier.
userIdStringYesNoneOwner of the preference.
kindNotificationKindYesNoneNotification category controlled by this row.
enabledBooleanYestrueWhether this category is enabled.
techTagsStringYes""Optional lowercase comma-separated filter.

@@unique([userId, kind]) means a user has at most one setting per category. Missing rows behave as enabled in src/lib/notify.ts; the settings GET endpoint materializes defaults for the UI.

Identity relationships

Example interpretation

poc.ai@navy.mil is a government POC assigned to NIWC Pacific with an AI/ML portfolio node and corresponding interest tags. A generated industry contact such as contact@pelagic-dynamics.example.com has role = INDUSTRY and an organizationId pointing to Pelagic Dynamics, but no commandId. The nullable columns are what allow both records to share one model without pretending an industry contact belongs to a Navy command.

Constraints and operational notes

email is unique, so registration and admin-user creation must handle a conflict rather than create a second identity. userId is unique on InterestProfile, making that relation one-to-one. Notification settings use a compound unique key by user and kind, so PUT is naturally an upsert.

passwordHash has no default because every account must come from an explicit registration, seed, or admin creation path. role does have a default of INDUSTRY, which is safe for public registration but means government role assignment must be deliberate.

mustChangePassword is the rotation gate for temporary admin-issued credentials. sessionVersion is the revocation counter: changing a password increments it, so every live JWT issued under the previous version becomes stale, including the caller's session on its next refresh.

A deleted or changed command assignment does not alter historical audit rows. Session authorization refreshes current role and command values on subsequent requests; the database remains the authority for present access.