Skip to content

v1.26.0

Choose a tag to compare

@github-actions github-actions released this 27 Jun 14:35

Релиз качества и стабильности ПОИСКА: устойчивость полной пересборки индекса под открытым reader (Windows), guard против regex-DoS на входах grep, устранение ложных callers от многострочных литералов, FS-fallback find_files на zero-hit и приведение parse_metadata_xml к единому error-контракту. Read-only слой: схема и BUILDER_VERSION (14) НЕ менялись, пересборка боевых индексов НЕ требуется, layout индекса не тронут. Контракты хелперов сохранены; единственное наблюдаемое изменение поведения — find_files на zero-hit может уйти в FS-скан (не «instant»), см. «Совместимость».

Исправлено

  • Полная пересборка индекса (build) под открытым IndexReader на Windows больше не падает PermissionError. Корень — db_path.unlink() (и был бы rename) под открытым RO-reader. Теперь пересборка идёт на месте (in-place): схема очищается DROP-ом всех объектов кроме index_meta → пересоздаётся из _SCHEMA_SQL → populate, БЕЗ unlink/rename файла в штатном пути. Опасное окно прикрыто маркером незавершённости index_meta.build_in_progress=1 (ставится ДО деструктивных операций, снимается ТОЛЬКО после успешного ANALYZE/VACUUM — единый success-gate). Пока маркер стоит, индекс трактуется как MISSING для rlm_start/check_index_* и incomplete для CLI/MCP info и get_index_info() — новые сессии недострой не поднимают; update на недострое делает полную пересборку (восстановление, сохраняя намеренные build-опции has_*). unlink остаётся ТОЛЬКО для self-healing битого/несовместимого файла (живого reader на нём быть не может). Соединение билдера обёрнуто в try/finally — нет утечки handle/WAL при исключении в середине сборки (M1).
  • grep/safe_grep отклоняют catastrophic-backtracking паттерны вместо зависания. Новый leaf-модуль regex_safety.has_catastrophic_nesting ловит вложенные неограниченные кванторы ((a+)+b, (\w+\s*)+, (?:a+)+ и т.п.) ПЕРВЫМ действием на обоих входах — до cache-lookup/re.compile/прогрева индекса/thread-pool — и кидает ValueError с подсказкой (литерал/name_hint). C-движок _sre на таких паттернах виснет, а Windows-таймаут песочницы (PyThreadState_SetAsyncExc) его не прерывает. Bounded-кванторы ({n}/{n,m}/?: (\d{4})+) НЕ блокируются (линейны); экранированные скобки и классы символов учтены. Это отсечение ЯВНЫХ exponential-паттернов по структуре, НЕ полноценный wall-clock-kill (для него нужна процессная изоляция); docstring safe_grep больше не обещает ложно «Timeout-safe».
  • Многострочные строковые литералы (тексты запросов с |) больше не дают ложных callers в FS-fallback. Caller-скан без call-индекса чистил строки наивным однострочным regex (_strip_code_line), не видя многострочные литералы → текст «вызова» из тела запроса попадал в граф как ложный caller. Переведён на тот же multiline-aware _scan_module, что и индексер (DRY). Затрагивает find_callers/find_callers_context/find_call_hierarchy на FS-fallback-пути. Мёртвый _strip_code_line (+ осиротевший _STRING_LITERAL_RE) удалён из обоих модулей.
  • find_files на zero-hit из индекса уходит в FS-fallback. Раньше честный пустой ответ индекса ([]) глотался проверкой if indexed is not None, и файл, отсутствующий в stale-индексе, не находился, хотя есть на диске. Теперь if indexed: → пустой индекс-результат идёт в существующий FS-скан. Закрывает ТОЛЬКО zero-hit staleness; partial-hit (индекс отдал старые строки) — вне scope.
  • parse_metadata_xml приведён к общему error-контракту сиблингов. Был единственным парсером метаданных без try/except вокруг ET.fromstring. Теперь → dict | None: пустой/whitespace-контент и ET.ParseErrorNone (зеркало parse_form_xml), verbatim-текст ошибки — в debug-лог (callsite после перехода на None терял str(exc)). Не-parse структурные ошибки по-прежнему всплывают к callsite-обёрткам. Диагностические warning'и на всех затронутых callsite сохранены. Agent-facing parse_object_xml сохраняет контракт «исключение на битом XML» (тип меняется ET.ParseErrorValueError, наблюдаемо только в диагностик-строке).

Добавлено

  • Defensive reader: конкурентный старый IndexReader во время in-place пересборки деградирует к пустым результатам, а не падает. Декоратор @_transient_safe на read-методах IndexReader, достижимых через idx_reader. (полнота покрытия проверяется AST-гейтом по bsl_helpers/server): гасит ТОЛЬКО два транзиентных класса OperationalError пересборки (no such table в окне DROP→CREATE, database is locked при VACUUM), ре-raise'ит любой другой (no such column/опечатка SQL не маскируется в пустой результат и не проходит мимо тестов). Sentinel — свежий per-call (factory); ZERO_STATS зеркалит набор ключей реального get_statistics (тест-паритет).
  • Post-read marker recheck в get_index_info / MCP rlm_index info / CLI build·update·info. Пересборка, проставившая маркер ПОСЛЕ pre-read проверки, но во время get_statistics, в подокне «пустые таблицы + ещё не вычищенная stale-мета» не ловится stats_indicate_load_failure (там built_at/builder_version ещё на месте) → добавлена пост-проверка index_incomplete(db_path), зеркалящая комбинированный guard rlm_start. Недострой больше не репортится как ok/нормальная выдача.

Изменено

  • _BuildLock теперь thread-aware. Реентрантность только для того же потока (owner-thread-id + depth на общей записи; последовательные build+update и рекурсивный update → build при восстановлении); другой поток того же процесса получает RuntimeError, как и другой процесс — иначе два потока могли бы одновременно DROP/CREATE одну БД «на месте». OS-блокировка и lock-файл снимаются только при выходе из внешнего захвата (depth → 0); критическая секция check→OS-lock→register под threading.Lock (OS-lock всегда non-blocking).
  • Публичное поле index_status в rlm_start.index (стабильный машинный контракт, во ВСЕХ ветках): ok/stale_age/stale_content/missing/incomplete. Фиксированный маппинг IndexStatus.FRESH→"ok"; недострой и краевой load-failure reader'а → incomplete (выводится через index_incomplete/stats_indicate_load_failure, не из возможно-протухшего статуса).

Совместимость

  • Схема и BUILDER_VERSION (14) НЕ менялись — пересборка боевых индексов не требуется. Все правки read-only поверх индекса/хелперов/сервера; layout и get_index_db_path не тронуты. Новых таблиц/колонок нет.
  • find_files на zero-hit больше не «instant» — может уйти в FS-скан (на большой базе ощутимо). Подсказки стратегии и доки (HELPERS.md/INDEXING.md) обновлены: «find_files() instant при попадании в индекс, иначе FS-fallback».
  • parse_metadata_xml теперь dict | None (был dict, бросал на битом XML). Внутренние index-side callsite уже None-безопасны; agent-facing parse_object_xml сохраняет «исключение на битом XML» (тип ValueError).
  • safe_grep/grep кидают ValueError на вложенных неограниченных кванторах (включая «невинно выглядящие» перекрывающиеся (\w+\s*)+ — это реальный ReDoS). Литералы и обычные паттерны (\w+, [А-Яа-я]+, .*Док.*) не затронуты.
  • Честная граница in-place пересборки: билд делает множество коммитов — единая атомарная транзакция нереалистична; concurrent-запрос в середине пересборки может вернуть частичные/пустые данные (но НЕ падает — defensive-обёртки) и самовосстанавливается по завершении; внешние читатели не координируются (не запускать пересборку параллельно активным запросам). drop (CLI/MCP) по-прежнему делает unlink — «удалить индекс при живой сессии» на Windows останется PermissionError (известное ограничение, поиск не ломает).

Полный список изменений: CHANGELOG.md