Trip lifecycle: the state machine of record
Same pattern as driver onboarding (1.2): long-running process = persisted state machine. A trip is shorter (minutes not days) but higher stakes: money (1.8) and safety hang off its transitions. Matching (1.6) hands us a trip in Assigned.
The obvious design is to store the latest trip status and trust each client to send transitions in a sensible order. It makes the happy path simple, but it protects the wrong property. Only an authorized command valid for the current state may advance a trip; refused commands leave both current state and historical evidence intact.
The Trip service is the system of record for what actually happened. Every transition is an immutable event row (who, when, where) appended alongside the current-state column — cancellation fees, fare calculation, support disputes, and regulatory reports (1.13) all replay this record. States and guards:
stateDiagram-v2
[*] --> Requested : rider confirms quote
Requested --> Assigned : matcher assigns driver (1.6)
Requested --> NoDriver : candidates exhausted
Assigned --> Arriving : driver en route to pickup
Arriving --> Waiting : driver within 50 m of pickup
Waiting --> InProgress : driver taps start — GPS at pickup
InProgress --> Completed : driver taps end — fare finalized
Assigned --> CancelledByRider : may incur fee after grace period
Arriving --> CancelledByRider : fee if driver near
Waiting --> CancelledByDriver : no-show after 5 min wait
Assigned --> CancelledByDriver : penalized — re-dispatch rider
Completed --> [*]
NoDriver --> [*]
CancelledByRider --> [*]
CancelledByDriver --> [*]
Two design rules keep this robust. First, transitions are commands with guards, not notifications: "start trip" is rejected unless the trip is in Waiting and the driver's GPS is near the pickup — the state machine defends itself against buggy or malicious clients. Second, during the trip, the location stream (1.4) is forked: pings from a driver on an active trip are additionally appended to that trip's route trail, which becomes the billing-grade distance record at completion. Quotes estimated with cell math; fares settle on the recorded route.
sequenceDiagram
autonumber
participant D as Driver app
participant T as Trip service
participant L as Location service
participant R as Rider app
participant P as Payments (1.8)
D->>T: start trip
T->>T: guard — state Waiting, GPS at pickup? OK → InProgress
T->>R: trip started
loop every 4 s during trip
D->>L: ping
L->>T: append to trip route trail
T-->>R: live position + updated ETA
end
D->>T: end trip
T->>T: guard OK → Completed. Fare = rates × recorded route
T->>P: capture fare {trip id, amount}
T-->>R: trip complete — receipt pending
T-->>D: earnings updated
GPS lies: tunnels, urban canyons, phones dying mid-trip. The trail recorder must tolerate gaps (interpolate for billing, flag large gaps for review) and the state machine must allow ops to repair a trip stuck in InProgress because a phone died at the destination. "What does support do when this wedges?" is a design requirement, not an afterthought.