Architecture & Platform Strategy

One source of truth for every driver

Right now, "who is this driver, and what do we know about them" has three different answers depending which product you ask: Upahar's Supabase project, Express's separate Postgres, and whatever's cached in GPS Dashboard's browser session. Fintech mobility needs a single honest answer to that question more than any product so far. This page is the current-state audit, the debt inventory, and the investment plan, not a rewrite of everything, but a deliberate sequence of fixes plus one piece of new shared infrastructure.

Vision

What "one source of truth" actually means here

It does not mean one shared database. The opposite lesson is already sitting in the codebase: Upahar and GPS Dashboard sharing one Supabase project is the single worst-behaved part of the whole stack (debt inventory below). It means:

1Each product keeps owning its own operational data: Upahar rides/points, Express orders/deliveries, Financing loans/KYC
2One canonical driver identity, read by every product, owned by none of them individually
3A narrow, versioned API between products, never direct table access
4A lightweight analytics layer for cross-product signal, fed by each product's own database, not queried live

Today a driver is a phone number, independently a row in Upahar's drivers table and Express's drivers_table, with no link between them. A driver who rides for Yango and couriers for Express is, as far as either system knows, two different people. Item 3 is the specific fix for how GPS Dashboard currently works: reading and writing Upahar's production tables with a client-side key is the pattern to retire, not extend to Express or Financing.

Right-sized, not enterprise-sized: this was checked, not assumed. Items 2 and 4 are not a recommendation to build Kafka-style event streaming or a Debezium/CDC pipeline. Research into what small teams at this scale actually adopt is consistent: a dedicated identity/profile service that others call over a simple API is the right first move, no message bus needed. For analytics, a single small Postgres reporting database, fed by scheduled batch jobs, optionally with postgres_fdw for live cross-service reads, comfortably covers a data volume in the hundreds of thousands of rows. A full warehouse product (BigQuery, Snowflake, ClickHouse) and event-driven architecture are both genuinely premature here. Revisit only if batch latency becomes an actually-felt problem, not preemptively.
flowchart LR UP["Upahar"] --> ID[("Identity layer
new, one small service")] EX["Express"] --> ID FIN["Financing
not built"] --> ID GPS["GPS Dashboard"] -->|"read API,
not direct table access"| ID ID --> RPT[("Reporting DB
batch-fed, new")] classDef good fill:#d7ecdf,stroke:#1a7a42,color:#14532d,stroke-width:2px; classDef rethink fill:#f7e6c4,stroke:#b26a00,color:#7a4900,stroke-width:2px; classDef missing fill:#e5e7eb,stroke:#6b7280,color:#374151,stroke-width:2px; class UP good; class EX,GPS rethink; class FIN,ID,RPT missing;

Gray: the identity layer and reporting database don't exist yet. This is the target state, not what's running today (see below).

Current state

What's actually running, confirmed by reading each repository

ProductRepo / orgDatabaseStatus
Upaharhusig-ai/yapigoSupabase Postgres (Mumbai), shared with GPS DashboardGood
Expresshusig-ai/drivra_express_backendSelf-hosted Postgres 16, own Docker containerRethink
GPS DashboardDrivra-Mobility/gpsUpahar's Supabase project, no database of its ownRethink
GPS ETLtrakzee-supabase-etl (not in this workspace)Writes into Upahar's Supabase projectGood, out of scope
Analytics toolhusig-ai/drivraNone, manual CSV uploadAd hoc
Marketing siteDrivra-Mobility/drivra-webNoneStatic

Governance note: repos are split across two GitHub organizations (husig-ai and Drivra-Mobility) with no single infra-as-code source of truth visible anywhere. Worth deciding whether that's intentional (agency vs. client separation) or an artifact of how this got built, before adding a fifth product.

Debt inventory

What we're doing wrong, ranked by how much it matters

Critical: fix regardless of anything else

Express's CORS policy is wide open: allow_origins=["*"] combined with allow_credentials=True. This is a real vulnerability today, independent of the partner-API work. Any website can make authenticated requests against Express's API from a user's browser. Fix before building the partner API layer on top of it, not after.

GPS Dashboard has unrestricted, direct, client-side access to Upahar's production tables: a public anon key plus row-level security is the entire access-control boundary. This already caused one incident (below) and is the architectural reason a shared-Supabase pattern is unsafe to extend to a fintech product. Not new: Upahar's own tables had this exact gap, zero RLS, fully open, until a recent migration fixed it. GPS Dashboard is the one place the underlying pattern is still live.

High: already causing real problems

Supabase egress quota exceeded

A real production incident: the shared free-tier Supabase project exceeded its 5GB/month egress quota, traced to GPS Dashboard over-polling. Documented in GPS Dashboard's own code comments. This is what "will Supabase scale the way we're using it" concretely means: it already didn't, once.

Silent data truncation

PostgREST's default 1,000-row response cap silently truncates GPS Dashboard's queries on wide date ranges: a correctness bug, not just a performance one. Wide date-range reports may already be wrong without anyone knowing.

Express has no managed backup story

Its Postgres runs in a container with a persistent volume directly on the EC2 instance, the opposite of Upahar's deliberately stateless pattern. No confirmed automated backup/PITR. If that instance is lost, order/merchant/wallet history goes with it. Urgent enough to not wait for an RDS migration: a scheduled pg_dump to durable storage closes most of this risk in an afternoon.

Express has no test suite

Zero test files anywhere in the repo, versus Upahar's real suite. Combined with the CORS gap and the schema-management issue below, this is the least production-hardened of the three live products. Worth knowing before it's the foundation for point-to-point delivery and partner APIs.

Moderate: real, but not urgent
  • Express runs two competing schema sources on every startup, both Base.metadata.create_all() and alembic upgrade head, which can silently diverge from what its own migration history says the schema is.
  • Duplicated gamification logic: Upahar and Express each independently built their own points/badges/quests system. Not broken, but it's engineering effort spent twice on the same idea, and a sign of how little these products currently share.
  • No canonical driver identity across products, the core "one source of truth" gap. Two databases, two drivers tables, no link, no shared ID.

What to actually do

Low-hanging fruit vs. real investment

Sorted so the cheap, high-value fixes aren't waiting on the big structural ones. Most of the left column can happen this month without touching the right column at all.

Low-hanging fruit: days, not months

  • Lock down Express's CORS policy to actual known origins
  • Pick one schema source of truth for Express (Alembic only, drop create_all() from startup)
  • Add a basic Express smoke-test suite, mirroring Upahar's tests/ pattern
  • Take an immediate pg_dump backup of Express's database today, then schedule it: the single cheapest, most urgent item here, well ahead of any RDS migration timeline
  • Fix GPS Dashboard's silent truncation with explicit pagination on wide-range queries
  • Write down the "every new table revokes anon/authenticated" rule as a checklist item for any new Supabase-backed service, including Financing

Real investment: needs planning & budget

  • Build the canonical driver-identity layer (vision, above): one small service as system of record, plain API, no message bus needed at this scale
  • Retire GPS Dashboard's direct table access in favor of a proper read API or a dedicated analytics replica
  • Migrate Express's database to RDS: a pg_dump/restore during a maintenance window is right-sized here (AWS's DMS/logical-replication path is built for 250GB+ databases, well beyond an early-stage delivery app), roughly $15/month for a small Single-AZ instance, likely covered by AWS's 12-month free tier if the account is eligible
  • Build Express's partner API layer: auth, per-partner rate limiting, signed webhooks, idempotency, OpenAPI docs
  • Add point-to-point delivery as a distinct Express order type and matching path
  • Stand up the cross-product analytics layer: one small reporting Postgres database, batch-fed on a schedule, postgres_fdw for live cross-service reads where useful, not a full warehouse product
  • Build the new Financing service (see financing.html)
  • Decide and consolidate GitHub org/governance structure

Explicitly not needed yet: Kafka or any event bus, Debezium/CDC, a dedicated warehouse product, or PgBouncer/connection pooling, all genuinely premature at Drivra's current scale. Revisit pooling specifically via AWS RDS Proxy (managed, no new service to run) if and when it's ever needed, not before.

The full driver journey

Why fintech mobility is next in line, not a separate track

This is the journey the "one source of truth" investment is actually in service of. Every step before financing already exists in some form; financing is the first product that needs all of them working together to underwrite a real decision.

flowchart LR A["A driver appears
via Yango roster sync
or Express onboarding"] --> B["Activity builds
a history
rides and/or deliveries"] B --> C["Eligibility surfaces
a pre-check against
combined history"] C --> D["KYC & underwriting
new, in Financing"] D --> E["Ownership
installments +
progress tracker"] classDef good fill:#d7ecdf,stroke:#1a7a42,color:#14532d,stroke-width:2px; classDef missing fill:#e5e7eb,stroke:#6b7280,color:#374151,stroke-width:2px; class A,B,C good; class D,E missing;

Green: exists today, in some form. Gray: new, part of the Financing build.

Full design for the step this document was actually asked to produce: the rent-to-own financing system.