echo "URL: $SUPABASE_URL" && echo "REF: $SUPABASE_PROJECT_REF" && echo "DB_URL: $SUPABASE_DB_URL" | sed 's/:[^:]*@/:****@/'
URL: https://<ref>.supabase.co REF: <ref> DB_URL: postgresql://postgres.<ref>:<redacted>@aws-0-us-east-1.pooler.supabase.com:5432/postgres
PGPASSWORD="$SUPABASE_DB_PASSWORD" psql "$SUPABASE_DB_URL" -c "\dt public.*"
List of tables Schema | Name | Type | Owner --------+-------------+-------+---------- public | org_members | table | postgres public | orgs | table | postgres public | tickets | table | postgres (3 rows)
PGPASSWORD="$SUPABASE_DB_PASSWORD" psql "$SUPABASE_DB_URL" -c "SELECT * FROM public.orgs;"
id | name --------------------------------------+-------- bc3643bf-c848-4406-80a4-93cde591aef9 | Acme 1c6982f3-4eb0-409c-8899-405729ffccdc | Globex (2 rows)
PGPASSWORD="$SUPABASE_DB_PASSWORD" psql "$SUPABASE_DB_URL" -c "SELECT * FROM public.org_members;"
org_id | user_id | role --------------------------------------+--------------------------------------+-------- bc3643bf-c848-4406-80a4-93cde591aef9 | 1a403877-990d-4cbf-a093-cb4f90d13d55 | admin bc3643bf-c848-4406-80a4-93cde591aef9 | d6afa1a2-7c27-4ee8-9dbf-9c62be035f55 | member 1c6982f3-4eb0-409c-8899-405729ffccdc | f8c61839-90ec-4c29-8df4-22d7f20e2f6b | member (3 rows)
PGPASSWORD="$SUPABASE_DB_PASSWORD" psql "$SUPABASE_DB_URL" -c "SELECT * FROM public.tickets;"
id | org_id | title | status ----+--------------------------------------+----------------+-------- 1 | bc3643bf-c848-4406-80a4-93cde591aef9 | Fix login | open 2 | bc3643bf-c848-4406-80a4-93cde591aef9 | Update billing | open 3 | bc3643bf-c848-4406-80a4-93cde591aef9 | Rotate keys | open 4 | 1c6982f3-4eb0-409c-8899-405729ffccdc | Migrate DNS | open 5 | 1c6982f3-4eb0-409c-8899-405729ffccdc | Renew cert | open (5 rows)
PGPASSWORD="$SUPABASE_DB_PASSWORD" psql "$SUPABASE_DB_URL" -c "SELECT schemaname, tablename, policyname, permissive, roles, cmd, qual, with_check FROM pg_policies WHERE schemaname = 'public' ORDER BY tablename, policyname;"
schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check ------------+-----------+------------+------------+-------+-----+------+------------ (0 rows)
PGPASSWORD="$SUPABASE_DB_PASSWORD" psql "$SUPABASE_DB_URL" -c "
SELECT tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public' AND tablename IN ('tickets', 'org_members', 'orgs');
"tablename | rowsecurity -------------+------------- org_members | f orgs | f tickets | f (3 rows)
PGPASSWORD="$SUPABASE_DB_PASSWORD" psql "$SUPABASE_DB_URL" -c " SELECT id, email, raw_user_meta_data FROM auth.users; "
id | email | raw_user_meta_data
--------------------------------------+-----------------------+--------------------------
d6afa1a2-7c27-4ee8-9dbf-9c62be035f55 | vic-member@cari.dev | {"email_verified": true}
1a403877-990d-4cbf-a093-cb4f90d13d55 | uma-admin@cari.dev | {"email_verified": true}
f8c61839-90ec-4c29-8df4-22d7f20e2f6b | wen-outsider@cari.dev | {"email_verified": true}
(3 rows)
PGPASSWORD="$SUPABASE_DB_PASSWORD" psql "$SUPABASE_DB_URL" <<'SQL'
-- SECURITY DEFINER functions bypass RLS, avoiding recursion when checking org_members
-- Get all org IDs the current user belongs to
CREATE OR REPLACE FUNCTION public.get_user_org_ids()
RETURNS SETOF uuid
LANGUAGE sql
SECURITY DEFINER
SET search_path = ''
STABLE
AS $$
SELECT org_id FROM public.org_members WHERE user_id = auth.uid()
$$;
-- Check if current user is a member of a given org
CREATE OR REPLACE FUNCTION public.is_member_of_org(p_org_id uuid)
RETURNS boolean
LANGUAGE sql
SECURITY DEFINER
SET search_path = ''
STABLE
AS $$
SELECT EXISTS (
SELECT 1 FROM public.org_members
WHERE org_id = p_org_id AND user_id = auth.uid()
)
$$;
-- Check if current user is an admin of a given org
CREATE OR REPLACE FUNCTION public.is_admin_of_org(p_org_id uuid)
RETURNS boolean
LANGUAGE sql
SECURITY DEFINER
SET search_path = ''
STABLE
AS $$
SELECT EXISTS (
SELECT 1 FROM public.org_members
WHERE org_id = p_org_id AND user_id = auth.uid() AND role = 'admin'
)
$$;
SQLCREATE FUNCTION CREATE FUNCTION CREATE FUNCTION
PGPASSWORD="$SUPABASE_DB_PASSWORD" psql "$SUPABASE_DB_URL" <<'SQL' -- ===================================================================== -- ENABLE ROW LEVEL SECURITY -- ===================================================================== ALTER TABLE public.org_members ENABLE ROW LEVEL SECURITY; ALTER TABLE public.tickets ENABLE ROW LEVEL SECURITY; -- orgs remains readable; no RLS needed (requirement says "may remain readable") -- ===================================================================== -- TICKETS POLICIES -- ===================================================================== -- SELECT: org members (any role) can read tickets of their org CREATE POLICY tickets_select_policy ON public.tickets FOR SELECT USING (public.is_member_of_org(org_id)); -- INSERT: only admins can insert tickets for their org CREATE POLICY tickets_insert_policy ON public.tickets FOR INSERT WITH CHECK (public.is_admin_of_org(org_id)); -- UPDATE: only admins can update tickets of their org CREATE POLICY tickets_update_policy ON public.tickets FOR UPDATE USING (public.is_admin_of_org(org_id)) WITH CHECK (public.is_admin_of_org(org_id)); -- DELETE: only admins can delete tickets of their org CREATE POLICY tickets_delete_policy ON public.tickets FOR DELETE USING (public.is_admin_of_org(org_id)); -- ===================================================================== -- ORG_MEMBERS POLICIES -- ===================================================================== -- SELECT: members can see member list of their own org only CREATE POLICY org_members_select_policy ON public.org_members FOR SELECT USING (public.is_member_of_org(org_id)); -- INSERT: only admins can add members to their org CREATE POLICY org_members_insert_policy ON public.org_members FOR INSERT WITH CHECK (public.is_admin_of_org(org_id)); -- UPDATE: only admins can update members in their org CREATE POLICY org_members_update_policy ON public.org_members FOR UPDATE USING (public.is_admin_of_org(org_id)) WITH CHECK (public.is_admin_of_org(org_id)); -- DELETE: only admins can remove members from their org CREATE POLICY org_members_delete_policy ON public.org_members FOR DELETE USING (public.is_admin_of_org(org_id)); SQL
ALTER TABLE ALTER TABLE CREATE POLICY CREATE POLICY CREATE POLICY CREATE POLICY CREATE POLICY CREATE POLICY CREATE POLICY CREATE POLICY
PGPASSWORD="$SUPABASE_DB_PASSWORD" psql "$SUPABASE_DB_URL" -c "SELECT tablename, policyname, cmd, permissive, qual, with_check FROM pg_policies WHERE schemaname = 'public' ORDER BY tablename, cmd;"
tablename | policyname | cmd | permissive | qual | with_check -------------+---------------------------+--------+------------+--------------------------+------------------------- org_members | org_members_delete_policy | DELETE | PERMISSIVE | is_admin_of_org(org_id) | org_members | org_members_insert_policy | INSERT | PERMISSIVE | | is_admin_of_org(org_id) org_members | org_members_select_policy | SELECT | PERMISSIVE | is_member_of_org(org_id) | org_members | org_members_update_policy | UPDATE | PERMISSIVE | is_admin_of_org(org_id) | is_admin_of_org(org_id) tickets | tickets_delete_policy | DELETE | PERMISSIVE | is_admin_of_org(org_id) | tickets | tickets_insert_policy | INSERT | PERMISSIVE | | is_admin_of_org(org_id) tickets | tickets_select_policy | SELECT | PERMISSIVE | is_member_of_org(org_id) | tickets | tickets_update_policy | UPDATE | PERMISSIVE | is_admin_of_org(org_id) | is_admin_of_org(org_id) (8 rows)
# UMA (admin of Acme)
curl -s -X POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d '{"email":"uma-admin@cari.dev","password":"Passw0rd-uma-11"}' | jq '{access_token: .access_token, user_id: .user.id, email: .user.email}'{
"access_token": "<jwt:authenticated>",
"user_id": "1a403877-990d-4cbf-a093-cb4f90d13d55",
"email": "uma-admin@cari.dev"
}
# VIC (member of Acme)
curl -s -X POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d '{"email":"vic-member@cari.dev","password":"Passw0rd-vic-22"}' | jq '{access_token: .access_token, user_id: .user.id, email: .user.email}'{
"access_token": "<jwt:authenticated>",
"user_id": "d6afa1a2-7c27-4ee8-9dbf-9c62be035f55",
"email": "vic-member@cari.dev"
}
# WEN (member of Globex)
curl -s -X POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d '{"email":"wen-outsider@cari.dev","password":"Passw0rd-wen-33"}' | jq '{access_token: .access_token, user_id: .user.id, email: .user.email}'{
"access_token": "<jwt:authenticated>",
"user_id": "f8c61839-90ec-4c29-8df4-22d7f20e2f6b",
"email": "wen-outsider@cari.dev"
}
UMA_TOKEN=$(curl -s -X POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d '{"email":"uma-admin@cari.dev","password":"Passw0rd-uma-11"}' | jq -r .access_token)
VIC_TOKEN=$(curl -s -X POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d '{"email":"vic-member@cari.dev","password":"Passw0rd-vic-22"}' | jq -r .access_token)
WEN_TOKEN=$(curl -s -X POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d '{"email":"wen-outsider@cari.dev","password":"Passw0rd-wen-33"}' | jq -r .access_token)
echo "=== TEST 1: UMA (admin of Acme) reads tickets ==="
curl -s "${SUPABASE_URL}/rest/v1/tickets?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${UMA_TOKEN}" | jq -c '.[] | {id, title, org_id}'
echo ""
echo "=== TEST 2: VIC (member of Acme) reads tickets ==="
curl -s "${SUPABASE_URL}/rest/v1/tickets?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${VIC_TOKEN}" | jq -c '.[] | {id, title, org_id}'
echo ""
echo "=== TEST 3: WEN (Globex only) reads tickets ==="
curl -s "${SUPABASE_URL}/rest/v1/tickets?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${WEN_TOKEN}" | jq -c '.[] | {id, title, org_id}'
echo ""
echo "=== TEST 4: ANONYMOUS reads tickets ==="
curl -s "${SUPABASE_URL}/rest/v1/tickets?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" | jq -c '.[] | {id, title, org_id}'=== TEST 1: UMA (admin of Acme) reads tickets ===
{"id":1,"title":"Fix login","org_id":"bc3643bf-c848-4406-80a4-93cde591aef9"}
{"id":2,"title":"Update billing","org_id":"bc3643bf-c848-4406-80a4-93cde591aef9"}
{"id":3,"title":"Rotate keys","org_id":"bc3643bf-c848-4406-80a4-93cde591aef9"}
=== TEST 2: VIC (member of Acme) reads tickets ===
{"id":1,"title":"Fix login","org_id":"bc3643bf-c848-4406-80a4-93cde591aef9"}
{"id":2,"title":"Update billing","org_id":"bc3643bf-c848-4406-80a4-93cde591aef9"}
{"id":3,"title":"Rotate keys","org_id":"bc3643bf-c848-4406-80a4-93cde591aef9"}
=== TEST 3: WEN (Globex only) reads tickets ===
{"id":4,"title":"Migrate DNS","org_id":"1c6982f3-4eb0-409c-8899-405729ffccdc"}
{"id":5,"title":"Renew cert","org_id":"1c6982f3-4eb0-409c-8899-405729ffccdc"}
=== TEST 4: ANONYMOUS reads tickets ===
UMA_TOKEN=$(curl -s -X POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d '{"email":"uma-admin@cari.dev","password":"Passw0rd-uma-11"}' | jq -r .access_token)
VIC_TOKEN=$(curl -s -X POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d '{"email":"vic-member@cari.dev","password":"Passw0rd-vic-22"}' | jq -r .access_token)
WEN_TOKEN=$(curl -s -X POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d '{"email":"wen-outsider@cari.dev","password":"Passw0rd-wen-33"}' | jq -r .access_token)
# Globex org_id
GLOBEX="1c6982f3-4eb0-409c-8899-405729ffccdc"
echo "=== TEST 5: UMA (admin of Acme) tries to INSERT into Globex (should FAIL) ==="
curl -s -X POST "${SUPABASE_URL}/rest/v1/tickets" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${UMA_TOKEN}" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d "{\"org_id\":\"${GLOBEX}\",\"title\":\"UMA sneaking into Globex\",\"status\":\"open\"}" | jq .
echo ""
echo "=== TEST 6: VIC (member of Acme) tries to INSERT into Acme (should FAIL - not admin) ==="
ACME="bc3643bf-c848-4406-80a4-93cde591aef9"
curl -s -X POST "${SUPABASE_URL}/rest/v1/tickets" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${VIC_TOKEN}" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d "{\"org_id\":\"${ACME}\",\"title\":\"VIC trying insert\",\"status\":\"open\"}" | jq .
echo ""
echo "=== TEST 7: UMA (admin of Acme) INSERT into Acme (should SUCCEED) ==="
curl -s -X POST "${SUPABASE_URL}/rest/v1/tickets" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${UMA_TOKEN}" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d "{\"org_id\":\"${ACME}\",\"title\":\"UMA legit insert\",\"status\":\"open\"}" | jq .
echo ""
echo "=== TEST 8: ANONYMOUS tries to INSERT ==="
curl -s -X POST "${SUPABASE_URL}/rest/v1/tickets" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d "{\"org_id\":\"${ACME}\",\"title\":\"anon insert\",\"status\":\"open\"}" | jq .=== TEST 5: UMA (admin of Acme) tries to INSERT into Globex (should FAIL) ===
{
"code": "42501",
"details": null,
"hint": null,
"message": "new row violates row-level security policy for table \"tickets\""
}
=== TEST 6: VIC (member of Acme) tries to INSERT into Acme (should FAIL - not admin) ===
{
"code": "42501",
"details": null,
"hint": null,
"message": "new row violates row-level security policy for table \"tickets\""
}
=== TEST 7: UMA (admin of Acme) INSERT into Acme (should SUCCEED) ===
[
{
"id": 8,
"org_id": "bc3643bf-c848-4406-80a4-93cde591aef9",
"title": "UMA legit insert",
"status": "open"
}
]
=== TEST 8: ANONYMOUS tries to INSERT ===
{
"code": "42501",
"details": null,
"hint": null,
"message": "new row violates row-level security policy for table \"tickets\""
}
UMA_TOKEN=$(curl -s -X POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d '{"email":"uma-admin@cari.dev","password":"Passw0rd-uma-11"}' | jq -r .access_token)
VIC_TOKEN=$(curl -s -X POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d '{"email":"vic-member@cari.dev","password":"Passw0rd-vic-22"}' | jq -r .access_token)
WEN_TOKEN=$(curl -s -X POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d '{"email":"wen-outsider@cari.dev","password":"Passw0rd-wen-33"}' | jq -r .access_token)
echo "=== TEST 9: VIC (member of Acme) tries to UPDATE ticket 1 (should FAIL) ==="
curl -s -X PATCH "${SUPABASE_URL}/rest/v1/tickets?id=eq.1" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${VIC_TOKEN}" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{"status":"closed"}' | jq .
echo ""
echo "=== TEST 10: UMA (admin of Acme) UPDATE ticket 1 (should SUCCEED) ==="
curl -s -X PATCH "${SUPABASE_URL}/rest/v1/tickets?id=eq.1" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${UMA_TOKEN}" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{"status":"closed"}' | jq .
echo ""
echo "=== TEST 11: WEN (Globex) tries to UPDATE Acme ticket 2 (should FAIL) ==="
curl -s -X PATCH "${SUPABASE_URL}/rest/v1/tickets?id=eq.2" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${WEN_TOKEN}" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{"status":"closed"}' | jq .
echo ""
echo "=== TEST 12: WEN (member of Globex) tries to DELETE ticket 4 (should FAIL - not admin) ==="
curl -s -X DELETE "${SUPABASE_URL}/rest/v1/tickets?id=eq.4" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${WEN_TOKEN}" \
-H "Prefer: return=representation" | jq .
echo ""
echo "=== TEST 13: UMA (admin of Acme) tries to DELETE Globex ticket 4 (should FAIL) ==="
curl -s -X DELETE "${SUPABASE_URL}/rest/v1/tickets?id=eq.4" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${UMA_TOKEN}" \
-H "Prefer: return=representation" | jq .=== TEST 9: VIC (member of Acme) tries to UPDATE ticket 1 (should FAIL) ===
[]
=== TEST 10: UMA (admin of Acme) UPDATE ticket 1 (should SUCCEED) ===
[
{
"id": 1,
"org_id": "bc3643bf-c848-4406-80a4-93cde591aef9",
"title": "Fix login",
"status": "closed"
}
]
=== TEST 11: WEN (Globex) tries to UPDATE Acme ticket 2 (should FAIL) ===
[]
=== TEST 12: WEN (member of Globex) tries to DELETE ticket 4 (should FAIL - not admin) ===
[]
=== TEST 13: UMA (admin of Acme) tries to DELETE Globex ticket 4 (should FAIL) ===
[]
UMA_TOKEN=$(curl -s -X POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d '{"email":"uma-admin@cari.dev","password":"Passw0rd-uma-11"}' | jq -r .access_token)
VIC_TOKEN=$(curl -s -X POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d '{"email":"vic-member@cari.dev","password":"Passw0rd-vic-22"}' | jq -r .access_token)
WEN_TOKEN=$(curl -s -X POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d '{"email":"wen-outsider@cari.dev","password":"Passw0rd-wen-33"}' | jq -r .access_token)
echo "=== TEST 14: UMA (admin of Acme) reads org_members (should see Acme members only) ==="
curl -s "${SUPABASE_URL}/rest/v1/org_members?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${UMA_TOKEN}" | jq -c '.[] | {org_id: (.org_id | .[0:8]), user_id: (.user_id | .[0:8]), role}'
echo ""
echo "=== TEST 15: VIC (member of Acme) reads org_members (should see Acme members only) ==="
curl -s "${SUPABASE_URL}/rest/v1/org_members?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${VIC_TOKEN}" | jq -c '.[] | {org_id: (.org_id | .[0:8]), user_id: (.user_id | .[0:8]), role}'
echo ""
echo "=== TEST 16: WEN (Globex only) reads org_members (should see Globex members only) ==="
curl -s "${SUPABASE_URL}/rest/v1/org_members?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${WEN_TOKEN}" | jq -c '.[] | {org_id: (.org_id | .[0:8]), user_id: (.user_id | .[0:8]), role}'
echo ""
echo "=== TEST 17: ANONYMOUS reads org_members ==="
curl -s "${SUPABASE_URL}/rest/v1/org_members?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" | jq -c '.[] | {org_id: (.org_id | .[0:8]), user_id: (.user_id | .[0:8]), role}'
echo ""
echo "=== TEST 18: Check orgs table is still readable ==="
curl -s "${SUPABASE_URL}/rest/v1/orgs?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" | jq -c '.[]'=== TEST 14: UMA (admin of Acme) reads org_members (should see Acme members only) ===
{"org_id":"bc3643bf","user_id":"1a403877","role":"admin"}
{"org_id":"bc3643bf","user_id":"d6afa1a2","role":"member"}
=== TEST 15: VIC (member of Acme) reads org_members (should see Acme members only) ===
{"org_id":"bc3643bf","user_id":"1a403877","role":"admin"}
{"org_id":"bc3643bf","user_id":"d6afa1a2","role":"member"}
=== TEST 16: WEN (Globex only) reads org_members (should see Globex members only) ===
{"org_id":"1c6982f3","user_id":"f8c61839","role":"member"}
=== TEST 17: ANONYMOUS reads org_members ===
=== TEST 18: Check orgs table is still readable ===
{"id":"bc3643bf-c848-4406-80a4-93cde591aef9","name":"Acme"}
{"id":"1c6982f3-4eb0-409c-8899-405729ffccdc","name":"Globex"}
UMA_TOKEN=$(curl -s -X POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d '{"email":"uma-admin@cari.dev","password":"Passw0rd-uma-11"}' | jq -r .access_token)
echo "=== Revert ticket 1 status to 'open' ==="
curl -s -X PATCH "${SUPABASE_URL}/rest/v1/tickets?id=eq.1" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${UMA_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"status":"open"}' | jq .
echo "=== Delete test ticket 8 ==="
curl -s -X DELETE "${SUPABASE_URL}/rest/v1/tickets?id=eq.8" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${UMA_TOKEN}" \
-H "Prefer: return=representation" | jq .=== Revert ticket 1 status to 'open' ===
=== Delete test ticket 8 ===
[
{
"id": 8,
"org_id": "bc3643bf-c848-4406-80a4-93cde591aef9",
"title": "UMA legit insert",
"status": "open"
}
]
PGPASSWORD="$SUPABASE_DB_PASSWORD" psql "$SUPABASE_DB_URL" -c "SELECT * FROM public.tickets ORDER BY id;"
id | org_id | title | status ----+--------------------------------------+----------------+-------- 1 | bc3643bf-c848-4406-80a4-93cde591aef9 | Fix login | open 2 | bc3643bf-c848-4406-80a4-93cde591aef9 | Update billing | open 3 | bc3643bf-c848-4406-80a4-93cde591aef9 | Rotate keys | open 4 | 1c6982f3-4eb0-409c-8899-405729ffccdc | Migrate DNS | open 5 | 1c6982f3-4eb0-409c-8899-405729ffccdc | Renew cert | open (5 rows)
9 earlier narration turn(s) are in the raw transcript.
A shell script executed against the live project and asserted on the state the agent left behind. No model judged another model.
Raw transcript · Task definition and verifier · Every attempt