Runbook: cutover идентичности каноника (per-offer + матчер) + re-key миграция

Область: устранение коллизии «10127» на проде — переход с legacy article-hash каноников ("mpn|article", производитель-слепой) на per-offer провижининг + матчер-владеет-идентичностью + valuenorm-дедуп производителя. См. ADR-0070/0071/0072, спека docs/superpowers/specs/2026-07-09-canonical-identity-manufacturer-scoped-design.md.

Природа операции: необратимая, широкий блэк-радиус (~98k legacy-каноников / ~220k офферов). Только off-peak, с dry-run перед apply. Отката к article-hash нет (это и был баг) — при проблемах сходимся пере-прогоном матчера/проекторов.

0. Предусловия

  • Задеплоены (одной связкой): матчер part-1 (3e9b6b13), провижинер per-offer (55e24e5a), brand-normalizer (8ff0c06a), re-key cmd (a65a9ce0). Провижинер per-offer обязателен ДО миграции — иначе свежий ингест продолжит плодить article-hash каноники.
  • Off-peak окно; DB-VPS не насыщен (свериться с health-матрицей).
  • Свежий бэкап/снапшот БД.

1. Deploy связки

Push main → CI собирает и пушит образы по SHA в LAN-registry → App-VPS/DB-VPS pull по SHA → recreate воркеров: supplier-sync, matcher-worker, canonical-assignment-worker, catalog-projector, valuenorm-worker (pull-only deploy, см. memory tracium_pull_only_deploy).

2. (Опц.) Seed valuenorm-словаря производителей

Для известных вариантов написания завести записи characteristic_value_dictionary scope manufacturer (source seed/moderator): напр. «Konkord Ltd»/«ООО Конкорд» → «Konkord». Неизвестные варианты valuenorm сам ставит в unmapped-очередь → LLM/модератор дозаполняют. Дедуп производителя работает и без сида (по мере наполнения словаря).

3. Dry-run миграции

canonical-rekey            # dry-run по умолчанию

Проверить в логе: scanned, need_repoint, distinct_legacy_canonicals (ожидаемо ~98k), сэмплы old_canonical → new_canonical. Санити: величины совпадают с замером (SELECT count(*) … HAVING count(distinct manufacturer_id) > 1).

4. Apply миграции (off-peak, батчами)

canonical-rekey --apply --batch 500 --statement-timeout 2m

Репойнтит офферы на per-offer каноники (через рантайм EnsureForOffer — тот же canonical_id и viewable_id), чистит match_decisions офферов, ставит assignment-dirty и projector-reconcile; осушённые legacy-каноники → deprecated. Резюм-safe (идемпотентно по per-offer id). Мониторить нагрузку; при насыщении — пауза.

5. Пере-кластеризация + пере-проекция

  • matcher-worker: очищенные офферы (md.offer_id IS NULL) заново матчатся → корректная кластеризация по (производитель, артикул) / fingerprint / характеристикам (без-артикульные) / LLM. Дать беклогу стечь.
  • canonical-assignment-worker + catalog-projector: пересобирают характеристики/представителя из dirty-очередей.

6. Верификация

  • Ноль каноников с >1 производителем:
    SELECT count(*) FROM (
      SELECT canonical_id FROM supplier_offers
      WHERE canonical_id IS NOT NULL AND manufacturer_id IS NOT NULL
      GROUP BY canonical_id
      HAVING count(DISTINCT manufacturer_id) > 1) t;   -- ожидаем 0
  • Боевые SKU («10127»): 2491970/1512547/1926123/265448/2013423/7810266 — на разных канониках; пара Конкорд-кабель (2491970+2013423) слита, остальные раздельно.
  • Админ-карточка TR-VB18679 больше не мешает кабель+защёлку+прожектор.

7. Запреты / после

  • НЕ запускать canonical-dedupe-repair — он мерджит по артикулу (старая идентичность), отменит фикс. Считать obsolete.
  • Мониторить рост Tier3-LLM (пустобрендовые/без-артикульные уходят на LLM до дозаполнения) и unmapped-очередь valuenorm (производители на дозаполнение).