# tf_docs: код-ревью AI-ассистента и правки (поиск подресурсов + таймаут ЛЛМ) Дата: 2026-10-12 Коммит: `12d29ee` (репозиторий `tf_docs`, ветка `master`, запушен) Контекст: по запросу — фактическое ревью кода/архитектуры ассистента с упором на ЛЛМ. ## Что проверялось Прочитан backend целиком: `server.js`, `assist.js`, `search.js`, `stage1.js`, `stage2.js`, `orchestrator.js`; сверено с `llm_project/docs/ASSISTANT_ARCHITECTURE.md` и результатами прогонов (`llm_project/tests/RESULTS_*.json`). ## Найдено (факты, `файл:строка` на момент ревью) 1. **🔴 `search.js:206-215`** — `resourceBaseSegment` перебирал суффиксы-подресурсы в исходном порядке, где `_user` стоит раньше `_sub_user`. Для `nubes_s3_sub_user` (rest=`s3_sub_user`) отрезался `_user` → base `s3_sub` вместо `s3`. Следствие: `isResourcePage` не находила страницу ресурса (`"s3_sub_user" !== "s3"`), поиск по `resource_type` для `*_sub_user` был неэффективен. Реальный ресурс (есть в `golden_questions_300.json`); эффект маскировался лексическим поиском по исходному вопросу. 2. **🔴 `assist.js:25`** — `ASSIST_LLM_TIMEOUT_MS` (25 000) был объявлен в `ENV` и задокументирован (README, `ASSISTANT_ARCHITECTURE.md:§5`), но **нигде не использовался**: `orchestrator.js` не передавал `timeoutMs` в `stage1`, а `stage2Generate` его не принимал. Фактически работал только общий дедлайн 40 с. 3. **🟡 `orchestrator.js:205`** — `clientIp` брал **последний** элемент `X-Forwarded-For` (ближайший хоп) вместо первого (исходный клиент). Влияет только при заданном `ASSIST_TRUSTED_PROXY_CIDR` (по умолчанию пусто). 4. **🟡 `orchestrator.js:317,328`** — мёртвая ветка дедупа (`handleAssistAgain` — заглушка); в однопоточном JS недостижима. 5. **🟢** кэш ответов без TTL/лимита (`orchestrator.js:306`); `npm test` покрывает только прокси/`health` (LLM-пайплайн без юнит-тестов); `fetchIndex` без лимита размера; `FORWARD_HEADERS` без `etag/last-modified`; при `aborted` отдаётся нестандартный HTTP 499. ## Исправлено (2 пункта) ### 1. `search.js` — порядок суффиксов подресурсов Массив `knownSubs` вынесен в `KNOWN_RESOURCE_SUBS`, отсортирован по убыванию длины при загрузке модуля. Проверено изолированно: `nubes_s3_sub_user → s3` (было `s3_sub`), `nubes_mariadb_sub_user → mariadb`; `nubes_postgres_database → postgres`, `nubes_postgres → postgres` — без изменений. ### 2. `assist.js` + `stage1.js` + `orchestrator.js` — реальный per-call таймаут - `llmChat` получил параметр `timeoutMs` (дефолт `ENV.ASSIST_LLM_TIMEOUT_MS`) и составной сигнал: дочерний `AbortController`, абортящийся по таймауту ИЛИ по аборту вызывающего. Таймаут покрывает вызов целиком (в т.ч. чтение тела). - Таймаут отдаётся отдельным кодом **`llm_timeout`** (отмена клиентом/дедлайн — по-прежнему `aborted`), чтобы `stage2` различал причины и корректно решал о повторе. - `stage1.js` освобождён от дублирующего `AbortController`. - `orchestrator.js`: `llm_timeout` → HTTP **504**. Проверено на локальном «зависшем» сервере: таймаут → `llm_timeout` (314 мс при лимите 300), abort родителя → `aborted` (152 мс). `node -c` всех модулей OK, `node --test` 4/4. ## Проверка на живом стенде - `redeploy` инстанса `tf-docs` (`7a48a863-…`) через `llm_project/tests/redeploy.js` → успешно (`finish=2026-10-12T07:56:44+03:00`, `ok=true`); `/health` → 200. - Прогон 62 вопросов на `nubes-test` (`--out RESULTS_AFTER_FIX`): **62/62 — 200 OK**, шаблон **62/62**, ошибок 0; задержки p50 5.9 с, p95 10.8 с, max 12.4 с; `llm_timeout` не срабатывал ни разу. - ⚠️ Сравнение с прежним `RESULTS_LIVE.json` **некорректно**: тот от 2026-10-07, за 5 дней индекс документации изменился (у 45 из 62 вопросов другой набор источников) → это дрейф индекса, а не эффект правки. ## Что НЕ менялось (осознанно) Пункты 3–5 из «Найдено» (XFF, мёртвая ветка дедупа, кэш без TTL, юнит-тесты) — оставлены как есть: не мешают текущей работе; вернуться при масштабировании/переработке. ## Режим «редактор файла» (добавлено в этой же сессии) Новый эндпоинт `POST //_api/file` (`filemode.js`): тело `{content, instruction}` → ответ `{content, changes[], warnings[]}`. LLM возвращает ПОЛНЫЙ обновлённый файл (обычно `terraform.tfvars`). Опирается на текстовый `search_index.json`; секреты сюда не кладут (отдельный `secrets.auto.tfvars`), логируются только размеры. ## Следующий этап — требования (НЕ реализовано) - **Схемы (YAML/raw.json) надо публиковать на сайт.** Чтобы режим `/_api/file` (и любой LLM-режим на бэкенде) проверял файл ПО СХЕМЕ, спеки `provider/resources_yaml/*.yaml` / `stack///raw.json` нужно отдавать эндпоинтом (напр. `GET //_api/schema?resource=...`) рядом со статикой. Сейчас `/_api/file` опирается только на текстовые доки — схемы бэкенду недоступны. - **`AGENTS.md` для IDE-агентов** — сделан в `tf_provider` (самодостаточный: структура `raw.json`, инвентарь 35 ресурсов, workflow, секреты, типичные ошибки). - **MCP** — отложен: требует ручной настройки у юзера в IDE (на этом этапе исключено); ставка на zero-config `AGENTS.md` + режим `/_api/file`.