Well, I was able to create a custom role that seems to work, with a small number of permissions (26). I did this by giving the service account the "Owner" role, then later looking at the permissions that had apparently been used (in the "excess permissions" view available on the "IAM & Admin / IAM" page of the cloud console).
I'm not sure why or when each of them had been used; some may not be needed. In fact there was one permission that seemed to have been used, but I failed to add it to the role because I misunderstood something in the custom role UI, and it worked anyway. So some others may not be needed either.
appengine.applications.get
artifactregistry.packages.delete
artifactregistry.packages.get
cloudfunctions.functions.create
cloudfunctions.functions.delete
cloudfunctions.functions.get
cloudfunctions.functions.list
cloudfunctions.functions.sourceCodeSet
cloudfunctions.functions.update
cloudfunctions.operations.get
datastore.indexes.create
datastore.indexes.delete
datastore.indexes.list
datastore.indexes.update
firebase.clients.get
firebase.projects.get
firebasehosting.sites.update
firebaserules.releases.create
firebaserules.releases.list
firebaserules.releases.update
firebaserules.rulesets.create
firebaserules.rulesets.get
firebaserules.rulesets.test
runtimeconfig.configs.list
secretmanager.versions.get
serviceusage.services.get
This is for a pipeline that deploys using "npx firebase-tools deploy" in a project that uses Firestore, Firebase Hosting, Firebase Cloud Functions, Firebase Auth, and Firebase Storage. (Or actually it's not using Storage yet, but it has a rules file for Storage that presumably is getting deployed.)
I hope this is useful for other people having this problem. Hopefully they'll be able to find this post.