Бескоординатная карточка областного формата («ул. Кирова,4» — города в адресе НЕТ)
садилась в одноимённый дом другого города: ключ Tier-2a = sha256(адрес|lat|lon), без
координат он вырождается в один нормализованный адрес и становится глобально уникальным
в house_address_aliases. Прод 2026-08-10: 7303 из 21603 алиасов — такие ключи, 6047 из них
без токена города вовсе. Страж от этой коллизии в матчере был, но выводил город из ТЕКСТА
адреса (resolve_city_token), а областной SERP города не называет — сам код это и описывал
как «residual, needs sweep-context».
Sweep-context был собран и не доезжал: save_listings знает город-цель batch'а, прогоняет
его через свой гео-guard и пишет в listings.city, а _link_listing_to_house города не
получал и у match_or_create_house такого параметра не было. Признак лежал в соседней
колонке той же строки. Теперь он идёт через границу вызова (Protocol → адаптер → хук),
и его достижимость сторожит тест по образцу #2674 — структурной проверки сигнатуры мало.
Город здесь — НЕЗАВИСИМОЕ наблюдение (какой город запрашивала развёртка), а не второе
поле, выведенное из той же строки адреса: ровно то, на чём в #2690 сорвалось усиление
ключа через gar_house_guid.
Отдельно: listings.address не обновлялся при ON CONFLICT вообще — строка, вставленная без
адреса, оставалась безадресной навсегда, даже когда следующий скрейп адрес приносил (прод:
862 строки). Это и есть «привязка при пустом адресе» из #2777: 95 таких строк имеют
house_id_fk, потому что в момент матчинга адрес БЫЛ (fingerprint/new без адреса структурно
невозможны — алиаса с пустым ключом на проде 0). Дозаполняем COALESCE(existing, new) —
перезапись откатила бы чистку адресов миграциями 062/108/124.
Потолок правки назван числом в коде: 30 из 35 межгородских склеек закрываются, 5 — нет
(приходящая карточка ЕКБ, алиас завёл областной дом; города владельца алиаса мы не знаем).
Посёлки внутри ЕКБ-развёртки (Кедровка, 12 км) не ловятся вовсе — гранулярность признака
город, не населённый пункт. Уже сшитые дома НЕ трогаются.
Refs #2777