echo "SUPABASE_URL: $SUPABASE_URL"
echo "SUPABASE_PROJECT_REF: $SUPABASE_PROJECT_REF"
echo "SUPABASE_ANON_KEY: ${SUPABASE_ANON_KEY:0:20}..."
echo "SUPABASE_SERVICE_ROLE_KEY: ${SUPABASE_SERVICE_ROLE_KEY:0:20}..."
echo "SUPABASE_DB_URL: ${SUPABASE_DB_URL:0:30}..."
echo "SUPABASE_DB_PASSWORD: ${SUPABASE_DB_PASSWORD:0:5}..."SUPABASE_URL: https://<ref>.supabase.co SUPABASE_PROJECT_REF: <ref> SUPABASE_ANON_KEY: eyJhbGciOiJIUzI1NiIs... SUPABASE_SERVICE_ROLE_KEY: eyJhbGciOiJIUzI1NiIs... SUPABASE_DB_URL: postgresql://postgres.pgepczua... SUPABASE_DB_PASSWORD: 1a9f7...
# Check the schema and tables 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)
# Check table structures
PGPASSWORD=$SUPABASE_DB_PASSWORD psql "$SUPABASE_DB_URL" -c "
SELECT column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name IN ('orgs', 'org_members', 'tickets')
ORDER BY table_name, ordinal_position;
"column_name | data_type | is_nullable | column_default -------------+-----------+-------------+------------------- org_id | uuid | NO | user_id | uuid | NO | role | text | NO | id | uuid | NO | gen_random_uuid() name | text | NO | id | bigint | NO | org_id | uuid | NO | title | text | NO | status | text | NO | 'open'::text (9 rows)
# Check existing data PGPASSWORD=$SUPABASE_DB_PASSWORD psql "$SUPABASE_DB_URL" -c " SELECT 'orgs' as tbl, id, name FROM public.orgs UNION ALL SELECT 'org_members', org_id, role || ' / ' || user_id::text FROM public.org_members UNION ALL SELECT 'tickets', org_id, title FROM public.tickets; " 2>&1 # Also check existing RLS status PGPASSWORD=$SUPABASE_DB_PASSWORD psql "$SUPABASE_DB_URL" -c " SELECT schemaname, tablename, rowsecurity, forcerowsecurity FROM pg_tables WHERE schemaname = 'public'; "
tbl | id | name -------------+--------------------------------------+----------------------------------------------- orgs | dff72c1f-01d9-4121-9a78-00e8e84f78cd | Acme orgs | e2f71a75-49c7-4d77-ad8f-82549bec6fdf | Globex org_members | dff72c1f-01d9-4121-9a78-00e8e84f78cd | admin / c3dad1e3-4909-4f09-ab3a-9de58b4751ef org_members | dff72c1f-01d9-4121-9a78-00e8e84f78cd | member / c345ef70-9df0-40b4-ac6b-dab37e13c855 org_members | e2f71a75-49c7-4d77-ad8f-82549bec6fdf | member / 0fcb296f-76f3-4695-b4e5-ec9353c9e0d1 tickets | dff72c1f-01d9-4121-9a78-00e8e84f78cd | Fix login tickets | dff72c1f-01d9-4121-9a78-00e8e84f78cd | Update billing tickets | dff72c1f-01d9-4121-9a78-00e8e84f78cd | Rotate keys tickets | e2f71a75-49c7-4d77-ad8f-82549bec6fdf | Migrate DNS tickets | e2f71a75-49c7-4d77-ad8f-82549bec6fdf | Renew cert (10 rows)
ERROR: column "forcerowsecurity" does not exist
LINE 2: SELECT schemaname, tablename, rowsecurity, forcerowsecurity
^
# Check RLS status PGPASSWORD=$SUPABASE_DB_PASSWORD psql "$SUPABASE_DB_URL" -c " SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public'; " # Check existing policies 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'; "
schemaname | tablename | rowsecurity ------------+-------------+------------- public | orgs | f public | org_members | f public | tickets | f (3 rows) schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check ------------+-----------+------------+------------+-------+-----+------+------------ (0 rows)
# Check foreign keys
PGPASSWORD=$SUPABASE_DB_PASSWORD psql "$SUPABASE_DB_URL" -c "
SELECT
tc.constraint_name,
tc.table_name,
kcu.column_name,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name
FROM information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
JOIN information_schema.constraint_column_usage AS ccu
ON ccu.constraint_name = tc.constraint_name
AND ccu.table_schema = tc.table_schema
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_schema = 'public';
"constraint_name | table_name | column_name | foreign_table_name | foreign_column_name -------------------------+-------------+-------------+--------------------+--------------------- org_members_org_id_fkey | org_members | org_id | orgs | id tickets_org_id_fkey | tickets | org_id | orgs | id (2 rows)
# Check primary keys and unique constraints
PGPASSWORD=$SUPABASE_DB_PASSWORD psql "$SUPABASE_DB_URL" -c "
SELECT
tc.table_name,
kcu.column_name,
tc.constraint_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
WHERE tc.constraint_type IN ('PRIMARY KEY', 'UNIQUE')
AND tc.table_schema = 'public';
"table_name | column_name | constraint_name -------------+-------------+------------------ orgs | id | orgs_pkey orgs | name | orgs_name_key org_members | org_id | org_members_pkey org_members | user_id | org_members_pkey tickets | id | tickets_pkey (5 rows)
PGPASSWORD=$SUPABASE_DB_PASSWORD psql "$SUPABASE_DB_URL" <<'SQL'
-- ============================================================
-- 1. Enable Row-Level Security on all three tables
-- ============================================================
ALTER TABLE public.orgs ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.org_members ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.tickets ENABLE ROW LEVEL SECURITY;
-- ============================================================
-- 2. ORGS – readable by everyone (including anon)
-- No restrictions; just allow SELECT for all roles.
-- ============================================================
CREATE POLICY "orgs_public_read"
ON public.orgs
FOR SELECT
USING ( true );
-- ============================================================
-- 3. ORG_MEMBERS
-- SELECT only for authenticated members of the same org
-- (anonymous sees nothing – no policy for anon role)
-- ============================================================
CREATE POLICY "org_members_select_own_org"
ON public.org_members
FOR SELECT
TO authenticated
USING (
org_id IN (
SELECT om.org_id
FROM public.org_members om
WHERE om.user_id = auth.uid()
)
);
-- ============================================================
-- 4. TICKETS
-- 4a. SELECT – any authenticated member of the org
-- 4b. INSERT – only admin members of the org
-- 4c. UPDATE – only admin members of the org
-- 4d. DELETE – only admin members of the org
-- (anonymous sees nothing – no policy for anon role)
-- ============================================================
-- 4a. Read access for any org member
CREATE POLICY "tickets_select_member"
ON public.tickets
FOR SELECT
TO authenticated
USING (
org_id IN (
SELECT om.org_id
FROM public.org_members om
WHERE om.user_id = auth.uid()
)
);
-- 4b. Insert access for admin only
CREATE POLICY "tickets_insert_admin"
ON public.tickets
FOR INSERT
TO authenticated
WITH CHECK (
org_id IN (
SELECT om.org_id
FROM public.org_members om
WHERE om.user_id = auth.uid()
AND om.role = 'admin'
)
);
-- 4c. Update access for admin only
CREATE POLICY "tickets_update_admin"
ON public.tickets
FOR UPDATE
TO authenticated
USING (
org_id IN (
SELECT om.org_id
FROM public.org_members om
WHERE om.user_id = auth.uid()
AND om.role = 'admin'
)
)
WITH CHECK (
org_id IN (
SELECT om.org_id
FROM public.org_members om
WHERE om.user_id = auth.uid()
AND om.role = 'admin'
)
);
-- 4d. Delete access for admin only
CREATE POLICY "tickets_delete_admin"
ON public.tickets
FOR DELETE
TO authenticated
USING (
org_id IN (
SELECT om.org_id
FROM public.org_members om
WHERE om.user_id = auth.uid()
AND om.role = 'admin'
)
);
SQLALTER TABLE ALTER TABLE ALTER TABLE CREATE POLICY CREATE POLICY CREATE POLICY CREATE POLICY CREATE POLICY CREATE POLICY
# Verify RLS is enabled and policies exist PGPASSWORD=$SUPABASE_DB_PASSWORD psql "$SUPABASE_DB_URL" -c " SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname='public'; " PGPASSWORD=$SUPABASE_DB_PASSWORD psql "$SUPABASE_DB_URL" -c " SELECT tablename, policyname, permissive, roles, cmd FROM pg_policies WHERE schemaname = 'public' ORDER BY tablename, policyname; "
schemaname | tablename | rowsecurity
------------+-------------+-------------
public | org_members | t
public | orgs | t
public | tickets | t
(3 rows)
tablename | policyname | permissive | roles | cmd
-------------+----------------------------+------------+-----------------+--------
org_members | org_members_select_own_org | PERMISSIVE | {authenticated} | SELECT
orgs | orgs_public_read | PERMISSIVE | {public} | SELECT
tickets | tickets_delete_admin | PERMISSIVE | {authenticated} | DELETE
tickets | tickets_insert_admin | PERMISSIVE | {authenticated} | INSERT
tickets | tickets_select_member | PERMISSIVE | {authenticated} | SELECT
tickets | tickets_update_admin | PERMISSIVE | {authenticated} | UPDATE
(6 rows)
# Sign in as uma-admin and get JWT
ADMIN_JWT=$(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')
# Sign in as vic-member and get JWT
VIC_JWT=$(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')
# Sign in as wen-outsider and get JWT
WEN_JWT=$(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 "ADMIN_JWT length: ${#ADMIN_JWT}"
echo "VIC_JWT length: ${#VIC_JWT}"
echo "WEN_JWT length: ${#WEN_JWT}"
# Show first 30 chars to confirm they're JWTs
echo "ADMIN_JWT prefix: ${ADMIN_JWT:0:30}..."
echo "VIC_JWT prefix: ${VIC_JWT:0:30}..."
echo "WEN_JWT prefix: ${WEN_JWT:0:30}..."ADMIN_JWT length: 796 VIC_JWT length: 798 WEN_JWT length: 800 ADMIN_JWT prefix: eyJhbGciOiJFUzI1NiIsImtpZCI6Ij... VIC_JWT prefix: eyJhbGciOiJFUzI1NiIsImtpZCI6Ij... WEN_JWT prefix: eyJhbGciOiJFUzI1NiIsImtpZCI6Ij...
# Re-obtain JWTs and store as variables
ADMIN_JWT=$(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_JWT=$(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_JWT=$(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')
# Save for subshell use
export ADMIN_JWT VIC_JWT WEN_JWT
echo "=== TEST 1: TICKETS – Admin (Acme) reads tickets ==="
curl -s "$SUPABASE_URL/rest/v1/tickets?select=id,org_id,title,status" \
-H "apikey: $SUPABASE_ANON_KEY" \
-H "Authorization: Bearer $ADMIN_JWT" | jq .
echo ""
echo "=== TEST 2: TICKETS – Member (Acme/vic) reads tickets ==="
curl -s "$SUPABASE_URL/rest/v1/tickets?select=id,org_id,title,status" \
-H "apikey: $SUPABASE_ANON_KEY" \
-H "Authorization: Bearer $VIC_JWT" | jq .
echo ""
echo "=== TEST 3: TICKETS – Outsider (Globex/wen) reads tickets ==="
curl -s "$SUPABASE_URL/rest/v1/tickets?select=id,org_id,title,status" \
-H "apikey: $SUPABASE_ANON_KEY" \
-H "Authorization: Bearer $WEN_JWT" | jq .
echo ""
echo "=== TEST 4: TICKETS – Anonymous reads tickets ==="
curl -s "$SUPABASE_URL/rest/v1/tickets?select=id,org_id,title,status" \
-H "apikey: $SUPABASE_ANON_KEY" | jq .=== TEST 1: TICKETS – Admin (Acme) reads tickets ===
{
"code": "42P17",
"details": null,
"hint": null,
"message": "infinite recursion detected in policy for relation \"org_members\""
}
=== TEST 2: TICKETS – Member (Acme/vic) reads tickets ===
{
"code": "42P17",
"details": null,
"hint": null,
"message": "infinite recursion detected in policy for relation \"org_members\""
}
=== TEST 3: TICKETS – Outsider (Globex/wen) reads tickets ===
{
"code": "42P17",
"details": null,
"hint": null,
"message": "infinite recursion detected in policy for relation \"org_members\""
}
=== TEST 4: TICKETS – Anonymous reads tickets ===
[]
# Drop existing policies that cause recursion PGPASSWORD=$SUPABASE_DB_PASSWORD psql "$SUPABASE_DB_URL" <<'SQL' -- Drop all existing policies DROP POLICY IF EXISTS "orgs_public_read" ON public.orgs; DROP POLICY IF EXISTS "org_members_select_own_org" ON public.org_members; DROP POLICY IF EXISTS "tickets_select_member" ON public.tickets; DROP POLICY IF EXISTS "tickets_insert_admin" ON public.tickets; DROP POLICY IF EXISTS "tickets_update_admin" ON public.tickets; DROP POLICY IF EXISTS "tickets_delete_admin" ON public.tickets; -- Create a SECURITY DEFINER function to get user's org IDs -- This bypasses RLS on org_members, preventing infinite recursion CREATE OR REPLACE FUNCTION public.user_org_ids() RETURNS SETOF uuid LANGUAGE sql STABLE SECURITY DEFINER SET search_path = public AS $$ SELECT om.org_id FROM public.org_members om WHERE om.user_id = auth.uid(); $$; -- Grant execute to both anon and authenticated roles GRANT EXECUTE ON FUNCTION public.user_org_ids() TO anon; GRANT EXECUTE ON FUNCTION public.user_org_ids() TO authenticated; SQL
DROP POLICY DROP POLICY DROP POLICY DROP POLICY DROP POLICY DROP POLICY CREATE FUNCTION GRANT GRANT
PGPASSWORD=$SUPABASE_DB_PASSWORD psql "$SUPABASE_DB_URL" <<'SQL'
-- ============================================================
-- ORGS – readable by everyone (including anon)
-- ============================================================
CREATE POLICY "orgs_public_read"
ON public.orgs
FOR SELECT
USING ( true );
-- ============================================================
-- ORG_MEMBERS – SELECT only for authenticated members of the same org
-- ============================================================
CREATE POLICY "org_members_select_own_org"
ON public.org_members
FOR SELECT
TO authenticated
USING (
org_id IN ( SELECT public.user_org_ids() )
);
-- ============================================================
-- TICKETS
-- ============================================================
-- SELECT: any authenticated member of the org
CREATE POLICY "tickets_select_member"
ON public.tickets
FOR SELECT
TO authenticated
USING (
org_id IN ( SELECT public.user_org_ids() )
);
-- INSERT: admin only
CREATE POLICY "tickets_insert_admin"
ON public.tickets
FOR INSERT
TO authenticated
WITH CHECK (
org_id IN (
SELECT om.org_id
FROM public.org_members om
WHERE om.user_id = auth.uid()
AND om.role = 'admin'
)
);
-- UPDATE: admin only
CREATE POLICY "tickets_update_admin"
ON public.tickets
FOR UPDATE
TO authenticated
USING (
org_id IN (
SELECT om.org_id
FROM public.org_members om
WHERE om.user_id = auth.uid()
AND om.role = 'admin'
)
)
WITH CHECK (
org_id IN (
SELECT om.org_id
FROM public.org_members om
WHERE om.user_id = auth.uid()
AND om.role = 'admin'
)
);
-- DELETE: admin only
CREATE POLICY "tickets_delete_admin"
ON public.tickets
FOR DELETE
TO authenticated
USING (
org_id IN (
SELECT om.org_id
FROM public.org_members om
WHERE om.user_id = auth.uid()
AND om.role = 'admin'
)
);
SQLCREATE POLICY CREATE POLICY CREATE POLICY CREATE POLICY CREATE POLICY CREATE POLICY
# Re-obtain JWTs
ADMIN_JWT=$(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_JWT=$(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_JWT=$(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')
export ADMIN_JWT VIC_JWT WEN_JWT
echo "=== TEST 1: TICKETS – Admin (Acme/uma) READS tickets → expect 3 Acme tickets ==="
curl -s "$SUPABASE_URL/rest/v1/tickets?select=id,org_id,title,status&order=title" \
-H "apikey: $SUPABASE_ANON_KEY" \
-H "Authorization: Bearer $ADMIN_JWT" | jq .
echo ""
echo "=== TEST 2: TICKETS – Member (Acme/vic) READS tickets → expect 3 Acme tickets ==="
curl -s "$SUPABASE_URL/rest/v1/tickets?select=id,org_id,title,status&order=title" \
-H "apikey: $SUPABASE_ANON_KEY" \
-H "Authorization: Bearer $VIC_JWT" | jq .
echo ""
echo "=== TEST 3: TICKETS – Outsider (Globex/wen) READS tickets → expect 0 (no Acme tickets visible) ==="
curl -s "$SUPABASE_URL/rest/v1/tickets?select=id,org_id,title,status" \
-H "apikey: $SUPABASE_ANON_KEY" \
-H "Authorization: Bearer $WEN_JWT" | jq .
echo ""
echo "=== TEST 4: TICKETS – Anonymous READS tickets → expect [] ==="
curl -s "$SUPABASE_URL/rest/v1/tickets?select=id,org_id,title,status" \
-H "apikey: $SUPABASE_ANON_KEY" | jq .=== TEST 1: TICKETS – Admin (Acme/uma) READS tickets → expect 3 Acme tickets ===
[
{
"id": 1,
"org_id": "dff72c1f-01d9-4121-9a78-00e8e84f78cd",
"title": "Fix login",
"status": "open"
},
{
"id": 3,
"org_id": "dff72c1f-01d9-4121-9a78-00e8e84f78cd",
"title": "Rotate keys",
"status": "open"
},
{
"id": 2,
"org_id": "dff72c1f-01d9-4121-9a78-00e8e84f78cd",
"title": "Update billing",
"status": "open"
}
]
=== TEST 2: TICKETS – Member (Acme/vic) READS tickets → expect 3 Acme tickets ===
[
{
"id": 1,
"org_id": "dff72c1f-01d9-4121-9a78-00e8e84f78cd",
"title": "Fix login",
"status": "open"
},
{
"id": 3,
"org_id": "dff72c1f-01d9-4121-9a78-00e8e84f78cd",
"title": "Rotate keys",
"status": "open"
},
{
"id": 2,
"org_id": "dff72c1f-01d9-4121-9a78-00e8e84f78cd",
"title": "Update billing",
"status": "open"
}
]
=== TEST 3: TICKETS – Outsider (Globex/wen) READS tickets → expect 0 (no Acme tickets visible) ===
[
{
"id": 4,
"org_id": "e2f71a75-49c7-4d77-ad8f-82549bec6fdf",
"title": "Migrate DNS",
"status": "open"
},
{
"id": 5,
"org_id": "e2f71a75-49c7-4d77-ad8f-82549bec6fdf",
"title": "Renew cert",
"status": "open"
}
]
=== TEST 4: TICKETS – Anonymous READS tickets → expect [] ===
[]
# Re-obtain JWTs
ADMIN_JWT=$(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_JWT=$(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_JWT=$(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 5: TICKETS – Admin (Acme/uma) INSERTS a ticket → should SUCCEED ==="
curl -s -w "\nHTTP %{http_code}" \
-X POST "$SUPABASE_URL/rest/v1/tickets" \
-H "apikey: $SUPABASE_ANON_KEY" \
-H "Authorization: Bearer $ADMIN_JWT" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{"org_id":"dff72c1f-01d9-4121-9a78-00e8e84f78cd","title":"Test ticket from admin"}' | jq .
echo ""
echo "=== TEST 6: TICKETS – Member (Acme/vic) INSERTS a ticket → should FAIL 403 ==="
curl -s -w "\nHTTP %{http_code}" \
-X POST "$SUPABASE_URL/rest/v1/tickets" \
-H "apikey: $SUPABASE_ANON_KEY" \
-H "Authorization: Bearer $VIC_JWT" \
-H "Content-Type: application/json" \
-d '{"org_id":"dff72c1f-01d9-4121-9a78-00e8e84f78cd","title":"Should fail"}' | jq .
echo ""
echo "=== TEST 7: TICKETS – Outsider (Globex/wen) INSERTS to Acme → should FAIL 403 ==="
curl -s -w "\nHTTP %{http_code}" \
-X POST "$SUPABASE_URL/rest/v1/tickets" \
-H "apikey: $SUPABASE_ANON_KEY" \
-H "Authorization: Bearer $WEN_JWT" \
-H "Content-Type: application/json" \
-d '{"org_id":"dff72c1f-01d9-4121-9a78-00e8e84f78cd","title":"Should fail"}' | jq .
echo ""
echo "=== TEST 8: TICKETS – Anonymous INSERTS → should FAIL 401/403 ==="
curl -s -w "\nHTTP %{http_code}" \
-X POST "$SUPABASE_URL/rest/v1/tickets" \
-H "apikey: $SUPABASE_ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"org_id":"dff72c1f-01d9-4121-9a78-00e8e84f78cd","title":"Should fail"}' | jq .=== TEST 5: TICKETS – Admin (Acme/uma) INSERTS a ticket → should SUCCEED ===
[
{
"id": 6,
"org_id": "dff72c1f-01d9-4121-9a78-00e8e84f78cd",
"title": "Test ticket from admin",
"status": "open"
}
]
=== TEST 6: TICKETS – Member (Acme/vic) INSERTS a ticket → should FAIL 403 ===
{
"code": "42501",
"details": null,
"hint": null,
"message": "new row violates row-level security policy for table \"tickets\""
}
=== TEST 7: TICKETS – Outsider (Globex/wen) INSERTS to Acme → should FAIL 403 ===
{
"code": "42501",
"details": null,
"hint": null,
"message": "new row violates row-level security policy for table \"tickets\""
}
=== TEST 8: TICKETS – Anonymous INSERTS → should FAIL 401/403 ===
{
"code": "42501",
"details": null,
"hint": null,
"message": "new row violates row-level security policy for table \"tickets\""
}
parse error: Invalid numeric literal at line 2, column 5 parse error: Invalid numeric literal at line 2, column 5 parse error: Invalid numeric literal at line 2, column 5 parse error: Invalid numeric literal at line 2, column 5
8 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