Data model

Authoritative schema: apps/web/prisma/schema.prisma. Highlights:

AreaTablesNotes
IdentityUser (optional unique username, githubId/googleId, last-seen githubOrgs / googleDomain, managedByWorkspaceId for workspace-managed logins), Session (authMethod: password / github / google)Dashboard users; sessions stored hashed
TenancyWorkspace (sign-in policy: allowedAuthMethods, auto-join githubOrgs / googleDomains (GIN-indexed), autoJoinRole), WorkspaceMembership (role, permissionGrants / permissionRevokes on top of it), InvitationUnique (workspaceId,userId); managed users cascade with their workspace
ProjectsProject, Environment (dev/staging/prod, publicKey, allowedOrigins, configVersion)configVersion drives SDK ETags
GuidesGuide (draftConfig JSON, kind, status, key), GuideVersion (immutable), GuideEnvironmentState (live/paused version per env), PublishEventSteps/targeting/trigger/frequency live in the validated config JSON; key is unique per project
AudiencesAudience (rules JSON, static flag), AudienceMember (static or evaluated)Evaluated at identify time
End usersEndUser (externalId, anonymousId, traits), Account, EndUserAccount, GuideUserStateAnonymous → identified merge on identify
AnalyticsProductEvent (unique (projectId,eventId)), GuideSession, StepInteraction, GuideDailyStatIndexed on (projectId, environment, occurredAt), (guideId, occurredAt), (projectId, name, occurredAt)
Checklists & surveysChecklistProgress (unique per guide+user), SurveyResponse (unique responseId)Definitions live in guide config
Resource centerResourceCenter, ResourceCollection (parentId for nesting, ≤3 levels; audienceIds), ResourceArticle (bodyText = plain text for search, importId, externalId = source id such as intercom:123, audienceIds), MediaAsset (uploaded article images/videos; bytes in S3 under media/{projectId}/; the nightly retention job deletes ones older than 7 days that no article, guide draft/version or resource center settings/snapshot references)Published snapshot stored on ResourceCenter.publishedConfig; article bodies drop out of it past 256 KB and load on demand. Empty audienceIds = everyone; ids of deleted audiences are kept (matching nobody)
ScoutScoutSettings (one per project; product name and context, and an optional encrypted project Anthropic key), Conversation (status bot/open/closed, tokenHash, unread counts, generatingAt claim), ConversationMessage (author user/scout/agent/system, citations, suggested guides, token usage, status), KnowledgeImport (uploaded file or Intercom import — source, encrypted secret token, progress — plus status; the row is the job), OutboundEmail (notification outbox)Imports and emails are claimed by the worker with FOR UPDATE SKIP LOCKED
Product analyticsReplaySession (one per recorded SDK session; failed-request count), ReplayChunk (index of recording uploads; the gzip JSON rrweb events are in S3, unique (replaySessionId, windowId, seq)), ReplayConsoleLog (distinct console lines per recording, trigram-indexed for search), ErrorIssue (unique (projectId, environment, fingerprint), status open/resolved/ignored), ProjectApiKey (hashed gly_sk_ keys) and SourceMap (index; maps in S3), ErrorAlertRule / ErrorAlert and ReplayAlertRule / ReplayAlert (rules and each fired alert with its email/Slack delivery), Insight (saved funnel/retention definitions)Exceptions, page views, clicks and heatmap batches are ProductEvents; ProductEvent.path holds the URL pathname and (projectId, sessionId) is indexed for replay timelines. See product-analytics.md
How-tosHowTo (title, description, shareToken / shareEnabled, viewCount, guideId / articleId of the tour and help article made from it), HowToStep (kind action/text/heading/tip/alert, action click/type/select/navigate, plain-text title and body, element, selectors, screenshotId → MediaAsset, highlight as fractions of the image, showHighlight, zoom, rendition* = the screenshot with the highlight drawn in for exports and articles, clientId unique per how-to for SDK retries); recordings are AuthoringSession rows with mode = capture and howToIdScreenshots live in media/{projectId}/ like article media and are kept by the unused-media sweep while a step uses them; deleting a step or how-to, or redacting a screenshot, deletes the images right away
MiscIntegration (per project), TicketIntegration (per workspace: Linear / Jira, secret encrypted with encryptSecret), TicketLink (a ticket opened for an error issue, recording or conversation; rows queued by error and replay alert rules start pending and are created by the outbox loop), AuthoringSession (mode pick = element picker, capture = how-to recording), AuditLog

Retention: events and guide sessions older than the retention window are deleted by the worker, along with error issues not seen within it; recordings follow the project's replay retention (default 30 days); audit logs are kept.