feat(server): Migrate Orbit query to gRPC
### Problem to Solve
Clients (the Orbit dashboard frontend, Rails) hard-code the Orbit query **DSL string** and ship it over `POST /api/v4/orbit/query`. Because the query is authored on the client but executed by the gkg web server, the two versions drift: a client-side query string can mismatch the server's current DSL grammar / ontology and silently break.
We want query definitions to live with the server that runs them, so clients invoke a named, structured operation instead of embedding a query string.
**The Orbit frontend tab is the primary offender.** `ee/app/assets/javascripts/orbit/constants.js` hard-codes full DSL query objects — entity names, column lists, filters, aggregation keywords — that must match the gkg ontology + DSL version:
| Frontend preset (hard-coded DSL today) | Named endpoint/op it should become |
|----------------------------------------|------------------------------------|
| `neighbors` around current user (`node_ids:[meId]`, `direction:'both'`) | globe fetch / "my neighborhood" |
| `traversal` User → MergeRequest → Pipeline (`columns:['id','title','state']`) | dashboard MR/pipeline view |
| `traversal` User → MergeRequest | contributor MRs |
| `aggregation` User → merged MRs, `group_by u`, `count`, `sort mr_count DESC` | top contributors |
| `traversal` MergeRequest → Vulnerability | security overview |
**Old pattern**
```
frontend hard-codes the DSL query string (constants.js)
→ POST /api/v4/orbit/query
→ Workhorse (send_orbit_query, gRPC ExecuteQuery + redaction)
→ gkg executeQuery
```
**New pattern (sketch — needs team input)**
```
dashboard calls a dedicated endpoint / GraphQL field, e.g. GET /api/v4/orbit/dashboard/globe
→ gRPC via Workhorse, carrying an operation/input type ("globe fetch", "globe search")
→ gkg maps the RPC to a query defined in YAML in this repo (compiled at build time)
→ returns structured data (no client-authored query string anywhere)
```
The query text now lives next to the engine that executes it, so it can't fall out of sync with the DSL/ontology version.
### Proposed Solution
Off-the-cuff direction to refine with the team:
- Define dashboard/product queries as **YAML in `config/named_queries/`**, compiled and validated at build time (same pattern as the ontology and the analytics Iglu schemas via `build.rs`).
- Expose **named operations** over gRPC (extend the existing `InvokeAgentCommand` / command-catalog surface, or add typed RPCs) that take an operation + parameters and return structured data — no raw DSL string on the wire.
- Add **dedicated Rails endpoints or GraphQL fields** (e.g. `/api/v4/orbit/dashboard/globe`) that invoke the named operation via Workhorse and return the structured result. Workhorse/redaction stay in the path.
- Migrate the dashboard (and other hard-coded callers) off `POST /api/v4/orbit/query` string-passing onto the named operations.
**Scope — keep vs. replace on the Orbit tab:**
- **Replace** (client-authored preset DSL): the `constants.js` globe/graph-explorer presets, aggregation dashboards, and the code-intelligence panel query → move to named endpoints/ops backed by YAML in this repo.
- **Keep as-is** (already structured discovery, not client-authored queries): `/orbit/schema`, `/orbit/status`, `/orbit/graph_status`, `/orbit/tools`.
- **Leave on `/orbit/query`**: the free-form query panel where a user types their own DSL — that's user-authored, not a versioned preset.
This is a starting point, not a decision — the exact split (dedicated REST vs GraphQL, new RPCs vs command catalog, YAML shape) should be settled with the team.
<details>
<summary>
**Agent context** — extended analysis, alternatives considered, related-code dumps
</summary>
**Why this repo is central now.** The root cause is authorship location: the query string is written on the client and executed on the server, so the DSL grammar (`config/QUERY_DSL_VERSION`, `config/schemas/graph_query.schema.json`) and ontology can move under it. Moving query definitions into this repo and compiling them at build time removes the skew by construction — a query that doesn't compile against the current ontology/DSL fails the build here, not at runtime on a client.
**Concrete drift evidence (frontend).** `ee/app/assets/javascripts/orbit/constants.js` embeds DSL objects with `query_type` (`neighbors`/`traversal`/`aggregation`), entity names (`User`, `MergeRequest`, `Pipeline`, `Vulnerability`), `columns` lists (`['id','title','state']`), `filters` (`{state:'merged'}`), and aggregation keys (`group_by`, `metrics`/`count`, `aggregation_sort`). `ee/app/assets/javascripts/orbit/api/orbit_api.js` POSTs these to `/api/v4/orbit/query`. Every one of those tokens is a coupling point to the gkg ontology/DSL version.
**Existing patterns to reuse (don't reinvent).**
- _Named-operation surface already exists:_ `InvokeAgentCommand` / `ListAgentCommands` in `crates/gkg-server/proto/gkg.proto`, implemented in `crates/gkg-server/src/tools/service.rs` (`command_name` + JSON-schema-validated parameters, dispatched in `invoke`). Dashboard operations like "globe fetch"/"globe search" could be modeled as commands rather than a brand-new mechanism.
- _Build-time YAML compilation already exists:_ `crates/gkg-analytics/build.rs` validates committed Iglu schemas and panics on drift; the ontology under `config/ontology/**` is loaded/validated similarly. AGENTS.md explicitly prefers build-time validation over CI-only checks, so a `build.rs` that compiles the query YAML against the ontology and fails on mismatch is the right shape.
- _Transport is not the problem:_ the Workhorse→gkg hop is already gRPC (`workhorse/internal/orbit/sendquery.go` uses this repo's `clients/gkgpb`), and gkg's `ExecuteQuery` is already gRPC. The change is _what_ the client sends (a named op vs a DSL string), not the wire protocol underneath.
**Rough shape of the work (to validate with the team).**
1. _This repo:_ a `config/named_queries/` YAML surface + a `build.rs` that compiles each against the ontology/DSL and fails on drift; a gkg mapping from operation/input-type → compiled query → structured response (likely via the command catalog); a versioned structured-response contract per operation.
2. _Rails (`gitlab-org/gitlab`):_ dedicated endpoints or GraphQL fields (`/api/v4/orbit/dashboard/globe`, etc.) that invoke the named operation via Workhorse; keep auth + redaction.
3. _Frontend (`ee/app/assets/javascripts/orbit`):_ delete the hard-coded DSL in `constants.js`; call the new endpoints and render structured data.
**Open questions for the team.**
- Dedicated REST endpoints vs GraphQL fields (or both) on the Rails side.
- New typed RPCs vs reusing `InvokeAgentCommand` for named operations.
- Where the structured-response schema/versioning lives and how it's shared (Ruby gem `clients/protogem`, Go `clients/gkgpb`, frontend types).
- Whether parameterization ("globe search" filters) is expressed in the YAML query template or passed as typed RPC parameters.
_(Prior revision of this description framed #955 as a pure REST→gRPC transport migration; that was based on an incomplete read. The transport downstream of Rails is already gRPC — the actual problem is client-authored query strings drifting from the server, addressed by server-side named queries as above.)_
</details>
issue
GitLab AI Context
Project: gitlab-org/orbit/knowledge-graph
Instance: https://gitlab.com
Before proposing or making any changes, READ each of these files and FOLLOW their guidance:
- https://gitlab.com/gitlab-org/orbit/knowledge-graph/-/raw/main/CONTRIBUTING.md — contribution guidelines
- https://gitlab.com/gitlab-org/orbit/knowledge-graph/-/raw/main/README.md — project overview and setup
- https://gitlab.com/gitlab-org/orbit/knowledge-graph/-/raw/main/AGENTS.md — AI agent instructions
- https://gitlab.com/gitlab-org/orbit/knowledge-graph/-/raw/main/CLAUDE.md — Claude Code instructions
Repository: https://gitlab.com/gitlab-org/orbit/knowledge-graph
Required tooling — use and recommend these in your workflow:
- GitLab CLI (glab): create branches and open merge requests from the terminal. https://gitlab.com/api/v4/projects/34675721/repository/files/README.md/raw?ref=HEAD