Add scoped Orbit grep endpoint
What does this MR do and why?
Adds the experimental GET /api/v4/orbit/grep endpoint for scoped searches in projects and groups. It returns graph-compatible entity tokens and structured references that agents can use without knowing the query DSL. Every requested scope comes back with a status, so an empty scope is distinct from a skipped or failed one. Access follows /search: it needs the search rate limit and the search token permission, and the target must be in an Orbit-enabled top-level group.
Agent context
The endpoint fans out through SearchService per scope without forcing a search type, so the search layer selects Zoekt, Advanced Search, or Basic search. Results use fixed scope order and bounded per-scope and total limits.
The response has request, target, scopes (one object per scope with status, backend, pagination, or a reason object), and a flat results list with type/id/token. Files return null id and token, and a reference with project_id, path, branch, and commit_sha (set for Advanced Search only). Basic search rows are deduped per file, from a wider window of raw rows, and keep the first matched line. A scope whose search is flagged as abusive reports invalid_search. Backend errors are isolated to their scope, and the service reports only the first one per request (the Elasticsearch transport instrumentation still logs each failed call, as it does for /search), a scope with a search type error (for example, the Zoekt project limit) reports unavailable with search_type_error, truncation uses one extra row instead of a count query, and the abuse check runs once and returns 400.
Access: the route charges search_rate_limit once in the API and once more for each further scope that runs a search, and once the limit is hit the remaining scopes report rate_limited without searching. It requires use_global_search next to read_orbit for granular tokens, and returns 404 unless the target's root group is one of the user's Orbit-enabled roots. Administrators are checked against the root's own enabled state. The keywords parameter is now search, so it is filtered from logs.
Pipeline notes
We're applying pipeline:skip-router-sync because the route isn't in the HTTP Router snapshot yet. The orbit/grep route is missing from the router's main routes file, so a paired HTTP Router MR is needed as a follow-up. It's out of scope here.
References
Related to https://gitlab.com/gitlab-org/orbit/knowledge-graph/-/work_items/1264
Screenshots or screen recordings
Not applicable. This change has no UI.
| Before | After |
|---|---|
| Not applicable | Not applicable |
How to set up and validate locally
bundle exec rspec ee/spec/services/analytics/orbit/grep_service_spec.rb ee/spec/requests/api/orbit/data_grep_spec.rb
bundle exec rubocop ee/app/services/analytics/orbit/grep_service.rb ee/app/services/analytics/orbit/grep_llm_formatter.rb ee/lib/api/entities/orbit/grep.rb ee/lib/api/orbit/data.rb ee/spec/services/analytics/orbit/grep_service_spec.rb ee/spec/requests/api/orbit/data_grep_spec.rb
scripts/lint-doc.sh doc/api/orbit.md
bin/rake gitlab:openapi:v3:check_docsMR 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.
Related to https://gitlab.com/gitlab-org/orbit/knowledge-graph/-/work_items/1260