WEB-422 · Задача · — · web
P1 ПРОД (prodeyes1): нет обычного поиска по тексту загруженного файла — ключевая проверка содержимого невозможна
Закрыт
P2
ведёт: —
Доказательства
PRODEYES1: искали метку PRODEYES1_SEARCH_MARKER_7429 в интерфейсе — обычного текстового поиска по содержимому источников не нашлось (скрин 07-search-filters, 08-standard-mode). Отчёт /Users/milamarty/PRODEYES1-REPORT.md. Возможно пересекается с линией WEB-415 (чтение документов ассистентом принято 28.08) — но тут именно ПОИСК, не ассистент.
---
## 2026-08-28 23:15Z — связанное наблюдение: «semantic index unavailable» в мессенджер-корпусе owner-а
- Owner получил от бота тетради «Документы есть, но нет семантического индекса. Переиндексируй» (22:48, chatWithSources). Журнал: тот же ответ был на l64 в 22:24 — НЕ регрессия l66; отказов rag_embeddings ноль.
- Гипотеза: документы, присланные через телеграм-вход, не проходят индексацию (или корпус мессенджера отделён и пуст). Пересекается с этим тикетом (нет поиска по загруженному файлу) — при разборе проверить ОБА входа: веб-загрузка и телеграм.
- Отдельное неудобство (кандидат в тикет): один бот на опс-канал и продукт — служебные сообщения owner-а получают продуктовые ответы.
---
## 2026-08-29 04:30Z — доказано: поиска по тексту НЕ БЫЛО; функция добавлена, идёт приёмка
Инвентарь волны (отчёт /home/ubuntu/waves/WEB422SEARCH-REPORT.md, база l66 eb577cd1):
- DocumentChunk.fts + GIN существовали, но только ВНУТРИ RAG hybridSearch, который требует embeddings (c.embedding IS NOT NULL) → без индексации не работает.
- /api/sources/semantic-search и /api/search/local-semantic — оба требуют embeddings.
- Клиентский фильтр в Sidebar/Focus ищет только по уже загруженному в браузер Source.content; для summary/partial источников тела нет → не находит.
- ВЫВОД: пользователь НЕ мог найти документ по слову из текста без ассистента. Это отсутствующая функция, а не поломка.
Сделано в круге (коммит 06fafe60): Source.contentFts + триггер на content + backfill + GIN индекс; маршрут POST /api/sources/text-search (ACL-scoped, websearch_to_tsquery, rank), подключён в Sidebar, Focus Sources, universal search. Не требует embeddings.
Приёмка ACC422 заряжена (queue/366) с упором на: аддитивность и стоимость миграции (триггер+backfill), ПРИВАТНОСТЬ (A не видит источники B — на линии видео уже была утечка), инъекции в tsquery, полезность (поиск по слову из тела, поведение для summary/partial).
---
## 2026-08-29 05:30Z — ACC422: GO. Поиск по тексту принят
Приёмка /home/ubuntu/waves/ACC422-REPORT.md подтвердила коммит 06fafe60: Source.contentFts + триггер + backfill + GIN, маршрут /api/sources/text-search (ACL-scoped), подключение в Sidebar/Focus/universal search. Кандидат следующей посадки (l68) — в l67 не попал, сдан позже слияния.
---
## 2026-08-29 07:40Z — ⚠️ ПОПРАВКА: индексация мертва ДЛЯ ВСЕХ источников, а не из-за одного «отравленного»
Живая проверка прода (работник Claude Code + мои пробы):
- За 6 часов 2925 батчей, 0 успешных, consecutiveFailures=79505, batches=585578. Все на cmt4e09lk00fqnd7ccceoab3g (FACTCHECK-V2M-R24-INDEPENDENT-AUDIT-2026, статус failed, 9265 символов, создан 22.08).
- ⚠️ Я временно убрал --include-failed, чтобы обойти «отравленную голову» — очередь сдвинулась на ДРУГИЕ источники (cmtb7cny500eje2iyi9koxbt7, cmtdbyhoq00dk2i9ghir51meo) и они упали ТОЙ ЖЕ ошибкой «Cannot read properties of undefined (reading 'has')». Значит дефект НЕ в конкретном документе: воркер сломан для всех. Конфиг возвращён в исходный вид (бэкап .bak-includefailed-063612).
- Состояние БД: 556 источников done, 30 pending, 1 failed, 2391 без статуса. То есть с 22.08 не проиндексировано ничего нового.
- Дополнительно исправлен дрейф релиза: воркер запускался из arm64-l66, прод — l67b; переведён на l67b (бэкап .bak-l66-063252). При этом снова обнаружилась потеря node_modules/server-only/empty.js при упаковке — восстановлено вручную (та же ловушка, что на l66; в l68 едет починка упаковки).
ВЫВОД: починка воркера (WEBIDX2 f86eb59d, ACCIDX2 GO) — не косметика, а восстановление функции. Сажать в l68 срочно.
[29.08] Две независимые починки мёртвой индексации от базы l68: WEBIDX3 2618b93b (корень: METERED_METHODS module-scope const + re-entrant цикл meteredOpenAIClient->containmentMode) и IDXFIX 6b0a3d86 (корень: createRequire(import.meta.url) на верхнем уровне vendorEgressGuard, __esm обнуляет фабрику до вызова тела; вторая стена — getEmbeddingsClient выбрасывал userId, шлюз шёл в auth() которого у воркера нет -> TrustedIdentityError). Оба доказаны сборкой+запуском бандла, не юнитами. Заряжена приёмка ACCIDX (438): проверить, настоящие ли ОБА корня, не обойдён ли учёт расхода, выбрать что сажать.
[29.08 ЖИВАЯ ПРОВЕРКА ПРОДА l68, обычная не-админская учётка] Поиск по тексту РАБОТАЕТ: панель поиска -> вкладка Content -> запрос "omega" (слово есть только в содержимом, в именах файлов его нет) отфильтровал ровно один источник EVISUAL2-fixture.txt. Негативный контроль: "zzqqxnotpresent" -> 0 источников. Фильтр реальный, не заглушка.
ВНИМАНИЕ на будущее: текстовый поиск (эта задача) и семантическая индексация — РАЗНЫЕ вещи. Индексация всё ещё мертва (баннер "Not searchable: Cannot read properties of undefined (reading 'has')"), её починки и приёмка ACCIDX ведутся отдельно — записи о них выше в этом же журнале относятся к индексации, а не к текстовому поиску.
[29.08 ПРИЁМКА ИНДЕКСАЦИИ: ACCIDX_VERDICT=GO-B, ACCIDX_LAND_SHA=6b0a3d86bb2fd001497251034240be1a01eddd02]
Выбрано решение Б (IDXFIX). Приёмка собрала и ЗАПУСТИЛА релизный воркер-бандл (pnpm smoke:release-workers, без обхода через юнит-тесты): source-indexing-worker sha256=2546827bb6d5..., lazyModules=98, failingInits=0, proxyBuilt=true, rawReachedThroughGateway="raw-never-called".
Почему не A: решение A чинит только первый слой — его реальный путь с owner id доходит до настоящего OpenAI SDK, но вторую стену (TrustedIdentityError из-за выброшенного userId в getEmbeddingsClient) не закрывает. Слияние A+B признано ненужным и рискованным (двойная правка одного места).
ДЕНЬГИ: обход учёта не найден ни в A, ни в Б. В meteredVendorCall сначала проверяется branded TrustedIdentity; личность строится из id владельца документа (createTrustedIdentityFromAuthenticatedSession), клиентский снимок не может подменить её чужим id.
Кандидат в линию l69.
[29.08 ПРИНЯТО — ACCIDXFIX2_VERDICT=GO, коммит 0cf324be9ff9dd98092f0ae56b57e8f8f6e89e0e]
Починка индексации (IDXFIX 7cfc2e9f) оживила CJS-бандл воркера и сломала серверную сборку Next: [nc-web] vendor egress guard failed to install: TypeError: l is not a function, следом paidReady=false. Поймано сухим прогоном ДО переключения прода.
КОРЕНЬ: webpack обрабатывает module.createRequire особым парсером и принимает только СТАТИЧЕСКИ анализируемый аргумент. Динамический вызов даёт предупреждение module.createRequire failed parsing argument, а результат замены становится undefined — отсюда TypeError. Старый createRequire(import.meta.url) случайно устраивал парсер webpack, но ломал CJS-воркер (там import.meta.url недоступен в нужном виде), поэтому откат был невозможен.
РЕШЕНИЕ: убран статический импорт createRequire; модуль берётся во время выполнения через process.getBuiltinModule('node:module') — его не переписывают ни webpack, ни esbuild; результат и createRequire проверяются на вызываемость.
ПРОВЕРЕНО ЗАПУСКОМ ОБОИХ БАНДЛОВ: воркер через node --conditions=react-server с OPENAI_BASE_URL=127.0.0.1:9 (loadError:null, failingInits:[], ошибки METERED_METHODS нет); серверный — отдельный smoke на production-minified CommonJS бандле (webpack 5.103.0 + Next SWC), guard installed и direct-deny self-check passed, причём сборка с предупреждением тоже считается провалом.
КООРДИНАТОР ПРОВЕРИЛ ГЛАВНЫЙ РИСК САМ: на проде node v20.19.5, typeof process.getBuiltinModule === function — метод есть.
ПОДТВЕРЖДЕНО НА СУХОМ ПРОГОНЕ l69: [billing][egress_guard_installed] { mode: enforce } и [billing][egress_direct_deny_self_check_passed].
[29.08 12:55Z НАСТОЯЩИЙ КОРЕНЬ МЁРТВОЙ ИНДЕКСАЦИИ — и находка крупнее задачи]
Проверка на НОВОМ документе (qqindex-74213.txt, 679 байт, sourceId cmtebbs7e00mtc0o8xwtwilo8): воркер l69 ВЗЯЛ его сразу и упал — «Error: Cannot persist invalid embedding provenance for chunk ...-0» в VectorStore.addDocuments; DONE total=1 processed=1 succeeded=0 failed=1; кусков в базе 0; баннер в интерфейсе показывает ту же ошибку дословно.
КОРЕНЬ: buildEmbeddingIntegrityRecord() берёт configuredSigningKeys()[0] и при отсутствии ключа возвращает null, а addDocuments на null бросает исключение. configuredSigningKeys() читает EMBEDDING_INTEGRITY_HMAC_KEY (>=32 байт). Переменной на проде НЕТ нигде: shared/.env, .env.local, env воркера, spend-runtime, окружение живого процесса — проверено по именам.
⭐⭐ НАХОДКА КРУПНЕЕ: SELECT count(*), count("embeddingIntegrityHash") FROM "DocumentChunk" -> 127441 | 0. А чтение фильтрует (src/lib/rag/vectorStore.ts:157-162, применяется в :458 и :519): embeddingTextHash IS NOT NULL, embeddingModel IS NOT NULL, embeddingModelVersion IS NOT NULL, embeddingIntegrityHash IS NOT NULL. То есть НИ ОДИН из 127441 накопленных кусков не проходит выборку — семантический поиск возвращает пусто из-за фильтра, а не из-за отсутствия данных. Подтверждение из лога Совета: degraded_reason «no relevance signal — fell back to recency (no chunk index available for this scope)».
ПОБОЧНО: expectedRelease=missing в строке старта воркера при INGEST_WORKER_REQUIRE_RELEASE_MATCH=1 — сторож «служба на старом артефакте» похоже не включён; именно он должен был поймать вчерашнюю ситуацию с воркером на артефакте l68.
Заряжена EMBINTEGRITY (очередь 505): громкий отказ на старте вместо тихого падения на каждом документе; ЧТО ДЕЛАТЬ С 127441 кусками (подписать / переиндексировать / послабление фильтра для исторических) с оценкой стоимости; различение «нет хеша исторически» и «хеш неверен»; требования к ключу. Ключ на прод ставит координатор ТОЛЬКО по решению владельца — волне генерировать секреты запрещено.
Воркер
не привязан — привязать:
curl -X POST https://bugs.wool2.online/api/web/assign -H 'content-type: application/json' \
-d '{"issueId":"WEB-422","session":"<имя tmux-сессии>","host":"m4"}'
Обновлён
2026-08-29T11:52:14.662Z