Workflow: Tutor Onboarding

Technical reference for the tutor onboarding workflow module. This page documents the canonical persisted states, the normalized projection returned to consumers, the command surface, the routes that use it, and the precise downstream effects on tutor access.

Canonical persisted states

tutor_profiles.onboarding_status is normalized to lowercase snake_case only: invite_pending, intake_in_progress, pending_review, approved, rejected. Legacy values such as Pending_Review, completed, and transitioned_pending are no longer valid persisted states.

Module Contract

ConcernImplementationWhat it does
Workflow moduleworkers_nivaa/src/lib/workflows/tutor-onboarding/Owns the tutor onboarding aggregate, normalized projection, and reviewability logic.
Aggregatetypes.ts + repository.tsLoads tutor users row, tutor_profiles row, invite-placeholder state, and capability booleans.
Projectionguards.ts + projections.tsMaps raw persisted state into a normalized UI/API shape such as approved, pending_review, or rejected plus next_step and access booleans.
Commandscommands.tsDefines observe_tutor_onboarding, decide_tutor_onboarding, and the status resolution helper used after intake submission.
Effectseffects.tsPackages workflow results in the shared workflow result shape so routes can emit consistent metadata.
Shared contractworkers_nivaa/src/lib/workflows/core/contracts.tsProvides actor/context/result/event/notification shapes shared across workflow modules.

Technical Inputs And Outputs

SurfaceReadsWrites / DecisionsImpacts
GET /intake/tutorTutor aggregate + projectionNoneDrives tutor workspace gate, profile shell, and onboarding CTA state.
POST /intake/tutorExisting raw status + profile completenessRoute writes tutor profile fields and then uses workflow helper to resolve the next persisted statusMoves rejected or incomplete tutors back into pending_review when appropriate, then emits coordinator-facing review notifications.
PUT /admin/tutors/:id/onboardingReviewable raw status from workflowAdmin route persists approved or rejected after workflow validationUnlocks or blocks tutor workspace/postings access and removes tutors from the review queue.
Tutor workspace layoutProjection status + capability booleansNoneShows pending/rejected/approved gate screens without re-implementing state mapping.
Tutor postings accessProjection can_browse_postingsNoneBlocks non-approved tutors from /postings/browse and application flows.
Telegram tutor commandsProjection + access guardsNonePrevents Telegram from becoming a side-channel bypass for unfinished onboarding.

GET /intake/tutor Contract

Baseline particulars contract

GET /intake/tutor is not only an onboarding-status endpoint. It is the canonical tutor-profile read contract for /tutor/profile. The response must always provide a usable baseline for tutor particulars even when tutor_profiles is sparse or missing.

Response shape relied on by `/tutor/profile`
type TutorIntakeReadResponse = {
  nric_name: string | null;
  display_name: string | null;
  contact_number: string | null;
  address: string | null;
  gender?: string | null;
  dob?: string | null;
  race?: string | null;
  employment_type?: string | null;
  postal_code?: string | null;
  area?: string | null;
  languages: string[];
  special_needs_experience: string[];
  subjects: Array<string | { subject: string; level?: string; stage?: string }>;
  grades?: number[];
  schedule: unknown[];
  onboarding_status?: string | null;
  onboarding: TutorOnboardingProjection;
};
FieldPrimary sourceFallbackReason
nric_nametutor_profiles.nric_nameusers.display_nameThe profile page labels this as Full Name; tutors created through the normal onboarding flow may only have users.display_name populated.
display_nameusers.display_namenullProvides the canonical user-level display name separately from the historical nric_name field.
contact_numbertutor_profiles.contact_numberusers.phoneLegacy or invite-only tutor rows may not have copied profile contact details yet.
addresstutor_profiles.addressusers.address_data.full_addressLegacy/imported tutors may have the user-level address but no profile-level address.
languages, subjects, schedule, special_needs_experiencetutor_profiles / hard_constraintsEmpty arrays when no tutor profile row existsThe page should render a usable empty state, not undefined fields.

Detailed Lifecycle

StepTriggerPersisted stateWorkflow interpretationWhat it impacts next
1. Account existsTutor self-registers or coordinator creates the account/inviteinvite_pending or intake_in_progress depending on setup stateThe workflow sees whether the tutor still has a placeholder invite password and whether intake is materially complete.Tutor can log in but will be held in the onboarding gate.
2. Invite completionPOST /auth/complete-invite or equivalent login/setup pathUsually remains invite_pending until intake is completeProjection can move from invite_pending toward intake_in_progress once the placeholder-password gate is cleared.Tutor can access /tutor/profile and continue setup.
3. Intake submissionPOST /intake/tutorResolved via resolveTutorOnboardingStatusAfterIntake()If the profile is ready for review, the helper returns pending_review. The route then emits coordinator in-app notifications and secondary Telegram fanout. If the profile is not ready, it preserves the current raw status.Coordinator tutor review queue and tutor gate state update immediately.
4. Coordinator reviewPUT /admin/tutors/:id/onboardingapproved or rejecteddecideTutorOnboardingCommand validates that the current raw status is reviewable, persists the decision, and returns the updated aggregate for response projection.Approved tutors get workspace + postings access; rejected tutors get update-and-resubmit UX.
5. Rejected resubmissionTutor edits profile and resubmits intakepending_reviewThe workflow helper clears the rejection note and returns the tutor to review rather than leaving them stranded in rejected.Coordinator sees the tutor back in review; tutor sees a pending state instead of a rejection state.
6. Approved operational accessTutor opens workspace, postings, or TelegramapprovedProjection sets can_access_workspace = true and can_browse_postings = true.Tutor can browse postings, apply, and use normal tutor workspace surfaces.

Projection Shape

Normalized tutor onboarding projection
type TutorOnboardingProjection = {
  status: 'account_created' | 'invite_pending' | 'intake_in_progress' | 'pending_review' | 'approved' | 'rejected';
  raw_status: string | null;
  can_complete_invite: boolean;
  can_submit_for_review: boolean;
  can_access_workspace: boolean;
  can_browse_postings: boolean;
  rejection_reason: string | null;
  next_step: string;
};

Tables And Fields Touched

  • users: id, email, display_name, phone, created_at plus placeholder-password detection for invite state.
  • tutor_profiles: onboarding_status, rejection_reason, contact/address/introduction/intake fields, launch_cohort for rollout tagging.
  • Read-only capability booleans are derived in the aggregate and must not be persisted separately.