0.167.31

Fixed

  • Retry с backoff для embedding-вызовов в reindex + актуализация комментария об индексе (#215 — P2). (1) #213 [A-025]: единичный 429/503 от embedding-API на батче помечал весь reindex-job как failed без retry и backoff — для большой БД и лимитированного провайдера это означало ручной перезапуск с нуля. Добавлен Reindexer.embedWithRetry (reindexFacts/reindexChunks): до 3 попыток с экспоненциальным backoff (1с, 2с; package-level состояние с RWMutex, тестовый мутатор живёт только в _test.go), сон прерывается отменой ctx (graceful shutdown/cancel), per-attempt guard 120с (выше клиентских 60с провайдеров — не затрагивает текущих, но ограничивает будущие реализации без собственного HTTP-таймаута), pre-flight ctx-проверка и единая точка выхода ctx.Err() при отмене. Классификация: не-2xx ответы OpenAI/Ollama-провайдеров теперь оборачиваются в новый *embeddings.StatusError (HTTP-статус + обрезанное до 2КБ тело с strings.Clone); isTransientEmbedError делегирует общему providers.IsTransientError (408/429/5xx; JSON-структурные error.type/code из тела — так 429 insufficient_quota корректно нетранзиентен; не-JSON тела не сканируются — статус-код единственный сигнал), из сетевых — таймауты (net.Error.Timeout) и connection refused/reset (консистентно с providers.isTransientGoError; кейс — Ollama на localhost при гонке старта), DNS-failure — нетранзиентен. Финальная ошибка несёт retry-exhaustion-метаданные (after 3 attempts), terminal Error-лог с job_id/agent_id отличим от per-attempt warn’ов (поле will_retry). (2) #215 [A-027]: пакетный комментарий reindex.go утверждал UNIQUE INDEX reindex_jobs(status) WHERE …, тогда как фактическая миграция 077 — ON reindex_jobs((1)) WHERE status IN ('pending','running') (constant-expression partial unique = максимум один active job); комментарий приведён к факту. Тесты (internal/embeddings): таблица isTransientEmbedError (429/5xx/408 транзиентны; 400/401/404, DNS, plain errors — нет; JSON-vs-plain insufficient_quota; net timeout; wrapped-цепочки); интеграционные — 2×429 → job completed за 3 вызова, вечный 503 → failed после 3 попыток с exhaust-текстом, 400 → failed за 1 вызов (нет ретраев), пустые delays → нет паники; httptest-обёртки StatusError для OpenAI/Ollama; обрезка тела; инвариант embedMaxAttempts == len(delays)+1. Прогон -race чистый.