# Phase 3 Setup Checklist ## Clerk + Supabase + Railway Staging Run these steps in order. Code is already written — you just need the keys. --- ## Step 1: Clerk Account Setup 1. Go to clerk.com and create an account 2. Create a new application — name it **Signal** 3. Select **React** as the framework 4. From **Dashboard > API Keys**, copy: - **Publishable Key** (starts with `pk_test_...`) — goes in the frontend `.env` - **Secret Key** (starts with `sk_test_...`) — NOT needed for our approach (we use JWKS) 5. From **Dashboard > API Keys > Show API URLs**, copy the **Frontend API URL** - Looks like: `https://clean-mayfly-62.clerk.accounts.dev` - Your JWKS URL is: `/.well-known/jwks.json` - Example: `https://clean-mayfly-62.clerk.accounts.dev/.well-known/jwks.json` - This goes in Railway as `CLERK_JWKS_URL` 6. From **Dashboard > Configure > Organizations**, enable Organizations - Create one organization named **Gaboro DME** with slug `gaboro-pilot` - Copy the Organization ID (starts with `org_...`) — you'll need it for the SQL step below - Add yourself as a member of that organization 7. From **Dashboard > Configure > Domains**, add: - `http://localhost:5173` (local dev) - Your Vercel production URL when it's live --- ## Step 2: Supabase Staging Project 1. Go to supabase.com, create a new project named **signal-staging** 2. Wait for it to provision (2-3 minutes) 3. Go to **Settings > API**, copy: - **Project URL** (looks like `https://xxxxxxxxxxxx.supabase.co`) - **Service Role Key** — must be the long `eyJ...` JWT format (NOT the `sb_` format) 4. Go to **SQL Editor**, run the migration SQL below --- ## Step 3: SQL Migration ### Run on BOTH Supabase projects (production + staging) ```sql -- Add Clerk org ID column to organizations table ALTER TABLE organizations ADD COLUMN IF NOT EXISTS clerk_org_id TEXT UNIQUE; -- Update the pilot org with the real Clerk org ID -- Replace org_xxxxxxxxxxxxxxxx with the actual org ID from Clerk Dashboard UPDATE organizations SET clerk_org_id = 'org_xxxxxxxxxxxxxxxx' WHERE slug = 'gaboro-pilot'; -- Helper function for RLS policies (reads Clerk org ID from JWT) CREATE OR REPLACE FUNCTION requesting_owner_id() RETURNS TEXT LANGUAGE SQL STABLE AS $$ SELECT COALESCE( (auth.jwt() -> 'o') ->> 'id', auth.jwt() ->> 'sub' ); $$; -- RLS on organizations ALTER TABLE organizations ENABLE ROW LEVEL SECURITY; CREATE POLICY "org_members_see_own_org" ON organizations FOR SELECT USING (clerk_org_id = requesting_owner_id()); -- RLS on upload_batches ALTER TABLE upload_batches ENABLE ROW LEVEL SECURITY; CREATE POLICY "org_members_see_own_batches" ON upload_batches FOR SELECT USING (org_id IN ( SELECT id FROM organizations WHERE clerk_org_id = requesting_owner_id() )); CREATE POLICY "org_members_insert_own_batches" ON upload_batches FOR INSERT WITH CHECK (org_id IN ( SELECT id FROM organizations WHERE clerk_org_id = requesting_owner_id() )); -- RLS on normalized_records ALTER TABLE normalized_records ENABLE ROW LEVEL SECURITY; CREATE POLICY "org_members_see_own_records" ON normalized_records FOR SELECT USING (EXISTS ( SELECT 1 FROM upload_batches ub JOIN organizations o ON o.id = ub.org_id WHERE ub.id = normalized_records.batch_id AND o.clerk_org_id = requesting_owner_id() )); -- RLS on report_runs ALTER TABLE report_runs ENABLE ROW LEVEL SECURITY; CREATE POLICY "org_members_see_own_runs" ON report_runs FOR SELECT USING (org_id IN ( SELECT id FROM organizations WHERE clerk_org_id = requesting_owner_id() )); -- RLS on export_files ALTER TABLE export_files ENABLE ROW LEVEL SECURITY; CREATE POLICY "org_members_see_own_exports" ON export_files FOR SELECT USING (EXISTS ( SELECT 1 FROM report_runs rr JOIN organizations o ON o.id = rr.org_id WHERE rr.id = export_files.report_run_id AND o.clerk_org_id = requesting_owner_id() )); -- NOTE: source_files and mapping_decisions are write-once audit records. -- The backend uses the service role key for writes (bypasses RLS by design). -- Add SELECT policies here when frontend reads are needed in a later phase. ``` --- ## Step 4: Connect Supabase to Clerk (Native Integration) ### In Clerk Dashboard: 1. Go to **Configure > Integrations** (or search "Supabase") 2. Click **Activate Supabase integration** 3. Copy your **Clerk domain** shown there ### In Supabase Dashboard (repeat for BOTH projects): 1. Go to **Authentication > Sign In / Sign Up > Third Party Auth** 2. Click **Add provider** > select **Clerk** 3. Paste the Clerk domain 4. Save This makes Supabase trust Clerk JWTs — no custom templates needed. --- ## Step 5: Set Railway Environment Variables ### Production environment: In Railway > signal-api > production > Variables, add: ``` CLERK_JWKS_URL = https://eternal-goblin-1.clerk.accounts.dev/.well-known/jwks.json ``` ### Staging environment: In Railway > signal-api > staging > Variables, add: ``` CLERK_JWKS_URL = https://eternal-goblin-1.clerk.accounts.dev/.well-known/jwks.json SUPABASE_URL = https://.supabase.co SUPABASE_ANON_KEY = eyJ... (anon key from staging project) SUPABASE_SERVICE_KEY = eyJ... (service role key from staging project) ``` Staging already has SIGNAL_API_KEY and ALLOWED_ORIGINS set. --- ## Step 6: Set Frontend .env Edit `signal-ui/.env`: ``` VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxxxxx ``` --- ## Step 7: Deploy Staging Backend Once all Railway staging vars are set, trigger a deploy: ```bash cd /Users/sttil-solutions/projects/signal railway up --detach --environment staging -m "Phase 3: Clerk auth + staging env" ``` --- ## Staging API Key (save this) The staging environment uses a separate API key for direct testing: ``` r0C6vm1RLeg84lB9jeZ5sSRieDZWsLL0dAMDfEcXubE ```