feat: add OrbitService for Knowledge Graph endpoints

What

Adds OrbitService exposing five Knowledge Graph (GKG) REST endpoints introduced in gitlab-org/gitlab#591015. Orbit is the product name for GitLab's Knowledge Graph service.

Method Endpoint Returns
GetStatus GET /api/v4/orbit/status *OrbitStatus — cluster health
GetSchema GET /api/v4/orbit/schema *OrbitSchema — graph ontology (?expand=A,B opt-in)
GetTools GET /api/v4/orbit/tools *OrbitTools — MCP tool manifest
Query POST /api/v4/orbit/query *OrbitQueryResult — query execution
GetGraphStatus GET /api/v4/orbit/graph_status *OrbitGraphStatus — namespace/project indexing status

All five endpoints are gated by the knowledge_graph feature flag and are marked experimental: response shapes may evolve before GA. They are user-scoped (not project-scoped).

Why

These endpoints are the prerequisite for the upcoming glab orbit subcommand suite (gitlab-org/cli, separate MR). Per @phikai's guidance on the parent issue, all GitLab API surface must go through client-go first; CLI consumers should use the typed service rather than raw glab api calls.

Design notes

do[T] vs NewRequest+Do

The existing NewRequest helper already JSON-marshals opt and sets Content-Type: application/json automatically for POST/PUT/PATCH (see gitlab.go:953-964). No raw-body or custom-header path is needed — do[T] with withAPIOpts(body) covers POST orbit/query cleanly. All five methods use the standard do[T] pattern.

Where json.RawMessage is used

Per ADR 003, response shapes are format-dependent (raw vs llm) and the query DSL evolves independently. Fields are typed where the shape is stable, and exposed as json.RawMessage where it isn't:

  • OrbitQueryRequest.Query — opaque DSL body, passed through unchanged. The authoritative JSON Schema lives in GET /api/v4/orbit/tools.
  • OrbitQueryResult.Result — shape varies by query type and response_format.
  • OrbitSchema.Nodes — heterogeneous within a single response when ?expand= is used (matched nodes get properties[], style, edge lists; others stay summary-only).
  • OrbitStatusComponent.Metrics, OrbitTool.Parameters — pass-through.

OrbitTools wire format

/api/v4/orbit/tools returns a bare JSON array of tool definitions, not {"tools": [...]}. The typed wrapper exposes a named Tools field for ergonomics and decodes the bare array via custom UnmarshalJSON / MarshalJSON. This was confirmed against the live gitlab.com endpoint.

GetGraphStatus query parameters

GET /api/v4/orbit/graph_status requires exactly one of namespace_id (int), project_id (int), or full_path (string) to be provided; the server returns 400 otherwise. An optional response_format (raw|llm) selects the response shape. The method is defined with GetGraphStatusOptions carrying url: struct tags, consistent with GetOrbitSchemaOptions.

Live contract verification

The struct shapes were validated against the live gitlab.com endpoints before opening this MR. A few details diverge from ADR 003 and have been adjusted to match the live contract:

  • components[].replicas is an object {ready, desired}, not a scalar — modelled as *OrbitStatusReplicas.
  • /orbit/tools returns a bare JSON array — handled via custom UnmarshalJSON.
  • Tool parameter schema field is parameters, not parameters_json_schema.
  • No endpoint field on tool entries — dropped.
  • Dropped top-level QueryType from OrbitQueryRequest (not a body root key).
  • Promoted Edges to typed []*OrbitSchemaEdge (shape is consistent regardless of ?expand).

Tests

Fourteen unit tests in orbit_test.go, covering all five methods plus their key error and format paths:

  • TestOrbitService_GetStatus — happy path with components and metrics
  • TestOrbitService_GetStatus_FeatureFlagOff404 is surfaced to caller
  • TestOrbitService_GetSchema — typed domains/edges, raw nodes
  • TestOrbitService_GetSchema_WithExpandexpand is comma-joined per API convention, format propagated
  • TestOrbitService_GetTools — bare-array wire format decodes via UnmarshalJSON
  • TestOrbitService_Queryresponse_format=raw, body matches typed request, Content-Type set
  • TestOrbitService_Query_LLMFormatresponse_format=llm, JSON-string result preserved
  • TestOrbitService_Query_NamespaceForbidden403 (no GKG-enabled namespaces) is surfaced
  • TestOrbitService_GetGraphStatus_ByNamespaceID — happy path with namespace_id, full response decode
  • TestOrbitService_GetGraphStatus_ByProjectIDproject_id query param forwarding
  • TestOrbitService_GetGraphStatus_ByFullPathfull_path URL-encoded correctly
  • TestOrbitService_GetGraphStatus_LLMFormatresponse_format=llm forwarding
  • TestOrbitService_GetGraphStatus_Forbidden403 is surfaced to caller
  • TestOrbitService_GetGraphStatus_ServiceUnavailable503 is surfaced to caller

mise exec -- make reviewable (generate, fmt, lint, test) passes locally.

References

Out of scope

  • Server-side endpoint implementation (already shipped, behind FF on .com)
  • glab orbit CLI subcommands — separate MR in gitlab-org/cli
  • Skill rewrite to use the typed client (follow-up)
Edited by Dmitry Gruzd

Merge request reports

Loading