Add POST /orbit/context endpoint

What does this MR do and why?

Adds POST /api/v4/orbit/context. It takes one token, such as a merge request or work item URL, and returns a short summary from PostgreSQL. With expand, it also attaches the entity's graph neighborhood from a single named query, so one request bills at most one graph query. It also takes epic tokens, which resolve to the epic's work item, and echoes the expansion query, so a client can rerun it through POST /api/v4/orbit/query/:name.

References

Screenshots or screen recordings

Not applicable, API only.

How to set up and validate locally

  1. Run bundle exec rspec ee/spec/requests/api/orbit ee/spec/lib/analytics/orbit/context_token_spec.rb ee/spec/services/analytics/orbit ee/spec/lib/analytics/knowledge_graph.
  2. With a running GKG, call the endpoint with {"tokens": ["<group>/<project>!1"], "expand": true}. Use a user who is at least a Reporter in a group where Orbit is turned on, otherwise expand returns outside_graph_scope.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist.

Agent context
  • Analytics::Orbit::ContextToken parses Type[id], GIDs, instance URLs, path!iid, path#iid and the epic forms (Epic[id], epic GIDs, /groups/.../-/epics/<iid> URLs, group&iid) from the text alone (invalid_token, unsupported_type).
  • Epic tokens set Parsed#epic and keep type WorkItem. The service resolves Epic[id] through Epic#work_item, and iid forms through the group-level work item lookup, requiring an epic work item. The same readable? check applies, so unlicensed, confidential, missing and wrong-group epics all return not_found.
  • Analytics::Orbit::ContextService looks up the record (including group-level work items), applies the policy ability and the granular-token boundary, and builds the summary. Every failure after a lookup starts returns the same not_found; linked records and users are filtered to readable ones before the list is cut, in a fixed order. summary.truncated names the lists that were cut, and ids are strings like the graph node ids.
  • Analytics::Orbit::ContextExpansionService runs expand_merge_request / expand_work_item with node_ids and a constant limit through QueryRunner. It returns expansion_unsupported_type when the type has no query, and outside_graph_scope (with no query and no billing) when the record is outside the user's Reporter+ Orbit-enabled traversal paths. Every expansion carries query: {name, parameters}, the exact payload sent to GKG, except for expansion_unsupported_type. It maps the governing-namespace guard to billing_namespace_required with the same message, deep link and candidate groups as query/:name, plus quota_exhausted, timeout and execution_error.
  • QueryRunner takes an optional renderer:. The new DataRenderer returns the graph as data instead of an HTTP response, parsed with the safe JSON parser. Because that renderer has no stream frame, QueryRunner reads chunked results into one body instead of streaming them. An error message, early stream end or deadline during that read maps to the same codes as a non-streamed query.
  • The route requires a JSON body like query/:name, uses read_orbit, the same rate limit and namespace ordering as query/:name, response_format raw or llm, and lifecycle experiment. Docs, fine-grained token table, routes JSON and OpenAPI are updated.
  • Tests: new request, token, service and expansion specs, plus DataRenderer cases in the runner spec, run locally (all green). The GKG boundary is mocked. mermaidlint hook skipped locally because its node deps are missing.
  • Merge after kg!2834 is deployed. Until then, expand returns execution_error, because GKG does not know the query names.
  • Follow-up: the HTTP router snapshot needs a refresh for the new route (check_router_routes_sync, which is allow_failure).

Related to https://gitlab.com/gitlab-org/orbit/knowledge-graph/-/work_items/1260

Edited by Michael Angelo Rivera

Merge request reports

Loading
Loading