echo "SUPABASE_URL=$SUPABASE_URL"; echo "SUPABASE_PROJECT_REF=$SUPABASE_PROJECT_REF"; echo "DB_URL=$SUPABASE_DB_URL"
SUPABASE_URL=https://<ref>.supabase.co SUPABASE_PROJECT_REF=<ref> DB_URL=<redacted:SUPABASE_DB_URL>
which psql || apt-get install -y postgresql-client 2>/dev/null | tail -3
/usr/bin/psql
export PGPASSWORD='<redacted:SUPABASE_DB_PASSWORD>' psql "$SUPABASE_DB_URL" -c "\dt public.*" -c "SELECT * FROM public.orgs;" -c "SELECT * FROM public.org_members;" -c "SELECT * FROM public.tickets;" 2>&1 | head -60
List of tables
Schema | Name | Type | Owner
--------+-------------+-------+----------
public | org_members | table | postgres
public | orgs | table | postgres
public | tickets | table | postgres
(3 rows)
id | name
--------------------------------------+--------
85cbddcb-8652-436d-aeab-e12bfe12079e | Acme
dd0bde6b-98d0-43ad-b11f-1c2b2497be40 | Globex
(2 rows)
org_id | user_id | role
--------------------------------------+--------------------------------------+--------
85cbddcb-8652-436d-aeab-e12bfe12079e | d074d31f-0600-41e5-bd90-840243609899 | admin
85cbddcb-8652-436d-aeab-e12bfe12079e | 1b9d4399-3d9d-43f9-87a9-6a154abd68a7 | member
dd0bde6b-98d0-43ad-b11f-1c2b2497be40 | 1362ea69-1f9a-47e5-8a1f-50caa4b80cff | member
(3 rows)
id | org_id | title | status
----+--------------------------------------+----------------+--------
1 | 85cbddcb-8652-436d-aeab-e12bfe12079e | Fix login | open
2 | 85cbddcb-8652-436d-aeab-e12bfe12079e | Update billing | open
3 | 85cbddcb-8652-436d-aeab-e12bfe12079e | Rotate keys | open
4 | dd0bde6b-98d0-43ad-b11f-1c2b2497be40 | Migrate DNS | open
5 | dd0bde6b-98d0-43ad-b11f-1c2b2497be40 | Renew cert | open
(5 rows)
export PGPASSWORD='<redacted:SUPABASE_DB_PASSWORD>'
psql "$SUPABASE_DB_URL" -c "SELECT relname, relrowsecurity, relforcerowsecurity FROM pg_class WHERE relname IN ('orgs','org_members','tickets');" 2>&1relname | relrowsecurity | relforcerowsecurity -------------+----------------+--------------------- orgs | f | f org_members | f | f tickets | f | f (3 rows)
export PGPASSWORD='<redacted:SUPABASE_DB_PASSWORD>'
psql "$SUPABASE_DB_URL" -c "SELECT schemaname, tablename, policyname, permissive, roles, cmd, qual, with_check FROM pg_policies WHERE schemaname='public';" -c "SELECT n.nspname, p.proname, p.prosecdef FROM pg_proc p JOIN pg_namespace n ON p.pronamespace=n.oid WHERE p.proname IN ('is_org_member','is_org_admin');" 2>&1schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check ------------+-----------+------------+------------+-------+-----+------+------------ (0 rows) nspname | proname | prosecdef ---------+---------+----------- (0 rows)
cat <<'SQL'
-- Enable RLS on org_members and tickets (orgs stays readable)
ALTER TABLE public.org_members ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.org_members FORCE ROW LEVEL SECURITY;
ALTER TABLE public.tickets ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.tickets FORCE ROW LEVEL SECURITY;
-- Tickets policies
CREATE POLICY tickets_select ON public.tickets
FOR SELECT USING (
org_id IN (SELECT org_id FROM public.org_members WHERE user_id = auth.uid())
);
CREATE POLICY tickets_insert ON public.tickets
FOR INSERT WITH CHECK (
org_id IN (
SELECT org_id FROM public.org_members
WHERE user_id = auth.uid() AND role = 'admin'
)
);
CREATE POLICY tickets_update ON public.tickets
FOR UPDATE USING (
org_id IN (
SELECT org_id FROM public.org_members
WHERE user_id = auth.uid() AND role = 'admin'
)
) WITH CHECK (
org_id IN (
SELECT org_id FROM public.org_members
WHERE user_id = auth.uid() AND role = 'admin'
)
);
CREATE POLICY tickets_delete ON public.tickets
FOR DELETE USING (
org_id IN (
SELECT org_id FROM public.org_members
WHERE user_id = auth.uid() AND role = 'admin'
)
);
-- org_members policies: visible only to members of the same org
CREATE POLICY org_members_select ON public.org_members
FOR SELECT USING (
org_id IN (SELECT om.org_id FROM public.org_members om WHERE om.user_id = auth.uid())
);
CREATE POLICY org_members_insert ON public.org_members
FOR INSERT WITH CHECK (
org_id IN (
SELECT om.org_id FROM public.org_members om
WHERE om.user_id = auth.uid() AND om.role = 'admin'
)
);
CREATE POLICY org_members_update ON public.org_members
FOR UPDATE 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'
)
);
CREATE POLICY org_members_delete ON public.org_members
FOR DELETE USING (
org_id IN (
SELECT om.org_id FROM public.org_members om
WHERE om.user_id = auth.uid() AND om.role = 'admin'
)
);
SQL-- Enable RLS on org_members and tickets (orgs stays readable)
ALTER TABLE public.org_members ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.org_members FORCE ROW LEVEL SECURITY;
ALTER TABLE public.tickets ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.tickets FORCE ROW LEVEL SECURITY;
-- Tickets policies
CREATE POLICY tickets_select ON public.tickets
FOR SELECT USING (
org_id IN (SELECT org_id FROM public.org_members WHERE user_id = auth.uid())
);
CREATE POLICY tickets_insert ON public.tickets
FOR INSERT WITH CHECK (
org_id IN (
SELECT org_id FROM public.org_members
WHERE user_id = auth.uid() AND role = 'admin'
)
);
CREATE POLICY tickets_update ON public.tickets
FOR UPDATE USING (
org_id IN (
SELECT org_id FROM public.org_members
WHERE user_id = auth.uid() AND role = 'admin'
)
) WITH CHECK (
org_id IN (
SELECT org_id FROM public.org_members
WHERE user_id = auth.uid() AND role = 'admin'
)
);
CREATE POLICY tickets_delete ON public.tickets
FOR DELETE USING (
org_id IN (
SELECT org_id FROM public.org_members
WHERE user_id = auth.uid() AND role = 'admin'
)
);
-- org_members policies: visible only to members of the same org
CREATE POLICY org_members_select ON public.org_members
FOR SELECT USING (
org_id IN (SELECT om.org_id FROM public.org_members om WHERE om.user_id = auth.uid())
);
CREATE POLICY org_members_insert ON public.org_members
FOR INSERT WITH CHECK (
org_id IN (
SELECT om.org_id FROM public.org_members om
WHERE om.user_id = auth.uid() AND om.role = 'admin'
)
);
CREATE POLICY org_members_update ON public.org_members
FOR UPDATE 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'
)
);
CREATE POLICY org_members_delete ON public.org_members
FOR DELETE USING (
org_id IN (
SELECT om.org_id FROM public.org_members om
WHERE om.user_id = auth.uid() AND om.role = 'admin'
)
);
cat <<'EOF' > /tmp/rls.sql
-- Helper: SECURITY DEFINER function to get org_ids for current user (avoids RLS recursion)
CREATE OR REPLACE FUNCTION public.current_user_org_ids()
RETURNS TABLE (org_id uuid)
LANGUAGE sql
SECURITY DEFINER
SET search_path = public
AS $$
SELECT org_id FROM public.org_members WHERE user_id = auth.uid();
$$;
-- Helper: SECURITY DEFINER function to get org_ids where current user is admin
CREATE OR REPLACE FUNCTION public.current_user_admin_org_ids()
RETURNS TABLE (org_id uuid)
LANGUAGE sql
SECURITY DEFINER
SET search_path = public
AS $$
SELECT org_id FROM public.org_members WHERE user_id = auth.uid() AND role = 'admin';
$$;
-- Enable RLS
ALTER TABLE public.org_members ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.org_members FORCE ROW LEVEL SECURITY;
ALTER TABLE public.tickets ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.tickets FORCE ROW LEVEL SECURITY;
-- ============ tickets policies ============
CREATE POLICY tickets_select ON public.tickets
FOR SELECT USING (
org_id IN (SELECT public.current_user_org_ids())
);
CREATE POLICY tickets_insert ON public.tickets
FOR INSERT WITH CHECK (
org_id IN (SELECT public.current_user_admin_org_ids())
);
CREATE POLICY tickets_update ON public.tickets
FOR UPDATE USING (
org_id IN (SELECT public.current_user_admin_org_ids())
) WITH CHECK (
org_id IN (SELECT public.current_user_admin_org_ids())
);
CREATE POLICY tickets_delete ON public.tickets
FOR DELETE USING (
org_id IN (SELECT public.current_user_admin_org_ids())
);
-- ============ org_members policies ============
CREATE POLICY org_members_select ON public.org_members
FOR SELECT USING (
org_id IN (SELECT public.current_user_org_ids())
);
CREATE POLICY org_members_insert ON public.org_members
FOR INSERT WITH CHECK (
org_id IN (SELECT public.current_user_admin_org_ids())
);
CREATE POLICY org_members_update ON public.org_members
FOR UPDATE USING (
org_id IN (SELECT public.current_user_admin_org_ids())
) WITH CHECK (
org_id IN (SELECT public.current_user_admin_org_ids())
);
CREATE POLICY org_members_delete ON public.org_members
FOR DELETE USING (
org_id IN (SELECT public.current_user_admin_org_ids())
);
EOF
export PGPASSWORD='<redacted:SUPABASE_DB_PASSWORD>'
psql "$SUPABASE_DB_URL" -f /tmp/rls.sql 2>&1CREATE FUNCTION CREATE FUNCTION ALTER TABLE ALTER TABLE ALTER TABLE ALTER TABLE CREATE POLICY CREATE POLICY CREATE POLICY CREATE POLICY CREATE POLICY CREATE POLICY CREATE POLICY CREATE POLICY
export PGPASSWORD='<redacted:SUPABASE_DB_PASSWORD>' psql "$SUPABASE_DB_URL" -c "GRANT EXECUTE ON FUNCTION public.current_user_org_ids() TO anon, authenticated; GRANT EXECUTE ON FUNCTION public.current_user_admin_org_ids() TO anon, authenticated; GRANT SELECT, INSERT, UPDATE, DELETE ON public.tickets TO anon, authenticated; GRANT SELECT, INSERT, UPDATE, DELETE ON public.org_members TO anon, authenticated; GRANT SELECT ON public.orgs TO anon, authenticated;" 2>&1
GRANT GRANT GRANT GRANT GRANT
# Test 1: Anonymous sees no tickets echo "=== Anonymous: tickets ===" curl -s "$SUPABASE_URL/rest/v1/tickets?select=*" -H "apikey: $SUPABASE_ANON_KEY" | head -5 echo "" echo "=== Anonymous: org_members ===" curl -s "$SUPABASE_URL/rest/v1/org_members?select=*" -H "apikey: $SUPABASE_ANON_KEY" | head -5 echo "" echo "=== Anonymous: orgs (should be readable) ===" curl -s "$SUPABASE_URL/rest/v1/orgs?select=*" -H "apikey: $SUPABASE_ANON_KEY" | head -5
=== Anonymous: tickets ===
[]
=== Anonymous: org_members ===
[]
=== Anonymous: orgs (should be readable) ===
[{"id":"85cbddcb-8652-436d-aeab-e12bfe12079e","name":"Acme"},
{"id":"dd0bde6b-98d0-43ad-b11f-1c2b2497be40","name":"Globex"}]# Get JWT tokens for the three test users
get_token() {
local email="$1" pass="$2"
curl -s "$SUPABASE_URL/auth/v1/token?grant_type=password" \
-H "apikey: $SUPABASE_ANON_KEY" \
-H "Content-Type: application/json" \
-d "{\"email\":\"$email\",\"password\":\"$pass\"}" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])"
}
UMA=$(get_token "uma-admin@cari.dev" "Passw0rd-uma-11")
VIC=$(get_token "vic-member@cari.dev" "Passw0rd-vic-22")
WEN=$(get_token "wen-outsider@cari.dev" "Passw0rd-wen-33")
echo "UMA token length: ${#UMA}"
echo "VIC token length: ${#VIC}"
echo "WEN token length: ${#WEN}"
# Save for later
echo "$UMA" > /tmp/uma_token
echo "$VIC" > /tmp/vic_token
echo "$WEN" > /tmp/wen_tokenUMA token length: 796 VIC token length: 798 WEN token length: 800
UMA=$(cat /tmp/uma_token) VIC=$(cat /tmp/vic_token) WEN=$(cat /tmp/wen_token) ACME="85cbddcb-8652-436d-aeab-e12bfe12079e" GLOBEX="dd0bde6b-98d0-43ad-b11f-1c2b2497be40" echo "=== Uma (Acme admin): tickets ===" curl -s "$SUPABASE_URL/rest/v1/tickets?select=id,title,org_id&order=id" \ -H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $UMA" echo "" echo "=== Vic (Acme member): tickets ===" curl -s "$SUPABASE_URL/rest/v1/tickets?select=id,title,org_id&order=id" \ -H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $VIC" echo "" echo "=== Wen (Globex member): tickets ===" curl -s "$SUPABASE_URL/rest/v1/tickets?select=id,title,org_id&order=id" \ -H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $WEN" echo ""
=== Uma (Acme admin): tickets ===
[{"id":1,"title":"Fix login","org_id":"85cbddcb-8652-436d-aeab-e12bfe12079e"},
{"id":2,"title":"Update billing","org_id":"85cbddcb-8652-436d-aeab-e12bfe12079e"},
{"id":3,"title":"Rotate keys","org_id":"85cbddcb-8652-436d-aeab-e12bfe12079e"}]
=== Vic (Acme member): tickets ===
[{"id":1,"title":"Fix login","org_id":"85cbddcb-8652-436d-aeab-e12bfe12079e"},
{"id":2,"title":"Update billing","org_id":"85cbddcb-8652-436d-aeab-e12bfe12079e"},
{"id":3,"title":"Rotate keys","org_id":"85cbddcb-8652-436d-aeab-e12bfe12079e"}]
=== Wen (Globex member): tickets ===
[{"id":4,"title":"Migrate DNS","org_id":"dd0bde6b-98d0-43ad-b11f-1c2b2497be40"},
{"id":5,"title":"Renew cert","org_id":"dd0bde6b-98d0-43ad-b11f-1c2b2497be40"}]
UMA=$(cat /tmp/uma_token)
VIC=$(cat /tmp/vic_token)
WEN=$(cat /tmp/wen_token)
ACME="85cbddcb-8652-436d-aeab-e12bfe12079e"
GLOBEX="dd0bde6b-98d0-43ad-b11f-1c2b2497be40"
echo "=== Vic (member) tries INSERT into Acme tickets (should FAIL) ==="
curl -s -o /dev/null -w "%{http_code}" "$SUPABASE_URL/rest/v1/tickets" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $VIC" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d "{\"org_id\":\"$ACME\",\"title\":\"RLS_TEST\",\"status\":\"open\"}"
echo ""
echo "=== Vic (member) tries UPDATE Acme ticket (should FAIL) ==="
curl -s -o /dev/null -w "%{http_code}" "$SUPABASE_URL/rest/v1/tickets?id=eq.1" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $VIC" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-X PATCH -d '{"status":"closed"}'
echo ""
echo "=== Vic (member) tries DELETE Acme ticket (should FAIL) ==="
curl -s -o /dev/null -w "%{http_code}" "$SUPABASE_URL/rest/v1/tickets?id=eq.1" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $VIC" \
-X DELETE
echo ""
echo "=== Uma (admin) tries INSERT into Acme (should SUCCEED, then we delete) ==="
curl -s -w "\n%{http_code}" "$SUPABASE_URL/rest/v1/tickets" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $UMA" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d "{\"org_id\":\"$ACME\",\"title\":\"RLS_TEST_TMP\",\"status\":\"open\"}"
echo ""
echo "=== Uma (admin) tries INSERT into Globex (should FAIL - not her org) ==="
curl -s -o /dev/null -w "%{http_code}" "$SUPABASE_URL/rest/v1/tickets" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $UMA" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d "{\"org_id\":\"$GLOBEX\",\"title\":\"RLS_TEST_CROSS\",\"status\":\"open\"}"
echo ""=== Vic (member) tries INSERT into Acme tickets (should FAIL) ===
403
=== Vic (member) tries UPDATE Acme ticket (should FAIL) ===
200
=== Vic (member) tries DELETE Acme ticket (should FAIL) ===
204
=== Uma (admin) tries INSERT into Acme (should SUCCEED, then we delete) ===
[{"id":7,"org_id":"85cbddcb-8652-436d-aeab-e12bfe12079e","title":"RLS_TEST_TMP","status":"open"}]
201
=== Uma (admin) tries INSERT into Globex (should FAIL - not her org) ===
403
UMA=$(cat /tmp/uma_token) echo "=== Check if Vic's UPDATE/DELETE actually affected data ===" echo "--- Uma reads tickets (check if id=1 still exists and status) ---" curl -s "$SUPABASE_URL/rest/v1/tickets?select=id,title,status&order=id" \ -H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $UMA" echo "" # Check with DB directly export PGPASSWORD='<redacted:SUPABASE_DB_PASSWORD>' psql "$SUPABASE_DB_URL" -c "SELECT id, title, status FROM public.tickets ORDER BY id;" 2>&1
=== Check if Vic's UPDATE/DELETE actually affected data ===
--- Uma reads tickets (check if id=1 still exists and status) ---
[{"id":1,"title":"Fix login","status":"open"},
{"id":2,"title":"Update billing","status":"open"},
{"id":3,"title":"Rotate keys","status":"open"},
{"id":7,"title":"RLS_TEST_TMP","status":"open"}]
id | title | status
----+----------------+--------
1 | Fix login | open
2 | Update billing | open
3 | Rotate keys | open
4 | Migrate DNS | open
5 | Renew cert | open
7 | RLS_TEST_TMP | open
(6 rows)
VIC=$(cat /tmp/vic_token)
echo "=== Retry: Vic tries UPDATE ticket id=1 (should be 403) ==="
curl -s -w "\nHTTP_CODE:%{http_code}" "$SUPABASE_URL/rest/v1/tickets?id=eq.1" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $VIC" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-X PATCH -d '{"status":"closed"}'
echo ""
echo "=== Retry: Vic tries DELETE ticket id=1 (should be 403) ==="
curl -s -w "\nHTTP_CODE:%{http_code}" "$SUPABASE_URL/rest/v1/tickets?id=eq.1" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $VIC" \
-X DELETE
echo ""=== Retry: Vic tries UPDATE ticket id=1 (should be 403) === [] HTTP_CODE:200 === Retry: Vic tries DELETE ticket id=1 (should be 403) === HTTP_CODE:204
export PGPASSWORD='<redacted:SUPABASE_DB_PASSWORD>' echo "=== Verify data was NOT modified by Vic ===" psql "$SUPABASE_DB_URL" -c "SELECT id, title, status FROM public.tickets ORDER BY id;" 2>&1
=== Verify data was NOT modified by Vic === id | title | status ----+----------------+-------- 1 | Fix login | open 2 | Update billing | open 3 | Rotate keys | open 4 | Migrate DNS | open 5 | Renew cert | open 7 | RLS_TEST_TMP | open (6 rows)
UMA=$(cat /tmp/uma_token)
echo "=== Uma (admin) DELETE test ticket id=7 (cleanup) ==="
curl -s -w "\nHTTP_CODE:%{http_code}" "$SUPABASE_URL/rest/v1/tickets?id=eq.7" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $UMA" \
-X DELETE
echo ""
# Verify cleanup
export PGPASSWORD='<redacted:SUPABASE_DB_PASSWORD>'
psql "$SUPABASE_DB_URL" -c "SELECT id, title, status FROM public.tickets ORDER BY id;" 2>&1=== Uma (admin) DELETE test ticket id=7 (cleanup) === HTTP_CODE:204 id | title | status ----+----------------+-------- 1 | Fix login | open 2 | Update billing | open 3 | Rotate keys | open 4 | Migrate DNS | open 5 | Renew cert | open (5 rows)
UMA=$(cat /tmp/uma_token) VIC=$(cat /tmp/vic_token) WEN=$(cat /tmp/wen_token) echo "=== Uma (Acme admin): org_members (should see only Acme members) ===" curl -s "$SUPABASE_URL/rest/v1/org_members?select=org_id,user_id,role" \ -H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $UMA" echo "" echo "=== Vic (Acme member): org_members (should see only Acme members) ===" curl -s "$SUPABASE_URL/rest/v1/org_members?select=org_id,user_id,role" \ -H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $VIC" echo "" echo "=== Wen (Globex member): org_members (should see only Globex members) ===" curl -s "$SUPABASE_URL/rest/v1/org_members?select=org_id,user_id,role" \ -H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $WEN" echo ""
=== Uma (Acme admin): org_members (should see only Acme members) ===
[{"org_id":"85cbddcb-8652-436d-aeab-e12bfe12079e","user_id":"d074d31f-0600-41e5-bd90-840243609899","role":"admin"},
{"org_id":"85cbddcb-8652-436d-aeab-e12bfe12079e","user_id":"1b9d4399-3d9d-43f9-87a9-6a154abd68a7","role":"member"}]
=== Vic (Acme member): org_members (should see only Acme members) ===
[{"org_id":"85cbddcb-8652-436d-aeab-e12bfe12079e","user_id":"d074d31f-0600-41e5-bd90-840243609899","role":"admin"},
{"org_id":"85cbddcb-8652-436d-aeab-e12bfe12079e","user_id":"1b9d4399-3d9d-43f9-87a9-6a154abd68a7","role":"member"}]
=== Wen (Globex member): org_members (should see only Globex members) ===
[{"org_id":"dd0bde6b-98d0-43ad-b11f-1c2b2497be40","user_id":"1362ea69-1f9a-47e5-8a1f-50caa4b80cff","role":"member"}]
UMA=$(cat /tmp/uma_token)
ACME="85cbddcb-8652-436d-aeab-e12bfe12079e"
echo "=== Uma (admin) UPDATE Acme ticket id=1 (should SUCCEED) ==="
curl -s -w "\nHTTP_CODE:%{http_code}" "$SUPABASE_URL/rest/v1/tickets?id=eq.1" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $UMA" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-X PATCH -d '{"status":"in-progress"}'
echo ""
# Revert it back
curl -s -o /dev/null "$SUPABASE_URL/rest/v1/tickets?id=eq.1" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $UMA" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-X PATCH -d '{"status":"open"}'
echo "=== Verify data restored ==="
export PGPASSWORD='<redacted:SUPABASE_DB_PASSWORD>'
psql "$SUPABASE_DB_URL" -c "SELECT id, title, status FROM public.tickets ORDER BY id;" 2>&1=== Uma (admin) UPDATE Acme ticket id=1 (should SUCCEED) ===
[{"id":1,"org_id":"85cbddcb-8652-436d-aeab-e12bfe12079e","title":"Fix login","status":"in-progress"}]
HTTP_CODE:200
=== Verify data restored ===
id | title | status
----+----------------+--------
1 | Fix login | open
2 | Update billing | open
3 | Rotate keys | open
4 | Migrate DNS | open
5 | Renew cert | open
(5 rows)
WEN=$(cat /tmp/wen_token)
ACME="85cbddcb-8652-436d-aeab-e12bfe12079e"
echo "=== Wen (Globex) tries to UPDATE Acme ticket (should be blocked - not his org) ==="
curl -s -w "\nHTTP_CODE:%{http_code}" "$SUPABASE_URL/rest/v1/tickets?id=eq.1" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $WEN" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-X PATCH -d '{"status":"closed"}'
echo ""
echo "=== Wen (Globex) tries to DELETE Acme ticket (should be blocked) ==="
curl -s -w "\nHTTP_CODE:%{http_code}" "$SUPABASE_URL/rest/v1/tickets?id=eq.1" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $WEN" \
-X DELETE
echo ""=== Wen (Globex) tries to UPDATE Acme ticket (should be blocked - not his org) === [] HTTP_CODE:200 === Wen (Globex) tries to DELETE Acme ticket (should be blocked) === HTTP_CODE:204
1 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