Corteksa

Onboarding

The Onboarding model — fields, relations, and API.

Transport: WebSocket — Socket.IO /ai-v2/onboarding (no REST) · Flag: AI_V2_ONBOARDING_ENABLED

PRD

Business problem

New users land in an empty workspace and don't know what to do (blank-page friction). This agent captures context — industry, role, team size, tools — once, verifies the owner's WhatsApp number, and drives fast activation: signup to a usable, personalized CRM in a couple of minutes.

User stories

  • As a new workspace owner, I want to be guided through a few quick questions and pick how my workspace is built, so that I go from an empty screen to a ready-to-use, personalized CRM without manual setup.

Success criteria

  • Connecting the socket immediately drives the flow: greeting + first question, or the terminal outcome if already done, or a rejection if the flag is off / the caller isn't the owner (there is no pre-socket status call).
  • WhatsApp verification is enforced (phone + phone_otp required); the global skip clamps at the phone step until the number is verified or a failed send releases it.
  • Answering setup_choice is terminal and idempotent — one onboarding_setup row per workspace, no duplicate handoff; is_new_user is cleared so onboarding never re-shows.

How it works

High-level overview — enough to understand the feature without reading the code.

Summary

It is a server-driven form: the backend owns every question and its render type; the frontend is a thin renderer that draws a widget per step.type and never hard-codes the list or order. The engine walks an ordered step registry by a cursor held in a transient Redis draft (answers accumulate; nothing is written to the DB until the terminal step). AI is used narrowly — normalizing free-text / "Other" answers and writing the confirmation summary — and the terminal setup_choice records the outcome and hands off to the Workspace Builder.

Flow

Steps

  1. Client connects the Socket.IO socket with the owner's admin Bearer token in the handshake (/ai-v2/onboarding).
  2. Server runs connect gates: feature flag, valid token, super-admin owner only; failure → onboarding:error + disconnect.
  3. Server emits onboarding:ready { agent: 'onboarding' }, then the current view — an onboarding:question (greeting on Q1) or onboarding:outcome if finished.
  4. Client renders by step.type; the user answers → onboarding:answer { step_key, value }; server validates, records, advances the cursor, returns the next question. onboarding:back revises the previous answer.
  5. Profile questions in order: name → industry → role → team_size → tool_integrations.
  6. Phone verify (required): submitting phone sends a WhatsApp OTP and returns the phone_otp step. Skip rule: phone/phone_otp are not freely skippable — only a genuine send failure sets phoneSkipUnlocked; an invalid-number 400 is not skippable.
  7. phone_otp: submit the code; it must verify to advance. onboarding:resend resends. The phone/OTP are never stored in the draft — the verified number lives in admin_whatsapp.
  8. invite (optional): capture teammate emails; they ride along in the handoff envelope — onboarding provisions none itself.
  9. setup_choice (terminal): records the choice, writes the onboarding_setup row, runs the builder handoff, clears the draft, flips is_new_user off, emits onboarding:outcome.

Key components

  • Gatewayrealtime/onboarding.gateway.ts (namespace /ai-v2/onboarding, websocket only) + realtime/onboarding-ws-auth.service.ts (super-admin owner gate).
  • Form engineservices/onboarding-form.service.ts (cursor walk / skip / back / jump); orchestration in services/onboarding-answer.service.ts (phone + OTP side-effects, terminal complete + handoff, idempotency).
  • AI gatesservices/onboarding-gates.service.ts; profile in services/onboarding-profile.builder.ts.
  • Persistenceservices/onboarding-draft.store.ts (Redis draft) and entities/onboarding-setup.entity.ts + repositories/onboarding-setup.repository.ts (RLS-pinned, idempotent).
  • Handoffservices/builder-handoff.service.ts (to the Workspace Builder).

FRD

Per-step functional requirements — the ordered step registry (the agent analogue of a model's fields), from steps/onboarding-steps.registry.ts:

#Step keyTypeRequiredSkippableNotes
1nametextyesno"What should I call you?"
2industryradionoyesallows "Other"
3roleradionoyesallows "Other"
4team_sizeradionoyessize buckets
5tool_integrationscheckboxnoyestools used today; allows "Other"
6phonephoneyesno (verify:true)sends WhatsApp OTP; failed send releases skip
7phone_otptextyesnoenter the code; forward-only (back → phone)
8inviteinvitenoyesinvite teammates by email (max 10)
9setup_choiceradioyesnoterminal: template / ai_build / zero

HLD

Frontend

A thin, server-driven renderer: it draws one widget per step.type and reacts to socket events — it never hard-codes the question list or order.

API

WebSocket only (Socket.IO /ai-v2/onboarding, transports: ['websocket']); auth via handshake Bearer token. There is no REST controller (the controllers//guards/ folders are empty scaffolding). The surface is summarized under API; the full wire contract is the co-located API.md, published under Guides.

Database changes

Nothing is written until the terminal step. In progress → a transient Redis draft. Terminal → one onboarding_setup row per workspace, plus flipping is_new_user off; the verified WhatsApp number is written to admin_whatsapp (never to the draft).

LLD

Tables

Redis draft (OnboardingDraft):

FieldTypeNotes
cursornumberindex of the next unanswered step
answersRecord<stepKey, StoredAnswer>discriminated by kind (text/radio/checkbox/invite); no phone variant
phoneSkipUnlocked?booleanset only on a genuine WhatsApp send failure

Key onboarding:draft:{tenantDatabase}:{workspaceId}:{adminId} (tenant scope baked in since RLS doesn't cover Redis), TTL 24h, cleared at the terminal write. Durable row onboarding_setup: workspace_id, admin_id, status, path, profile_snapshot (jsonb), handoff_at, timestamps — one row per workspace (uniqueness = idempotency).

Services

OnboardingFormService (cursor engine), OnboardingAnswerService (orchestration + side-effects), onboarding-gates.service.ts (AI normalize + summary), onboarding-outcome.mapper.ts (status → outcome), builder-handoff.service.ts.

Validators

answer-validator.ts validates each answer by step.type; the phone step enforces WhatsApp OTP verification. Connect-time gates (no per-message guard): AI_V2_ONBOARDING_ENABLED, a valid handshake token, and super-admin workspace owner (SEC-30) — rejection codes FORBIDDEN / AUTH_MISSING / AUTH_INVALID / FEATURE_DISABLED.

API

WebSocket only — no REST routes; it does not appear in the API Explorer. The authoritative wire contract (handshake, every event payload, error/rejection codes) is the co-located API.md, published under Guides. Event summary:

  • Client → server: onboarding:answer { step_key, value }, onboarding:back, onboarding:skip, onboarding:resend, onboarding:confirm, onboarding:refresh.
  • Server → client: onboarding:ready, onboarding:question, onboarding:outcome, onboarding:summary, onboarding:error { code, message }.

ERD

Status → outcome mapping and the terminal handoff (the agent analogue of an entity-relationship diagram):

  • OnboardingNextType = builder | pending | empty_workspace.
  • Statuses: pending, in_progress, completed, handed_off, handed_off_pending, skipped; terminal set = completed, handed_off, handed_off_pending, skipped.
  • markHandoffBuilt() later flips handed_off*completed once the builder has actually built (so next becomes empty_workspace).

On this page