[Fable 5][P2] Fallback flat_id через рандомизированный hash() ломает идемпотентность UPSERT квартир #1208

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/domrf_kn.py:401

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

if flat_id is None:
    # Fallback: hash of elemId (stable, fits BIGINT). Without an id we cannot UPSERT.
    elem = _g(row, "elemId")
    if elem:
        flat_id = abs(hash(elem)) % (2**63 - 1)

Why bug

hash(str) в CPython рандомизирован per-process (PYTHONHASHSEED нигде в репо не зафиксирован). Конфликт-ключ UPSERT — (id, snapshot_date) (uq_kn_flats_id_snap). При resume упавшего sweep (resume_from_run_id, scrape_kn.py:246 — новый процесс воркера) до 10 объектов после checkpoint перечитываются: квартиры без flatId получают другой hash-id → ON CONFLICT не срабатывает → дубли строк одной квартиры в одном snapshot. То же при повторном прогоне за ту же дату.

Suggested fix

Заменить hash() на детерминированный digest: int.from_bytes(hashlib.sha256(elem.encode()).digest()[:8], 'big') % (2**63 - 1). Опционально — дедуп существующих дублей по (ods_id, snapshot_date).

Verify notes

  • [real/P2] PYTHONHASHSEED нигде не задан → hash(str elemId) рандомизирован per-process. resume_kn_run стартует в новом воркере (worker_ready hook), наследует snapshot_date (стр.1638) и checkpoint (каждые 10 объектов); log_progress коммитит mid-decade при ошибках эндпоинтов → флэты после checkpoint уже в БД. При resume та же квартира получает другой hash-id → ON CONFLICT (id, snapshot_date) (миграция 50 сняла PK) не срабатывает → дубли в одном snapshot, инфляция агрегаций.
  • [real/P2] elemId — hex-строка, hash(str) рандомизирован; PYTHONHASHSEED нигде не задан. Единственная защита (атомарный commit данных+checkpoint) сломана: upsert_documents коммитит per-object в цикле → квартиры после checkpoint в БД без продвижения progress_obj_index. worker_ready на каждом рестарте resume-ит 'running' run в новом процессе (новый seed) → ≤10 объектов перечитываются, id-less квартиры получают другой id → ON CONFLICT мимо → дубли в snapshot. Дедупа нет.

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/domrf_kn.py:401`](backend/app/services/scrapers/domrf_kn.py:401) ## Evidence (цитата кода) ``` if flat_id is None: # Fallback: hash of elemId (stable, fits BIGINT). Without an id we cannot UPSERT. elem = _g(row, "elemId") if elem: flat_id = abs(hash(elem)) % (2**63 - 1) ``` ## Why bug hash(str) в CPython рандомизирован per-process (PYTHONHASHSEED нигде в репо не зафиксирован). Конфликт-ключ UPSERT — (id, snapshot_date) (uq_kn_flats_id_snap). При resume упавшего sweep (resume_from_run_id, scrape_kn.py:246 — новый процесс воркера) до 10 объектов после checkpoint перечитываются: квартиры без flatId получают другой hash-id → ON CONFLICT не срабатывает → дубли строк одной квартиры в одном snapshot. То же при повторном прогоне за ту же дату. ## Suggested fix Заменить hash() на детерминированный digest: int.from_bytes(hashlib.sha256(elem.encode()).digest()[:8], 'big') % (2**63 - 1). Опционально — дедуп существующих дублей по (ods_id, snapshot_date). ## Verify notes - [real/P2] PYTHONHASHSEED нигде не задан → hash(str elemId) рандомизирован per-process. resume_kn_run стартует в новом воркере (worker_ready hook), наследует snapshot_date (стр.1638) и checkpoint (каждые 10 объектов); log_progress коммитит mid-decade при ошибках эндпоинтов → флэты после checkpoint уже в БД. При resume та же квартира получает другой hash-id → ON CONFLICT (id, snapshot_date) (миграция 50 сняла PK) не срабатывает → дубли в одном snapshot, инфляция агрегаций. - [real/P2] elemId — hex-строка, hash(str) рандомизирован; PYTHONHASHSEED нигде не задан. Единственная защита (атомарный commit данных+checkpoint) сломана: upsert_documents коммитит per-object в цикле → квартиры после checkpoint в БД без продвижения progress_obj_index. worker_ready на каждом рестарте resume-ит 'running' run в новом процессе (новый seed) → ≤10 объектов перечитываются, id-less квартиры получают другой id → ON CONFLICT мимо → дубли в snapshot. Дедупа нет. ## 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#1208
No description provided.