v1.26.0
Релиз качества и стабильности ПОИСКА: устойчивость полной пересборки индекса под открытым 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/MCPinfoи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 (для него нужна процессная изоляция); docstringsafe_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.ParseError→None(зеркалоparse_form_xml), verbatim-текст ошибки — в debug-лог (callsite после перехода наNoneтерялstr(exc)). Не-parse структурные ошибки по-прежнему всплывают к callsite-обёрткам. Диагностические warning'и на всех затронутых callsite сохранены. Agent-facingparse_object_xmlсохраняет контракт «исключение на битом XML» (тип меняетсяET.ParseError→ValueError, наблюдаемо только в диагностик-строке).
Добавлено
- 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/ MCPrlm_index info/ CLIbuild·update·info. Пересборка, проставившая маркер ПОСЛЕ pre-read проверки, но во времяget_statistics, в подокне «пустые таблицы + ещё не вычищенная stale-мета» не ловитсяstats_indicate_load_failure(тамbuilt_at/builder_versionещё на месте) → добавлена пост-проверкаindex_incomplete(db_path), зеркалящая комбинированный guardrlm_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-facingparse_object_xmlсохраняет «исключение на битом XML» (типValueError).safe_grep/grepкидаютValueErrorна вложенных неограниченных кванторах (включая «невинно выглядящие» перекрывающиеся(\w+\s*)+— это реальный ReDoS). Литералы и обычные паттерны (\w+,[А-Яа-я]+,.*Док.*) не затронуты.- Честная граница in-place пересборки: билд делает множество коммитов — единая атомарная транзакция нереалистична; concurrent-запрос в середине пересборки может вернуть частичные/пустые данные (но НЕ падает — defensive-обёртки) и самовосстанавливается по завершении; внешние читатели не координируются (не запускать пересборку параллельно активным запросам).
drop(CLI/MCP) по-прежнему делаетunlink— «удалить индекс при живой сессии» на Windows останетсяPermissionError(известное ограничение, поиск не ломает).
Полный список изменений: CHANGELOG.md