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 inGET /api/v4/orbit/tools.OrbitQueryResult.Result— shape varies by query type andresponse_format.OrbitSchema.Nodes— heterogeneous within a single response when?expand=is used (matched nodes getproperties[],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[].replicasis an object{ready, desired}, not a scalar — modelled as*OrbitStatusReplicas./orbit/toolsreturns a bare JSON array — handled via customUnmarshalJSON.- Tool parameter schema field is
parameters, notparameters_json_schema. - No
endpointfield on tool entries — dropped. - Dropped top-level
QueryTypefromOrbitQueryRequest(not a body root key). - Promoted
Edgesto 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 metricsTestOrbitService_GetStatus_FeatureFlagOff—404is surfaced to callerTestOrbitService_GetSchema— typed domains/edges, raw nodesTestOrbitService_GetSchema_WithExpand—expandis comma-joined per API convention,formatpropagatedTestOrbitService_GetTools— bare-array wire format decodes viaUnmarshalJSONTestOrbitService_Query—response_format=raw, body matches typed request,Content-TypesetTestOrbitService_Query_LLMFormat—response_format=llm, JSON-stringresultpreservedTestOrbitService_Query_NamespaceForbidden—403(no GKG-enabled namespaces) is surfacedTestOrbitService_GetGraphStatus_ByNamespaceID— happy path withnamespace_id, full response decodeTestOrbitService_GetGraphStatus_ByProjectID—project_idquery param forwardingTestOrbitService_GetGraphStatus_ByFullPath—full_pathURL-encoded correctlyTestOrbitService_GetGraphStatus_LLMFormat—response_format=llmforwardingTestOrbitService_GetGraphStatus_Forbidden—403is surfaced to callerTestOrbitService_GetGraphStatus_ServiceUnavailable—503is surfaced to caller
mise exec -- make reviewable (generate, fmt, lint, test) passes locally.
References
- Upstream issue: gitlab-org/gitlab#591015
graph_statusendpoint MR: gitlab-org/gitlab!231381 (merged)- ADR 003 (Orbit API design):
gitlab-org/orbit/knowledge-graph!docs/design-documents/decisions/003_api_design.md - Companion MR (in
gitlab-org/cli): feat(orbit): add glab orbit subcommands (experi... (gitlab-org/cli!3178)
Out of scope
- Server-side endpoint implementation (already shipped, behind FF on .com)
glab orbitCLI subcommands — separate MR ingitlab-org/cli- Skill rewrite to use the typed client (follow-up)