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 obtaining access_token after getUser() 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 tenantId from the active tenant cookie
  • Must get tenantId via getActiveTenantId() (server-side)
  • Must not accept tenantId from 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 db instance to all query functions
  • Must not use Supabase SDK for data queries
  • Must not use raw pg client directly in routes

Schema Changes

  • Must run npm run db:generate after schema edits
  • Must add/verify RLS policies in the generated migration
  • Must run npm run db:migrate after 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_id or user_id from 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 tenantId in React Query keys for tenant-scoped data
  • Must invalidate queries on tenant switch

Dialogs

  • Must use DialogManager + useDialogStore for modals
  • Must define dialog types in utils/constants.ts
  • Must not use local dialog state in list components

On this page