Rules & Invariants
Rules & Invariants
These are the non-negotiable rules of the foundation. They must be followed at all times — by you and by AI tools. Breaking these rules leads to security vulnerabilities, data leaks, or architectural drift.
Authentication
- Must use Supabase Auth exclusively
- Must use
requireUser()in all protected routes - Must forward JWT to PostgreSQL via
withAuthDb() - Must use
getUser()to verify authentication (contacts Supabase Auth server) - Must not trust
getSession()alone — data comes from cookies and may not be authentic getSession()is only for obtainingaccess_tokenaftergetUser()succeeds
Authorization
- Must enforce via PostgreSQL RLS only
- Must not implement auth logic in application code
- Must not bypass RLS
- Must add/maintain RLS policies whenever schema changes add or modify tables
Cookies in Next.js 15
- Must not modify cookies in Server Components (layouts, pages)
- Must only modify cookies in Route Handlers or Server Actions
- Reading cookies in Server Components is allowed
- Use the "lazy sync" pattern: read in Server Component → sync via API on client mount
Tenant Filtering
- Must filter queries by
tenantIdfrom the active tenant cookie - Must get
tenantIdviagetActiveTenantId()(server-side) - Must not accept
tenantIdfrom client request body - RLS is the security layer; the tenant filter is the UX layer (shows correct data)
Database Access
- Must use
withAuthDb(jwt, callback)as the only entry point - Must pass the
dbinstance to all query functions - Must not use Supabase SDK for data queries
- Must not use raw
pgclient directly in routes
Schema Changes
- Must run
npm run db:generateafter schema edits - Must add/verify RLS policies in the generated migration
- Must run
npm run db:migrateafter updating policies
API Routes
- Must follow the pattern:
requireUser → withAuthDb → query → response - Must use response helpers from
lib/api/responses.ts - Must validate input with Zod
- Must not accept
tenant_idoruser_idfrom the client
Supabase
- Must use only for authentication
- Must not use as a database layer
- Must not expose the service role key
Components
- Must be presentational only
- Must receive data via props
- Must not fetch data directly
- Must not access Supabase or the database
Client State
- Must use Zustand for global UI state
- Must not store server data as the source of truth
- Must use React Query for server state
- Must include
tenantIdin React Query keys for tenant-scoped data - Must invalidate queries on tenant switch
Dialogs
- Must use
DialogManager+useDialogStorefor modals - Must define dialog types in
utils/constants.ts - Must not use local dialog state in list components