Хвосты Этапа 1 (журнал В-37). Строк приёмочного листа не закрывают — уборка.
1. Графа «кто внёс» в общем стоп-листе портала снесена. Она была не пустой, а
врущей: при открытом в том же браузере обычном кабинете туда записывался id
КЛИЕНТСКОГО пользователя — число, неотличимое от id администратора. Проверено
пробой: пользователь 1 → в графе 1. Админ-зона закрыта паролем nginx, своего
входа Laravel у неё нет, а сессия кабинета видна и там — то же эхо коллизии
24.07. Заполнить правдой нечем: настоящий вход админа ждёт Б-1, соседние экраны
пишут id служебной заглушки, то есть одно число во всех строках. Остались номер,
причина словами и дата — этого хватает и для разбора жалобы, и для договора с
МТС. Возврат — down() миграции.
2. Все 16 миграций модуля начинаются с «уже сделано — выходим», уникальный
индекс ключа заказа создаётся с IF NOT EXISTS. Причина не теоретическая: на бою
SQL миграций подаётся в базу руками, памяти «этот файл уже применён» там нет. У
тарифов и настроек это особенно важно — они засевают строки, и повторный запуск
завёл бы ВТОРУЮ строку настроек молча, без ошибки.
3. Восемь GRANT … TO crm_app_user стояли без проверки существования роли — на
чистой базе migrate падал бы целиком. Теперь все в гарде, как в v9.00 и v9.07.
Своя ловушка гарда: опечатка в имени роли внутри IF EXISTS ошибки НЕ даёт, права
просто не выдаются — а это ровно блокер выката В-36. Поэтому заведён сторож:
тест спрашивает у самой базы has_table_privilege / has_sequence_privilege по
каждой таблице и счётчику модуля. Заодно закрыта дыра — у фикса В-36 теста не
было вовсе.
Хвост «7 замечаний squawk» проверить его же инструментом нельзя: squawk читает
SQL, а миграции у нас PHP. Чужой отчёт не пересказываю — проверка своя и
воспроизводимая: тест прогоняет up() каждой миграции второй раз.
Тесты: +3 (повторный запуск, права ролей, «чужого следа не остаётся»), один
переписан. ClientSms 169/169, приём лидов 17/17, phpstan 0, pint чисто.
Три выреза, все покраснели: испорченное имя роли, снятая защита от повторного
запуска, возвращённая графа.
Живой прогон: вошёл в обычный кабинет (та самая опасная обстановка), внёс номер
через админ-раздел — в базе ровно три поля, чужого следа нет; убрал кнопкой.
Пять миграций запущены по второму разу прямо на dev-базе — прошли, тарифов 5,
строка настроек одна. Контроль: голый CREATE TABLE та же база отвергает.
Запись схемы — v9.10.
Этап 1 «Нельзя обжечься», задачи 1–3 приёмочного листа
(docs/superpowers/2026-07-27-PRIEMKA-client-sms-fixes.md, строки 1.4, 1.4а, 1.5).
- новая таблица sms_global_optouts (SaaS-уровень, RLS намеренно нет: номер
закрывается у всех тенантов сразу — защита договора с МТС при жалобе);
- ClientSmsRecipientSelector отсеивает такой номер ПЕРВЫМ, раньше тенантского
стоп-листа: наше обязательство перед оператором сильнее настроек клиента;
- клиенту причина видна словами — «номер закрыт администрацией», а не молчаливое
«не отправлено» (решение владельца, вопрос В-2 листа);
- админ-адреса /api/admin/sms/global-optouts: внести (номер в любом виде),
список, убрать; непонятый номер отклоняется внятно;
- запись v9.00 в db/CHANGELOG_schema.md.
Проверено: ClientSms 122/122, приём лидов 17/17, phpstan по своим файлам 0,
gitleaks чисто. Защита доказана вырезанием — без отсева падают ровно 3 теста.
Живого прогона строк 1.4/1.5 ещё нет: админ-экран идёт задачей 10.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>