[content] HydrationStage integration for content resolution
## Problem to Solve The query pipeline (Security → Compilation → ClickHouse → Extraction → Authorization → Redaction → Hydration → Output) currently has no hook for resolving externally-sourced data. Content resolution needs to happen after ClickHouse hydration (we need `project_id`, `branch`, `file_path`, `traversal_path` from the hydrated rows) but before the output stage. ## Proposed Solution Add a **content resolution step** within or immediately after `HydrationStage`: 1. After ClickHouse hydration completes, check if any node in the result set has a `content` virtual property requested 2. Extract `(node_id, project_id, branch, file_path, start_byte?, end_byte?, traversal_path)` from hydrated rows for nodes needing content 3. Derive `organization_id` from `SecurityContext.org_id` and `root_namespace_id` from `traversal_path` second segment 4. Build `ContentRequest` batch and call `ContentResolver::resolve_batch()` 5. Merge returned content bytes back into the row map as the `content` property **Integration point**: Extend `HydrationStage::execute()` to call content resolution after `parse_consolidated_batches()` and `merge_static_properties()`. **Configuration**: `ContentResolver` is injected into the pipeline context (via `AppState` or `PipelineContext`), selected at startup based on `content_cache.enabled` config. ## Acceptance Criteria - [ ] HydrationStage calls ContentResolver when `content` property is requested - [ ] org_id extracted from SecurityContext, root_namespace_id from traversal_path - [ ] Content bytes merged into result rows as `content` property - [ ] Works for both static and dynamic hydration plans - [ ] Batch limit error propagated as structured pipeline error - [ ] Integration tests with real ClickHouse + mock Gitaly
issue