From a7218560f1f14197498d9b95d8343398dac621ed Mon Sep 17 00:00:00 2001 From: biosxxx Date: Sun, 16 Aug 2026 14:10:22 +0300 Subject: [PATCH] Bootstrap the is_admin() stub before anything references it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preview branches have been failing since July with "function public.is_admin() does not exist", raised by the audit-log policy in 20260702000000. The function was defined straight in the production database and never entered this migration history, so a database built from these files alone does not have it. 20260720000000 already creates a deny-all stub, but that is three weeks too late to help. The stub moves to 20260613000001, the first migration that mentions is_admin() at all. That is deliberately not the first migration that breaks: a reference inside a function body resolves when the function runs, while a policy's USING expression resolves as the policy is created, so 20260613000001 applied cleanly and 20260702000000 was the one that failed. Guarding the earliest mention covers every later reference rather than only the first that happened to be a policy. The block in 20260720000000 stays — it is part of applied history, and it no-ops once the earlier one has run. Its comment is updated to say where the stub now comes from. Editing applied migrations is safe here: Supabase records them by version, so production will not re-run either file, and production's real is_admin() is untouched regardless — the guard creates the stub only when the function is absent. Verified on a fresh Postgres 17 with no is_admin() present, only the pieces Supabase itself provides (auth.uid, auth.users, profiles): all 15 migrations apply in order with no errors, where before the run died on 20260702000000. Re-applying both guard-bearing migrations over a real is_admin() that returns true leaves it returning true, so the stub cannot clobber production. Final state after the in-order replay is the intended one: anon holds no table or column INSERT on client_error_log, no INSERT policy remains, and record_client_error plus the quota table are present. Co-Authored-By: Claude Opus 5 --- ...60613000001_add_admin_utility_usage_fn.sql | 29 +++++++++++++++++++ ...20260720000000_create_client_error_log.sql | 8 ++--- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/supabase/migrations/20260613000001_add_admin_utility_usage_fn.sql b/supabase/migrations/20260613000001_add_admin_utility_usage_fn.sql index f3d82c0..9a817de 100644 --- a/supabase/migrations/20260613000001_add_admin_utility_usage_fn.sql +++ b/supabase/migrations/20260613000001_add_admin_utility_usage_fn.sql @@ -1,3 +1,32 @@ +-- public.is_admin() predates this migration history — it was defined straight +-- in the production database — so a database built from these files alone does +-- not have it. This is the first migration that mentions it, and the guard sits +-- here rather than at the first migration that *breaks* because the two are not +-- the same place: a reference inside a function body resolves when the function +-- runs, while a policy's USING expression resolves as the policy is created. So +-- this file applied cleanly on a fresh database and 20260702000000 was the one +-- that failed, which is why preview branches have been erroring on +-- "function public.is_admin() does not exist" since July. +-- +-- Create a deny-all stub only when the function is missing: production keeps +-- its real definition, anything built from scratch gets a safe default where +-- nobody is an admin. +do $$ +begin + if not exists ( + select 1 + from pg_proc p + join pg_namespace n on n.oid = p.pronamespace + where n.nspname = 'public' and p.proname = 'is_admin' + ) then + create function public.is_admin() + returns boolean + language sql + stable + as 'select false'; + end if; +end $$; + -- Admin-only per-user utility usage breakdown. Joins usage rows with profile -- identity so an admin can see who launched what. Guarded by is_admin(); -- SECURITY DEFINER is required to read across all users past RLS. diff --git a/supabase/migrations/20260720000000_create_client_error_log.sql b/supabase/migrations/20260720000000_create_client_error_log.sql index 9d52625..f1ede04 100644 --- a/supabase/migrations/20260720000000_create_client_error_log.sql +++ b/supabase/migrations/20260720000000_create_client_error_log.sql @@ -6,10 +6,10 @@ -- identity — only the error itself and non-identifying context (route, tool, -- user agent, locale). Only admins may read; nobody may update or delete. -- public.is_admin() predates the migration history (defined directly in the --- production database), so fresh preview branches don't have it and every --- policy referencing it fails. Create a deny-all stub only when the function --- is missing: production keeps its real definition, preview branches get a --- safe default (nobody is admin). +-- production database), so a database built from these files alone does not +-- have it. The stub now goes up in 20260613000001, the first migration that +-- mentions the function at all; this block stays because it is already part of +-- applied history, and it no-ops once the earlier one has run. do $$ begin if not exists (