Тема
ТЗ — Яндекс-сервисы (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_cache | id, address_hash, coordinates (json), cached_at | кеш геокодирования адресов |
cms_integration_yandex_business_feed_log | id, status, items_count, generated_at | журнал выгрузки фида Яндекс.Бизнес |
cms_integration_yandex_oauth_connections | id, service (enum: metrika|webmaster|direct), access_token (encrypted), refresh_token (encrypted), expires_at, status (enum), connected_by, connected_at, last_refreshed_at | OAuth-подключения к трём сервисам, одна запись на сервис |
cms_integration_yandex_offline_conversions_log | id, lead_id (FK, nullable), yclid, goal_code, service (enum: metrika|direct), status (enum), sent_at | журнал отправки офлайн-конверсий |
cms_integration_yandex_indexnow_log | id, url, event_type (enum: published|updated|deleted), status (enum), submitted_at | журнал уведомлений IndexNow |
Индексы: уникальный address_hash; coordinates — json() + cast array; status/event_type/service — PHP Enum; access_token/refresh_token — encrypted cast, не читаются в логах/API-ответах; уникальный service в oauth_connections (одно подключение на сервис); lead_id в offline_conversions_log — constrained('cms_leads')->nullOnDelete() (лид может быть удалён по ПДн-запросу — журнал остаётся для сверки с рекламным кабинетом, без связи с личностью); журнальные таблицы — append-only, BRIN по *_at.
ПДн-паспорт: yclid — идентификатор рекламного клика, не ПДн (сопоставление с личностью — на стороне Яндекса); в Метрику/Директ уходят только yclid + goal_code, не email/телефон/ФИО лида. access_token/refresh_token — секреты подключения, не ПДн, шифруются и не подлежат экспорту в «выгрузить всё по субъекту».
Входные и выходные данные
Входы — всё, что не перечислено ниже, отвергается (whitelist-принцип §11 стандарта):
| Источник | Данные/поля | Чем валидируется |
|---|---|---|
GET /api/v1/integration-yandex/geocode | address (строка) | FormRequest: длина, whitelist допустимых символов адреса, rate-limit по IP |
| Filament: настройка модуля | maps_enabled, smartcaptcha_enabled, business_feed_enabled, geocode_cache_ttl_days, geocode_timeout_ms, offline_conversions_enabled, indexnow_enabled | FormRequest, числовые поля — диапазоны (см. «Настройки») |
OAuth callback (GET .../oauth/{service}/callback) | code, state от Яндекс.OAuth | state сверяется с ранее выданным (защита от 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/Failed | payload по таблице в «События и обмен» |
Настройки (группа integration-yandex)
| Ключ | Тип | Дефолт | affectsPageCache | Описание |
|---|---|---|---|---|
integration-yandex.maps_enabled | bool | true | да | Использовать Яндекс.Карты в блоке карты |
integration-yandex.smartcaptcha_enabled | bool | false | нет | Включить SmartCaptcha на формах ядра |
integration-yandex.business_feed_enabled | bool | false | нет | Kill-switch генерации фида Яндекс.Бизнес — без выключения модуля целиком |
integration-yandex.geocode_cache_ttl_days | int | 90 | нет | Срок жизни кеша геокодирования |
integration-yandex.geocode_timeout_ms | int | 1500 | нет | Таймаут синхронного вызова геокодера (обязан быть ≤2000 мс — §9 стандарта) |
integration-yandex.geocode_daily_quota | int | 25000 | нет | Информационный лимит бесплатного тарифа Яндекс.Карт — сверх него запросы платные |
integration-yandex.offline_conversions_enabled | bool | false | нет | Kill-switch отправки офлайн-конверсий в Метрику/Директ |
integration-yandex.indexnow_enabled | bool | false | нет | Kill-switch уведомлений IndexNow в Вебмастер |
integration-yandex.oauth_refresh_before_expiry_minutes | int | 60 | нет | За сколько минут до истечения токена запускать рефреш |
integration-yandex.business_feed_log_retention_days | int | 180 | нет | Хранение журнала выгрузки фида |
integration-yandex.offline_conversions_log_retention_days | int | 90 | нет | Хранение журнала офлайн-конверсий |
Секреты (ключ API Карт, ключ SmartCaptcha, client_id/client_secret OAuth-приложения Яндекса, site-verification key для IndexNow) — только .env. OAuth access_token/ refresh_token конкретных подключений — в БД зашифрованы (не секрет приложения, а результат авторизации конкретного сайта).
API
| Метод | Путь | Доступ | Назначение |
|---|---|---|---|
| GET | /api/v1/integration-yandex/geocode | rate-limit | Геокодирование адреса для блока карты (с кешем) |
| GET | /api/v1/admin/integration-yandex/business-feed | admin (integration-yandex.view) | Журнал выгрузки фида |
| POST | /api/v1/admin/integration-yandex/business-feed/generate | admin (integration-yandex.manage), Idempotency-Key | Ручной запуск генерации фида |
| GET | /api/v1/admin/integration-yandex/oauth | admin (integration-yandex.view) | Статусы OAuth-подключений (Метрика/Вебмастер/Директ) |
| POST | /api/v1/admin/integration-yandex/oauth/{service}/connect | admin (integration-yandex.oauth-manage) | Инициировать OAuth-подключение (редирект на Яндекс) |
| GET | /api/v1/admin/integration-yandex/oauth/{service}/callback | admin, state-валидация | Callback OAuth, сохранение токена |
| POST | /api/v1/admin/integration-yandex/oauth/{service}/disconnect | admin (integration-yandex.oauth-manage) | Отключить подключение |
| GET | /api/v1/admin/integration-yandex/offline-conversions | admin (integration-yandex.view) | Журнал офлайн-конверсий |
| GET | /api/v1/admin/integration-yandex/indexnow-log | admin (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 |
YandexIndexNowSubmitted | URL успешно передан в IndexNow | url, 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) | in | Job IndexNow об изменении/публикации URL (учитывает previous_slug) |
Ядро: PageDeleted/ContentEntryDeleted | событие (1) | in | Job IndexNow об удалении URL |
cms/integrations-bus (requires) | сервис-вызов (4) | out | Все HTTP-вызовы к API Яндекса (Карты, SmartCaptcha, Бизнес, Метрика/Директ, Вебмастер) |
cms/utm (suggests) | сервис-вызов (4, опционально) | in | Обогащение атрибуции офлайн-конверсии данными визита, если модуль включён — не обязательная зависимость |
cms/health | событие (1) | out | YandexOAuthTokenExpired/квоты на исходе → алерт в общий 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_expired | refresh_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 регистрируется модулем через канонический фильтр FilterBussecurity.csp, инлайн-инициализация — с nonce ядра; - генерация фида Яндекс.Бизнес идемпотентна — повторный запуск не дублирует записи в журнале;
- OAuth
access_token/refresh_token—encryptedcast, не попадают в логи (маскирующий фильтрcms/integrations-busприменяется и к телу ответа, где Яндекс может вернуть обновлённый токен); state-параметр OAuth-callback проверяется против ранее выданного значения — защита от CSRF/подмены авторизации чужим кодом;- секреты (ключи API,
client_id/client_secretOAuth-приложения) — только.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 вызывается при
PagePublished(сprevious_slug) и приPageDeleted/ContentEntryDeleted; исчерпанная квота Директа/Вебмастера откладывает job, не теряет данные (мок 429); - [ ] Неожиданная схема ответа геокодера не роняет запрос — трактуется как
YandexGeocodeFailed; - [ ] Права
integration-yandex.manage/.oauth-manageразграничивают запуск генерации фида и подключение/отключение OAuth; - [ ] Контрактный набор
cms-testingзелёный, testbench-изоляция, feature-тест на каждый роут; тестовая БД толькоintegration_yandex_test,migrate:fresh/refresh/resetзапрещены.