# DOC_EXPLAIN / FIND_FILES Workflow

## Контракт сабинтента

| Поле | Значение |
|---|---|
| `domain` | `DOCS` |
| `intent` | `DOC_EXPLAIN` |
| `subintent` | `FIND_FILES` |
| `workflow_id` | `v2.docs_explain.find_files` |
| `source` | `workflow.v2.find_files` |

## Диаграмма флоу

```mermaid
flowchart TD
    A["RequireRagSessionStep"] --> B["ResolveRetrievalPlanStep"]
    B --> C["FetchRagRowsStep"]
    C --> D["PrepareCandidateRowsStep"]
    D --> E["BuildFilesEvidenceStep"]
    E --> F["ApplyFilesEvidenceGateStep"]
    F --> G["FinalizeFindFilesAnswerStep"]
```

## Шаги процесса

### 1) `RequireRagSessionStep`

Шаг проверяет, есть ли активная RAG-сессия. Если `rag_session_id` пустой, workflow останавливает дальнейший retrieval и пишет пользовательское сообщение в `answer`. Для `find_files` gate-решение на этом шаге обычно не ставится, но механизм поддержан.

**Входные параметры**

| Параметр | Откуда берётся | Описание |
|---|---|---|
| `context.rag_session_id` | `V2Process` -> `DocExplainFindFilesContext` | Идентификатор активной RAG-сессии |
| `self._missing_message` | Конфигурация в `graph.py` | Текст ответа, если сессии нет |
| `self._missing_gate` | Конфигурация шага | Опциональный gate для раннего выхода |

**Выходные параметры**

| Параметр | Как формируется |
|---|---|
| `context.answer` | Заполняется `missing_message`, если `rag_session_id` пустой |
| `context.gate_decision` | Заполняется `missing_gate`, если он передан и сессии нет |
| `context.answer_generated_payload` | Формируется как `{"answer_mode", "answer_length"}` при раннем ответе |

### 2) `ResolveRetrievalPlanStep`

Шаг превращает route в retrieval-план через `RetrievalPlanResolver`. Профиль для этого сабинтента — `file_lookup`, с подходящими слоями и фильтрами. Параллельно пишет trace-событие `retrieval_plan_resolved`.

**Входные параметры**

| Параметр | Откуда берётся | Описание |
|---|---|---|
| `context.route` | Результат `intent_router` | Route с `anchors`, `target_terms`, `scope_type` |
| `self._resolver` | DI из `graph.py` | Реализация policy-резолвера |
| `context.answer` | Предыдущие шаги | Если уже есть ответ, шаг пропускается |

**Выходные параметры**

| Параметр | Как формируется |
|---|---|
| `context.retrieval_plan` | `self._resolver.resolve(context.route)` |
| `process.v2.retrieval_policy.retrieval_plan_resolved` | Лог с `profile`, `layers`, `limit`, `filters` |

### 3) `FetchRagRowsStep`

Шаг выполняет retrieval через `V2RagRetrievalAdapter`. Внутри адаптера объединяются seed-строки по `target_doc_hints` и основной retrieval по эмбеддингам/фильтрам плана. Если план не сформирован или уже есть готовый ответ, шаг ничего не делает.

**Входные параметры**

| Параметр | Откуда берётся | Описание |
|---|---|---|
| `context.rag_session_id` | Контекст workflow | Сессия для поиска в `rag_chunks` |
| `context.route.normalized_query` | Route | Нормализованный текст запроса |
| `context.retrieval_plan` | Предыдущий шаг | План retrieval |

**Выходные параметры**

| Параметр | Как формируется |
|---|---|
| `context.retrieved_rows` | `await rag_adapter.fetch_rows(rag_session_id, normalized_query, retrieval_plan)` |

### 4) `PrepareCandidateRowsStep`

Шаг собирает итоговые candidate rows для ранжирования файлов. Он добавляет metadata-lookup кандидаты и подмешивает seed по `target_doc_hints`, затем сохраняет merged-список в `context.rows`. Дополнительно пишет детальный retrieval-trace.

**Входные параметры**

| Параметр | Откуда берётся | Описание |
|---|---|---|
| `context.retrieved_rows` | `FetchRagRowsStep` | Строки после retrieval |
| `context.route` | Route | Нужен для hints/aliases/terms |
| `self._builder` | `CandidateRowsBuilder()` | Логика merge и metadata lookup |

**Выходные параметры**

| Параметр | Как формируется |
|---|---|
| `context.rows` | `prepared.rows` из `CandidateRowsBuilder.build(...)` |
| `process.v2.rag_retrieval.rag_rows_fetched` | Лог деталей rows, источников и top результатов |

### 5) `BuildFilesEvidenceStep`

Шаг ранжирует candidate rows в список файлов через `DocsEvidenceAssembler.assemble_files`. На выходе формируется shortlist `RetrievedFile` с оценками и причинами совпадения. Этот shortlist становится опорой для gate и финального ответа.

**Входные параметры**

| Параметр | Откуда берётся | Описание |
|---|---|---|
| `context.rows` | `PrepareCandidateRowsStep` | Подготовленные кандидаты |
| `context.route` | Route | Сигналы маршрута для ranking |
| `self._assembler` | DI из `graph.py` | Сборщик evidence |

**Выходные параметры**

| Параметр | Как формируется |
|---|---|
| `context.files` | `assemble_files(context.rows, context.route)` |
| `process.v2.evidence.evidence_assembled` | Лог file-count и путей |

### 6) `ApplyFilesEvidenceGateStep`

Шаг проверяет качество shortlist через `DocsEvidenceGate.check_files`. Решение gate определяет, можно ли отвечать детерминированно или нужно более осторожное поведение. Для прозрачности пишет pipeline-лог с полями `passed/reason/answer_mode`.

**Входные параметры**

| Параметр | Откуда берётся | Описание |
|---|---|---|
| `context.route` | Route | Сигналы запроса для gate |
| `context.files` | `BuildFilesEvidenceStep` | Ранжированные файлы |
| `self._gate` | DI из `graph.py` | Правила оценки evidence |

**Выходные параметры**

| Параметр | Как формируется |
|---|---|
| `context.gate_decision` | `self._gate.check_files(context.route, context.files)` |
| `process.v2.pipeline.evidence_gate_checked` | Лог результата gate |

### 7) `FinalizeFindFilesAnswerStep`

Шаг собирает финальный текстовый ответ без LLM. Если файлов нет, возвращается `insufficient_evidence`; если файл один — отдаётся один путь; если несколько — до 4 путей. Если gate вернул `low_confidence_shortlist`, также возвращается ограниченный список путей.

**Входные параметры**

| Параметр | Откуда берётся | Описание |
|---|---|---|
| `context.files` | `BuildFilesEvidenceStep` | Список найденных файлов |
| `context.gate_decision` | `ApplyFilesEvidenceGateStep` | Режим и причина ответа |
| `context.answer` | Предыдущие шаги | Если уже заполнен, шаг пропускается |

**Выходные параметры**

| Параметр | Как формируется |
|---|---|
| `context.answer` | Детерминированно: пусто/1 путь/до 4 путей |
| `context.answer_generated_payload` | `{"answer_mode", "answer_length"}` по ветке формирования |

