Fix commit Advanced Search for Planner role in private projects
What does this MR do and why?
A user with the Planner role who is a member of a private project got no results from Advanced Search (Elasticsearch) when searching commits in that project.
Root cause
ProjectPolicy grants a Planner read_code: read_code is listed in
config/authz/roles/planner.yml, and ProjectPolicy#access_allowed_to?(:repository) only
requires team_access_level >= ProjectFeature.required_minimum_access_level(:repository),
which is Guest.
The Elasticsearch membership filters in ee/lib/search/elastic/filters.rb were stricter.
They take the minimum access level for the repository feature from
ProjectFeature::PRIVATE_FEATURES_MIN_ACCESS_LEVEL_FOR_PRIVATE_PROJECT[:repository], which
is Reporter, and apply it to private projects. A Planner passed the policy check but was
filtered out of search.
Probing Ability.allowed?(user, :read_code, project) across all 45 combinations of project
visibility, repository access level and role pins where the two disagree. Planner is true
in a private project at both the enabled and the private repository access level, and
false when the repository is disabled. Guest is false in every private project
combination. Public and internal projects already matched, because the minimum there is
Guest for both.
So the divergence is the whole private-project column for the Planner role, at both feature
access levels. That is why the relaxed rule below covers [ENABLED, PRIVATE] rather than
just ENABLED as the merge request fix does.
Fix
Elastic::Latest::GitClassProxy::COMMIT_RELAXED_FEATURE_ACCESS sets
relaxed_feature_access for the repository feature to a Planner minimum over the ENABLED
and PRIVATE project feature access levels. It is merged into the options only in the
'commit' branch of GitClassProxy#elastic_search, which is the single funnel for commit
search: find_commits_by_message_with_elastic on both the class and the instance proxy
routes through it, so global, group and project level search are all covered by one place.
relaxed_feature_access is an existing option in ee/lib/search/elastic/filters.rb. It
adds a second, lower membership rule rather than widening the existing one. Milestone search
introduced it in
!252980 (merged), and merge request search uses
it in !253160 (merged).
Two shared changes in ee/lib/search/elastic/filters.rb. First, a relaxed rule only applies
to the branch where it lowers the minimum access level. For the repository feature it stays on
the private branch (Reporter down to Planner) and is dropped from the public/internal branch,
where the minimum is already Guest. Each branch also looks up ids only for the access levels
its own rules use, so commit search does 3 project_authorizations lookups: Guest on
public/internal, Reporter and Planner on private.
Second, build_feature_access_level_filter removes duplicate predicates. On the private
branch, for a Reporter or above, the base and relaxed rules resolve to the same project ids and
emit the same repository_access_level predicate, so they collapse into one clause. Together,
these keep the query body unchanged for every role other than Planner.
Scope: commit search only. Blob (code) search and Zoekt exact code search still use the Reporter minimum for private projects, and comments on commits are unchanged. Epic gitlab-org#17674 stays open for those.
Why not lower the shared constant?
An earlier version of this MR lowered
PRIVATE_FEATURES_MIN_ACCESS_LEVEL_FOR_PRIVATE_PROJECT[:repository] from Reporter to
Planner. That constant is also read by Project.filter_by_feature_visibility and by
Search::GroupsFinder, so the change reached project visibility filtering outside search. It
would also have enabled blob search and Zoekt exact code search for Planners in private
projects, which doc/user/permissions.md still lists as unsupported for that role. The
spec diff of that version showed it: it had to add a Planner row expecting results to the
private-project blob table, while the footnote it edited still said code search does not
work for Planners.
Tests
spec/support/shared_contexts/policies/project_policy_table_shared_context.rbgainspermission_table_for_repository_access, used by the global, group and project commit visibility specs in place ofpermission_table_for_guest_feature_access_and_non_private_project_only. It adds the Planner rows and a:private | :enabledblock that no existing table covered, which is why no spec guarded this bug. The old table is unchanged and still used by the blob visibility specs, where Planner support is still pending.ee/spec/lib/search/elastic/filters_spec.rbasserts that with the relaxed option a Planner member of a private project produces a query byte-identical to the existingglobal_search_user_access_to_project_as_reporter.jsonfixture, that a Reporter's query is unchanged, and that a Guest's query is unchanged. Removingterms.uniq!fails the Reporter example, so the dedupe is covered. A context with a Guest in one public project and a Reporter in another asserts the query is identical with and withoutrelaxed_feature_access, so the relaxed rule has no effect on public and internal projects.ee/spec/lib/elastic/latest/git_class_proxy_spec.rbgains a Planner row expecting no results for blob search in a private project. This pins the boundary that code search still requires Reporter. The blob visibility specs can't show it because their table has no Planner rows.
References
- Closes #627146 (closed)
- Part of gitlab-org#17674
- Milestone search, which introduced
relaxed_feature_access: !252980 (merged) - Merge request search, which uses it for the same class of mismatch: !253160 (merged)
How to set up and validate locally
- Enable Elasticsearch and Advanced Search in the GDK.
- Create a private project with a repository and at least one commit.
- Leave the project's repository visibility at Everyone with access, or set it to Only project members. Both are covered by the fix.
- Add a user to the project with the Planner role.
- Index the project so its commits reach Elasticsearch.
- Sign in as that user and search commits, scoped to the project, the group, and globally.
- Confirm the commit is returned in all three. Before this change, all three returned nothing.
- Repeat as a Guest member and confirm no commits are returned, which matches
read_code. - Repeat a code search as the Planner and confirm it still returns nothing, which confirms the change did not widen code search.
Screenshot
| Before | After |
|---|---|
![]() |
![]() |
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

