Supabase setup
cd packages/server
npx supabase login
npx supabase link --project-ref <your-ref>
npx supabase db push # applies every migration in supabase/migrations/Run these commands from packages/server/ — the Supabase CLI looks for supabase/ relative to the current directory.
Required extensions
Enable these extensions in your Supabase project (Database → Extensions):
| Extension | Purpose |
|---|---|
vector | Embeddings for dedup + semantic search (commonly called pgvector) |
pg_cron | Scheduled MV refreshes, nightly judge runs, retention cron |
pgcrypto | UUIDs and HMAC |
pg_net | Async HTTP from cron jobs and triggers (plugin dispatcher, edge-function crons) |
citext | Case-insensitive text columns |
supabase_vault | BYOK + plugin-secret storage (on by default in Supabase projects) |
age (optional) | Apache AGE graph backend for the knowledge graph |
The migrations run create extension if not exists for vector, pgcrypto, pg_cron, pg_net, citext and age. Vault is part of every Supabase project. Report columns are not encrypted with pgsodium.
Required custom functions
The migration sequence creates the following custom Postgres functions:
| Function | Purpose |
|---|---|
mushi_rls_coverage_snapshot() | Lists every public table and whether RLS is enabled on it |
mushi_apply_retention() | Applies project_retention_policies (also scheduled by pg_cron) |
DSARs are handled via the data_subject_requests table + the api edge function (POST /v1/admin/compliance/dsars), not a stored procedure. SOC 2 evidence is generated by the soc2-evidence edge function triggered from the admin console — no soc2_evidence_snapshot() RPC exists. Both routes are documented in the Security & Compliance section.
RLS pattern
Every table ships with RLS enabled and project-scoped policies. The canonical policy pattern is:
-- Use the subquery form — Postgres caches via initPlan, not per-row
create policy "users see own project data"
on public.reports
for select
using (project_id in (
select project_id from public.project_members
where user_id = (select auth.uid())
));Do not use bare auth.uid(): the subquery form is evaluated once per query instead of once per row, which matters on large tables.
Seed data
There is no separate seed file. The migrations insert the lookup rows themselves
(pricing plans, the plugin registry, the mushi_runtime_config defaults), so
db push is enough.
Verifying the setup
-- Tables with RLS disabled (should return no rows)
select * from mushi_rls_coverage_snapshot() where not rls_enabled;
-- Check extensions
select name, default_version, installed_version
from pg_available_extensions
where name in ('vector', 'pg_cron', 'pg_net', 'pgcrypto', 'supabase_vault');