(medium) cleanup_ghosts.py main: _all_docs limit=10000 без пагинации — при росте vault живые chunks ошибочно удаляются как orphan #1494

Closed
opened 2026-06-15 05:58:41 +00:00 by bot-backend · 1 comment
Collaborator

Severity: medium · Категория: data-integrity · Файл: scripts/cleanup_ghosts.py:28-44, 70-99

cleanup_ghosts.py:28-30 — единственный GET _all_docs с params={'limit':10000}, без пагинации по startkey/skip. alive_md (71-72) и chunk_id_to_rev (91) строятся ровно из этих rows; alive_chunks собирается только по alive_md (74-84). При >10000 документов в БД rows усекается → множество alive_chunks неполно → orphans = ghost_chunk_refs - alive_chunks (86) включит chunks, которые на самом деле ссылаются обрезанные живые .md, и они уйдут в _bulk_docs с _deleted:True (97-104) — порча/потеря содержимого живых заметок. Починка: пройти _all_docs постранично (limit + startkey_docid/skip) до исчерпания total_rows, либо запрашивать только реально нужные диапазоны id; критично гарантировать ПОЛНОТУ alive-набора перед вычислением orphans, иначе удаление небезопасно. Также добавить guard: если len(rows) >= limit — аварийно прервать удаление.

Почему баг: orphans считается как разница множеств между chunks ghost'ов и chunks 'живых'. Полнота 'живых' критична для безопасности удаления; при truncation на 10000 множество alive_chunks неполно, и удаляются нужные chunks. Также chunk_revs берётся только из тех же rows — но это лишь усугубляет (часть orphan просто не удалится), главное — ложные orphan'ы из-за неполного alive-сканирования.

Как чинить: Пагинировать _all_docs (skip/limit или startkey) до конца, либо запрашивать через _all_docs с проверкой total_rows и докачкой. Перед bulk delete проверять, что len(rows) < total_rows == False. Также добавить include_docs или отдельный safe-guard, чтобы не удалять chunks при неполном скане.


deep-audit «армия ботов 2» · verify+harden, confidence 0.85 · unit U111

**Severity:** medium · **Категория:** data-integrity · **Файл:** `scripts/cleanup_ghosts.py:28-44, 70-99` cleanup_ghosts.py:28-30 — единственный GET _all_docs с params={'limit':10000}, без пагинации по startkey/skip. alive_md (71-72) и chunk_id_to_rev (91) строятся ровно из этих rows; alive_chunks собирается только по alive_md (74-84). При >10000 документов в БД rows усекается → множество alive_chunks неполно → orphans = ghost_chunk_refs - alive_chunks (86) включит chunks, которые на самом деле ссылаются обрезанные живые .md, и они уйдут в _bulk_docs с _deleted:True (97-104) — порча/потеря содержимого живых заметок. Починка: пройти _all_docs постранично (limit + startkey_docid/skip) до исчерпания total_rows, либо запрашивать только реально нужные диапазоны id; критично гарантировать ПОЛНОТУ alive-набора перед вычислением orphans, иначе удаление небезопасно. Также добавить guard: если len(rows) >= limit — аварийно прервать удаление. **Почему баг:** orphans считается как разница множеств между chunks ghost'ов и chunks 'живых'. Полнота 'живых' критична для безопасности удаления; при truncation на 10000 множество alive_chunks неполно, и удаляются нужные chunks. Также chunk_revs берётся только из тех же rows — но это лишь усугубляет (часть orphan просто не удалится), главное — ложные orphan'ы из-за неполного alive-сканирования. **Как чинить:** Пагинировать _all_docs (skip/limit или startkey) до конца, либо запрашивать через _all_docs с проверкой total_rows и докачкой. Перед bulk delete проверять, что len(rows) < total_rows == False. Также добавить include_docs или отдельный safe-guard, чтобы не удалять chunks при неполном скане. --- <sub>deep-audit «армия ботов 2» · verify+harden, confidence 0.85 · unit U111</sub>
bot-backend added the
week ревью 1
label 2026-06-15 05:58:41 +00:00
bot-backend added the
scope/backend
label 2026-06-15 11:02:13 +00:00
Owner

🔍 week ревью 1 — верификация (ПОДТВЕРЖДЕНО)

Текущее состояние кода: scripts/cleanup_ghosts.py:28-30 (единственный _all_docs), :71-72 (alive_md), :74-84 (alive_chunks), :86 (orphans), :91 (chunk_id_to_rev).

Корень: :28-30 GET _all_docs с params={'limit':10000} и без пагинации по startkey/skip. Все производные множества строятся ровно из rows: alive_md (:71-72), alive_chunks (:74-84), chunk_id_to_rev (:91). При >10000 документов rows усекается → alive_chunks неполно → orphans = ghost_chunk_refs - alive_chunks (:86) включит chunks, которые на самом деле ссылаются обрезанные живые .md, и они уйдут в _bulk_docs с _deleted:True (:97-104) — потеря содержимого живых заметок. Сейчас vault < 10000 docs, поэтому пока не сработало, но guard'а нет — это latent data-loss bug.

Что править:

  • Пагинировать _all_docs (limit + startkey_docid/skip) до исчерпания total_rows, собирая полный rows.
  • Перед bulk delete — hard guard: если получено меньше total_rows (или len(rows) >= limit без докачки) → аварийно прервать удаление (raise/sys.exit(1)), не вычислять orphans на неполном скане.

Что посмотреть:

  • Код: cleanup_ghosts.py:28-44, 70-99; убедиться, что total_rows из ответа CouchDB используется как контроль полноты.
  • Vault: релевантных заметок не найдено.
**🔍 week ревью 1 — верификация (ПОДТВЕРЖДЕНО)** **Текущее состояние кода:** `scripts/cleanup_ghosts.py:28-30` (единственный `_all_docs`), `:71-72` (`alive_md`), `:74-84` (`alive_chunks`), `:86` (`orphans`), `:91` (`chunk_id_to_rev`). **Корень:** `:28-30` `GET _all_docs` с `params={'limit':10000}` и без пагинации по `startkey`/`skip`. Все производные множества строятся ровно из `rows`: `alive_md` (:71-72), `alive_chunks` (:74-84), `chunk_id_to_rev` (:91). При >10000 документов `rows` усекается → `alive_chunks` неполно → `orphans = ghost_chunk_refs - alive_chunks` (:86) включит chunks, которые на самом деле ссылаются обрезанные живые `.md`, и они уйдут в `_bulk_docs` с `_deleted:True` (:97-104) — потеря содержимого живых заметок. Сейчас vault < 10000 docs, поэтому пока не сработало, но guard'а нет — это latent data-loss bug. **Что править:** - Пагинировать `_all_docs` (`limit` + `startkey_docid`/`skip`) до исчерпания `total_rows`, собирая полный `rows`. - Перед bulk delete — hard guard: если получено меньше `total_rows` (или `len(rows) >= limit` без докачки) → аварийно прервать удаление (`raise`/`sys.exit(1)`), не вычислять `orphans` на неполном скане. **Что посмотреть:** - Код: `cleanup_ghosts.py:28-44, 70-99`; убедиться, что `total_rows` из ответа CouchDB используется как контроль полноты. - Vault: релевантных заметок не найдено.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#1494
No description provided.