# Yama

> Supabase standards — auth, Row Level Security, schema migrations, typed clients, and the boundary between Supabase app data and the FastAPI/Render Postgres side. Use when writing Supabase queries, auth flows, RLS policies, Supabase migrations, storage rules, or deciding where data should live.

- Skill: `arjuncrevathi/yama` (Agent Skill)
- Install (CLI): `npx skillmds@latest add arjuncrevathi/yama`
- Raw SKILL.md: https://api.skillmd.com/api/skills/arjuncrevathi/yama/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: arjuncrevathi (https://skillmd.com/u/arjuncrevathi)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/arjuncrevathi/yama

---


# Yama — Judge at the Threshold (Supabase)

Yama weighs every request at the door: who you are, what you may see, what you may touch. In this stack Supabase is the source of identity and the home of app data; nothing crosses the threshold unjudged.

## The three iron rules

- **RLS on every table, always.** `alter table … enable row level security` is part of creating a table, not a hardening step for later. A table without RLS is publicly writable through the anon key — treat a missing policy as a P1 (see `dhanvantari`).
- **The anon key is public.** It ships in the browser bundle; design as if it's printed on a billboard. All protection comes from RLS and auth, none from the key.
- **The service-role key bypasses RLS and never leaves the server side.** It may exist only in FastAPI/Render env vars — never in frontend env, never in a `VITE_`/`NEXT_PUBLIC_` var, never in a repo (see `kubera`). If it ever reaches a browser, rotate it the same hour.

## Auth

- Use Supabase Auth as the single identity provider for the whole stack; `auth.uid()` is the one canonical user ID everywhere — Supabase tables, FastAPI claims, and Render Postgres rows all reference it.
- Frontend: use the official client's session handling; subscribe to `onAuthStateChange`; never persist tokens yourself, never decode the JWT client-side to make security decisions.
- FastAPI verifies the Supabase access token on every request — signature against the project JWKS, `exp`, and audience — and derives the user from the verified claims, never from a request body or query param (see `vayu`).
- Access tokens are short-lived by design; the client refreshes automatically. Anything that caches a token (server jobs, tests) must handle expiry.
- Prefer OAuth providers + magic links over passwords where product allows; if passwords are enabled, turn on leaked-password protection in the dashboard.

## RLS policy patterns

- Default deny: create the table, enable RLS, then grant the narrowest policies. No `using (true)` on user data, ever.
- Owner pattern (most tables): `using (auth.uid() = user_id)` for select/update/delete, `with check (auth.uid() = user_id)` for insert. Both clauses — `using` without `with check` lets users insert rows they can't read.
- Separate policies per operation (`for select`, `for insert`, …) instead of one `for all` — auditability beats brevity.
- Policies are code: they live in migration files in the repo, reviewed in PRs, never edited ad hoc in the dashboard.
- Test policies like logic: for each table, an automated check that user A cannot read or write user B's rows (see `agni`). A policy that has never been tested against the negative case doesn't exist.
- `security definer` functions are RLS escape hatches — each one needs a comment justifying it and a review from the `muruka` mindset.

## Schema & migrations

- All schema changes go through the Supabase CLI as migration files (`supabase migration new …`), committed and applied by CI — the dashboard SQL editor is for reading, not writing. Dashboard drift is schema you can't reproduce.
- Keep a local dev stack (`supabase start`) so migrations are exercised before they touch the hosted project; `supabase db reset` must always succeed from zero.
- Additive first: add nullable column → backfill → constrain → remove old (see `hanuman` for the heavy versions).
- After every schema change, regenerate types (`supabase gen types typescript`) in the same PR — the frontend compiles against the new truth or the PR doesn't merge.

## Client usage

- One client instance per runtime, created in one module — not one per component, not one per call.
- Select exactly the columns you need; `select('*')` in app code is a review comment.
- Batch with `in()` and range-paginate; N sequential single-row fetches from a component is an N+1 you shipped to the user's phone.
- Handle the error member of every response: `const { data, error }` where `error` is ignored is a silent failure.

## Storage & Realtime

- Storage buckets are private by default; public buckets are an explicit, justified decision. Access via signed URLs with the shortest workable TTL; path convention `bucket/{user_id}/…` so policies can match on ownership.
- Realtime subscriptions: subscribe narrowly (per conversation, not per table), always unsubscribe on unmount (see `maya`), and treat events as cache-invalidation hints, not as the source of truth.

## The boundary — Supabase vs Render Postgres

- **Supabase holds**: identity, profiles, workspace/app domain data — anything the frontend reads directly under RLS.
- **Render Postgres holds**: conversations, messages, embeddings, AI run artifacts — anything written by FastAPI in the AI path (see `shesha`).
- The frontend never talks to Render Postgres; FastAPI never becomes a proxy for data RLS already protects. If a feature needs both stores in one view, the frontend makes two calls — no cross-store joins, no FK across databases; the shared `auth.uid()` string is the only link.
- Copying data across the boundary is a migration decision (see `hanuman`), not a convenience.

## Before merging Supabase work — checklist

- [ ] Every new table has RLS enabled with per-operation policies, `using` and `with check`
- [ ] Negative policy test exists: user A blocked from user B's rows
- [ ] Change is a CLI migration in the repo; `supabase db reset` passes
- [ ] Types regenerated; frontend compiles
- [ ] No service-role key outside server-side env; no new `security definer` without justification
- [ ] Data placed on the correct side of the Supabase/Render boundary

