Tenant isolation
Postgres row-level security, not schema-per-tenant or a database per tenant
I chose RLS with a tenant-scoped session context because it enforces isolation at the database, without the operational cost of migrating N schemas or N databases on every change. Every query is filtered by policy, not by developers remembering a WHERE tenant_id.
The trade-off I accepted: a single policy bug becomes a cross-tenant breach, so isolation correctness moves into testing and review discipline. And a very large tenant cannot be physically separated later without rework. For a small team, that was a better risk than the operational weight of a schema or a database per tenant.
Authentication
A dual-token flow that separates identity from tenant context
A user can belong to several organisations, so I split who you are (an identity token) from which tenant you are acting in (a tenant token). Tenant switching stays clean, and the tenant claim never rides on the long-lived identity credential.
The trade-off I accepted: more moving parts in the auth flow, and more tokens to issue, rotate, and reason about than a single fat JWT. I paid that deliberately to keep the tenant boundary explicit.
System shape
A modular monolith on an event-driven spine, not microservices yet
The core is a domain-driven monolith, but all heavy work flows through BullMQ and Redis to dedicated workers. Domains are already decoupled through queues, so they can be extracted into services when scale actually demands it, without paying distributed-systems tax on day one.
The trade-off I accepted: I deferred the operational maturity that microservices force early, things like distributed tracing, per-service deploys, and a service mesh. The event-driven seams keep that door open rather than welding it shut.
AI pipeline
Multi-provider RAG grounded in UAE legal texts
Generation and analysis run as retrieval-augmented generation over a corpus of UAE legal source material, so outputs are anchored in real law rather than model memory. Supporting both Anthropic and OpenAI gives provider failover and lets me route by task.
The trade-off I accepted: two provider integrations and two prompt surfaces to maintain instead of one. Worth it for resilience and routing flexibility on a legally sensitive workload.