---
name: avtero_oem_provenance
description: "avtero корень ошибок = primary_oem без метки источника; Фаза 1+2 гейт СДЕЛАН; TecDoc MariaDB мигрирована 12.2.2→11.4; Volvo llm-tier recon завершён (valve_guide баг+AWD); provenance_stats.py фильтрует synthetic-VIN шум (только Volvo имеет реальную выборку); VW/VAG front/rear колодки и Kia ручник барабан/диск — honest ambiguous-гейт (обобщён, не только axle); Kia муфта кондиционера разбирается на 4 компонента вместо 'не найдено'; год авто — catalog production-date (emex/exist) теперь побеждает ISO 3779 модельный год вместо наоборот; 2026-07-18 полный аудит каталога (~90 категорий, 6 агентов) — get_oem_by_vin получил ту же tie-детекцию что resolve_by_name (системный фикс, разом прикрыл alternator/engine_mount/lambda_sensor/camshaft/clutch_kit/flywheel/tensioner), _PAIRED_SIDE_CATS расширен (axle_shaft/drive_shaft/brake_caliper/brake_hose/tie_rod), strut/strut_mount фикс, deferred-список полностью закрыт (control_arm/arm_bushing/clutch_pressure_plate/release_bearing/wiper_blade); 2026-07-19 родительный падеж после 'Замена' ломал _LEAF_TRIGGERS (ac_clutch/ac_compressor/heater_radiator/oil_cooler); 2026-07-22 докручено до системного фикса — русский стеммер в матчере (не 291 правка данных), закрыл 90% фраз всего каталога, +quirk-детекция в strict-пути"
metadata:
  node_type: memory
  type: project
  originSessionId: 4eea915e-3b47-4cd5-8a0e-bfa29b78a87c
  modified: 2026-07-22T08:21:34.153Z
---

**2026-07-10 — глобальный фикс корректности (юзер: фидбэк-коррекции лечат симптомы, реши корень).** Разведка (Explore, карта всех присвоений `primary_oem`) вскрыла: **корень = `primary_oem` строка без метки источника** — genuine-EPC / TecDoc-производные / live_harvest / **LLM-догадки** сливаются в одно поле и на выходе неразличимы; гасилки судят по форме, не по источнику, и пропускают отеч./кит. марки (`_validate_primary_oems_via_emex` line 4402). Патчить фидбэком миллионы деталь×машина — структурно проигрышно.

**Фаза 1 СДЕЛАНА (выдачу НЕ меняет, только мерит; бэкапы `*.bak-provenance-1783699945`):**
- `schemas.Part.oem_source` (дефолт ""; НЕ от LLM — сбрасывается в начале финализации `run()`, проставляется резолверами).
- `app/services/provenance.py` — `tier_of(source)`: **genuine** (autodoc/emex/epc_data/luzar/local_tree/domestic/get_oem_by_vin/catalog) · **curated** (static) · **correction** · **community** (live_harvest) · **derived** (tecdoc/cross/twin/alt_promoted) · **llm** (llm_output/llm_guess). + `log_report` → JSONL `/Volumes/Ext/avtero/provenance/provenance.jsonl` (дефолт-путь, env `PROVENANCE_LOG_DIR` опц.).
- Протянут `hit["source"]` в ~13 точках `orchestrator.py`: `_enforce_named_parts`, оба Part-конструктора каталог-фолбэка, `_apply_part_corrections`(→`correction`), backfill/twin/domestic/local-tree, 6 gap-пассов (PASS0 autodoc, PASS1 emex_catalog, PASS1.5 luzar, PASS1.6 autodoc_axle, PASS2 `epc_closedset`, PASS3 `llm_guess`). На границе (перед `PartListResponse.model_validate`) непритязанные номера → `llm_output`. **ГРАБЛЯ тиров:** порядок правил в `_TIER_RULES` — «emex» раньше «llm», поэтому метки-guess называть БЕЗ «emex» (PASS3 = `llm_guess`, не `llm_emex_verified`), иначе ложно genuine.
- `scripts/provenance_stats.py` — «цифры»: `genuine vs llm vs derived`, топ-источники, `--days N`, `--by-make`.
- Проверено: Kia KNAFW411BD5677381 → `oem_source=autodoc_catalog_byname` (genuine), JSONL пишется; suite **1056 passed** (2 admin_guard-фейла пред-существующие). Продакшн-API пишет лог сам.

**2026-07-12 — первые реальные цифры (69 номеров).** Органический трафик за 2 дня почти не долетел до лога (5-мин кэш + предеплойные запросы) — вместо ожидания недель прогнал 30 уникальных исторических (VIN, работа) из `report_snapshots` через `orchestrator.run()` (юзер подтвердил). Итог `scripts/provenance_stats.py --by-make`: **genuine-ish 46.4%** (genuine 36.2%+correction 10.1%), **llm 40.6%**, derived 8.7%, community 4.3%. По маркам: Kia 12% llm (n=17, надёжно), Volvo 34% llm (n=38, надёжно), VW/Hyundai 100% llm (n=5-6, не показательно). Оговорка: `tecdoc_local`/`tecdoc2018` MariaDB (:3307) словила `read timeout` под конкурентной нагрузкой прогона — могло занизить `derived`/раздуть `llm` в этом срезе.

**2026-07-13 — Фаза 2 гейт СДЕЛАН.** Юзер выбрал полный гейт сейчас + попросил параллельно допилить бесплатные источники для сокращения llm-доли. Вставка в `orchestrator.py` сразу после финального `oem_source`-catch-all (перед `_coerce_schema_slips`): `llm`-тир → `primary_oem=""` + `needs_check=True` + оговорка «Точный номер не подтверждён по каталогу — уточните по VIN у дилера или в оригинальном EPC» (alternatives/tech_fluids не трогаются — если есть рабочий кросс-номер, юзер его видит). `derived`-тир → номер остаётся, `needs_check=True` + мягкая метка «Номер получен по кросс-таблице аналогов… рекомендуем сверить». Переиспользован СУЩЕСТВУЮЩИЙ `needs_check`/`check_note` бэдж (уже рендерится в боте+сайте) — нового UI не потребовалось. Повторный вызов `_note_unresolved_parts` после гейта ловит детали, у которых стёрли единственный ответ (идемпотентна, дедуп по тексту хинта). **4 теста упали и были обновлены** (`test_oem_brand_mismatch_at_boundary.py` ×3, `test_orchestrator.py::test_orchestrator_validation_retry_succeeds`) — они мокали ВСЕ резолверы на провал, так что раньше «сырой LLM-номер без источника» проходил как валидный; это ровно то, что новый гейт режет по дизайну (WMI-boundary логика сама не сломана — проверено отдельно). Suite 1056 passed. Живой прогон подтвердил: WVW-VIN + редкий запрос → `oem_source=llm_output`, номер пуст, оговорка на месте; genuine/correction (autodoc_catalog_byname, коррекция) проходят насквозь без изменений. Бэкап `orchestrator.py.bak-provgate-1783919810`.

**2026-07-13 — TecDoc MariaDB мигрирована 12.2.2→11.4 LTS (крупный побочный инцидент).** Расследование Volvo-гэпа (задача выше) вскрыло: TecDoc MariaDB падала 20 раз в истории. Ложный диагноз — баг движка 12.2.2; реальный — **физический отвал внешнего USB-SSD `/Volumes/Ext`** под нагрузкой (юзер переподключил кабель/порт). Юзер всё равно попросил мигрировать на 11.4 LTS (снижает урон: graceful-ошибка вместо SIGSEGV при следующем отвале). Миграция (dump+reload 39ГБ/~31М строк) прошла через: `net_write_timeout=60s` рвал 200М-строчную `article_links` (фикс: 600-1800s), транзиентные обрывы SSH-сессии (процесс на mini переживал), и главную грабли — **`brew install` сбросил TCC/Full Disk Access для ОБОИХ бинарей** (`kTCCServiceSystemPolicyRemovableVolumes`, подтверждено `log show`) — headless launchd-процесс вис намертво в ожидании GUI-промпта; юзер выдал FDA через AnyDesk на оба пути → сразу заработало. Cutover: `data-114`, `my-114.cnf` (порт 3307 не менялся), старый `data`→`data.old-122-<ts>` (не удалён). Suite 1056 passed, все таблицы 1:1 с источником. См. [[mini_launchd_ext_tcc_fda]] (полные детали TCC-грабли), Obsidian Decisions#D-tecdoc-mariadb-114-lts. **Не сделано:** ops_dashboard-тайл для tecdoc-mariadb (никогда не заводился — краши шли незамеченными).

**Попутная находка — «щётки генератора» (Kia Cerato) вернули честное «не найдено».** Юзер, увидев это в реальном отчёте, испугался регресса охвата — оказалось НЕ регресс: категории «щётки генератора» вообще нет в таблице синонимов `emex_catalog.py` (только `wiper_blade`→щётка стеклоочистителя), запрос никогда не доходил до каталогов. Задача [[avtero_hard_corrections_learning]] пересечение — фикс маппинга + аудит таксономии на похожие дыры взят в работу отдельно.

**Дальше (по запросу юзера):** расширение бесплатных источников (autodoc/emex/luzar/mahle/mann/china-parts/tecdoc-local/live_harvest) для роста genuine/derived и снижения llm по маркам — приоритет по цифрам: **Volvo 34% llm (n=38, надёжная выборка) — первая цель**; VW/Hyundai 100% llm пока не показательны (n=5-6). Laximo (платный genuine-OEM РФ) ортогонален, на будущее. Полностью: Obsidian [[Decisions#D-oem-provenance-typed]] + Changelog. Связано [[avtero_hard_corrections_learning]], [[avtero_named_canonical_gate]].

**2026-07-13 — Kia: «шайба» освобождена из stopstems (тот же класс бага, что «клапа»), докручено тем же днём.** Живая диагностика реального Kia Cerato (`KNAFW411BD5677381`) вскрыла: «Шайба сливной пробки» стабильно llm-tier — «шайба» была в `_RESOLVE_STOPSTEMS`, единственный дискриминатор между пробкой (`2151223000`) и её шайбой/уплотнением (`2151323001`, каталог зовёт «ПРОКЛАДОЧНОЕ КОЛЬЦО») терялся. Убрано из стоп-слов (осталась в `_GENERIC_STEMS`, не единственное основание матча). Второй слой (тот же день): синоним `"шайба":{"прокл"}` + фикс head-noun-гейта (родовые головы вроде «шайба» больше не сужают кандидатов по случайному литеральному совпадению — раньше не связанная «ШАЙБА» из клапанной группы выигрывала гейт). Живьём подтверждено на обоих источниках. Suite 1065 passed.

**2026-07-17 — Год авто: каталожная дата производства (2012) побеждает ISO 3779 модельный год (2013).** Владелец Kia Cerato TD (`KNAFW411BD5677381`) указал, что бот показывает 2013 вместо реальных 2012 по документам. Root cause — 3 места: (1) `_apply_vin_overrides` безусловно писал `decoded["year"]=det_year` (10-й символ VIN) поверх любого уже известного года — единственное поле в функции без гейта «не перезаписывать» (make/model рядом это соблюдают); (2) тот же паттерн на границе после LLM-цикла; (3) корень, почему это никогда не проявлялось иначе: первичный источник `exist.resolve_vin` вообще не возвращает year (проверено живьём), а `emex.resolve_vin` (который ПРАВИЛЬНО парсит `date→2012`) вызывался только `if not decoded` — раз exist уже «успешно» ответил без года, emex не консультировался вовсе. Фикс: emex теперь дополнительно спрашивается за годом когда exist его не дал (без замены остальных полей exist); `_apply_vin_overrides` пишет det_year только как fallback на пустое поле; граница после LLM восстанавливает catalog-year при расхождении с LLM (вместо ISO-таблицы), но ТОЛЬКО когда исходный decode пришёл из доверенного источника (`exist_catalog`/`emex_catalog`/`vpic`) — LLM-only/VIN-структура-only путь не тронут, там ISO 3779 таблица как и была единственным надёжным источником. Живой полный `orchestrator.run()` подтвердил year=2012 в итоговом отчёте. 3 новых теста, suite **1071 passed** (было 1068).

**2026-07-17 — Kia ручник (барабан/диск) честная неоднозначность + разбор муфты кондиционера на компоненты — реальная жалоба владельца, деньги потеряны на неверной детали.** Владелец Kia Cerato TD (`KNAFW411BD5677381`) получил НЕ ТЕ колодки ручника («просто барабанные») и не смог получить разбор «муфты кондиционера в сборе» (её не существует как SKU). Живой дамп каталожной группы подтвердил: платформа Cerato TD продаётся с ДВУМЯ вариантами задних тормозов (барабан, где колодки = колодки ручника; диск + отдельная маленькая колодка ручника) — VIN не различает, каталог отдаёт оба кандидата с ОДИНАКОВЫМ скором на запрос без слов перед/зад/лев/прав, а ambiguous-флаг (см. VW-запись выше) фиксировал tie только ПРИ явных axle-словах — для этого случая условие не срабатывало вообще. Фикс: убрано условие `(front_q or rear_q or left_q or right_q) and` перед ambiguous-флагом в `select_by_name_axle_aware` — теперь любой неразрешённый tie между разными oem флагуется, независимо от наличия axle-слов; текст оговорки обобщён с «перед/зад» на «какой из них подходит вашему авто (например перед/зад или другая конструкция узла)». Отдельно: муфта кондиционера на Kia не продаётся в сборе, но её 3 части (шкив `976431X020` — назван ТОЛЬКО по-английски `PULLEY ASSY`, катушка `976414D100`, ступица `9764434550`) уже есть в том же узле каталога — категория `ac_clutch` была известна canonical-гейту, но не подключена к резолверу. Новая `orchestrator.py::_expand_ac_clutch` (аналог уже существующего `_expand_paired_sides`) разворачивает один запрос в 3 именованных, резолвящихся существующим `resolve_by_name`; шкиву потребовался EN/RU синоним-мост (`_SYNONYM_STEMS["компр"]={"compr"}`, `_HEAD_SYN["шкив"]=("pulle",)` — тот же класс фикса, что «вкладыш→bearing»). Живьём проверено на реальном VIN пользователя, 3 новых теста, suite **1068 passed** (было 1065). Докрутка тем же днём: юзер уточнил доменные детали (шкив с подшипником/прижимная пластина/электромагнит) и попросил свериться + добавить болты — переименовал «Ступицу» в «Ступица (прижимная пластина)» (без смены головного слова, иначе рвётся head-gate), болты сознательно НЕ добавил (4+ безымянных «БОЛТ» в узле, TecDoc их не знает — гадать не стал), проверил афтермаркет-комплект как альтернативу (TecDoc/exist.search_cross/веб-поиск — везде пусто, честно доложил юзеру), добавил в check_note фразу про некаталогизированный крепёж. Suite **1071 passed**. Ещё одна докрутка тем же днём: юзер прислал фото с крепежом, включая стопорное кольцо — проверил, «Кольцо стопорное» (2 кандидата, `976451J000`/`976481J000`) в отличие от «БОЛТ» имеет содержательное имя и `resolve_by_name` его находит + сам честно ставит `ambiguous=True` (сработал сегодняшний обобщённый tie-фикс) — безопасно добавить. Добавлен 4-й компонент муфты. Критерий (задокументирован в Obsidian Decisions.md → `D-no-guess-unlabeled-fastener`): не «крепёж не трогать», а «можно ли каталог различить текстом» — кольцо можно (даёт явный ambiguous-флаг), болты нельзя (имя пустое, дискриминировать не на чем). Suite остался 1071 (обновлён существующий тест). См. Changelog для полных деталей.

**2026-07-13 — VW/VAG: honest ambiguous-гейт для front/rear колодок (реальный баг, не гэп).** Живая диагностика на demo-VIN Passat (легитимно для техпроверки, не для статистики — см. запись выше) вскрыла: «Колодки тормозные передние» и «...задние» отдавали ОДИН oem (`3C0698151C`). Каталог реально различает перед/зад (VAG-конвенция: перед — с индикатором износа, зад — без), но ни один detailname этого не говорит буквально «передние»/«задние» — axle-гейт матчит только литеральный текст, не находит, откатывается на весь пул, оба запроса берут один и тот же top-score кандидат. Юзер выбрал НЕ угадывать VAG-конвенцию (проверена на 1 VIN — риск ошибки на других моделях выше пользы), а честно флагать: `select_by_name_axle_aware` теперь ставит `ambiguous=True`, когда axle/side явно запрошены, гейт не смог сузить текстом, и ≥2 разных oem делят top-score — номер остаётся (он реальный), но `orchestrator._resolve_named_part` превращает это в `confident=False`+`check_note`, что уже существующей цепочкой (Фикс #3/#4 аудита) даёт `needs_check` бэдж в боте и на сайте без нового UI. Suite 1063 passed (+2 теста). Тот же фикс автоматически работает и для autodoc_catalog (общая `select_by_name_axle_aware`).

**2026-07-13 — VW/Hyundai % оказались шумом от синтетических demo-VIN, не реальным гэпом.** При попытке продолжить разведку по этим маркам обнаружено: VIN за "100% Hyundai"/"75% VW" — канонические test/demo-VIN проекта (используются в 20+ тестах, один прямо помечен `"kind": "synthetic_demo"`), провенанс-лог ловит их из ручных live-прогонов разработчика (та же практика, что я сам применял для Volvo AWD). `scripts/provenance_stats.py` теперь авто-исключает такие VIN (сканирует `tests/`+`scripts/` на 17-симв. литералы), печатает что и сколько исключено. После фильтра: **только Volvo имеет реальную выборку** (37 строк, 35% llm) — VW/Hyundai/Kia сейчас практически без органического трафика, дальше "разведка гэпов" по ним невозможна до накопления реальных запросов. См. [[env_source_defeats_test_setdefault]] — тот же класс проблемы (тест-артефакты в проде), другой лог.

**2026-07-13 — Volvo llm-tier recon ЗАВЕРШЁН (задача #151), реальный баг найден и пофикшен.** `_RESOLVE_STOPSTEMS` в `emex_catalog.py` вычёркивал стем «клапа», думая что защищает от кросс-матча сальник↔направляющая — на деле это стирало ЕДИНСТВЕННЫЙ дискриминирующий стем у `valve_guide`, и `_classify_cat_loose` (хот-пас orchestrator.py:3806,3902) матчил ЛЮБУЮ «направляющий X» на valve_guide, уверенно (genuine-тир) отдавая НЕ ТОТ номер — хуже честного «не найдено». Подтверждено живым вызовом на реальном Volvo XC60 VIN из провенанс-лога. Убрал «клапа» из stopstems — фикс делает ВСЕ valve_*-паттерны только точнее. Добавлены категории `transfer_case`/`transfer_case_oil`/`angle_gear` (Volvo AWD-раздатка) + синоним `idler_pulley` += «направляющий ролик» + `_HEAD_SYN["напр"]` + фикс скоринга масляных запросов в `select_by_name_axle_aware` (`fluid_q`, без секции группы «Трансмисс. масло» ничего не говорит какое масло). Живая проверка: «Масло раздаточной коробки» (XC90) было None → теперь резолвится (OEM 31259380). AWD-сальники слева/справа (XC70) остались None — подтверждено ДВОЙНОЕ ограничение (каталог зовёт деталь «Уплотнение» не «Сальник», И нет left/right-разделения в данных вообще) — решил не форсировать рискованный широкий синоним ради частичного покрытия. Suite 1057 passed, но всплыли **2 НОВЫХ независимых провала** `test_e2e_dialog.py` (KeyError vin) — подтверждено через откат правки, что НЕ связаны с этим фиксом; флагнуто юзеру, отдельный чип на расследование. Полностью в Obsidian Changelog.

**2026-07-18 — Полный аудит каталога по запросу юзера («аудит по остальным запчастям на всех авто»), 6 параллельных агентов на ~90 категорий `_CATEGORY_PATTERNS`.** Главная системная находка (подтверждена независимо 6/6): резолв идёт двумя путями — `resolve_by_name` (по тексту, единственный с tie-детекцией) и `get_oem_by_vin` (по категории напрямую, используется LLM-инструментом search_oem и как фоллбэк) — второй вообще не имел защиты от неоднозначности, `confident=True` всегда. Живые находки на Kia Cerato TD: `alternator`→шкив вместо генератора, `engine_mount`→1 из 4 опор без флага, `lambda_sensor`→до/после катализатора наугад, `camshaft`→звёздочка вместо вала, все БЕЗ предупреждения. Фикс: `_rank_tie_ambiguous()` в `emex_catalog.py`, зеркалит `select_by_name_axle_aware` — разом прикрыло все перечисленные + `clutch_kit`/`flywheel`/`tensioner` (пересчитал структурно, отдельных правок не потребовалось). Плюс точечные: `_category_rank` исключение strut↔shock_absorber (Kia стойка называется просто "АМОРТИЗАТОР", штрафовалась чужим паттерном); `_GENERIC_STEMS` снято "приво" (единственный концепт "Приводной вал" был генериком, гарантированный `None`); `strut_mount` паттерны под Kia/Mobis словарь ("изолятор-стойка"); `_PAIRED_SIDE_CATS` += axle_shaft/drive_shaft/brake_caliper/brake_hose/tie_rod (живые LH/RH разные OEM, но каталог не всегда даёт tie — без явного dual-resolve алгоритм не мог это увидеть); попутно нашёл и почин repository-wide мёртвый триггер `brake_hose` (требовал "тормозн шланг" без гласной — недостижим при любой реальной формулировке). Всё live-подтверждено через `bot/tests/e2e_harness.py` на реальном VIN. Suite 1034 passed (было 1027). Откачено (не оставлено нерабочим): `ball_joint` синоним "шаров"↔"шарик" — живой прогон показал, что реальный production-запрос "Опора шаровая" требует покрытия ОБОИХ концептов, "опора" не бриджится безопасно (задело бы engine_mount/др. "опора X" категории). НЕ менял `_DRIVETRAIN_CATS["driveshaft"]` — один агент принял это за опечатку "drive_shaft", проверка показала: реальная отдельная категория "Карданный вал" (пропеллерный вал) для FWD/AWD-развилки, слепой фикс сломал бы рабочую проверку. Deferred (задокументировано в Changelog, нужна такая же живая проверка как у AC-clutch): `ac_compressor` (уверенно катушка вместо компрессора, не тай), `oil_cooler`/`heater_radiator` (оба → жгут проводки печки), `valve_guide` (впуск/выпуск не в axle-гейте), `brake_hose` по resolve_by_name (диск вместо шланга), `control_arm`/`arm_bushing`/`crank_seal`/`cam_seal`/`handbrake_shoes`/`brake_booster`/`clutch_pressure_plate`/`release_bearing` (молча None), ремразмеры main/rod_bearings (`size_variants` мёртв), `wiper_blade` (нужны 2 разные щётки). Полностью: Obsidian Changelog + Decisions `D-two-resolve-paths-need-same-honesty`.

**2026-07-18 — Докрутка deferred-списка (тот же день): 7 фиксов + критический self-fill баг.** Продолжение аудита по запросу юзера. Живой разведкой почищены: `ac_compressor` (резолвер-only упрощение запроса через новый `_RESOLVE_NAME_OVERRIDE`, дисплей-имя не трогается), `oil_cooler`/`heater_radiator` (новый `_CATEGORY_NEGATIVE_PATTERNS` — «проводка» никогда не может быть радиатором; побочный эффект — разблокировал TecDoc-кросс-фоллбэк, юзер теперь получает реальный номер радиатора печки), `valve_guide` (добавлен intake/exhaust gate по аналогии с front/rear — `_intake_exhaust_flags`), `brake_hose` через resolve_by_name (root cause: accompanying/detailsNonMatch-элементы получали ложный «кредит» по имени СЕКЦИИ при скоринге концептов — теперь `is_accompanying` метка убирает этот кредит), `brake_booster`/`crank_seal`/`cam_seal`/`handbrake_shoes` (реальные листья каталога Kia не совпадали с паттернами буквально — падеж/генерик-название/недостающие слова, добавлены реальные фразы). **Deferred остались:** `control_arm`/`arm_bushing` (риск подменить рычаг втулкой), `clutch_pressure_plate`/`release_bearing` (VIN похож на АКПП, нет узла сцепления для проверки), ремразмеры вкладышей, `wiper_blade` дубль. **КРИТИЧЕСКАЯ находка:** обе self-fill ветки в `_resolve_named_part_core` (get_oem_by_vin-путь и resolve_by_name-путь) писали в постоянный `live_harvest` кэш БЕЗ проверки `ambiguous` — неоднозначный тай-кандидат кэшировался как `confident=True` НАВСЕГДА, отдаваясь всем будущим no-VIN запросам того же авто с полной уверенностью. Вторая ветка — баг предсуществующий (не этой сессии). Обе пофикшены (`and not h.get("ambiguous")`). Найдены и вручную удалены 3 реально отравленные строки в live_harvest.sqlite (kia/cerato/2012: alternator/ac_compressor/handbrake_shoes — все от собственных тестов до фикса), живьём подтверждено что повторный резолв больше не пишет. Suite **1038 passed** (было 1027 на старте всей сессии аудита). Полностью: Obsidian Decisions `D-self-fill-must-respect-ambiguous`.

**2026-07-18 — Добивка deferred-списка до конца (тот же день, по прямому запросу юзера "добей").** control_arm/arm_bushing: TecDoc cross подтвердил 551601M000 — реально втулка/крепление балки (не рычаг), arm_bushing получил паттерн; control_arm оказался решаем через autodoc_catalog (не emex — там нет отдельного SKU, торсионная задняя балка) — тот же класс L/R-бага, что весь день чинился (545001M100 лев/545011M100 прав, молча брался левый без единого предупреждения), добавлен в `_PAIRED_SIDE_CATS`, теперь оба рычага резолвятся ТОЧНО. Сцепление проверено на реальном Volvo VIN с МКПП: release_bearing (реальный лист "Подшипник ВЫКЛЮЧЕНИЯ сцепления" vs паттерн "выжимной" — синонимы, никогда не матчились; на платформе физически гидроцилиндр, не подшипник) и clutch_pressure_plate (нажимной диск отдельно не продаётся, только весь "Комплект сцепления") оба пофикшены + внесены в `_COARSE_SUBPART_CATS`. Побочный фикс: `_mark_internal` (оговорка "каталог не разделяет деталь/узел") применялась ТОЛЬКО в no-VIN фоллбэке, не на основном VIN-точном пути — перенесена выше по функции, подключена к обоим VIN-путям. main_bearings/rod_bearings — деферред был основан на неверной предпосылке (я думал про STD/+0.25, реально Volvo кодирует ремразмеры ЦВЕТОМ) — main_bearings УЖЕ безопасно резолвится через сегодняшний tie-детектор (42 цветовых OEM, ambiguous=True), rod_bearings — подлинный пробел данных, не паттерн-баг; оба закрыты БЕЗ правок кода, только верификация. Щётки стеклоочистителя — частично: реальный лист "Стеклоочиститель / резина" не матчился (паттерны знали только имена деталей) — паттерн добавлен, get_oem_by_vin теперь честно находит и флагует ambiguous. НЕ добавлено в _PAIRED_SIDE_CATS: нашёл отдельный необъяснённый баг — resolve_by_name с явным "левый" стабильно возвращает None, хотя идентичный синтетический пул через select_by_name_axle_aware резолвится корректно напрямую (баг не в скоринге, а в сборке пула специфично для лево-запросов, корень не найден) — форсировать dual-resolve сейчас ухудшило бы левую сторону до "не найдено", решил не рисковать, задокументировал как отдельный будущий баг. Итог по всему deferred-списку с начала аудита: 9 из ~10 пунктов закрыты, 1 частично. Suite **1043 passed** (было 1038). Полностью: Obsidian Changelog запись "Добивка deferred-списка аудита до конца".

**2026-07-18 — Найден и починен баг "левый" в resolve_by_name для щёток (последний открытый пункт всего аудита).** Оказалось не отдельный баг, а побочный эффект уже сделанного паттерн-фикса: wiper_blade изначально матчил только имена ДЕТАЛЕЙ, никогда имя листа дерева ("Стеклоочиститель / резина") — из-за этого own-category путь резолва проваливался, сборка пула падала на fallback, который ассиметрично работал для "левый"/"правый". Когда паттерн под реальный лист добавили (в рамках более раннего фикса get_oem_by_vin), own-category путь заработал заново и стал надёжно находить группу для ОБЕИХ сторон. Живьём подтверждено: "(лев)"/"(прав)" оба резолвятся уверенно в 2 разных правильных номера (983502G000/983603K000). wiper_blade добавлен в _PAIRED_SIDE_CATS. Suite **1044 passed** (было 1043). Все ~10 пунктов исходного deferred-списка теперь закрыты. Полностью: Obsidian Changelog.

**2026-07-19 — родительный падеж после «Замена» ломал классификацию _LEAF_TRIGGERS (реальный скриншот юзера, не найдено самостоятельно).** Юзер прислал реальный диалог бота: «Замена муфты компрессора кондиционера» вернула ОДИН непроверенный OEM вместо ожидаемых 4 честных компонентов ac_clutch (сделано ранее, 2026-07-17/18). Root cause: `_LEAF_TRIGGERS` (orchestrator.py, `_requested_parts`) — literal-substring паттерны в ИМЕНИТЕЛЬНОМ падеже («муфта компрессора», «компрессор кондиционера», «радиатор печки», «масляный радиатор»); родительный падеж после «Замена [чего?]» («муфты», «компрессора», «радиатора», «масляного радиатора») не матчился вообще → `_requested_parts` возвращал `None` ЦЕЛИКОМ (не другую категорию!) → оркестратор молча уходил в generic LLM/work-group путь без гейтов честности ac_clutch. Живьём подтверждено на 5 фразах (все None), систематически проверено — баг НЕ повсеместный: короткие однословные стемы (амортизат-/сцеплен-/генератор-/стартер-) переживают склонение, ломаются только многословные паттерны с полным первым словом. Пофикшены 4 категории: ac_clutch, ac_compressor, heater_radiator, oil_cooler (родительные варианты добавлены в кортежи). Побочная регрессия найдена в том же заходе: «шкив компрессора кондиционера» (деталь МУФТЫ) после фикса стал ошибочно попадать в ac_compressor — добавил «шкив» в exclude-лист ac_compressor. +3 теста, suite **1047 passed** (было 1044). Живой e2e через `bot/tests/e2e_harness.py` на VIN `KNAFW411BD5677381` той же фразой из скриншота подтвердил: теперь честные 4 компонента с оговоркой. НЕ аудировал все ~85 оставшихся категорий на тот же класс бага — почин только живьём подтверждённые. Полностью: Obsidian Changelog.

**2026-07-22 — родительный падеж докручен до системного фикса на уровне алгоритма (юзер: «на остальных запчастях и марках тоже больше проблем не было»).** Точечный фикс 4 категорий закрыл конкретную жалобу, но аудит-скрипт (эвристический родительный падеж каждой многословной фразы `_LEAF_TRIGGERS`, 291 категория/801 фраза, живой прогон через `_requested_parts`) показал масштаб: 479/531 (90%) фраз ломались тем же классом — структурный дефект всего механизма, не 4 категории. Фикс — не 291 правка данных, а лёгкий русский стеммер (`_ru_declension_stem`+`_stem_phrase_match`/`_word_stem_in`), встроенный В СУЩЕСТВУЮЩИЙ per-tuple цикл сравнения (критично: НЕ отдельный fallback-проход — это ломало приоритет списка, специфичная категория теряла шанс раньше широкой). Сравнивает КАЖДОЕ слово фразы (не только головное — прилагательное+существительное склоняются согласованно: «обгонная муфта»→«обгонной муфты»). Обобщение открыло новую волну коллизий широкая-категория-крадёт-у-специфичной (тот же класс, что шкив/ac_compressor раньше) — почищено 7 exclude-словами (brake_disc/coil_spring/arm_bushing/driveshaft/radiator/hood/headlight/oil_filter/dpf/steering_rack). 3 нерегулярных существительных с беглой гласной (венец/ремень/бачок) стеммер принципиально не ловит — явные строки-исключения. Побочная находка: strict-путь `_pre_resolve_strict` никогда не проверял known-quirk (BMW N47 и т.п.) — маскировалось тем же генитивным багом (классификация проваливалась → код случайно уходил на другой путь, который quirk проверяет); добавлена та же проверка в strict-путь. +3 теста, suite **1050 passed** (было 1047). Живой e2e на Kia (исходный репорт) + **Volvo XC90** (другая марка) — оба подтверждены реальным ботом. НЕ аудировал остальные ~85 категорий на ДРУГИЕ классы багов (вне скоупа, отдельная задача). Полностью: Obsidian Changelog + Decisions `D-genitive-fix-is-algorithm-not-data`.
