Cross-cutting Concepts
Authentication and roles
Section titled “Authentication and roles”Riders, operators, and support use the same identity provider with different roles (rider, operator, support). A Fastify onRequest hook checks the token and rejects the call before the route handler. Dock devices authenticate to MQTT with per-station credentials issued at install time.
Database access
Section titled “Database access”PostgreSQL is on the private network, TLS only. The serving process uses the role citycycle_app and a small connection pool. Migrations run as citycycle_migrator from CI, before the new containers take traffic. See ADR 0005.
Redis requires a password and TLS. It is not a place for trip history.
TimescaleDB is a second instance for battery samples only. Unlock does not read it. See ADR 0003.
Idempotency of unlock
Section titled “Idempotency of unlock”POST /trips/unlock accepts an Idempotency-Key from the app. Retries after a flaky mobile network must not create a second trip or a second MQTT unlock for the same key within the lock TTL.
Observability
Section titled “Observability”Every unlock and return carries a trip_id through logs, MQTT correlation, and payment calls. The SLO dashboards watch unlock latency p95, Redis hit rate for the station map, and pending_payment age.