echo "=== ENV CHECK ===" && echo "URL: $SUPABASE_URL" && echo "REF: $SUPABASE_PROJECT_REF" && echo "DB_URL present: $( [ -n "$SUPABASE_DB_URL" ] && echo yes || echo no )"
=== ENV CHECK === URL: https://<ref>.supabase.co REF: <ref> DB_URL present: yes
echo "=== SCHEMA: orgs ===" && psql "$SUPABASE_DB_URL" -c "\d public.orgs" && echo "=== SCHEMA: org_members ===" && psql "$SUPABASE_DB_URL" -c "\d public.org_members" && echo "=== SCHEMA: tickets ===" && psql "$SUPABASE_DB_URL" -c "\d public.tickets"
=== SCHEMA: orgs ===
Table "public.orgs"
Column | Type | Collation | Nullable | Default
--------+------+-----------+----------+-------------------
id | uuid | | not null | gen_random_uuid()
name | text | | not null |
Indexes:
"orgs_pkey" PRIMARY KEY, btree (id)
"orgs_name_key" UNIQUE CONSTRAINT, btree (name)
Referenced by:
TABLE "org_members" CONSTRAINT "org_members_org_id_fkey" FOREIGN KEY (org_id) REFERENCES orgs(id) ON DELETE CASCADE
TABLE "tickets" CONSTRAINT "tickets_org_id_fkey" FOREIGN KEY (org_id) REFERENCES orgs(id) ON DELETE CASCADE
=== SCHEMA: org_members ===
Table "public.org_members"
Column | Type | Collation | Nullable | Default
---------+------+-----------+----------+---------
org_id | uuid | | not null |
user_id | uuid | | not null |
role | text | | not null |
Indexes:
"org_members_pkey" PRIMARY KEY, btree (org_id, user_id)
Check constraints:
"org_members_role_check" CHECK (role = ANY (ARRAY['admin'::text, 'member'::text]))
Foreign-key constraints:
"org_members_org_id_fkey" FOREIGN KEY (org_id) REFERENCES orgs(id) ON DELETE CASCADE
"org_members_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE
=== SCHEMA: tickets ===
Table "public.tickets"
Column | Type | Collation | Nullable | Default
--------+--------+-----------+----------+----------------------------------
id | bigint | | not null | generated by default as identity
org_id | uuid | | not null |
title | text | | not null |
status | text | | not null | 'open'::text
Indexes:
"tickets_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"tickets_org_id_fkey" FOREIGN KEY (org_id) REFERENCES orgs(id) ON DELETE CASCADE
echo "=== CURRENT RLS ===" && psql "$SUPABASE_DB_URL" -c "SELECT tablename, rowsecurity FROM pg_tables WHERE schemaname='public' AND tablename IN ('orgs','org_members','tickets');" && echo "=== POLICIES ===" && psql "$SUPABASE_DB_URL" -c "SELECT policyname, tablename, roles, cmd, qual, with_check FROM pg_policies WHERE schemaname='public' ORDER BY tablename, cmd;"=== CURRENT RLS === tablename | rowsecurity -------------+------------- org_members | f orgs | f tickets | f (3 rows) === POLICIES === policyname | tablename | roles | cmd | qual | with_check ------------+-----------+-------+-----+------+------------ (0 rows)
echo "=== ORGS ===" && psql "$SUPABASE_DB_URL" -c "SELECT * FROM public.orgs;" && echo "=== ORG_MEMBERS ===" && psql "$SUPABASE_DB_URL" -c "SELECT om.org_id, om.user_id, om.role, u.email FROM public.org_members om JOIN auth.users u ON om.user_id = u.id;" && echo "=== TICKETS ===" && psql "$SUPABASE_DB_URL" -c "SELECT * FROM public.tickets ORDER BY org_id, id;"
=== ORGS ===
id | name
--------------------------------------+--------
f62e0068-f06b-4e1c-8eaa-36f9ca149c25 | Acme
d7c87899-2ce9-48e3-92a3-5b9358593e39 | Globex
(2 rows)
=== ORG_MEMBERS ===
org_id | user_id | role | email
--------------------------------------+--------------------------------------+--------+-----------------------
f62e0068-f06b-4e1c-8eaa-36f9ca149c25 | c5849dc3-b9a6-4270-9fb3-6e940b532ad2 | admin | uma-admin@cari.dev
f62e0068-f06b-4e1c-8eaa-36f9ca149c25 | c7ce7c74-6d06-4148-b4e2-23c46f071995 | member | vic-member@cari.dev
d7c87899-2ce9-48e3-92a3-5b9358593e39 | 11312ebc-d4c1-4dff-ad9b-ff6e5b660237 | member | wen-outsider@cari.dev
(3 rows)
=== TICKETS ===
id | org_id | title | status
----+--------------------------------------+----------------+--------
4 | d7c87899-2ce9-48e3-92a3-5b9358593e39 | Migrate DNS | open
5 | d7c87899-2ce9-48e3-92a3-5b9358593e39 | Renew cert | open
1 | f62e0068-f06b-4e1c-8eaa-36f9ca149c25 | Fix login | open
2 | f62e0068-f06b-4e1c-8eaa-36f9ca149c25 | Update billing | open
3 | f62e0068-f06b-4e1c-8eaa-36f9ca149c25 | Rotate keys | open
(5 rows)
psql "$SUPABASE_DB_URL" <<'SQL'
-- ============================================================
-- ENABLE ROW-LEVEL SECURITY
-- ============================================================
ALTER TABLE public.tickets ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.org_members ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.orgs ENABLE ROW LEVEL SECURITY;
-- ============================================================
-- TICKETS: SELECT — any member of the org can read its tickets
-- ============================================================
CREATE POLICY tickets_select_org_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()
)
);
-- ============================================================
-- TICKETS: INSERT — only admin of the org
-- ============================================================
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'
)
);
-- ============================================================
-- TICKETS: UPDATE — only admin of the org
-- ============================================================
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'
)
);
-- ============================================================
-- TICKETS: DELETE — only admin of the org
-- ============================================================
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'
)
);
-- ============================================================
-- ORG_MEMBERS: SELECT — visible only to members of the same org
-- ============================================================
CREATE POLICY org_members_select_same_org ON public.org_members
FOR SELECT
TO authenticated
USING (
org_id IN (
SELECT om2.org_id
FROM public.org_members om2
WHERE om2.user_id = auth.uid()
)
);
-- ============================================================
-- ORGS: remain readable by everyone (including anonymous)
-- ============================================================
CREATE POLICY orgs_select_all ON public.orgs
FOR SELECT
TO authenticated, anon
USING (true);
-- ============================================================
-- Summary
-- ============================================================
SELECT tablename, rowsecurity FROM pg_tables
WHERE schemaname='public' AND tablename IN ('orgs','org_members','tickets')
ORDER BY tablename;
SELECT policyname, tablename, roles, cmd
FROM pg_policies
WHERE schemaname='public'
ORDER BY tablename, cmd;
SQLALTER TABLE
ALTER TABLE
ALTER TABLE
CREATE POLICY
CREATE POLICY
CREATE POLICY
CREATE POLICY
CREATE POLICY
CREATE POLICY
tablename | rowsecurity
-------------+-------------
org_members | t
orgs | t
tickets | t
(3 rows)
policyname | tablename | roles | cmd
-----------------------------+-------------+----------------------+--------
org_members_select_same_org | org_members | {authenticated} | SELECT
orgs_select_all | orgs | {anon,authenticated} | SELECT
tickets_delete_admin | tickets | {authenticated} | DELETE
tickets_insert_admin | tickets | {authenticated} | INSERT
tickets_select_org_member | tickets | {authenticated} | SELECT
tickets_update_admin | tickets | {authenticated} | UPDATE
(6 rows)
echo "=== UMA 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' > /tmp/jwt_uma && echo "saved" && \
echo "=== VIC MEMBER 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' > /tmp/jwt_vic && echo "saved" && \
echo "=== WEN OUTSIDER 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' > /tmp/jwt_wen && echo "saved"=== UMA ADMIN JWT === saved === VIC MEMBER JWT === saved === WEN OUTSIDER JWT === saved
JWT_UMA=$(cat /tmp/jwt_uma)
JWT_VIC=$(cat /tmp/jwt_vic)
JWT_WEN=$(cat /tmp/jwt_wen)
echo "===== TEST 1: Uma (admin/Acme) reads tickets ====="
curl -s "${SUPABASE_URL}/rest/v1/tickets?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${JWT_UMA}" | jq .
echo ""
echo "===== TEST 2: Vic (member/Acme) reads tickets ====="
curl -s "${SUPABASE_URL}/rest/v1/tickets?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${JWT_VIC}" | jq .
echo ""
echo "===== TEST 3: Wen (outsider/Globex only) reads tickets ====="
curl -s "${SUPABASE_URL}/rest/v1/tickets?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${JWT_WEN}" | jq .
echo ""
echo "===== TEST 4: Anonymous reads tickets ====="
curl -s "${SUPABASE_URL}/rest/v1/tickets?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" | jq .===== TEST 1: Uma (admin/Acme) reads tickets =====
{
"code": "42P17",
"details": null,
"hint": null,
"message": "infinite recursion detected in policy for relation \"org_members\""
}
===== TEST 2: Vic (member/Acme) reads tickets =====
{
"code": "42P17",
"details": null,
"hint": null,
"message": "infinite recursion detected in policy for relation \"org_members\""
}
===== TEST 3: Wen (outsider/Globex only) reads tickets =====
{
"code": "42P17",
"details": null,
"hint": null,
"message": "infinite recursion detected in policy for relation \"org_members\""
}
===== TEST 4: Anonymous reads tickets =====
[]
psql "$SUPABASE_DB_URL" <<'SQL'
-- Drop the recursive policies
DROP POLICY IF EXISTS org_members_select_same_org ON public.org_members;
DROP POLICY IF EXISTS tickets_select_org_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 safely get the current user's org IDs.
-- This bypasses RLS and avoids recursion.
CREATE OR REPLACE FUNCTION public.my_org_ids()
RETURNS SETOF uuid
LANGUAGE sql
STABLE
SECURITY DEFINER
SET search_path = ''
AS $$
SELECT om.org_id
FROM public.org_members om
WHERE om.user_id = auth.uid()
$$;
-- Also create a variant that filters by role
CREATE OR REPLACE FUNCTION public.my_admin_org_ids()
RETURNS SETOF uuid
LANGUAGE sql
STABLE
SECURITY DEFINER
SET search_path = ''
AS $$
SELECT om.org_id
FROM public.org_members om
WHERE om.user_id = auth.uid()
AND om.role = 'admin'
$$;
-- ============================================================
-- TICKETS policies (use the helper functions)
-- ============================================================
CREATE POLICY tickets_select_org_member ON public.tickets
FOR SELECT
TO authenticated
USING (
org_id IN (SELECT public.my_org_ids())
);
CREATE POLICY tickets_insert_admin ON public.tickets
FOR INSERT
TO authenticated
WITH CHECK (
org_id IN (SELECT public.my_admin_org_ids())
);
CREATE POLICY tickets_update_admin ON public.tickets
FOR UPDATE
TO authenticated
USING (
org_id IN (SELECT public.my_admin_org_ids())
)
WITH CHECK (
org_id IN (SELECT public.my_admin_org_ids())
);
CREATE POLICY tickets_delete_admin ON public.tickets
FOR DELETE
TO authenticated
USING (
org_id IN (SELECT public.my_admin_org_ids())
);
-- ============================================================
-- ORG_MEMBERS: visible only to members of the same org
-- Use my_org_ids() to avoid recursion
-- ============================================================
CREATE POLICY org_members_select_same_org ON public.org_members
FOR SELECT
TO authenticated
USING (
org_id IN (SELECT public.my_org_ids())
);
SELECT policyname, tablename, roles, cmd
FROM pg_policies
WHERE schemaname='public'
ORDER BY tablename, cmd;
SQLDROP POLICY
DROP POLICY
DROP POLICY
DROP POLICY
DROP POLICY
CREATE FUNCTION
CREATE FUNCTION
CREATE POLICY
CREATE POLICY
CREATE POLICY
CREATE POLICY
CREATE POLICY
policyname | tablename | roles | cmd
-----------------------------+-------------+----------------------+--------
org_members_select_same_org | org_members | {authenticated} | SELECT
orgs_select_all | orgs | {anon,authenticated} | SELECT
tickets_delete_admin | tickets | {authenticated} | DELETE
tickets_insert_admin | tickets | {authenticated} | INSERT
tickets_select_org_member | tickets | {authenticated} | SELECT
tickets_update_admin | tickets | {authenticated} | UPDATE
(6 rows)
JWT_UMA=$(cat /tmp/jwt_uma)
JWT_VIC=$(cat /tmp/jwt_vic)
JWT_WEN=$(cat /tmp/jwt_wen)
echo "===== TEST 1: Uma (admin/Acme) reads tickets ====="
curl -s "${SUPABASE_URL}/rest/v1/tickets?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${JWT_UMA}" | jq .
echo ""
echo "===== TEST 2: Vic (member/Acme) reads tickets ====="
curl -s "${SUPABASE_URL}/rest/v1/tickets?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${JWT_VIC}" | jq .
echo ""
echo "===== TEST 3: Wen (Globex) reads tickets ====="
curl -s "${SUPABASE_URL}/rest/v1/tickets?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${JWT_WEN}" | jq .
echo ""
echo "===== TEST 4: Anonymous reads tickets ====="
curl -s "${SUPABASE_URL}/rest/v1/tickets?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" | jq .===== TEST 1: Uma (admin/Acme) reads tickets =====
[
{
"id": 1,
"org_id": "f62e0068-f06b-4e1c-8eaa-36f9ca149c25",
"title": "Fix login",
"status": "open"
},
{
"id": 2,
"org_id": "f62e0068-f06b-4e1c-8eaa-36f9ca149c25",
"title": "Update billing",
"status": "open"
},
{
"id": 3,
"org_id": "f62e0068-f06b-4e1c-8eaa-36f9ca149c25",
"title": "Rotate keys",
"status": "open"
}
]
===== TEST 2: Vic (member/Acme) reads tickets =====
[
{
"id": 1,
"org_id": "f62e0068-f06b-4e1c-8eaa-36f9ca149c25",
"title": "Fix login",
"status": "open"
},
{
"id": 2,
"org_id": "f62e0068-f06b-4e1c-8eaa-36f9ca149c25",
"title": "Update billing",
"status": "open"
},
{
"id": 3,
"org_id": "f62e0068-f06b-4e1c-8eaa-36f9ca149c25",
"title": "Rotate keys",
"status": "open"
}
]
===== TEST 3: Wen (Globex) reads tickets =====
[
{
"id": 4,
"org_id": "d7c87899-2ce9-48e3-92a3-5b9358593e39",
"title": "Migrate DNS",
"status": "open"
},
{
"id": 5,
"org_id": "d7c87899-2ce9-48e3-92a3-5b9358593e39",
"title": "Renew cert",
"status": "open"
}
]
===== TEST 4: Anonymous reads tickets =====
[]
JWT_UMA=$(cat /tmp/jwt_uma)
JWT_VIC=$(cat /tmp/jwt_vic)
JWT_WEN=$(cat /tmp/jwt_wen)
echo "===== TEST 5: Uma sees org_members ====="
curl -s "${SUPABASE_URL}/rest/v1/org_members?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${JWT_UMA}" | jq .
echo ""
echo "===== TEST 6: Vic sees org_members ====="
curl -s "${SUPABASE_URL}/rest/v1/org_members?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${JWT_VIC}" | jq .
echo ""
echo "===== TEST 7: Wen sees org_members ====="
curl -s "${SUPABASE_URL}/rest/v1/org_members?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${JWT_WEN}" | jq .
echo ""
echo "===== TEST 8: Anon sees org_members ====="
curl -s "${SUPABASE_URL}/rest/v1/org_members?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" | jq .
echo ""
echo "===== TEST 9: Anon sees orgs ====="
curl -s "${SUPABASE_URL}/rest/v1/orgs?select=*" \
-H "apikey: ${SUPABASE_ANON_KEY}" | jq .===== TEST 5: Uma sees org_members =====
[
{
"org_id": "f62e0068-f06b-4e1c-8eaa-36f9ca149c25",
"user_id": "c5849dc3-b9a6-4270-9fb3-6e940b532ad2",
"role": "admin"
},
{
"org_id": "f62e0068-f06b-4e1c-8eaa-36f9ca149c25",
"user_id": "c7ce7c74-6d06-4148-b4e2-23c46f071995",
"role": "member"
}
]
===== TEST 6: Vic sees org_members =====
[
{
"org_id": "f62e0068-f06b-4e1c-8eaa-36f9ca149c25",
"user_id": "c5849dc3-b9a6-4270-9fb3-6e940b532ad2",
"role": "admin"
},
{
"org_id": "f62e0068-f06b-4e1c-8eaa-36f9ca149c25",
"user_id": "c7ce7c74-6d06-4148-b4e2-23c46f071995",
"role": "member"
}
]
===== TEST 7: Wen sees org_members =====
[
{
"org_id": "d7c87899-2ce9-48e3-92a3-5b9358593e39",
"user_id": "11312ebc-d4c1-4dff-ad9b-ff6e5b660237",
"role": "member"
}
]
===== TEST 8: Anon sees org_members =====
[]
===== TEST 9: Anon sees orgs =====
[
{
"id": "f62e0068-f06b-4e1c-8eaa-36f9ca149c25",
"name": "Acme"
},
{
"id": "d7c87899-2ce9-48e3-92a3-5b9358593e39",
"name": "Globex"
}
]
JWT_UMA=$(cat /tmp/jwt_uma)
JWT_VIC=$(cat /tmp/jwt_vic)
JWT_WEN=$(cat /tmp/jwt_wen)
ACME_ID="f62e0068-f06b-4e1c-8eaa-36f9ca149c25"
GLOBEX_ID="d7c87899-2ce9-48e3-92a3-5b9358593e39"
echo "===== TEST 10: Uma (admin) INSERT ticket in Acme ====="
curl -s -X POST "${SUPABASE_URL}/rest/v1/tickets" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${JWT_UMA}" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d "{\"org_id\":\"${ACME_ID}\",\"title\":\"Test insert by admin\"}" | jq .
echo ""
echo "===== TEST 11: Vic (member) INSERT ticket in Acme (SHOULD FAIL) ====="
curl -s -X POST "${SUPABASE_URL}/rest/v1/tickets" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${JWT_VIC}" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d "{\"org_id\":\"${ACME_ID}\",\"title\":\"Test insert by member\"}" | jq .
echo ""
echo "===== TEST 12: Wen (Globex member) INSERT ticket in Acme (SHOULD FAIL) ====="
curl -s -X POST "${SUPABASE_URL}/rest/v1/tickets" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${JWT_WEN}" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d "{\"org_id\":\"${ACME_ID}\",\"title\":\"Test insert by outsider\"}" | jq .
echo ""
echo "===== TEST 13: Anon INSERT (SHOULD FAIL) ====="
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_ID}\",\"title\":\"Test insert by anon\"}" | jq .===== TEST 10: Uma (admin) INSERT ticket in Acme =====
[
{
"id": 6,
"org_id": "f62e0068-f06b-4e1c-8eaa-36f9ca149c25",
"title": "Test insert by admin",
"status": "open"
}
]
===== TEST 11: Vic (member) INSERT ticket in Acme (SHOULD FAIL) =====
{
"code": "42501",
"details": null,
"hint": null,
"message": "new row violates row-level security policy for table \"tickets\""
}
===== TEST 12: Wen (Globex member) INSERT ticket in Acme (SHOULD FAIL) =====
{
"code": "42501",
"details": null,
"hint": null,
"message": "new row violates row-level security policy for table \"tickets\""
}
===== TEST 13: Anon INSERT (SHOULD FAIL) =====
{
"code": "42501",
"details": null,
"hint": null,
"message": "new row violates row-level security policy for table \"tickets\""
}
JWT_UMA=$(cat /tmp/jwt_uma)
JWT_VIC=$(cat /tmp/jwt_vic)
JWT_WEN=$(cat /tmp/jwt_wen)
echo "===== TEST 14: Uma (admin) UPDATE ticket 1 (Acme) ====="
curl -s -X PATCH "${SUPABASE_URL}/rest/v1/tickets?id=eq.1" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${JWT_UMA}" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{"status":"in-progress"}' | jq .
echo ""
echo "===== TEST 15: Vic (member) 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 ${JWT_VIC}" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{"status":"closed"}' | jq .
echo ""
echo "===== TEST 16: Wen (Globex) UPDATE ticket 1 (Acme) (SHOULD FAIL) ====="
curl -s -X PATCH "${SUPABASE_URL}/rest/v1/tickets?id=eq.1" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${JWT_WEN}" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{"status":"hacked"}' | jq .
echo ""
echo "===== TEST 17: Vic (member) DELETE ticket 6 (SHOULD FAIL) ====="
curl -s -X DELETE "${SUPABASE_URL}/rest/v1/tickets?id=eq.6" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${JWT_VIC}" \
-H "Prefer: return=representation" | jq .
echo ""
echo "===== TEST 18: Uma (admin) DELETE ticket 6 (should succeed) ====="
curl -s -X DELETE "${SUPABASE_URL}/rest/v1/tickets?id=eq.6" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${JWT_UMA}" \
-H "Prefer: return=representation" | jq .===== TEST 14: Uma (admin) UPDATE ticket 1 (Acme) =====
[
{
"id": 1,
"org_id": "f62e0068-f06b-4e1c-8eaa-36f9ca149c25",
"title": "Fix login",
"status": "in-progress"
}
]
===== TEST 15: Vic (member) UPDATE ticket 1 (SHOULD FAIL) =====
[]
===== TEST 16: Wen (Globex) UPDATE ticket 1 (Acme) (SHOULD FAIL) =====
[]
===== TEST 17: Vic (member) DELETE ticket 6 (SHOULD FAIL) =====
[]
===== TEST 18: Uma (admin) DELETE ticket 6 (should succeed) =====
[
{
"id": 6,
"org_id": "f62e0068-f06b-4e1c-8eaa-36f9ca149c25",
"title": "Test insert by admin",
"status": "open"
}
]
JWT_UMA=$(cat /tmp/jwt_uma)
echo "===== Restoring ticket 1 to 'open' ====="
curl -s -X PATCH "${SUPABASE_URL}/rest/v1/tickets?id=eq.1" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${JWT_UMA}" \
-H "Content-Type: application/json" \
-d '{"status":"open"}' | jq .
echo ""
echo "===== FINAL STATE: All tickets ====="
curl -s "${SUPABASE_URL}/rest/v1/tickets?select=*&order=id" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer ${JWT_UMA}" | jq .===== Restoring ticket 1 to 'open' =====
===== FINAL STATE: All tickets =====
[
{
"id": 1,
"org_id": "f62e0068-f06b-4e1c-8eaa-36f9ca149c25",
"title": "Fix login",
"status": "open"
},
{
"id": 2,
"org_id": "f62e0068-f06b-4e1c-8eaa-36f9ca149c25",
"title": "Update billing",
"status": "open"
},
{
"id": 3,
"org_id": "f62e0068-f06b-4e1c-8eaa-36f9ca149c25",
"title": "Rotate keys",
"status": "open"
}
]
psql "$SUPABASE_DB_URL" -c " SELECT tablename, policyname, cmd, qual, with_check FROM pg_policies WHERE schemaname='public' ORDER BY tablename, cmd; " && echo "" && echo "=== FUNCTIONS ===" && psql "$SUPABASE_DB_URL" -c "\df public.my_*"
tablename | policyname | cmd | qual | with_check
-------------+-----------------------------+--------+--------------------------------------------------------------+--------------------------------------------------------------
org_members | org_members_select_same_org | SELECT | (org_id IN ( SELECT my_org_ids() AS my_org_ids)) |
orgs | orgs_select_all | SELECT | true |
tickets | tickets_delete_admin | DELETE | (org_id IN ( SELECT my_admin_org_ids() AS my_admin_org_ids)) |
tickets | tickets_insert_admin | INSERT | | (org_id IN ( SELECT my_admin_org_ids() AS my_admin_org_ids))
tickets | tickets_select_org_member | SELECT | (org_id IN ( SELECT my_org_ids() AS my_org_ids)) |
tickets | tickets_update_admin | UPDATE | (org_id IN ( SELECT my_admin_org_ids() AS my_admin_org_ids)) | (org_id IN ( SELECT my_admin_org_ids() AS my_admin_org_ids))
(6 rows)
=== FUNCTIONS ===
List of functions
Schema | Name | Result data type | Argument data types | Type
--------+------------------+------------------+---------------------+------
public | my_admin_org_ids | SETOF uuid | | func
public | my_org_ids | SETOF uuid | | func
(2 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