1.7.5

Fixed (#403)

  • Ложный ERROR consolidator stall detected (прод: всегда unprocessed=2, 7 раз за 7 дней, после ERROR счётчик обнулялся и цикл повторялся) — сталл-детектор консолидатора считал unprocessed нескоупным CountMessagesAfterID (все сообщения после shared-курсора), тогда как выборка идёт GetMessagesAfterIDInScope (archived = FALSE + user_id-фильтр): служебные tool_iteration/memory_context, archived и per-user сообщения консолидатор никогда не выберет → unprocessed висело вечно → каждые 6 пустых прогонов (stallWarnThreshold) ложная тревога (#403). Новый метод CountMessagesAfterIDInScope в интерфейсе Store (SQLite + PG + DualStore-делегирование) с тем же фильтром скоупа; detectStall переписан: unprocessed считается по всем скоупам, которые реально обрабатывает консолидатор (shared + каждый ListAgentUserIDs), каждый — со своим курсором (consolidatorCursorKey); скоуп считается застрявшим только при хвосте ≥ consolidation_min_messages (единая точка чтения consolidatorMinMessages — хвост ниже порога легитимно ждёт накопления, это не stall); ошибки чтения скоупов = inconclusive (счётчик не сбрасывается — сброс на неполных данных маскировал бы реальный stall; доказанный в прочитанных скоупах stall поднимает уровень до ERROR сразу, один leveled-лог на тик вместо пары ERROR+WARN); ERROR несёт stalled_scopes (какой скоуп застрял — с появлением per-user-детекции это операционно значимо); невалидный consolidation_min_messages (< 1, мимо settings-API) — check skipped с ERROR, счётчик сохранён.

Review (#403)

  • ocr review — 6 раундов (4 → 4 → 5 → 2 → 1 → 9 находок), все разобраны; с 6-го раунда остановлен — замечания перешли в осцилляцию на защитных ветках (раунд 5 предлагал кламп minFresh<1 → false, раунд 6 требовал ровно противоположного; итоговое решение — валидация один раз до цикла → inconclusive, покрывает оба). Ключевые исправления по ходу: симметрия детектора работе консолидатора (per-user скоупы со своими курсорами — раньше детектор видел только shared и слеп к per-user stall); min-gate (consolidation_min_messages) — без него per-user скоупы с хвостом ниже порога детектировались бы как stall (легитимное ожидание накопления); inconclusive-семантика ошибок стора; пер-скоуп атрибуция в ERROR; единая точка чтения порога. Ложные отклонены: «ERROR срабатывает на partial-данных» — в коде inconclusive-return стоит до ERROR. Проверки: go test ./... -count=1, golangci-lint run ./internal/... (0), go vet, make build-cross (6 платформ) — зелёные. Тесты: TestCountMessagesAfterIDInScope (shared/user скоупы, archived исключён, контраст с нескоупным счётом), StallDetector_OutOfScopeMessagesNoStall (сценарий прода: за курсором только tool_iteration/memory_context/archived — сброс без ERROR), StallDetector_RealStallStillDetected (хвост ≥ порога — ERROR остаётся), StallDetector_InvalidMinMessages (плохой конфиг — счётчик сохранён, ERROR о конфиге). Исчезновение ERROR на проде — после деплоя.