Капча наказывает посетителя за чужую вину: человек разгадывает картинку, а бот с арендованным прокси проходит её за деньги. CF7 Spam Checker Pro 4.1.1 идёт другим путём — защита формы CF7 строится из слоёв, которые идут от бесплатных локальных проверок к платным внешним, и только в том случае, если отправка всё ещё выглядит чистой. Ниже — что именно проверяется, как устроена экономия на API и моделях, что показал прогон всех файлов плагина и на чём спотыкаются при настройке.

TL;DR: что это и как проверить
- Что это. Плагин для Contact Form 7: двенадцать слоёв проверки плюс валидация языка в полях имени.
- Порядок важен. Сначала локальные проверки (бан, тайминг, honeypot, UA, частота, SQLi, XSS, ссылки, слова), потом внешние (страна, AbuseIPDB), и последним — модель.
- Провайдеров одиннадцать. DeepSeek, OpenAI, Qwen, Claude, Gemini, Groq, Mistral, OpenRouter, Grok и любые совместимые точки через два протокола.
- Реестр с доказательствами. У каждого вердикта сохранены результаты проверок, поэтому ложное срабатывание разбирается в один клик.
- Проверка. Распаковать архив,
php -lпо всем файлам, установить на тестовый сайт с CF7.
unzip contact-form-7-spam-checker-4.1.1.zip -d wp-content/plugins/
php -l wp-content/plugins/contact-form-7-spam-checker/contact-form-7-spam-checker.php
Проблема, которую это решает
Спам в формах 2026 года — не «купите виагру» капсом. Это отправки от автоматизированных агентов, которые читают форму, заполняют её правдоподобными данными и приходят с адресов, которые через минуту уже не используются. Ни одна одиночная проверка такое не ловит.
Honeypot без тайминга обходится: бот заполняет скрытое поле, но иногда его и не заполняет — просто отправляет форму с правильными полями. Тайминг без honeypot ловится ботом, который умеет ждать. Частотный лимит не помогает против распределённых отправок с сотни адресов. Подписи SQLi и XSS бесполезны, когда в форму пишут осмысленный текст с одной ссылкой. А репутация IP бессильна, если бот пришёл с чистого адреса домашнего провайдера.
Отсюда конструкция: слоёв много, они разные по природе, и срабатывание любого останавливает отправку. Плюс к этому — дорогие проверки включаются последними, чтобы не платить за каждую попытку.
Что такое CF7 Spam Checker Pro
flowchart TB
SUB["отправка формы CF7"] --> WL{"IP в белом списке?"}
WL -- да --> OK["пропустить"]
WL -- нет --> LOC["локальные проверки: бан, время, honeypot, UA, частота, SQLi, XSS, ссылки, слова"]
LOC -- сработало --> BAN["блокировка и запись в реестр"]
LOC -- чисто --> API["внешние проверки: страна, AbuseIPDB"]
API -- сработало --> BAN
API -- чисто --> LLM["LLM: вердикт SPAM или CLEAN"]
LLM -- SPAM --> BAN
LLM -- CLEAN --> OK| Свойство | Значение |
|---|---|
| Тип | плагин WordPress для Contact Form 7 |
| Версия в разборе | 4.1.1 (патч: исправления аудита, без новых возможностей) |
| Требования | WordPress 6.0+, PHP 8.3+, Contact Form 7 5.7+ |
| Лицензия | GPL-2.0-or-later |
| Слоёв проверки | 12 плюс валидация языка |
| Провайдеров LLM | 11 |
| Хранение | собственные таблицы: реестр спама, IP-список, кеш ответов, настройки |
Название в шапке плагина — «CF7 Spam Checker Pro», текстовый домен — contact-form-7-spam-checker, версия в коде CF7SEC_VERSION = 4.1.1, схема базы CF7SEC_DB_VERSION = 400.
Возможности
Слои проверки
Первые десять слоёв работают локально и ничего не стоят: белый список IP, бан-лист, тайминг заполнения, honeypot, разбор user-agent, частотный лимит, подписи SQLi, подписи XSS, ограничение числа ссылок и чёрный список слов. Затем идут два платных: фильтр по стране и AbuseIPDB. Замыкает цепочку модель.
Важная деталь в тайминге: время измеряется на клиенте монотонным таймером, поэтому не ломается ни от сдвига часов, ни от кеширования страницы. Если JavaScript не сработал, есть запасной вариант — серверная метка времени с допуском на расхождение.
Про user-agent стоит сказать отдельно: список включает больше шестидесяти признаков автоматизации, но с подавлением ложных срабатываний для настоящих браузерных имён. Без этого фильтр отсекал бы часть живых посетителей, и владелец сайта узнал бы об этом из жалоб, а не из логов.
Вердикт модели
| Что | Как работает |
|---|---|
| Провайдеры | DeepSeek, OpenAI, Qwen, Anthropic, Gemini, Groq, Mistral, OpenRouter, xAI, плюс свои совместимые точки |
| Протоколы | OpenAI-совместимые chat completions и Anthropic Messages |
| Ответ | строго SPAM или CLEAN |
| Ключи | отдельный на провайдера, маскируются в интерфейсе |
| Модель | свободный идентификатор с подсказками |
| Адрес | переопределение base URL для прокси и региональных точек |
| Ошибка | политика fail-open или fail-closed на выбор |
| Проверка связи | встроенный тестер подключения |
Свободный идентификатор модели — правильное решение: список моделей меняется быстрее, чем выходят версии плагинов.
Реестр и управление IP
flowchart LR
V["вердикт по отправке"] --> REG["реестр: проверки, данные, UA, реферер"]
REG --> STRIKE{"порог strikes?"}
STRIKE -- да --> PERM["бан, при повторе постоянный"]
STRIKE -- нет --> WATCH["только запись"]
REG --> EXP["CSV и срок хранения"]
REG --> NOTIFY["письмо о волне спама"]| Что | Как работает |
|---|---|
| Реестр | поиск по вердиктам, полные доказательства: результаты проверок, очищенные данные, UA, реферер |
| Экспорт | CSV |
| Хранение | настраиваемый срок, готовность к требованиям по персональным данным |
| IP-таблица | баны и белый список в одном месте |
| Эскалация | N-й бан становится постоянным |
| Ручной бан | приватные и зарезервированные диапазоны отклоняются |
| Массовые операции | удаление пакетом, выгрузка CSV |
| Справка по адресу | запрос к AbuseIPDB прямо из строки таблицы |
Эскалация strikes — то, чего обычно не хватает: один и тот же адрес возвращается, и вместо бесконечных временных банов он получает постоянный.
Репутация и отчёты
| Что | Как работает |
|---|---|
| Пороги AbuseIPDB | три независимых: процент уверенности, общее число отчётов, отчёты категории «почтовый спам» |
| Кеш | в базе, TTL настраивается |
| Квота | видно по заголовкам ответа API |
| Автоотчёт | подтверждённый спамер отправляется в сообщество, категория 10 |
| Уведомления | письма с ограничением частоты: баны, волны спама, ошибки интеграций |
Три порога вместо одного — практично: адрес с двадцатью отчётами и уверенностью 90 % и адрес с двумя отчётами, но оба «почтовый спам», — разные случаи.
Интерфейс и эксплуатация
| Что | Как работает |
|---|---|
| Оформление | Tailwind-подобная система, тёмная, светлая и системная тема |
| Экран | адаптивно до 360 пикселей |
| Внешние ресурсы | ни одного CDN, всё лежит локально |
| Языки | английский, русский, китайский, немецкий |
| Экспорт настроек | JSON, импорт обратно |
| Диагностика | страница состояния, просмотр журнала отладки |
| Обслуживание | cron раз в сутки плюс кнопка ручного запуска |
| Удаление | аккуратный uninstall.php |
| REST | пространство cf7sec/v1 с nonce и проверкой прав manage_options |
Отсутствие CDN — не мелочь: страница настроек безопасности не должна зависеть от доступности чужого сервера, а в закрытых сетях она вообще не загрузится без локальных ресурсов.
Как это работает
Три правила, сформулированные в документации, объясняют устройство лучше любого описания.
Первое: при внутренней ошибке — пропускать. Плагин безопасности не имеет права уронить форму: исключения перехватываются, записываются и попадают в реестр. Форма при этом работает.
Второе: внешние проверки — последними. Пока отправка не прошла локальные слои, ни AbuseIPDB, ни модель не вызываются. Это экономит квоту API и токены, а при большом потоке спама разница ощутима.
Третье: у каждого вердикта есть доказательство. Реестр хранит результаты каждой проверки, поэтому ложное срабатывание разбирается без гадания: видно, какой именно слой заблокировал отправку и по какому признаку.
Замеры и цифры
Проверено 11.11.2026, двумя проверками.
| Что проверено | Значение | Как |
|---|---|---|
| Архив релиза | 1 802 159 байт, SHA-256 adf6a62b…3408f |
скачивание релиза и подсчёт хеша |
| Размер дерева после распаковки | 2,6 МБ | du |
| PHP-файлов | 60 | find |
| Строк PHP | 7 751 | подсчёт по всем файлам |
| Проверка синтаксиса | php -l по всем 60 файлам — ошибок нет |
прогон в Git Bash, PHP 8.5.0 |
| Крупнейшие файлы | RestController 23 494 Б, Settings 20 259 Б, шаблон настроек 17 656 Б, SpamPipeline 15 493 Б | сортировка по размеру |
| Структура | src/Admin, src/Security, src/Services, src/Storage, src/Support |
список каталогов |
Вторая проверка — живой сайт. На этом блоге 24 плагина, и среди них нет Contact Form 7 — значит, и этот плагин здесь не установлен. Проверять его работу на боевом сайте без формы бессмысленно: нужен стенд с CF7.
| Что проверено на живом хосте | Значение |
|---|---|
| Плагинов на сайте | 24 |
| Contact Form 7 установлен | нет |
| Этот плагин установлен | нет |
| Репозиторий и страница релиза | HTTP 200 |
Из чисел видно две вещи. Первая: 7 751 строк в 60 файлах при аккуратном разделении на слои — размер, который реально читается: ядро проверок лежит в SpamPipeline, админка отдельно, хранилище отдельно. Вторая: php -l по всем файлам чист, то есть архив релиза собирается из синтаксически целого кода — это минимальная, но обязательная проверка для плагина, который ставится на живой сайт.
Чего замеры не показывают: поведение плагина в работе — сколько спама он реально отсекает, как часто ошибается и сколько стоит модель при потоке заявок. Для этого нужен сайт с формой и потоком обращений, а не архив релиза.
Сильные стороны продукта
- Слоёв много, природа разная. Одновременное срабатывание ловит и ботов, и людей с плохими намерениями.
- Экономия на внешних вызовах. Платные проверки включаются только после локальных.
- Форма не падает. Внутренняя ошибка пропускает отправку, а не ломает сайт.
- Доказательства в реестре. Ложное срабатывание видно по шагам, а не «по ощущению».
- Одиннадцать провайдеров. Нет привязки к одному поставщику модели.
- Политика ошибки на выбор. fail-open для доступности, fail-closed для строгости.
- Эскалация вместо вечных банов. Повторные адреса получают постоянный бан, случайные — временный.
- Ноль внешних ресурсов в интерфейсе. Админка работает в закрытой сети.
Установка
| Шаг | Что сделать |
|---|---|
| 1 | скачать архив релиза и сверить хеш |
| 2 | распаковать в каталог плагинов |
| 3 | активировать в админке |
| 4 | проверить требования: WordPress 6.0+, PHP 8.3+, CF7 5.7+ |
| 5 | открыть страницу настроек и включить локальные слои |
| 6 | настроить AbuseIPDB и провайдера модели, если нужны |
| 7 | отправить тестовую заявку и посмотреть вердикт в реестре |
Первым делом стоит включить именно локальные слои: они бесплатны и закрывают основную массу ботов. Внешние проверки — второй шаг, когда видно, что локальные справляются не полностью.
Настройка
flowchart TD SET["настройки плагина"] --> AB["AbuseIPDB: три порога и кеш ответов"] SET --> GW["шлюз LLM: 11 провайдеров, два протокола"] GW --> POL["политика ошибки: fail-open или fail-closed"] AB --> AUTO["автоотчёт о подтверждённом спаме"] POL --> REG2["запись в реестр при ошибке"]
| Что настраивать | На что влияет |
|---|---|
| Чёрный список слов | мгновенная блокировка по тексту, работает без внешних вызовов |
| Максимум ссылок в поле | отсекает типовые спам-вставки |
| Частотный лимит | окно 60 секунд на адрес |
| Постоянный бан после N нарушений | эскалация strikes |
| Пороги AbuseIPDB | три независимых условия |
| TTL кеша ответов | баланс между свежестью и квотой |
| Провайдер и модель | качество семантической проверки |
| Политика при ошибке | что делать, если модель недоступна |
| Автобан по вердикту SPAM | автоматика против волн спама |
| Уведомления | частота писем о волнах и банах |
Отдельно стоит подумать про fail-open и fail-closed. Первый вариант означает, что при недоступности модели заявка пройдёт — форма работает всегда. Второй блокирует отправку при ошибке — строже, но при сбое провайдера вы теряете обращения клиентов. Для формы обратной связи на сайте услуг разумнее первый; для формы с ограниченным доступом — второй.
Что изменилось в 4.1.1
| Что | Суть |
|---|---|
| B1 | проверка моделью оказалась мёртвым кодом при схеме настроек v2 — все чтения переведены на llm.* |
| B2 | обновление 4.0 → 4.1 стирало перенесённый ключ API и флаг включения — миграция теперь идёт до снимка настроек |
| M1–M12 | правки оформления, убрана шапка Update URI, безопасный доступ к переданным данным, имена полей honeypot с солью HMAC |
| L1–L10 | мелкие правки, включая описание риска подмены адреса при общем прокси |
| Добавлено | CI на PHP 8.3/8.4/8.5: линт, smoke, проверка путей настроек, сборка ресурсов, гварды обновления и ссылок |
| Переводы | 396 строк, 100 % для русского, китайского и немецкого |
Два первых пункта — хорошая иллюстрация того, зачем нужны smoke-проверки: обе ошибки не ломали сайт, они делали защиту тихо неработающей. Первая выключала целый слой, вторая стирала ключ при обновлении.
Почему порядок слоёв — это про деньги
Соблазн проверять моделью каждую заявку понятен: семантическая проверка выглядит самой умной. На практике это две проблемы сразу.
Первая — стоимость. Каждая заявка — это запрос к платному API, и на сайте с сотней обращений в неделю разница между «моделью проверяется всё» и «моделью проверяется то, что прошло локальные слои» видна в счёте. Спам в форме обычно идёт волнами: десятки отправок за час, и все они одинаково бессмысленны для модели.
Вторая — задержка. Посетитель, нажавший «отправить», ждёт ответа; если в цепочке два внешних вызова по секунде каждый, форма начинает «тупить». Локальные проверки занимают миллисекунды, а внешние — секунды, и это надо тратить только там, где они что-то решают.
Отсюда правило, которое стоит держать в голове при настройке: сначала включаются и настраиваются бесплатные слои, потом добавляется репутация адреса, и только в конце — модель. Если после этого ложных пропусков почти не осталось, модель можно не включать вовсе: она перестаёт быть обязательной.
Третье следствие — реестр. При такой схеме всегда видно, какой слой сработал первым, а какой уже не вызывался. Именно поэтому доказательства хранятся по каждому вердикту, а не только итоговое «спам или нет».
Что делать при ложном срабатывании
Ложное срабатывание — не гипотетическая ситуация, а нормальная часть работы антиспама. Важно, чтобы его разбор занимал минуты, а не часы переписки.
Порядок такой. Открыть запись в реестре и посмотреть, какой слой вынес вердикт. Если это чёрный список слов — проверить, не попало ли в него слово, которое встречается в обычной речи. Если honeypot — убедиться, что поле не заполняет автозаполнение браузера. Если тайминг — проверить, не стоит ли на форме агрессивный кеш, из-за которого клиентский таймер стартует раньше времени. Если модель — прочитать текст заявки и решить, ошиблась ли она по сути.
Дальше действие либо точечное (убрать слово, поправить порог), либо системное (пересмотреть список признаков). И то и другое лучше делать после двух-трёх случаев: одиночное срабатывание часто оказывается случайным совпадением.
Отдельно про белый список: если клиент регулярно попадает под фильтр, его адрес стоит внести в белый список и закрыть вопрос. Это быстрее, чем обучать фильтры на исключениях.
Подводные камни
- Слой, который «включён», может не работать. Именно это случилось в 4.0 с проверкой моделью: настройки читались по старой схеме. После обновления стоит отправить тестовую заявку и убедиться, что вердикт появился в реестре.
- Обновление со сбросом ключа. В 4.1 ключ модели терялся при миграции; в 4.1.1 исправлено, но проверять после обновления всё равно нужно.
- Honeypot без тайминга слабее. Скрытое поле обходится ботом, который его просто не заполняет; смысл в комбинации слоёв.
- Тайминг требует JavaScript. Если скрипт не загрузился, работает серверный вариант с допуском — но он грубее.
- Общий прокси искажает адрес. Если сайт за общим прокси, все посетители выглядят одним адресом; в документации риск описан, в настройках его не отключить.
- Квота AbuseIPDB не бесконечна. Три порога и кеш как раз для экономии; без кеша при потоке заявок квота кончится.
- Модель стоит денег. fail-open экономит не только доступность, но и бюджет: при сбое провайдера обращения проходят без проверки моделью.
- Автобан по вердикту — обоюдоострый. При ложном срабатывании модели IP уходит в бан автоматически; политика strikes должна быть разумной.
- Реестр хранит данные отправок. Срок хранения настраивается, и его стоит выставить осознанно: это персональные данные посетителей.
- Плагин не из стандартной поставки. Установка — загрузка архива; обновление вручную, обновлений «из коробки» не будет.
- Требуется PHP 8.3+. На 8.2 плагин не заработает: используются readonly-свойства, перечисления и строгие типы.
Чек-лист
- [ ] Проверить требования: WordPress 6.0+, PHP 8.3+, CF7 5.7+.
- [ ] Скачать архив и сверить SHA-256.
- [ ] Распаковать в каталог плагинов и активировать.
- [ ] Включить локальные слои: honeypot, тайминг, частотный лимит, чёрный список слов.
- [ ] Отправить тестовую заявку и найти вердикт в реестре.
- [ ] Настроить AbuseIPDB: ключ, три порога, TTL кеша.
- [ ] Подключить провайдера модели и проверить соединение встроенным тестером.
- [ ] Выбрать политику при ошибке: fail-open или fail-closed.
- [ ] Задать срок хранения записей реестра.
- [ ] Настроить уведомления и проверить, что письма приходят.
FAQ
Нужна ли капча вместе с этим плагином?
Нет, и в этом смысл: слои проверки работают без заданий для посетителя. Капча остаётся крайним вариантом, если сайт атакуют целенаправленно.
Сколько стоит проверка моделью?
Зависит от провайдера и модели; в плагине есть только политика вызова — внешние проверки идут последними, поэтому платят за отправки, которые уже прошли локальные слои.
Что будет, если провайдер модели недоступен?
Зависит от настройки: fail-open пропустит заявку, fail-closed заблокирует. Ошибка попадёт в реестр.
Куда пишутся заблокированные заявки?
В реестр спама: результаты всех проверок, очищенные данные, user-agent и реферер. Экспорт в CSV и настраиваемый срок хранения.
Можно ли банить по стране?
Да, через данные AbuseIPDB. Это внешняя проверка, поэтому она идёт после локальных.
Работает ли без ключа модели?
Да. Локальные слои, AbuseIPDB и фильтр по стране работают независимо; модель — дополнительный семантический слой.
Итог
CF7 Spam Checker Pro 4.1.1 — плагин, который решает задачу правильной декомпозицией: двенадцать слоёв разной природы, дешёвые проверки впереди, дорогие — позади, и реестр с доказательствами для каждого вердикта. Проверка архива подтвердила базовое: 60 файлов, 7 751 строка, php -l чист по всем файлам, структура разделена на админку, безопасность, сервисы и хранилище. Проверить поведение на живом сайте в этом прогоне не удалось — на блоге нет Contact Form 7, а ставить форму и плагин ради статьи я не стал. История 4.1.1 заодно объясняет, зачем нужны smoke-проверки: обе блокирующие ошибки не ломали сайт, а тихо выключали защиту.
Цитата: Хорошая защита формы не спрашивает у человека, робот ли он, — она смотрит на то, как он себя вёл.
Связанные материалы
- PHP Shield: файрвол для PHP — другой подход к защите приложения на уровне запросов.
- WP Author Switcher: смена автора записей пачкой — плагин, который тоже меняет данные и требует проверки числа.
- Bash WP-CLI Update: обслуживание парка WordPress — обновления и копии для парка сайтов.
- RAGRAF 0.11.0: контракт-пин и пробы поведения — про проверки вместо обещаний.
- Репозиторий плагина — код, документация и заметки к релизу.
LLM Summary
- Тема:
CF7 Spam Checker Pro4.1.1 — плагин защиты формы Contact Form 7: 12 слоёв проверки плюс валидация языка, реестр вердиктов, управление IP и оценка моделью. - Порядок слоёв: белый список → бан-лист → тайминг → honeypot → user-agent → частотный лимит → SQLi → XSS → ссылки → чёрный список слов → фильтр по стране → AbuseIPDB → вердикт модели.
- Требования: WordPress 6.0+, PHP 8.3+, Contact Form 7 5.7+; лицензия GPL-2.0-or-later; текстовый домен
contact-form-7-spam-checker; версия в коде 4.1.1, схема базы 400. - Модель: 11 провайдеров через два протокола (OpenAI-совместимый и Anthropic Messages), строгие вердикты
SPAM/CLEAN, отдельные ключи, переопределение base URL, политика fail-open или fail-closed, встроенный тестер. - Проверено 11.11.2026: архив 1 802 159 байт, SHA-256
adf6a62b…3408f; дерево 2,6 МБ; 60 PHP-файлов, 7 751 строка;php -lбез ошибок по всем 60 файлам; крупнейшие модули — RestController 23 494 Б и Settings 20 259 Б. - Живой сайт: 24 плагина, Contact Form 7 не установлен, сам плагин не установлен — поведение в работе не проверялось.
- 4.1.1 — патч: B1 (проверка моделью была мёртвым кодом при схеме v2, чтения переведены на
llm.*), B2 (обновление 4.0 → 4.1 стирало ключ и флаг включения; миграция идёт до снимка настроек), плюс правки M1–M12, L1–L10, CI на трёх версиях PHP и переводы 396 строк на 100 % для ru/zh/de. - Правила дизайна: при внутренней ошибке пропускать отправку, внешние проверки — последними, у каждого вердикта есть доказательство в реестре.
- Ссылки: https://github.com/paulmann/Contact-Form-7-LLM-Spam-Checker, https://github.com/paulmann/Contact-Form-7-LLM-Spam-Checker/releases/tag/v4.1.1
Промт для агента (установка и настройка)
Готовый промт: скопируйте блок целиком. Источник шагов — README.md и readme.txt в архиве релиза (файла AGENTS.md в дереве нет), сверено с прогоном синтаксиса и осмотром живого сайта. Файла AGENTS.md у этого проекта я не нашёл, поэтому опора — на документацию в архиве.
Задача: поставить CF7 Spam Checker Pro 4.1.1 на сайт с Contact Form 7 и настроить защиту формы без капчи.
Материалы:
- репозиторий: https://github.com/paulmann/Contact-Form-7-LLM-Spam-Checker
- релиз: https://github.com/paulmann/Contact-Form-7-LLM-Spam-Checker/releases/tag/v4.1.1
- README в архиве релиза
Шаг 1. Проверить требования: WordPress 6.0+, PHP 8.3+, Contact Form 7 5.7+.
Шаг 2. Скачать архив релиза и сверить SHA-256.
Шаг 3. Распаковать в каталог плагинов, активировать в админке.
Шаг 4. Включить локальные слои: honeypot, тайминг, частотный лимит, чёрный список слов, ограничение ссылок.
Шаг 5. Отправить тестовую заявку и найти её вердикт в реестре.
Шаг 6. Настроить AbuseIPDB: ключ, три порога, TTL кеша ответов.
Шаг 7. Подключить провайдера модели, проверить соединение встроенным тестером, задать политику при ошибке.
Шаг 8. Выставить срок хранения записей реестра и частоту уведомлений.
Шаг 9. Проверить эскалацию: временный бан после N нарушений, постоянный после повторов.
Запрещено:
- считать слой включённым, если не отправлена тестовая заявка с записью в реестре (так проверка моделью была мертва в 4.0);
- ставить fail-closed на форму обратной связи без понимания последствий: при сбое провайдера заявки не пройдут;
- включать автобан по вердикту модели без проверки на ложные срабатывания;
- хранить записи реестра дольше, чем нужно: это персональные данные посетителей;
- ставить плагин на сервер с PHP ниже 8.3: он не запустится;
- выдавать отсутствие ошибок линта за проверку работы.
Что считать успехом: тестовая заявка сначала попадает в реестр как спам по локальному правилу, затем — как чистая при отключённом правиле; AbuseIPDB отвечает и показывает квоту; модель возвращает SPAM или CLEAN; уведомления приходят.