diff --git a/packages/core/src/postgres/migrations/0006_project_ownership.sql b/packages/core/src/postgres/migrations/0006_project_ownership.sql index 9a1bb6589d..97f745d6ac 100644 --- a/packages/core/src/postgres/migrations/0006_project_ownership.sql +++ b/packages/core/src/postgres/migrations/0006_project_ownership.sql @@ -42,14 +42,19 @@ BEGIN PostgreSQL roles are cluster-wide while Gate databases apply this migration concurrently. Advisory locks are database-local, so make CREATE ROLE itself race-safe across databases by accepting the concurrent winner. + + FNXC:ProjectDataIsolation 2026-07-15-01:50: + Always CREATE ROLE (no IF NOT EXISTS). The check-then-create path is not atomic + across databases of one cluster: concurrent appliers all observe the role as + absent and race on CREATE ROLE, raising 23505 on pg_authid_rolname_index. + EXCEPTION WHEN duplicate_object OR unique_violation tolerates losing that race + (unique_violation is what the index race actually raises). */ - IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'fusion_runtime') THEN - BEGIN - CREATE ROLE fusion_runtime NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION; - EXCEPTION - WHEN duplicate_object OR unique_violation THEN NULL; - END; - END IF; + BEGIN + CREATE ROLE fusion_runtime NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION; + EXCEPTION WHEN duplicate_object OR unique_violation THEN + NULL; -- concurrent applier created the role first; safe to skip + END; EXECUTE format('GRANT fusion_runtime TO %I', current_user); END IF; END $$;