-- Idempotent RBAC/root migration for existing deployments. -- Apply with a privileged DB user before restarting account service in production. CREATE TABLE IF NOT EXISTS public.rbac_roles ( role_key TEXT PRIMARY KEY, description TEXT NOT NULL DEFAULT '', priority INTEGER NOT NULL DEFAULT 100, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS public.rbac_permissions ( permission_key TEXT PRIMARY KEY, description TEXT NOT NULL DEFAULT '', created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS public.rbac_role_permissions ( role_key TEXT NOT NULL REFERENCES public.rbac_roles(role_key) ON DELETE CASCADE, permission_key TEXT NOT NULL REFERENCES public.rbac_permissions(permission_key) ON DELETE CASCADE, enabled BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (role_key, permission_key) ); CREATE UNIQUE INDEX IF NOT EXISTS users_single_root_role_uk ON public.users ((lower(role))) WHERE lower(role) = 'root'; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_constraint WHERE conname = 'users_root_email_ck' ) THEN ALTER TABLE public.users ADD CONSTRAINT users_root_email_ck CHECK (lower(role) <> 'root' OR lower(email) = 'admin@svc.plus'); END IF; END $$; INSERT INTO public.rbac_roles (role_key, description, priority) VALUES ('root', 'single root account', 0), ('operator', 'operation role with configurable permissions', 10), ('user', 'standard subscription user', 20), ('readonly', 'read-only experience account', 30) ON CONFLICT (role_key) DO NOTHING; INSERT INTO public.rbac_permissions (permission_key, description) VALUES ('admin.settings.read', 'read admin matrix settings'), ('admin.settings.write', 'update admin matrix settings'), ('admin.users.metrics.read', 'read user metrics'), ('admin.agents.status.read', 'read agent status'), ('admin.users.pause.write', 'pause users'), ('admin.users.resume.write', 'resume users'), ('admin.users.delete.write', 'delete users'), ('admin.users.renew_uuid.write', 'renew user proxy uuid'), ('admin.users.role.write', 'update/reset user role'), ('admin.blacklist.read', 'read blacklist'), ('admin.blacklist.write', 'update blacklist') ON CONFLICT (permission_key) DO NOTHING; INSERT INTO public.rbac_role_permissions (role_key, permission_key, enabled) SELECT 'operator', permission_key, true FROM public.rbac_permissions ON CONFLICT (role_key, permission_key) DO NOTHING;