Database roles
Who connects to Postgres, as what, and what each can do.
Migrations create roles. The server’s migration runner (app/jobs/migrate.py) creates one login per role
on the first start and sets its password again on every start, with passwords it made on the first start and keeps
in the secrets volume (on Google Cloud Run, a Secret Manager secret). No login is superuser or has BYPASSRLS;
the runner checks that on every start, so an owner that isn’t a superuser (Supabase’s) works. Through Supabase’s
session pooler each login gets the owner’s project suffix: api_login.PROJECTREF.
| Role | Login | Used by | Can do |
|---|---|---|---|
app_user |
api_login |
The API | Whatever the user’s role in each book allows, through row-level security; the sign-in and admin functions |
account_purger |
purge_login |
The daily purge, and the scheduler | Run the purge and delete expired sessions; read the jobs’ schedule and record their runs (job_schedule(), job_started(), job_finished()). Nothing else |
fx_updater |
fx_login |
The daily rates job | Write market exchange rates. No user data |
| owner | postgres (built-in database) or the operator’s owner login |
Migrations and the backup | Change the schema; pg_dump must see every row. The API never has it |
anon and authenticated (Supabase’s browser roles, on an operator’s own Supabase) have no access to any table
or function. An AI role comes with AI.
Book roles (inside RLS)
| Book role | Can |
|---|---|
| owner | Everything: invite and remove people, change roles, settings, delete the book |
| editor | Add, edit, delete accounts, categories, people, transactions, budgets, exchange rates |
| viewer | Read only |
The server’s admin
Not a database role: users.is_admin, set by the database for the account made with the setup code (or by
python -m app.admin make-admin). The admin’s functions check it with am_admin().