CRUMB a card from devarno-cloud

Onboarding Lifecycle Events

choco intermediate 5 min read

ELI5

The onboarding flow is a tour bus with stops. Every time the bus pulls in to a stop, leaves a stop, skips a stop, gets stuck at a stop, or restarts the route, it radios the dispatcher. The dispatcher is the analytics service.

Technical Deep Dive

Defined in proto/events/onboarding/v1/lifecycle.proto. Stream choco:events:onboarding, ordering causally_ordered by session_id.

Happy-Path Sequence

sequenceDiagram
participant U as User
participant W as cho-co-web
participant G as choco-gateway
participant N as NATS (onboarding)
participant C as choco-consumers
U->>W: visit /onboarding
W->>G: POST /onboarding/start
G->>N: OnboardingStarted (session_id, user_id)
loop steps
U->>W: complete step
W->>G: POST /onboarding/step
G->>N: OnboardingStepCompleted (step_id, ordinal)
end
U->>W: pick path
W->>G: POST /onboarding/path
G->>N: OnboardingPathSelected (path_id)
G->>N: SiteProvisionRequested (site_id)
G-->>N: SiteProvisionStateChanged * 6
G->>N: SiteProvisionCompleted OR WelcomeSiteProvisioned
G->>N: OnboardingCompleted (total_steps, total_duration_ms)
N->>C: fan-out to analytics + observability

Skipped vs Abandoned

TriggerFieldSource
Skippeduser explicitly clicks “skip”skip_reason (string)lifecycle.proto:48-54
Abandonedtimeout-based heuristic, no user actionidle_duration_ms (int32)lifecycle.proto:67-73

Different consumers care about different things: skip drives funnel analytics; abandon drives reactivation campaigns.

Aggregate Identity

For lifecycle events the aggregate is session_id; for the provisioning saga events the aggregate is site_id (lifecycle.proto:159). Two-aggregate flow: a single onboarding session can spawn one or more sites, and SiteProvisionRequested.workspace_id ties them back to the session’s user/workspace.

Site Provisioning Sub-Saga

The terminal step of onboarding is provisioning a documentation site (lifecycle.proto:81-99). Two terminal events depending on hosting mode:

  • SiteProvisionCompleted — user_git path; ships once the saga reaches live. Carries vercel_project_id, deploy_url, total_duration_ms.
  • WelcomeSiteProvisioned — platform_template path; emitted synchronously from the onboarding final step, no saga.

SiteProvisionFailed distinguishes failure from awaiting_github (lifecycle.proto:203-205); the latter is a non-terminal pause and does NOT emit Failed.

Reset Semantics

OnboardingReset (lifecycle.proto:77) carries initiated_by to distinguish self-reset from admin reset, both of which return the session to step 0 but log differently.

Key Terms

  • session_id → primary aggregate for the lifecycle events; one row in the gateway’s onboarding sessions table.
  • causally_ordered → events for the same session_id arrive in publish order; cross-session order is not preserved.
  • terminal pauseawaiting_github; saga stops emitting state-changed events but is not failed.

Q&A

Q: Does OnboardingResumed reset session_id? A: No — it reuses the existing session_id and reports gap_duration_ms (lifecycle.proto:60-64). Reset is the event that mints a new session.

Q: Which consumers subscribe to this stream? A: The proto comment for the saga states “consumed by choco-consumers (analytics, observability) and the cho-co-web SSE status endpoint” (lifecycle.proto:91-93).

Q: Are SiteProvisionStateChanged events high frequency? A: Yes — ~6 per happy-path provision (lifecycle.proto:175-176). Consumers should subscribe selectively (e.g. only to_state IN (live, failed)) unless they’re driving the SSE stream.

Examples

A user starts onboarding, completes 3 steps, picks the “blog” path, doesn’t have GitHub linked, and the saga parks in awaiting_github. Wire trace: OnboardingStarted → 3× OnboardingStepCompletedOnboardingPathSelectedSiteProvisionRequested(has_github_link=false)SiteProvisionStateChanged(requested → source_resolving → awaiting_github). No Completed or Failed is emitted. The user later links GitHub and a fresh Start() resumes the saga.

neighbors on the map