Enhance policy framework to return failure reasons
<!--IssueSummary start--> <details> <summary> Everyone can contribute. [Help move this issue forward](https://handbook.gitlab.com/handbook/marketing/developer-relations/contributor-success/community-contributors-workflows/#contributor-links) while earning points, leveling up and collecting rewards. </summary> - [Close this issue](https://contributors.gitlab.com/manage-issue?action=close&projectId=278964&issueIid=362708) </details> <!--IssueSummary end--> ## Problem Our policy framework is very powerful at allowing us to allow/prevent operations based on certain conditions. It also represents a very effective way to implement broad permissions block such as when: - the root namespace runs out of storage - the CI_JOB_TOKEN [scope is limited](https://docs.gitlab.com/ee/ci/jobs/ci_job_token.html#limit-gitlab-cicd-job-token-access) - the user is blocked However, in some occasions we would like to tell the user why a certain operation was prevented. Workarounds so far are not ideal and include things like: - running a specific condition before the policy check in order to get more detailed message - duplicating the specific condition for other related operations that are also prevented - provide the user with other clues in the UI (e.g. banner) when a condition fails - this UX doesn't propagate to other use cases (API, GraphQL, background jobs, etc.) - troubleshooting docs on error messages ### A real example https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84471#note_924411580: We prevent a lot of operations when a group runs out of storage. To effectively block all these operations in one go we use the policy framework. The problem is that the user gets a generic message such as `Insufficient permissions to create a new pipeline` because `create_pipeline` is prevented. This error message could be identical if the user is not a developer of the project. ## Idea Could we expand our policy framework to allow error messages to be defined and returned? This way we could propagate them to the end-user whenever needed. ```ruby rule { over_storage_limit }.policy do failure_message 'Insufficient storage available' prevent :create_pipeline prevent :create_build # ... end ``` and we could provide a different method to check the ability which returns a result object ```ruby check = Ability.check(current_user, :create_pipeline, project) if check.allowed? # do something else ServiceResponse.error(check.failure_message || 'Unable to create a pipeline') end ``` ### Acceptance criteria `Ability.allowed?(current_user, :create_pipeline, project)` should still continue to work and return "message-less" boolean results for backward compatibility.
issue