Skip to content

ТЗ — Яндекс-сервисы (cms/integration-yandex)

Слой: 🟡 функц-модуль · Зрелость доноров: ★★ · Донор: несколько Статус: ТЗ к разработке

Назначение и возможности

Серверные интеграции с сервисами Яндекса: Карты (данные для блока карты), SmartCaptcha (защита форм ядра), выгрузка фида Яндекс.Бизнес и OAuth-подключение к Метрике/ Вебмастеру/Директу для серверных сценариев, недоступных клиентскому счётчику. Клиентская вставка счётчика Метрики — в cms/analytics, здесь только серверные вызовы API.

  • Коннектор Яндекс.Карт — геокодирование адресов для блока карты (координаты по адресу);
  • provides: captcha-provider — SmartCaptcha для защиты форм ядра (лиды, комментарии, отзывы);
  • Выгрузка фида Яндекс.Бизнес (организация, товары/услуги) по расписанию;
  • OAuth-подключение к Яндекс.Метрике, Яндекс.Вебмастеру и Яндекс.Директу из Filament, с автоматическим рефрешем токена и алертом при невозможности рефреша;
  • Метрика/Директ: отправка офлайн-конверсий по LeadCreated — заявка становится целью конверсии, привязанной к рекламному клику (yclid), для оптимизации автостратегий;
  • Вебмастер: уведомление об изменении URL через IndexNow при публикации/смене адреса/ удалении страницы (используется previous_slug и события удаления из ревизии ядра 14.07.2026 — без них корректный IndexNow невозможен);
  • Кеширование результатов геокодирования — повторный запрос того же адреса не бьёт в API;
  • Единая точка настройки OAuth-подключений и ключей API Яндекс-сервисов.

Зависимости и выключение

requires: cms/integrations-bus · suggests: cms/analytics, cms/utm (обогащение атрибуции при наличии) · provides: captcha-provider

Поведение при выключении:

  • блок карты перестаёт геокодировать новые адреса (использует последний закешированный результат — не пустой блок);
  • формы ядра переключаются на резервный captcha-provider (если подключён) или работают без капчи с усиленным rate-limit (см. cms/core §11);
  • фид Яндекс.Бизнес перестаёт обновляться, последняя опубликованная версия остаётся доступной по прямой ссылке (не удаляется);
  • офлайн-конверсии в Метрику/Директ не отправляются — лиды при этом создаются штатно, форма не блокируется отсутствием интеграции;
  • IndexNow-уведомления не отправляются — переиндексация Яндексом происходит по обычному краулингу, без ускорения;
  • OAuth-токены остаются в БД (зашифрованы), но не используются; при повторном включении действующий токен продолжает работать без повторной авторизации, если не истёк.

Модель данных

ТаблицаКлючевые поляПримечание
cms_integration_yandex_geocode_cacheid, address_hash, coordinates (json), cached_atкеш геокодирования адресов
cms_integration_yandex_business_feed_logid, status, items_count, generated_atжурнал выгрузки фида Яндекс.Бизнес
cms_integration_yandex_oauth_connectionsid, service (enum: metrika|webmaster|direct), access_token (encrypted), refresh_token (encrypted), expires_at, status (enum), connected_by, connected_at, last_refreshed_atOAuth-подключения к трём сервисам, одна запись на сервис
cms_integration_yandex_offline_conversions_logid, lead_id (FK, nullable), yclid, goal_code, service (enum: metrika|direct), status (enum), sent_atжурнал отправки офлайн-конверсий
cms_integration_yandex_indexnow_logid, url, event_type (enum: published|updated|deleted), status (enum), submitted_atжурнал уведомлений IndexNow

Индексы: уникальный address_hash; coordinatesjson() + cast array; status/event_type/service — PHP Enum; access_token/refresh_tokenencrypted cast, не читаются в логах/API-ответах; уникальный service в oauth_connections (одно подключение на сервис); lead_id в offline_conversions_logconstrained('cms_leads')->nullOnDelete() (лид может быть удалён по ПДн-запросу — журнал остаётся для сверки с рекламным кабинетом, без связи с личностью); журнальные таблицы — append-only, BRIN по *_at.

ПДн-паспорт: yclid — идентификатор рекламного клика, не ПДн (сопоставление с личностью — на стороне Яндекса); в Метрику/Директ уходят только yclid + goal_code, не email/телефон/ФИО лида. access_token/refresh_token — секреты подключения, не ПДн, шифруются и не подлежат экспорту в «выгрузить всё по субъекту».

Входные и выходные данные

Входы — всё, что не перечислено ниже, отвергается (whitelist-принцип §11 стандарта):

ИсточникДанные/поляЧем валидируется
GET /api/v1/integration-yandex/geocodeaddress (строка)FormRequest: длина, whitelist допустимых символов адреса, rate-limit по IP
Filament: настройка модуляmaps_enabled, smartcaptcha_enabled, business_feed_enabled, geocode_cache_ttl_days, geocode_timeout_ms, offline_conversions_enabled, indexnow_enabledFormRequest, числовые поля — диапазоны (см. «Настройки»)
OAuth callback (GET .../oauth/{service}/callback)code, state от Яндекс.OAuthstate сверяется с ранее выданным (защита от CSRF/подмены), service — whitelist metrika|webmaster|direct
Событие ядра LeadCreated (слушается)стандартный payload лида + опциональное поле формы _yclid (скрытое поле конструктора форм)Контракт события ядра; _yclid — не гарантирован (см. «Крайние случаи»)
Событие ядра PagePublished/ContentEntrySaved (слушается)slug, previous_slug (nullable)Контракт события ядра (ревизия 14.07.2026)
Событие ядра PageDeleted/ContentEntryDeleted (слушается)id, slugКонтракт события ядра (ревизия 14.07.2026)
Admin API: ручной запуск генерации фида— (без тела)Idempotency-Key обязателен (мутация с внешним эффектом, §7 стандарта)
Импорт / вебхукНе применимо: модуль не принимает внешние импорт-файлы; входящих вебхуков от Яндекса нет (только исходящие вызовы через cms/integrations-bus)

Выходы

ПотребительДанныеФормат
Блок карты (посетитель)coordinates (или fallback/meta.degraded)JSON {data, meta}
Filament: журнал фида/офлайн-конверсий/IndexNowзаписи журналовkeyset-пагинация {data, meta.next_cursor}
Яндекс.Бизнес (внешняя система)фид организации/товаровформат Яндекс.Бизнес (YML-подобный), через cms/integrations-bus
Яндекс.Метрика/Директ (Offline Conversions API)yclid, goal_code, datetimeформат API Яндекса, через cms/integrations-bus
Яндекс.Вебмастер (IndexNow)url, key (site-verification)протокол IndexNow, через cms/integrations-bus
Подписчики событийYandexBusinessFeedGenerated, YandexGeocodeFailed, YandexOAuthTokenExpired, YandexOfflineConversionSent/Failed, YandexIndexNowSubmitted/Failedpayload по таблице в «События и обмен»

Настройки (группа integration-yandex)

КлючТипДефолтaffectsPageCacheОписание
integration-yandex.maps_enabledbooltrueдаИспользовать Яндекс.Карты в блоке карты
integration-yandex.smartcaptcha_enabledboolfalseнетВключить SmartCaptcha на формах ядра
integration-yandex.business_feed_enabledboolfalseнетKill-switch генерации фида Яндекс.Бизнес — без выключения модуля целиком
integration-yandex.geocode_cache_ttl_daysint90нетСрок жизни кеша геокодирования
integration-yandex.geocode_timeout_msint1500нетТаймаут синхронного вызова геокодера (обязан быть ≤2000 мс — §9 стандарта)
integration-yandex.geocode_daily_quotaint25000нетИнформационный лимит бесплатного тарифа Яндекс.Карт — сверх него запросы платные
integration-yandex.offline_conversions_enabledboolfalseнетKill-switch отправки офлайн-конверсий в Метрику/Директ
integration-yandex.indexnow_enabledboolfalseнетKill-switch уведомлений IndexNow в Вебмастер
integration-yandex.oauth_refresh_before_expiry_minutesint60нетЗа сколько минут до истечения токена запускать рефреш
integration-yandex.business_feed_log_retention_daysint180нетХранение журнала выгрузки фида
integration-yandex.offline_conversions_log_retention_daysint90нетХранение журнала офлайн-конверсий

Секреты (ключ API Карт, ключ SmartCaptcha, client_id/client_secret OAuth-приложения Яндекса, site-verification key для IndexNow) — только .env. OAuth access_token/ refresh_token конкретных подключений — в БД зашифрованы (не секрет приложения, а результат авторизации конкретного сайта).

API

МетодПутьДоступНазначение
GET/api/v1/integration-yandex/geocoderate-limitГеокодирование адреса для блока карты (с кешем)
GET/api/v1/admin/integration-yandex/business-feedadmin (integration-yandex.view)Журнал выгрузки фида
POST/api/v1/admin/integration-yandex/business-feed/generateadmin (integration-yandex.manage), Idempotency-KeyРучной запуск генерации фида
GET/api/v1/admin/integration-yandex/oauthadmin (integration-yandex.view)Статусы OAuth-подключений (Метрика/Вебмастер/Директ)
POST/api/v1/admin/integration-yandex/oauth/{service}/connectadmin (integration-yandex.oauth-manage)Инициировать OAuth-подключение (редирект на Яндекс)
GET/api/v1/admin/integration-yandex/oauth/{service}/callbackadmin, state-валидацияCallback OAuth, сохранение токена
POST/api/v1/admin/integration-yandex/oauth/{service}/disconnectadmin (integration-yandex.oauth-manage)Отключить подключение
GET/api/v1/admin/integration-yandex/offline-conversionsadmin (integration-yandex.view)Журнал офлайн-конверсий
GET/api/v1/admin/integration-yandex/indexnow-logadmin (integration-yandex.view)Журнал уведомлений IndexNow

Все журналы — keyset-пагинация, не OFFSET. Ответ /geocode при недоступности геокодера — 200 с meta.degraded: true и последним закешированным результатом либо пустым data (конвенция деградации, ревизия ядра 14.07.2026 п.2 — применяется ко всем provides-провайдерам и внешним вызовам, не только к явно перечисленным в ревизии).

Компоненты

Блоки: карта с точками (геокодирование через сервис). Filament: журнал выгрузки фида Яндекс.Бизнес, панель OAuth-подключений (статус/кнопка «переподключить» на сервис), журнал офлайн-конверсий, журнал IndexNow. Команды: cms:integration-yandex:generate-feed --json, cms:integration-yandex:refresh-oauth-tokens --json, cms:integration-yandex:warm-geocode-cache --json (пакетный прогрев кеша по списку адресов каталога/точек продаж — не легаси-импорт, а плановая утилита).

Демо-контент: сидер создаёт несколько записей geocode_cache (типовые адреса) и запись в business_feed_log — блок карты и журнал фида видны в /_gallery без ручного ввода ключей API.

Фронтенд-бюджет: блок карты — ленивая загрузка JS API Яндекс.Карт по видимости (IntersectionObserver), не в критическом пути первой отрисовки; резерв размеров контейнера карты — без CLS. Источник домена JS API Яндекс.Карт регистрируется в CSP через канонический фильтр FilterBus security.csp (ревизия №2 ядра, п. 5); инициализация — с nonce ядра.

События и обмен

СобытиеКогдаPayload
YandexBusinessFeedGeneratedфид Яндекс.Бизнес сгенерированitems_count, generated_at
YandexGeocodeFailedгеокодирование адреса завершилось ошибкой/таймаутомaddress_hash, error
YandexOAuthTokenRefreshedтокен сервиса успешно обновлёнservice, expires_at
YandexOAuthTokenExpiredрефреш невозможен (истёк refresh_token)service — требуется ручная переавторизация в админке
YandexOfflineConversionSentофлайн-конверсия принята Метрикой/Директомlead_id, service, goal_code
YandexOfflineConversionFailedофлайн-конверсия отклонена/не отправленаlead_id, service, error
YandexIndexNowSubmittedURL успешно передан в IndexNowurl, event_type
YandexIndexNowFailedпередача URL в IndexNow не удаласьurl, event_type, error

Реализует provides: captcha-provider (SmartCaptcha) — потребитель формы ядра. Слушает LeadCreated (офлайн-конверсии), PagePublished/ContentEntrySaved с previous_slug и PageDeleted/ContentEntryDeleted (ревизия ядра 14.07.2026 — без событий удаления корректный IndexNow об удалённых страницах невозможен). Весь вызов внешних API Яндекса — через cms/integrations-bus (канал 4), прямых HTTP-вызовов из модуля нет.

Таблица взаимодействий

Сущность/модульКаналНаправлениеЧто происходит
Ядро: формы (лиды/комментарии/отзывы)provides-контракт captcha-provider (3)inЯдро резолвит SmartCaptcha через DI при smartcaptcha_enabled=true, без знания о модуле
Ядро: LeadCreatedсобытие (1)inТонкий слушатель кладёт job отправки офлайн-конверсии в очередь, если есть _yclid
Ядро: PagePublished/ContentEntrySavedсобытие (1)inJob IndexNow об изменении/публикации URL (учитывает previous_slug)
Ядро: PageDeleted/ContentEntryDeletedсобытие (1)inJob IndexNow об удалении URL
cms/integrations-bus (requires)сервис-вызов (4)outВсе HTTP-вызовы к API Яндекса (Карты, SmartCaptcha, Бизнес, Метрика/Директ, Вебмастер)
cms/utm (suggests)сервис-вызов (4, опционально)inОбогащение атрибуции офлайн-конверсии данными визита, если модуль включён — не обязательная зависимость
cms/healthсобытие (1)outYandexOAuthTokenExpired/квоты на исходе → алерт в общий health-отчёт
cms/audit (если включён)событие (1)outФиксирует подключение/отключение OAuth и генерацию фида в аудит-лог

Фоновая работа

Очередь integration-yandex:

  • GenerateBusinessFeed — по расписанию через ScheduleRegistrar, идемпотентна (новая запись журнала на прогон, не дубль контента фида);
  • RefreshOAuthTokens — по расписанию, проверяет токены, у которых до expires_at осталось меньше oauth_token_refresh_before_expiry_minutes; при неудаче рефреша (истёк refresh_token) — YandexOAuthTokenExpired, статус подключения → expired;
  • SendOfflineConversion — из слушателя LeadCreated, идемпотентна по ключу lead_id + service (повторная постановка job не создаёт вторую запись в журнале);
  • SubmitIndexNow — из слушателей PagePublished/ContentEntryDeleted и т.п., идемпотентна по ключу url + event_type.

Геокодирование — единственный синхронный внешний вызов модуля, с таймаутом geocode_timeout_ms (≤2000 мс) и graceful fallback на закешированный/пустой результат; не блокирует рендер блока карты. Ретраи с backoff и circuit breaker — на уровне cms/integrations-bus.

Эксплуатация (ранбук): метрики integration_yandex_geocode_requests_total, integration_yandex_geocode_cache_hit_ratio, integration_yandex_oauth_token_ttl_seconds{service}, integration_yandex_offline_conversions_sent_total{service,status}. Алерты: YandexOAuthTokenExpired (любой сервис), доля YandexGeocodeFailed выше порога за час, квота Директа/Вебмастера на исходе (см. «Крайние случаи»).

СимптомЧто проверить / команда
Карта не показывает новые адресаmaps_enabled, ключ API Карт в .env, доля YandexGeocodeFailed за сутки
Офлайн-конверсии не долетаютстатус OAuth-подключения Метрики/Директа (cms:integration-yandex:refresh-oauth-tokens --json), наличие _yclid в форме
IndexNow не срабатываетindexnow_enabled, статус OAuth Вебмастера, журнал indexnow-log на ошибки
Фид Бизнес не обновляетсяbusiness_feed_enabled, cms:integration-yandex:generate-feed --json вручную, журнал фида
Массовые token_expiredrefresh_token отозван пользователем на стороне Яндекса вручную (не только по времени) — требуется полная переавторизация

Бэкап/рестор: все таблицы модуля — в бэкапе. geocode_cache после рестора не требует пересоздания (не критично для функциональности, только для скорости — прогревается заново естественным трафиком или warm-geocode-cache); журналы (business_feed_log, offline_conversions_log, indexnow_log) — исторические данные, восстанавливаются как есть.

Производительность и кеш

Ожидаемые объёмы: типичный сайт-визитка/каталог — геокодирование запускается при сохранении адреса в блоке карты (единицы-десятки адресов), горячего трафика на публичный /geocode немного (карта грузит координаты по уже сохранённому адресу, а не гоняет геокодер на каждый визит); сайт с калькулятором доставки/точками продаж — до нескольких сотен геокодирований в сутки. Бесплатный тариф Яндекс.Карт — порядка 25 000 запросов/сутки (geocode_daily_quota, информационная настройка), для проектов студии обычно с запасом. Офлайн-конверсий — по числу лидов, пришедших с рекламы (не каждый лид). IndexNow — по числу публикаций/правок/удалений страниц в сутки (единицы-десятки).

Горячие пути и бюджет запросов:

  • GET /geocode — синхронный внешний вызов только при промахе кеша; при попадании — 0 внешних вызовов, 1 индексный SELECT по address_hash;
  • запись офлайн-конверсии/IndexNow — асинхронно из очереди, не в горячем пути публикации страницы или создания лида (слушатель тонкий: валидирует факт → кладёт job).

Почему синхронный вызов геокодера допустим по правилу студии (§9 стандарта: внешний HTTP на горячем пути запрещён, кроме синхронных подсказок с таймаутом и graceful fallback): вызов ограничен geocode_timeout_ms (дефолт 1500 мс, обязан быть ≤2000 мс), при таймауте/ошибке — немедленный fallback на кеш/пустой результат, страница не виснет. Все остальные внешние вызовы модуля (фид, офлайн-конверсии, IndexNow, рефреш токенов) — строго из очереди integration-yandex.

Критичные индексы: уникальный address_hash на geocode_cache; уникальный service на oauth_connections; составной (lead_id, service) на offline_conversions_log под идемпотентность job; (url, event_type) на indexnow_log — аналогично; BRIN по *_at на журнальных таблицах.

Что кешируется: тег integration-yandex:geocode — результаты геокодирования (TTL geocode_cache_ttl_days, инвалидация по истечению TTL, не событием — адрес физически не меняет координаты). Статусы OAuth-подключений и настройки — стандартным кешем группы (SettingsStore ядра, §6 стандарта). Журналы — не кешируются (низкочастотные admin-запросы с keyset-пагинацией).

Безопасность

  • SmartCaptcha реализует контракт captcha-provider, проверка токена на сервере перед обработкой формы (лиды, комментарии, отзывы) — токен не проверяется на клиенте; домен виджета SmartCaptcha регистрируется модулем через канонический фильтр FilterBus security.csp, инлайн-инициализация — с nonce ядра;
  • генерация фида Яндекс.Бизнес идемпотентна — повторный запуск не дублирует записи в журнале;
  • OAuth access_token/refresh_tokenencrypted cast, не попадают в логи (маскирующий фильтр cms/integrations-bus применяется и к телу ответа, где Яндекс может вернуть обновлённый токен);
  • state-параметр OAuth-callback проверяется против ранее выданного значения — защита от CSRF/подмены авторизации чужим кодом;
  • секреты (ключи API, client_id/client_secret OAuth-приложения) — только .env.

Права: integration-yandex.view, integration-yandex.manage, integration-yandex.oauth-manage.

Матрица ролей:

ДействиеАдминистраторМенеджерРедакторStudio
Просмотр журналов (фид/конверсии/IndexNow) — view
Ручной запуск генерации фида — manage
Подключение/отключение OAuth (Метрика/Вебмастер/Директ) — oauth-manage
Правка ключей API/client_id/client_secret (.env)✅ (доступ к серверу)
Kill-switch (offline_conversions_enabled, indexnow_enabled, business_feed_enabled)

UX-требования

Админ: ключи API не заданы в .env (Карты/SmartCaptcha) → понятная заглушка «интеграция не настроена — обратитесь к разработчику», не пустое поле без объяснения; истёкший OAuth-токен → баннер «Токен Метрики истёк, требуется переавторизация» с кнопкой «Переподключить», не тихая остановка конверсий; массовое действие в журнале офлайн- конверсий — повторная отправка выбранных failed-записей; ошибки на человеческом языке («Квота Яндекс.Директа исчерпана на сегодня, конверсии уйдут завтра»); подтверждение при отключении OAuth («Офлайн-конверсии прекратятся, ранее отправленные данные не удаляются»).

Посетитель: недоступность карты (ключ невалиден/лимит/таймаут) → последний закешированный результат либо адрес текстом со ссылкой «открыть на Яндекс.Картах», не пустой прямоугольник; недоступность SmartCaptcha → форма отправляется без капчи с усиленным rate-limit (деградация §11 ядра), без видимой посетителю технической ошибки.

Крайние случаи и типовые баги

  • Истёк refresh_token (доступ отозван вручную на стороне Яндекса или превышен максимальный срок жизни) → RefreshOAuthTokens не может обновить токен → YandexOAuthTokenExpired, статус expired, отправка офлайн-конверсий/IndexNow для этого сервиса приостанавливается без ошибки в остальном сайте — нужна ручная переавторизация (см. «UX-требования»);
  • Квота API Директа/Вебмастера исчерпана → job получает 429/квоту от API → откладывается до следующего окна через cms/integrations-bus, не теряется молча — событие *Failed с error содержит причину;
  • LeadCreated без _yclid (лид не с рекламы Яндекса, либо форма без скрытого поля) → офлайн-конверсия не отправляется — ожидаемое поведение, не ошибка;
  • Смена схемы ответа Геокодера API → парсер валидирует ожидаемую структуру перед извлечением координат; неожиданная структура — YandexGeocodeFailed, не необработанное исключение, fallback на кеш/пустой результат;
  • IndexNow при повторном удалении уже удалённой страницы → идемпотентность по (url, event_type) не создаёт дубль записи журнала (сам вызов API Яндексу допустим — IndexNow идемпотентен на своей стороне);
  • Гонка: двойной сабмит формы с одним yclid → уникальность (lead_id, service) в offline_conversions_log предотвращает дубль job (дедупликация самих лидов — зона ответственности конструктора форм ядра, не этого модуля);
  • Выключен cms/integrations-bus (requires) → ни один внешний вызов не работает; geocoding деградирует на кеш, фид/конверсии/IndexNow приостанавливаются с понятным сообщением в админке (§3 стандарта — «сбой обязательной зависимости»);
  • Пустой/огромный адрес в GET /geocode → FormRequest ограничивает длину, пустой — 422, не тихий запрос к API с бессмысленным результатом;
  • Измерение city/site отсутствует → кеш геокодирования и офлайн-конверсии не завязаны на city_id/site_id, один код-путь в обоих режимах;
  • Конкурентный reconnect OAuth (два админа переподключают один сервис) → последний успешный callback побеждает без конфликта — lock_version не нужен, это не редактируемая сущность, а результат внешнего протокола;
  • ⚠️ Противоречие: канонический payload LeadCreated не гарантирует _yclid — клик-идентификатор не входит в стандартный контракт лида. Разрешение: это требование к конкретной форме, не к событию ядра — скрытое поле _yclid через движок полей конструктора форм, значение прокидывается в context/meta лида; отсутствие поля — осознанный выбор настройки формы, не баг;
  • ⚠️ Противоречие: исходное ТЗ не задавало таймаут синхронного вызова геокодера (нарушение §9 — синхронный внешний HTTP обязан иметь таймаут). Разрешение: настройка geocode_timeout_ms (дефолт 1500 мс, ≤2000 мс) — см. «Настройки», «Производительность».

Донорский код

Донор: несколько проектов (без выделенного пути — типовая интеграция, см. общий раздел /cms-v2/integrations).

Легаси-импорт (§16 стандарта): не применимо в классическом смысле — интеграция не хранит доменных данных, которые нужно мигрировать с донора (нет «старых заявок Метрики» для переноса). Единственный полезный batch-сценарий — прогрев кеша геокодирования по уже существующим адресам каталога/точек продаж командой cms:integration-yandex:warm-geocode-cache --source=<профиль> --dry-run, чтобы после переезда клиента блок карты не делал синхронных вызовов геокодера массово в первые дни.

Тесты и приёмка

  • [ ] Мок API Яндекс.Карт: геокодирование с кешированием и graceful fallback на таймаут geocode_timeout_ms (дефолт ≤2000 мс);
  • [ ] SmartCaptcha реализует captcha-provider, проверяется мок-токеном в тесте формы;
  • [ ] Генерация фида идемпотентна — повторный запуск не дублирует записи журнала, Idempotency-Key на ручном запуске проверен тестом;
  • [ ] При выключении модуля формы ядра работают без капчи либо с резервным провайдером;
  • [ ] Кеш геокодирования снижает число вызовов API при повторных запросах адреса;
  • [ ] Рефреш OAuth-токена запускается за oauth_refresh_before_expiry_minutes; неудачный рефреш переводит подключение в expired и издаёт YandexOAuthTokenExpired, не роняя очередь;
  • [ ] Офлайн-конверсия отправляется только при _yclid в лиде; без него job не ставится (не ошибка); повторная постановка SendOfflineConversion/SubmitIndexNow идемпотентна (тест на дубль job);
  • [ ] IndexNow вызывается при PagePublishedprevious_slug) и при PageDeleted/ContentEntryDeleted; исчерпанная квота Директа/Вебмастера откладывает job, не теряет данные (мок 429);
  • [ ] Неожиданная схема ответа геокодера не роняет запрос — трактуется как YandexGeocodeFailed;
  • [ ] Права integration-yandex.manage/.oauth-manage разграничивают запуск генерации фида и подключение/отключение OAuth;
  • [ ] Контрактный набор cms-testing зелёный, testbench-изоляция, feature-тест на каждый роут; тестовая БД только integration_yandex_test, migrate:fresh/refresh/reset запрещены.

Внутренняя база знаний студии