Identity and onboarding
From 1.1: the Identity service owns users, credentials, sessions, and devices. From 0.2: background checks are bought, not built — which means onboarding contains a step we don't control.
The simplest version would implement onboarding as one long transaction that either activates a driver or rolls everything back. The better question is what survives when that assumption fails. Every completed onboarding step must survive process and vendor delays, and only valid transitions may reach an approved or denied terminal decision.
Rider signup is deliberately boring: phone number + OTP, a JWT session token validated at the gateway (so downstream services never re-authenticate), and a device record for push notifications. Boring is the goal — authentication sits on the critical path of every request, so it must be the most cache-friendly, least clever component we own.
Driver onboarding is the interesting half. It spans days, involves document upload, human review, and a third-party background check whose latency we cannot control. Any process with external waits must be a resumable state machine persisted in the database — never an in-memory workflow that dies with a deploy. This is the first appearance of a pattern that recurs in trips (1.7) and payments (1.8): long-running work = explicit persisted states + events that move between them.
stateDiagram-v2
[*] --> Applied : driver submits form
Applied --> DocsPending : account created
DocsPending --> DocsReview : license, insurance, vehicle photos uploaded
DocsReview --> DocsPending : rejected — resubmit
DocsReview --> BackgroundCheck : docs approved
BackgroundCheck --> ManualReview : vendor flags record
BackgroundCheck --> Approved : vendor clears
ManualReview --> Approved : ops approves
ManualReview --> Denied : ops denies
Approved --> Active : training completed, first go-online
Denied --> [*]
Worked example: the background-check step
sequenceDiagram
autonumber
participant D as Driver app
participant ID as Identity service
participant S3 as Object store
participant V as Check vendor
participant OPS as Ops console
D->>ID: upload documents
ID->>S3: store encrypted docs
ID->>ID: state = DocsReview
OPS->>ID: approve docs
ID->>V: request background check (async, webhook callback)
ID->>ID: state = BackgroundCheck
Note over ID,V: hours to days pass — state survives deploys and restarts
V-->>ID: webhook: clear
ID->>ID: verify webhook signature, state = Approved
ID->>D: push notification: you're approved
Webhooks arrive late, duplicated, and out of order. The handler must be idempotent (a duplicate "clear" is a no-op) and must validate state transitions (a "clear" arriving after ops already denied the driver must not resurrect the application). Idempotency gets a full treatment in 2.4 — here you just meet it.