1.2.2

Fixed (#381)

  • Висячие записи llm_request_log больше не выглядят сбоем при открытии тела (#381, вариант (a) из issue): файлы тел живут 14 дней, индекс — 30, и записи возрастом 14–30 дней указывали на удалённые файлы (12k строк на проде 29.08.2026), UI получал 404 «log body unavailable». Backend: llmlog.PurgeOldLogs возвращает PurgeResult{Purged, RemovedKeys} — ключи удалённых датированных файлов в формате <agentID>/<YYYY-MM-DD> (совпадает с file_path в БД); новый метод стора ClearLLMRequestLogBodies(keys) (интерфейс + SQLite чанками ≤500 плейсхолдеров + PG = ANY($1::text[]) + DualStore) обнуляет file_path у записей с удалёнными файлами — метаданные (модель, токены, статус, длительность) остаются доступны для аналитики все 30 дней; cleanup_service вызывает его после purge файлов (лог retention purge table=llm_request_log_bodies). LoadEntryBody при пустом file_path возвращает сентинел ErrBodyRotated; хендлер GET /api/v1/admin/llm-log/{id} на ErrBodyRotated/os.IsNotExist (второе покрывает существующие legacy-записи без миграции данных) отвечает 410 Gone {"error":"log_body_rotated","message":"Тело запроса удалено ротацией…"} вместо 404; 404 остаётся только для «запись не найдена» (через writeJSONStatus). API-клиент: ошибки request/rawRequest теперь несут HTTP-статус (ApiError.status через apiError()), не только текст. Frontend (/settings/llm-log): при пустом file_path запись сразу показывает «Тело запроса удалено ротацией…» без запроса к API; 410 в catch переводит в тот же баннер (единый UX), прочие ошибки — серверный текст в text-error-text + кнопка «Повторить»; состояние (expandedError/expandedRotated) сбрасывается при collapse/смене страницы/refresh (хелпер resetExpanded), ранний выход инкрементирует expandSeq (in-flight запрос предыдущей строки не перезапишет состояние текущей). Тесты: TestPurgeOldLogs (удаление + точный формат RemovedKeys + сохранение свежих/недатированных), TestPurgeOldLogs_MissingBaseDir, TestLoadEntryBody_EmptyFilePathMeansRotated, TestSQLiteStore_ClearLLMRequestLogBodies (обнуление 2 из 3, no-op на пустом списке), handler-тесты 410 (пустой путь и legacy-несуществующий файл) и 404. ocr review — 11 находок: 7 закрыты (writeJSONStatus вместо inline-encode, expandSeq в ранней ветке, статус в ApiError → ветвление 410 без сниффинга текста, сброс expanded-состояния, text-error-text, лог не-IsNotExist ошибок os.Remove, JSDoc типов), прочие low отклонены (дубль русского текста баннера неизбежен при раннем выходе без запроса; regex-тест формата ключей избыточен — формат уже зафиксирован точным сравнением в TestPurgeOldLogs).