[content] ContentResolver trait and GitalyDirectContentResolver
## Problem to Solve When agents expand `File`, `Definition`, or `ImportedSymbol` nodes in GKG queries, the server returns metadata (path, name, line numbers) but cannot return the actual source code. There is no mechanism to fetch file content at query time — the indexer has content during processing (via Gitaly) but discards it after extracting metadata. Content resolution must be batch-aware: a typical agent query returns 100–1,000 nodes, and per-file round-trips to Gitaly are unacceptable (1,000 × 5ms = 5 seconds). ## Proposed Solution Introduce a `ContentResolver` trait and `GitalyDirectContentResolver` implementation in `gkg-server`: **`ContentResolver` trait** (`gkg-server/src/content/resolver.rs`): - `resolve_batch(requests: &[ContentRequest]) -> Result<HashMap<i64, Bytes>>` - `ContentRequest`: `{ node_id, organization_id, root_namespace_id, project_id, branch, file_path, blob_sha?, start_byte?, end_byte? }` - Batch limit enforcement (default 1,000) **`GitalyDirectContentResolver`**: - Groups requests by `project_id` (Gitaly's `list_blobs` is per-project) - Dispatches concurrent `list_blobs` calls via the existing `GitlabClient` HTTP API - Uses `<branch>:<path>` revision format (Path B — no schema changes needed) - Slices content for `Definition` nodes using `start_byte`/`end_byte` **Key files to create/modify**: - `crates/gkg-server/src/content/mod.rs` — module root - `crates/gkg-server/src/content/resolver.rs` — trait + request types - `crates/gkg-server/src/content/gitaly_direct.rs` — GitalyDirect impl - `crates/gkg-server/src/content/types.rs` — ContentRequest, error types ## Acceptance Criteria - [x] `ContentResolver` trait defined with batch API - [x] `GitalyDirectContentResolver` groups by project_id and uses concurrent `list_blobs` - [x] Batch limit enforced with structured error - [x] Definition content sliced by start_byte/end_byte - [x] Missing blobs handled gracefully (logged, not batch-failing) - [x] Unit tests with mock Gitaly responses
issue