[Fable 5][P2] ON CONFLICT по nullable doc_num: документы без номера дублируются при каждом прогоне #1206

Closed
opened 2026-06-13 04:49:43 +00:00 by bot-backend · 0 comments
Collaborator

Из армейского ревью Claude Fable 5 (2026-06-11)

Confirmed by 2/2 адверсариальных верификатора, severity P2 (data-integrity), confidence 0.75.

Файл

backend/app/services/scrapers/documents.py:59

Evidence (цитата кода)

ON CONFLICT (obj_id, doc_type, doc_num, file_url) DO UPDATE
    SET size_bytes    = EXCLUDED.size_bytes,

Why bug

DDL (data/sql/112_22i_domrf_documents.sql:18) — UNIQUE (obj_id, doc_type, doc_num, file_url) без NULLS NOT DISTINCT, doc_num nullable. В Postgres NULL != NULL в уникальном индексе → для документов с docNum=null (частый случай, поле «text или null» по docstring) арбитр ON CONFLICT никогда не матчится, и каждый ежедневный sweep вставляет новую строку-дубль вместо UPDATE. Таблица растёт ×N прогонов — тот же класс бага, что уже чинили для POI в 22j (коммент в domrf_kn.py:858).

Suggested fix

Миграция: UNIQUE NULLS NOT DISTINCT (PG16) либо doc_num NOT NULL DEFAULT '' + дедуп существующих строк; в коде маппить отсутствующий номер в '' вместо None.

Verify notes

  • [real/P2] Трасса: docNum=null — документированный вход (extract_documents:125-134 даёт doc_num=None) → INSERT c doc_num=NULL. UNIQUE из 112_22i:18 без NULLS NOT DISTINCT → дефолт PG: NULL≠NULL, арбитр ON CONFLICT не матчится → каждый повторный sweep (domrf_kn.py:1903) молча вставляет дубль; DO UPDATE никогда не срабатывает. Фиксов позже 112_ нет. Репо сам чинит этот класс через NULLS NOT DISTINCT (м.110 POI, м.125/128). Дубли ×N уходят в API (analytics_queries.py:1068).
  • [real/P2] Защиты нет. doc_num=NULL реально уходит в INSERT («text или null», doc_num or None, documents.py:126/135). DDL 112 — UNIQUE без NULLS NOT DISTINCT; патчей после нет; DELETE FROM domrf_kn_documents нигде нет. Вызов — повторяющийся scrape-sweep (domrf_kn.py:1903), reader без DISTINCT → дубли видны в API. Прецедент 110_22j: тот же класс дал ~267k дублей в infrastructure, чинился NULLS NOT DISTINCT; таблица 112 создана позже, урок не применён.

Provenance

  • Finders: scrapers
  • Confidence: 0.75
  • Originally batched at: main@d796027
## Из армейского ревью Claude Fable 5 (2026-06-11) Confirmed by 2/2 адверсариальных верификатора, severity **P2** (data-integrity), confidence 0.75. ## Файл [`backend/app/services/scrapers/documents.py:59`](backend/app/services/scrapers/documents.py:59) ## Evidence (цитата кода) ``` ON CONFLICT (obj_id, doc_type, doc_num, file_url) DO UPDATE SET size_bytes = EXCLUDED.size_bytes, ``` ## Why bug DDL (data/sql/112_22i_domrf_documents.sql:18) — UNIQUE (obj_id, doc_type, doc_num, file_url) без NULLS NOT DISTINCT, doc_num nullable. В Postgres NULL != NULL в уникальном индексе → для документов с docNum=null (частый случай, поле «text или null» по docstring) арбитр ON CONFLICT никогда не матчится, и каждый ежедневный sweep вставляет новую строку-дубль вместо UPDATE. Таблица растёт ×N прогонов — тот же класс бага, что уже чинили для POI в 22j (коммент в domrf_kn.py:858). ## Suggested fix Миграция: UNIQUE NULLS NOT DISTINCT (PG16) либо doc_num NOT NULL DEFAULT '' + дедуп существующих строк; в коде маппить отсутствующий номер в '' вместо None. ## Verify notes - [real/P2] Трасса: docNum=null — документированный вход (extract_documents:125-134 даёт doc_num=None) → INSERT c doc_num=NULL. UNIQUE из 112_22i:18 без NULLS NOT DISTINCT → дефолт PG: NULL≠NULL, арбитр ON CONFLICT не матчится → каждый повторный sweep (domrf_kn.py:1903) молча вставляет дубль; DO UPDATE никогда не срабатывает. Фиксов позже 112_ нет. Репо сам чинит этот класс через NULLS NOT DISTINCT (м.110 POI, м.125/128). Дубли ×N уходят в API (analytics_queries.py:1068). - [real/P2] Защиты нет. doc_num=NULL реально уходит в INSERT («text или null», `doc_num or None`, documents.py:126/135). DDL 112 — UNIQUE без NULLS NOT DISTINCT; патчей после нет; DELETE FROM domrf_kn_documents нигде нет. Вызов — повторяющийся scrape-sweep (domrf_kn.py:1903), reader без DISTINCT → дубли видны в API. Прецедент 110_22j: тот же класс дал ~267k дублей в infrastructure, чинился NULLS NOT DISTINCT; таблица 112 создана позже, урок не применён. ## Provenance - Finders: `scrapers` - Confidence: 0.75 - Originally batched at: main@d796027
bot-backend added the
Fable 5 ревью
label 2026-06-13 04:49:43 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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#1206
No description provided.