ADR-0078: Провал всех тиров — не матч
Status: accepted Date: 2026-08-11 Deciders: belkanov, agent-claude
Контекст
ADR-0076 закрыл вырождение правила точности на пустом множестве проверок: условие «каждая активная ось сравнима» выполнялось тривиально, когда сравнивать было нечего, и кандидат объявлялся точным аналогом, не пройдя ни одной проверки.
Тот же класс ошибки обнаружился уровнем ниже — в матчере. Когда Tier1
(exact MPN), Tier2 (fingerprint) и характеристический путь не дали ни одного
кандидата, currentCanonicalDecision записывала решение с
match_confidence = 'strong' и tier = 'fingerprint', указывающее на
пер-офферный каноник самого оффера. Пустое множество кандидатов записывалось
как уверенный матч.
Последствие — терминальность. strong исключён из всех трёх источников
рематча: очередь решений пропускает exact/strong, ридер расхождений требует
несовпадения решения и оффера (у fallback они совпадают), ридер несматченных
требует отсутствия решения. Оффер выпадал из матчинга навсегда.
Провижинер каноника пер-офферный (ключ supplier|<supplier>|<sku>),
кросс-офферная склейка целиком передана матчеру. Значит fallback штамповал
пожизненную бронь на заведомо одиночный каноник именно на том пути, который
обязан был склеивать.
Замер прода 2026-08-11: 1 255 586 решений fingerprint+strong против
260 157 exact_mpn и 24 probable. В выборке 20 000 последних
fingerprint+strong одиночек 19 975 против 25 настоящих
кросс-поставщиковых матчей. Из 1 283 724 канонов с офферами 1 091 700 (85%)
держат офферы ровно одного поставщика.
Решение
- Решение, записанное при провале всех тиров, имеет
match_confidence = 'weak'иtier = 'none'. Оно остаётся пересматриваемым. - Решение, записанное при блокировке проекции конфликтом идентичности, имеет
match_confidence = 'weak'и тир предыдущего решения: кандидат был, переставить не дали. Различие тиров сохраняет причину в данных. - Терминальными остаются только
exactиstrong, и получить их можно лишь предъявив кандидата: Tier1 при точном совпадении(manufacturer_id, нормализованный артикул)либо Tier2 при согласованном непустом бренде. - Очередь рематча делится на две ветви с разными окнами: конфликты и обычные
не-терминальные решения обходятся штатным
MATCHER_RECHECK_COOLDOWN, ветвьtier='none'— своимMATCHER_WEAK_RECHECK_COOLDOWNи порядком от давно не проверявшихся. - Источник рематча ограничен долей батча
MATCHER_RECHECK_BATCH_SHARE; невыбранный остальными источниками остаток возвращается ему обратным добором.
Последствия
Плюсы
- Оффер, которому не нашлось двойника, возвращается на пересмотр, когда меняются входные данные — когда дозаполняется бренд, появляются характеристики или у поставщика появляется двойник.
- Состояние «кандидата нет» становится отличимым в SQL по
tier='none'и наблюдаемым метрикойtracium_match_no_candidate_backlog. - Оффер-огрызок без характеристик, приклеенный к канону с полным набором осей, получает их и становится пригодным для подбора аналогов без ожидания поставщика.
Минусы
weakне проходитshouldProjectCanonical, поэтому ранний выход по совпадениюevidence_hashдля таких решений не срабатывает и тиры прогоняются целиком: каждый прогон стоит два запроса поsupplier_offers. Отсюда квота, расширенное окно и обязательный порядок работ — распечатывать прежние решения только когда пару есть кому разрешить. Совпадение(производитель, артикул)подтверждением тождества не является: одинаковый артикул под разными брендами регулярно принадлежит разным изделиям, поэтому сведение бренд-псевдонимов как способ склейки отвергнуто (решение владельца продукта, 2026-08-11). Разрешает такие пары арбитр Tier3 (ADR-0079), поэтому гейт ремедиации проверяет его живость (--min-llm-decisions-24h), а не наполнение осейrole='identity': миграция 0356 понизила алгоритмические оси доsignificant, и арбитражу они не нужны.- Очередь рематча становится многочисленной, поэтому порядок обхода требует отдельного частичного индекса.
Нейтральные последствия
- Существующие 1,25 млн решений остаются терминальными до разовой ремедиации
(
cmd/matcher-fallback-unlock), которая переводит в новое состояние только одиночки и не трогает офферы под ручным замком (ADR-0073). - Привязка оффера к канонику при распечатке не меняется, поэтому выдача в момент ремедиации остаётся прежней.
cmd/matcher-fallback-unlockне меняетmatch_decisions.updated_at: дляtier = 'none'это момент последнего просмотра матчером, от которого считаетсяMATCHER_WEAK_RECHECK_COOLDOWN. Распечатка не является просмотром; её timestamp отложил бы рематч на всё окно.
Рассмотренные альтернативы
Новое значение no_candidate вместо weak
Семантически точнее: weak буквально значит «слабый матч», а не «матча нет».
Отвергнуто из-за цены — миграция CHECK и ревизия каждого места, где
перечисляются confidence-значения, при том же поведении. weak в этом коде уже
занимает дно шкалы: classifyLLMConfidence отдаёт его при LLM-уверенности ниже
0.70.
Не писать решение вовсе
Оффер уехал бы в ридер несматченных (md.offer_id IS NULL), где нет cooldown,
то есть прогон каждый тик вместо раза в окно. Дополнительно теряются
tried_tiers и наблюдаемость.
Ремедиация всех 1,25 млн решений сразу
Отвергнуто: переигрывает и верные склейки (0,1% выборки), часть из них может
уйти в probable и сменить каноник через Tier3. Скоуп ограничен одиночками —
у них склейки не было по определению.
Ссылки
docs/superpowers/specs/2026-08-11-matcher-terminal-strong-fallback-design.mddocs/superpowers/plans/2026-08-11-matcher-terminal-strong-fallback.md- ADR-0070 — ключ
идентичности
(manufacturer_id, артикул) - ADR-0073 — ручная модерация идентичности каноников
- ADR-0076 — тот же класс ошибки на уровне подбора аналогов