Беда. Платформа Билайна отдаёт отказ кодом ответа 200, а слова кладёт в тело:
{"error":{"code":"-20107","message":"Ошибка авторизации"}}. Отправка это уже
проверяла, а опрос судьбы — нет: fetchOne звал разбор напрямую, разбор поля
`error` не смотрит и возвращал пустой список, неотличимый от честного «такого
сообщения нет».
Чем грозило. Сломается ключ — судьба сообщений просто перестанет обновляться,
МОЛЧА. А за «не доставлено» клиенту возвращаются рубли (В-198), то есть
молчание = невозвращённые деньги. Тот же класс, что 04.08 с уровнем журнала.
Правка. В fetchOne перед разбором прогоняется уже готовый kodOshibki(); нашёлся
код — бросается SmsSendException с terminal: false. Не окончательный намеренно:
судьбу всегда можно переспросить следующим заходом, в отличие от отправки.
Приёмник обратного звонка платформы не тронут — у него своя дорога.
Кто ловит. PollClientSmsDeliveryCommand уже пишет Log::warning и идёт дальше;
у Билайна пачка = 1 сообщение, так что отказ стоит одного номера за заход, а не
всего прохода. Warning на бою слышен — ниже него журнал глотает (урок 04.08).
Доказано, а не «должно работать». Тест написан ПЕРВЫМ и был увиден красным:
без проверки падал ровно один тест, новый, со словами «опрос прошёл молча»,
остальные 15 стояли зелёными. Вторым тестом закрыта обратная сторона: ответ без
отказа и без действий по-прежнему считается честным «сообщения нет» и НЕ роняет
заход — иначе один забытый номер срывал бы весь опрос.
Прогон: СМС 577/577, 7155 проверок (было 575/575 и 7151 — ровно на два теста и
четыре проверки больше). Pint чист, PHPStan 0 ошибок.
На бой НЕ выкачено — отдельным разрешением владельца.
Затык был ровно один — routes/web.php: главная добавила ручку суточного итога
сторожей с машины робота, ветка добавила приёмник статусов Билайна. Ручки
независимые, оставлены обе.
Попутно поправлено примечание в config/services.php: оно говорило «заявка подана,
срок до десяти рабочих дней», а имя `Liderra.ru` было ОДОБРЕНО обеими сетями
10.08.2026 в тот же день — проверено глазами в кабинете. Примечание, которое
врёт, хуже отсутствующего: следующая смена прочла бы его и не стала поднимать
выключатель.
Замер на сведённом коде:
- модуль СМС 575/575, 7151 проверка (опора ветки была 548 — главная принесла 27);
- фронтенд 2205 из 2210. Пять падений — ЧУЖИЕ и приехали из главной:
KoshelyokZakladkiPoKanalam.spec.ts, экран AdWalletMoneyColumn.vue спотыкается
об отсутствующее поле frozen_breakdown. Доказано отпечатками: и тест, и экран
байт в байт совпадают с gitea/main, моя правка их не касалась;
- Pint и PHPStan — чисто.
Номер абонента Мотива не доходил даже до каналов: словарь операторов о Мотиве
не знал, и номер выбывал на первой заставе «кому мы вообще шлём».
Что сделано:
· словарь знает ОБА сырых написания — торговое «Мотив» от ДаДаты и юридическое
ООО "ЕКАТЕРИНБУРГ-2000" из реестра Россвязи, где слова «Мотив» нет ни разу
замер по DEF-9xx.csv: 29 диапазонов, среди 89 имён реестра столкновений нет;
· Мотив добавлен к четвёрке владельца — сетей стало пять;
· маршрут через Билайна стоит на выключателе SMS_BEELINE_SERVES_MOTIV,
умолчание ВЫКЛЮЧЕНО: до одобрения имени отправка вернёт 20230, а этот отказ
окончательный — повтора нет, сообщение умрёт;
· цена по сети, а не одна на канал: 770 копеек за Мотива. Экран сверки тоже
научен считать по сети — иначе занижал бы расход на 1,84 ₽ с сообщения;
· проверка ввода настроек и галки в админке пропускают Мотива: без этого
владелец не смог бы его отметить, а заполненный список отменяет код;
· на клиентском экране «Имя отправителя» появилась пятая строка — скрыть её
значило бы солгать умолчанием.
Тесты: модуль 548/548, было 530; 1801 проверка. Фронтенд 120/120, Sales 483/483.
Контрольные прогоны: убранное юридическое написание красит ровно четыре
зависящих от него теста, перевёрнутое умолчание выключателя — ровно один.
Не выкачено. Включать после того, как Билайн согласует имя — ориентир 24.08.2026.
**Сведение ветки в главную** (`2026-08-10-PROMT-svedenie-vetki-smeny-v-glavnuyu.md`).
Замерено: 262 коммита рабочей ветки против 14 коммитов главной, 735 файлов.
Конфликтов всего три, и все решаются «берём версию ветки» — доказано замером:
по обоим спорным файлам их версия оказалась строгой надстройкой (ноль удалённых
строк). Главное предупреждение не про сведение, а про то, что после: боевой не
равен ни одной ветке, а `routes/web.php` на бою существует в виде, какого нет
нигде в git. Выкат целиком после сведения сотрёт работающее.
**Робот МТС** (`2026-08-07-PROMT-mts-bot-kabinet-otbivaet-brauzer.md`).
Совет из письма-тревоги («нужен другой адрес выхода») ошибочен — доказано тремя
опытами: кабинет отдаёт 403 именно браузеру робота, а обычный запрос с той же
машины, и напрямую, и через прокси, получает нормальный 401. Отдельно вынесен
живой риск: оплаченная кампания идёт, а портал за ней не видит.
Оба написаны по одному правилу: сначала то, что уводит не туда, потом замеры,
потом отдельным разделом «что НЕ доказано» — чтобы догадки не выдавались за факты.
В каждом — раздел граблей, на которые смена наступила сама.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Найдено 10.08.2026 при попытке отправить починку разводки лидов: сторож секретов
объявил три находки с пометкой 152-ФЗ и запер отправку. Находки оказались не
новой утечкой, а следствием того, что настройка сторожа в главной отстала.
Что было на самом деле: в этот же день вторая смена внесла в `.gitleaks.toml`
разрешения на рабочие номера дела — по решению владельца. Это номер, С КОТОРОГО
звонит наш обзвон (в человеческой записи и в той, какой его пишет Asterisk), и
пять номеров из прайса оператора. Разрешены ПО ЗНАЧЕНИЮ, а не по файлу: сами
документы остаются под охраной, любой другой настоящий номер в них сторож
поймает по-прежнему.
Эти строки остались на рабочей ветке и в главную не попали. Поэтому ветка,
отведённая от главной, получала сторожа с устаревшим списком разрешённого — он
честно ловил уже разобранные номера и намертво запирал отправку.
Проверено после переноса: `gitleaks detect` с настройкой проекта — 8335
коммитов, «no leaks found».
NB для следующей смены: я сначала решил, будто трёх файлов настроек (.gitleaks.toml,
.gitleaksignore, .lychee.toml) в главной нет вовсе, и чуть не записал это в
историю. Проверка `git cat-file -e ветка:.файл` давала ложный отрицательный ответ
на именах с ведущей точкой. Верный способ — `git ls-tree --name-only ветка`.
Файлы на месте, отстало только содержимое одного из них.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Беда, пойманная на бою 10.08.2026: лид 683 по проекту 'B2_автозаим.рф' не
развёлся ни одному клиенту. Работа падала 35 раз с 10:35 до 16:02, потом сдалась
— лид остался недоставленным.
Причина не в поставщике и не в данных, а в разборе строки проекта. Тип сигнала
угадывался по виду строки: телефон — звонок, домен — сайт, всё остальное — СМС.
Но оба правила «это домен» были написаны только под латиницу: `[a-z0-9-]` и
окончание `[a-z]{2,}`. Строка `автозаим.рф` под них не подходила и проваливалась
в последнее правило «значит, СМС». А в справочнике поставщика тот же ключ
записан как 'site' — типы не сошлись, и разводчик отказался работать.
Замерено на бою: таких проектов пять, все на .рф — авто-займ-124.рф,
автозаим.рф, автоломбард-экспресс.рф, займ-24.рф, сфера-займа24.рф. Из них
включён один (автозаим.рф, план 15 лидов в день), остальные четыре — мины на
случай включения.
🔴 Это ТРЕТИЙ случай одной семьи. 18.05.2026 так же не распознался домен внутри
свободного текста ('B1_заявка carmoney.ru/') — тогда застряло 22 живых лида.
Оба раза причина одна: разбор судил по алфавиту, а не по строению строки.
Теперь правило одно и записано в коде объяснением: домен опознаётся по точке и
буквенному окончанию, буквы считаются любые (\p{L} + флаг u).
Граница проведена намеренно: кириллица сама по себе сайтом не считается. Слово
без точки ('АкцияЛето') остаётся СМС-ключом — иначе лечение было бы хуже
болезни. Охраняет отдельный тест.
Проверено: новые тесты сначала упали, причём три из четырёх — дословно с боевой
ошибкой. После правки 44/44 в тестах разводки и 467/467 по поставщику,
вебхукам, деньгам и имитации. Pint чист, статанализ по моим файлам чист.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Билайн умеет звонить нам сам: POST на /api/webhook/beeline-delivery/{secret}
с полями ORDID, CNRID, RESCOUNT, STATUS, FINALTIME. Раньше судьбу сообщения
добирал только опрос — теперь она приходит сразу, как у МТС и Теле2.
Защита та же, что у двух соседних приёмников: секрет короче 32 знаков или
пустой = приёмник закрыт наглухо; сверка секрета через hash_equals; список
адресов отправителя необязательный, пустой список объявляется в журнале;
ограничитель 600 обращений в минуту; на чужой адрес и на неверный секрет
отвечаем 404, а не 403, чтобы не подсказывать чужому, что дверь тут есть.
Записи в журнал уровня warning, не notice: на бою LOG_LEVEL=warning и всё,
что ниже, не доезжает вовсе.
12 тестов написаны до кода, каждый посмотрен красным. Защита проверена
подсовыванием ошибки — краснеют ровно те тесты, что должны. Модуль целиком
530/530, pint чисто, статанализ 0.
Адрес приёмника в кабинете Билайна ещё не заявлен и секрет на бою не задан.
Пока их нет, приёмник закрыт, а канал работает по-старому через опрос.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Защита оператора считает не запросы, а попытки ОТКРЫТЬ соединение. Прежний код
собирал HTTP-клиент заново на каждое сообщение: рассылка на тысячу номеров
открывала тысячу соединений подряд и выглядела для их защиты как атака, после
чего адрес уходил в чёрный список на часы. Замеры 05-07.08.2026, заявка
RUMAAS-73856: из шести одинаковых запросов проходили один-два, остальные
молча дропались, пауза пять минут доступ не возвращала.
Что сделано:
- общий обработчик curl на объект — шесть запросов идут по ОДНОМУ соединению
вместо шести. Замерено вживую на безобидном узле: начиная со второго запроса
установка соединения и согласование шифрования равны нулю, время запроса
втрое меньше — 0,13 с против 0,41 с;
- предохранитель SmsConnectionBreaker: пять обрывов подряд — и канал молчит
десять минут, в сеть не выходя вовсе. Это лечит главное: раньше каждая
неудачная попытка сыпала семь безответных стуков и сама продлевала
блокировку. Считаются ТОЛЬКО обрывы связи; прикладной отказ вроде
отсутствия имени отправителя предохранитель не трогает — сеть-то жива;
- ожидание соединения 3 с вместо прежних десяти, чтобы не копить оборванные
попытки, и своё имя клиента LiderraSms/1.0 вместо безымянного робота.
Подменяется обработчик, а НЕ клиент целиком: подмена клиента отключила бы
подделку сети в тестах, и они пошли бы в настоящий интернет. Записано
комментарием в коде.
Поведение отправки не меняется. Мост SMS_MTS_CONNECT_TO не тронут, заявка
RUMAAS-73856 остаётся открытой.
Проверено: 500 тестов канала зелёные, статанализ ноль замечаний, оформление
чистое. Новый сторож перед починкой был красным.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
По прямому слову владельца 07.08.2026 («выкати в мэйн, только всё проверь, чтоб никого не потёр»).
🔴 ЗАЧЕМ. Замерено смено́й 33: тело сторожа `172931cf` лежало в 13 рабочих папках из 23,
включая ЭТУ ветку. В нём не было НИ ОДНОЙ починки от 05.08 и 07.08. Хук подключён в настройках
ОТНОСИТЕЛЬНЫМ путём, значит исполняется тело, лежащее рядом с папкой сессии, — и всякая
НОВАЯ рабочая папка, отведённая от главной ветки, рождалась с дырявым сторожем.
🔴 ЧТО ПРОПУСКАЛО СТАРОЕ ТЕЛО (замерено живым вызовом, одним прибором по обоим файлам):
· php artisan db:wipe --force --database=liderra
· php artisan migrate:fresh --force
· dropdb "$PROD_DB" (цель спрятана в переменной)
· psql "$PROD_URL" -c "DROP SCHEMA public CASCADE"
Четыре красных из семи образцов. Контрольный образец (снос кластера) старое тело
останавливало — значит замер работал и не красил всё подряд.
🟢 ЧТО СТАЛО. Тот же замер по этому файлу после доставки: 0 красных из 7. Обе мирные
команды (обычная миграция `php artisan migrate`, снос тестовой базы `dropdb liderra_testing`)
по-прежнему проходят.
ЧТО ИМЕННО ДОБАВЛЕНО В СТОРОЖА:
4. штатная программа dropdb (стоит на этой машине)
5. SQL DROP SCHEMA … CASCADE — базу оставляет, содержимое выносит
6. снос средствами приложения: db:wipe / migrate:fresh / migrate:refresh
+ короткие имена облака (yc mdb pg / yc mdb postgresql)
+ цель, спрятанная в переменной, обязана быть названа словами
🔴 Цена строгости, названная честно: снос ТЕСТОВОЙ базы по связи из переменной теперь тоже
останавливается — сторож не может знать, что под переменной тестовая. Обе двери владельца
(маркер PROD-DESTROY-OK и ALLOW_PROD_DB_DESTROY=1) работают.
Указатель `prod-db-pointer.mjs` доставлен тем же коммитом: он рассказывает сессии, что ловит
сторож, и в старом виде ВРАЛ — называл 5 приёмов из 6 и прямо обещал, что снос со спрятанной
целью пройдёт.
🔴 ЧТО ПРОВЕРЕНО, ЧТОБЫ НИКОГО НЕ ЗАТЕРЕТЬ:
1. различия обоих файлов просмотрены построчно: из прежних тел не пропало НИ ОДНОЙ мысли —
все прежние строки либо сохранены, либо заменены своими же расширенными видами;
2. в этой папке идёт ЧУЖАЯ незакоммиченная работа по СМС (правка MtsSmsProvider.php,
новые SmsConnectionBreaker.php и MtsSmsProviderConnectionTest.php, ПРОМТ-следующей-смене.md).
Она НЕ тронута и в этот коммит НЕ входит: добавлены строго два файла поимённо;
3. 🔴 на сервер GitHub НИЧЕГО не отправлено. Замерено: тамошний main разошёлся с местным
очень сильно — местный впереди на 3572 коммита, серверный впереди на 1525, последний
серверный коммит от 03.06.2026. Любая отправка туда либо будет отвергнута, либо снесёт
полторы тысячи чужих коммитов. Это отдельное решение владельца, а не часть этой работы.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Продолжение предыдущего коммита. Возвращённые документы сами ссылались на два
файла, которых в git тоже не было: HANDOFF по состоянию клиентской рассылки и
её design-spec от 25.07. Оба лежали только на диске — вернул.
Осталась одна ссылка, которую возвратом не починить: приёмка от 27.07 указывала
на файл внутри временной рабочей папки `.claude/worktrees/client-sms`, а такой
папки давно нет. Переписал на настоящий путь в репозитории — файл на месте,
строка та же.
Итог: `lychee` командой самого хука даёт 0 ошибок (было 20). Отправка на сервер
снова открыта — она была закрыта для ВСЕХ смен, а не только для этой.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Найдено 07.08.2026 при попытке отправить починку сторожа пульса: проверка ссылок
падала на 20 битых ссылках, и ни одна не была моей. Оказалось, что закоммиченные
документы ссылаются на файлы, которые лежали только на диске рабочей машины и в
git не попали ни разу. То есть главная не проходила проверку сама по себе — и
отправка была закрыта ДЛЯ ВСЕХ смен, а не только для меня. У соседней смены на
этот момент висело 5 неотправленных коммитов.
Что вернулось: разборы и промты смен по клиентской СМС-рассылке (этапы 1-5,
задачи 5-9, конец июля) и три промта смен 25-26 по кошельку и телеграму от
06.08. Все 16 — ровно те, на которые ссылались уже сохранённые документы, то
есть их отсутствие было пропажей, а не задумкой.
Отбор не на глаз: список получен из вывода lychee, каждый файл проверен на
диске и на отсутствие в git.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Живой случай 07.08.2026. Выкат в 12:04 добавил три ежедневные команды
(obzvon:chistka 03:45, obzvon:chistka-materialov 03:50, lena:proverka-obhoda
06:10). Их время в этот день уже прошло, записи пульса не было — и часовой
сторож с окном дедупа 60 мин заводил три жалобы КАЖДЫЙ ЧАС, каждая со своим
письмом владельцу. К утру набежало бы под полсотни. Тем же способом 05-06.08
набралось 18 записей про incidents:watch-failures.
Две правки:
1. Нет записи пульса — не жалуемся, а заводим отметку «команда известна с
этого мгновения» и молчим, пока не выйдет срок первого запуска: два
ожидаемых интервала, отсчёт от отметки. Не отработала за срок — жалоба будет.
2. Команда снова здорова — её открытые жалобы гасятся. Без этого записи копятся
навсегда: 06.08 сторож починился в 20:37, а 18 жалоб остались открытыми, и
лампа «Здоровье портала» горела сутки после устранения причины. Кнопки
«закрыть инцидент» в админке нет.
Якорь пробел-тире в поиске жалобы обязателен: без него имя команды совпало бы с
любой другой, начинающейся так же. Обратный слэш в именах джобов экранируется —
в LIKE Postgres он служебный.
Тесты: 5 новых, из них 3 сначала красные, каждый по своей причине; 2 сторожевых
держат границы — молчание не вечно, больную команду не закрываем. Прогон 13/13,
соседние 84/84, composer stan 0 ошибок, pint чисто.
Выкачено на боевой 07.08.2026 14:30 МСК: тревог стало 0 вместо 3 в час.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
07.08 в 10:31 МТС одобрил кампанию №15. Числа сошлись с посчитанными ЗАРАНЕЕ
до копейки: кабинет взял 182.40, наценка 1.4 → списано 255.36, остаток 13.44
отпущен в ту же секунду, баланс 9869.50 → 9614.14, заморожено → 268.80.
Одобрение чуть не потерялось: кабинет написал седьмое слово «Запущена», словарь
робота знал шесть. Снаружи это неотличимо от «вердикта нет» — вскрыли, заглянув
в кабинет разовой пробой на чтение. Чинились ОБЕ половины (разбор и поиск ряда),
как предупреждает комментарий в коде; сторожа на обеих, тесты робота 202 → 206.
Непроверенной во всём блоке осталась одна вещь — живая половина пересдачи Т-М4:
для неё нужен свежий отказ МТС, а кампания одобрена.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Дверь проверяет адрес из настройки. Если разрешённая машина отвечает «иди на
другой адрес», перевозчик по умолчанию туда идёт и везёт наш заголовок дальше:
при уходе на чужой адрес Guzzle снимает только Authorization и Cookie, а
X-Render-Key и X-Obzvon-Key не из них.
Замерено до правки на подставных машинах: ключ рендера доехал до неразрешённой
машины из ленты Яндекса, из поиска клиентов одиночным запросом и пачкой, из
плитки прокси — и плитка при этом горела зелёным, показывая ЧУЖОЙ IP как свой.
Ключ голосового робота уезжал вместе с заданием.
Поставлен withoutRedirecting в пяти строениях запроса четырёх файлов. У
SelfRenderClient их два: пачка строит запрос сама, и запрет из одиночного пути
туда не доезжает — проверено вырезанием, снятие запрета только с пачки роняет
отдельного сторожа и даёт по утечке на каждый адрес порции.
Новых настроек не заводил: тумблер «сюда переадресовывать можно» и был бы той
дырой, ради которой запрет ставится.
Сторожа: app/tests/Feature/Security/KlyuchNeEdetPoPereadresaciiTest.php, 8
проверок, смотрят на запись переговоров, а не на текст исходника. Каждый показан
красным снятием своего запрета по одному. Отдельная проверка «датчик сам жив»
падает, если подставная сеть перестанет ходить за переадресацией, — иначе
остальные сторожа молча стали бы пустыми.
Отчёт смены: docs/superpowers/priyomka/stroyka-7/otchyot-pomoshchnika-zapret-pereadresacii-2026-08-07.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Круг смены 6 оборвали средой, работа помощника лежала в дереве несохранённой и её снёс бы
первый же чужой откат. Замерил целостность, прочёл его отчёт, прогнал своей рукой, вынес
приговор. Заново ничего не переделывал.
ПРИНЯТО. Третья и последняя дверь с ключом рендера закрыта той же проверенной дверью.
Список разрешённых машин общий с рендером и это доказано работой, а не доводом. Закрытая
дверь красит СЕРЫМ, а не красным, и ключ при этом физически никуда не уходит, замерено мной
двумя случаями по отдельному запуску на каждый.
Помощник сверх задания нашёл и починил ложь, которой никто не искал: подпись плитки называла
серый словами не отвечает, то есть гнала владельца продлевать исправные прокси.
Мои ошибки называю первыми, их три.
Пятьдесят девятая. Я назвал adminDashboardHelpers.ts чужим файлом соседней смены прямо в
промте смене 7, в списке не трогать и в коммит не брать. Взял из головы, не замерив. Вся
правка в этом файле - работа моего же помощника. Исполни следующая смена мой промт дословно,
починка подписи плитки не попала бы в коммит и сгорела.
Шестидесятая. Мой снимок оборванного круга был неполон: записал шесть файлов, их семь.
Перечень собрал командой, но раньше, чем помощник закончил, и не перемерил перед тем, как
назвать его окончательным.
Шестьдесят первая. Мой датчик прогона искал не то слово и я чуть не объявил состоявшийся
прогон несостоявшимся. Спас узкий прогон, которым я прибор перепроверил.
Вырезание второго рода дало дыру. Допущение защиты: дверь проверяет адрес из настройки и
считает, что запрос уйдёт именно туда. Случай мимо: разрешённая машина отвечает иди на другой
адрес. Замерено на двух подставных машинах: тайный ключ уехал на машину, которой в списке
разрешённых НЕТ, а плитка осталась ЗЕЛЁНОЙ и показала чужой адрес как свой. Перемерил на
живой двери боевого Поиска клиентов тем же ножом, отдельным запуском - то же самое. Причина
прочитана в исходнике библиотеки: при уходе на чужой адрес снимаются только два заголовка,
и нашего среди них нет. Вторым прибором замерено, что отключения переадресации нет нигде во
всём боевом коде. Это не отменяет Р126, дверь делает обещанное на своих случаях, но обещание
шире дела и про это не сказано нигде. Отдельным кругом.
Датчики: полный прогон моей рукой на своей базе 5344 проверки, 5340 зелёных, 4 пропущено,
красных 0, арифметика сходится. Проверки интерфейса 3 из 3. Разметка 0 ошибок.
Смену остановил владелец. Довожу до логического конца.
Проверка 1 задачи З-0.10 закрыта ЖИВЬЁМ. Доказать её раньше 02:20 ночи было
нечем, я записал замену и назвал вслух, чего она не доказывает. Дождался и
замерил: 07.08.2026 в 02:20:01 машина робота обошла своих сторожей САМА, без
человека, за две секунды, все три зелёные, итог лёг со своей датой. Уборка
записей чужих голосов перестала зависеть от человеческой памяти, и это замерено,
а не обещано. Журнал того же обхода честно говорит отправка не настроена:
портальная половина не выкачена, и краснота одной проверки из 42 у сторожа обхода
и есть датчик завершённости выката.
Круг 7 оборван на середине. Помощник закрывал третью дверь с тем же тайным ключом
и не успел сохранить работу коммитом. Файлы перечислены поимённо в промте смене 7,
раздел 3.2, вместе с порядком: замерить целостность, прочесть его отчёт, прогнать
своей рукой, вынести приговор, НЕ переделывать заново. Приёмочный лист по этому
кругу не заведён, и это записано.
Итог смены. Задач 54, врезок ПОСТРОЕНО 19, требований 112, решений владельца 127.
Полный прогон 5301 проверка, 5297 зелёных, 4 пропущено, красных 0.
Мои ошибки: их одиннадцать, с 48 по 59, и все одного класса, прибор мерил не то,
о чём я спрашивал. Тяжелейшие три. Пятьдесят пятая: построил главный довод
задания на ложном замере, назвав упоминания зовущими. Пятьдесят восьмая: назвал в
задании одну дверь с тайным ключом, а их оказалось три. Пятьдесят девятая: вынес
владельцу цену решения, взятую из головы, а не из замера.
Правил стало 30, шесть добыты этой сменой.
Срок обеих кампаний истёк 07.08 в 00:00 UTC, портал сам закрыл их и вернул
неоткрученное. Числа сняты с боевой базы, арифметика сходится до копейки:
833.50 - 9.00 = 824.50 и 833.50 - 40.50 = 793.00.
Я-Д5 воспроизвёлся трижды за четыре секунды на разных суммах — возврат по сроку
работает не по случайности.
Предсказание сбылось наполовину: Я4 обещали ~160 показов, вышел 81; Я3 обещали
ноль, вышло 18. Скорость по одному часу для суточного предсказания не годится.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Решение владельца Р127 исполнено коммитом 56ddaa1e. Мой нож замерил: восьмёрка и
семёрка дают теперь ОДИН знак и ОДНО тело, границы держатся, семизначный и
тринадцатизначный номера не тронуты, казахстанский со своим знаком, и правило
устойчиво к повторному приведению.
Моя ошибка первой, пятьдесят девятая. Вынося развилку владельцу, я написал, что у
одиннадцати цифр с восьмёркой второго толкования нет. Я взял это из головы, НЕ
ЗАМЕРИВ. Второе толкование есть: иностранные номера длиной ровно одиннадцать
цифр с кодом на восьмёрку. Большинство дают несуществующий российский номер, но
вьетнамский превращается в живой подмосковный код, и род ошибки меняется с
звонок не состоится на звонок состоится, но не тому человеку.
Правило велит выносить развилку с полным набором дорог. Дороги я вынес полные, а
цену неполную, и владелец решал, не зная её.
Смягчает это одно, и не моя заслуга. Помощник докопал дальше и поправил САМ СЕБЯ:
общий нормализатор телефонов портала живёт в двадцати файлах, включая сам обзвон,
и делает ровно то же самое. Вьетнамский номер портится на входе везде уже
сегодня. Р127 опасности не добавляет, он прекращает расхождение обзвона с
остальным порталом.
Его находка, которую я перемерил своим прибором и подтверждаю: пара из десяти и
одиннадцати цифр даёт ДВА знака на одного человека, и ему позвонят дважды. При
этом общий нормализатор и проверка запрета звонить считают ту же пару ОДНИМ
человеком. То есть беда, ради которой принято Р127, на этой паре жива. Он не стал
чинить и правильно: лечение шире решения, надо сводить три разных канона
телефона, живущих в обзвоне, а один из них сторожит запрет звонить. Вынесено
открытым вопросом первым номером.
Ошибка помощника, названная им первой, редкого рода: он назвал цену нечестно и
едва не отдал её в таком виде, подав общую беду портала как новую опасность
решения. Проверил дальше и поправил себя прямым текстом. Это ровно то, чего метод
требует от надзирателя и чего надзиратель на этот раз не сделал: сперва замер,
потом слово.
Датчики: решений 127, разметка 0 ошибок, правописание протокола 0 жалоб.
Прогон 5301 проверка, 5297 зелёных, 4 пропущено, красных 0.
Решение владельца Р127. В `odinVidNomera` восьмёрка приводится к семёрке
ТОЛЬКО когда цифр ровно одиннадцать. Короткие и длинные номера не трогаются
вовсе: слить двух разных людей в один знак — ошибка тише и хуже, второму не
позвонили бы никогда и молча.
Замерено до правки: `8 999 000-00-01` и `+7 999 000-00-01` давали два разных
знака и два разных тела. Сторожа смотрят на то, что вправду уехало роботу, и
сравнивают дословно — так же, как сравнивает приёмник.
Сторожа показаны красными тремя сломами: без починки, без проверки длины и с
приведением к восьмёрке вместо семёрки. Возврат доказан слепком файла.
Названная владельцу цена: иностранные одиннадцатизначные номера с кодом на
восьмёрку это правило портит. Вьетнамские `8496…`/`8497…`/`8498…` станут
похожи на живые коды Московской области — робот позвонит чужому человеку.
Лечится чисткой номеров на входе, это отдельная задача. Подробности и
незакрытые хвосты — в отчёте.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Решение владельца Р126 исполнено коммитом 92472816, и исполнено ШИРЕ, чем я
заказал.
Моя ошибка первой, пятьдесят восьмая. Я назвал в задании ОДНУ дверь, через
которую уходит тайный ключ боевого Поиска клиентов. Помощник замерил: дверей
ДВЕ, и вторая шлёт тот же ключ по той же настройке. Перепроверил вторым прибором
на версии до его правки: у второй двери был ровно тот же вид проверки и тот же
заголовок с ключом. Почини он только названный мной файл, решение владельца
осталось бы невыполненным, а дыра открытой. Отсюда правило 29: одно найденное
место это находка, а не перечень.
И третья дверь нашлась потом: та же тайна уходит из плитки срока прокси по
другой настройке. Помощник назвал её и НЕ тронул, правильно. Перемерил сам,
вынес владельцу, закрыто отдельным заходом.
Мой нож прогнал одиннадцать форм адреса на боевом окружении с умолчанием списка
машин из хранилища. Пять законных форм открыты, шесть опасных закрыты, и главное
опечатка в адресе машины ловится, а именно она уводила ключ чужому молча и
навсегда. Обратный край тоже верен: при пустом списке дверь закрыта совсем, а
местный адрес без шифрования проходит только не на боевом.
Помощник назвал свою ошибку первой, и она новая для этого проекта: он запустил
полный прогон раньше, чем закончил правку стиля, и через четыре минуты форматтер
переписал два его файла прямо во время прогона. Зелёный цвет такого прогона не
доказывает ничего. Отсюда правило 30.
Вторая его ошибка честнее первой: проверка покраснела на его же оплошности, и
первым побуждением было подогнать код под ошибку. Поправил проверку.
Оговорка, которую называю вслух. Боевое значение настройки адреса НЕ ЗАМЕРЕНО и
замерить его нельзя, это доступ к боевому. Перед выкатом нужна одна команда,
показывающая, начинается ли адрес с шифрованной схемы. Начинается с
нешифрованной, выкатывать нельзя: лечение не обход, а перевод на шифрование,
сертификат на виртуалке уже работает.
Датчики: разметка 0 ошибок, ссылки живые.
11 задач по TDD: два сервиса на бэкенде (вердикт с полоской и лента дел из
пяти источников), ручка подгрузки, живая сортировка списка клиентов, типы и
маппер, русские фразы, панель Vue, точка состояния в списке, полный прогон,
приёмка глазами.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Тайный ключ SELF_RENDER_KEY уходил заголовком X-Render-Key на любой адрес, какой окажется
в настройке. Проверялось ровно одно: адрес не пуст и ключ не пуст. Отсюда две молчаливые беды:
опечатка в SELF_RENDER_ENDPOINT увела бы ключ чужому человеку навсегда и без признаков,
а http:// увёз бы его открытым текстом.
Дверь повторяет проверенное устройство Р123 от голосового робота: своя причина на пустой
адрес и на пустой ключ, список разрешённых машин с пустым списком как «закрыто всё»,
обязательное https кроме своей машины, а «своя машина» — два независимых ответа: окружение
из положительного списка И двоичная проверка петли 127.0.0.0/8, ::1, localhost по RFC 6761.
Закрытая дверь = мягкая деградация: наружу не ходим, отдаём пусто, причина в журнал уровнем
warning, потому что на боевом всё ниже не доезжает.
Тумблера «можно без шифра» нет намеренно: он и был бы той самой опечаткой.
Дыр было ДВЕ, а не одна. Тот же ключ и тот же SELF_RENDER_ENDPOINT уходят из
RenderServiceYandexListSource — лента Яндекс.Карт. Починка только названного файла оставила
бы дыру открытой, поэтому дверь общая на обе.
Боевой поиск не сломается: умолчание списка НЕ пустое и содержит замеренный боевой адрес
виртуалки 51-250-1-97.sslip.io — пустое умолчание остановило бы поиск в момент выката.
Набор тот же, что владелец уже принял для OBZVON_ALLOWED_HOSTS: это одна и та же машина.
Прогнаны все формы адреса, какие могут стоять в боевой настройке, в окружении production.
Проверок 37, сторожа показаны красными пятью отдельными сломами, возврат доказан слепками.
Larastan 0, Pint чисто.
Не закрыто и ждёт слова владельца: ProxyMarketProbe шлёт тот же ключ на другую настройку
без двери; боевое значение SELF_RENDER_ENDPOINT не замерено, на боевой не ходил.
Отчёт: docs/superpowers/priyomka/stroyka-6/otchyot-pomoshchnika-r126-dver-rendera-2026-08-07.md
Портальная причина дыры закрыта коммитом a4ff69aea. Помощник выбрал путь, где
портал считает опознавательный знак задания САМ внутри вызова, а наружу
принимает то, из чего знак складывается. Рассогласовать стало нечего.
Довод, которого не было в моём задании и который решил дело: второй путь всё
равно потребовал бы сменить подпись, потому что для сверки знак надо
пересчитать. То есть он платит ту же цену, а взамен даёт лишний довод, который
может быть только неверным, и новый исход дверь закрыта. А чем кончается
закрытая дверь? Человеку не позвонили. Второй путь не убирает вред, а делает его
громким.
Мой нож мерил не текст, а то, что вправду уезжает роботу. Один человек в двух
записях даёт один знак И одно тело. Строка без цифр закрывает дверь, наружу не
уходит ничего. Вторая попытка и другой клиент дают другой знак.
И мой нож замерил цену того, что помощник НЕ стал чинить осознанно: человек,
записанный восьмёркой и семёркой, считается двумя разными, и ему позвонят
дважды. Он нашёл это сам и вынес владельцу, потому что ошибка в обратную сторону
тише и хуже: слияние двух разных людей означало бы, что второму не позвонят
никогда и молча. Владелец закрыл это решением Р127: восьмёрку приводим к
семёрке только у одиннадцатизначных номеров.
Лучшая ошибка смены, названная помощником первой: его главный сторож был слеп
ровно к той половине беды, ради которой писался. Он сверял согласие с точностью
до приведения номера, а робот сравнивает тело дословно. Вернул беду в код,
сторож остался зелёным. Не сломай он свою починку руками, сдал бы работу с
дырявым главным сторожем.
Промт смене 7 написан: docs/superpowers/2026-08-10-PROMT-obzvon-stroyka-4.md.
В нём правила стройки вперёд всего, 57 ошибок надзирателя, 28 правил, числа
собраны командой, порядок работ и три ловушки, которые выглядят правильными.
В промте смены 6 поставлен указатель, что он отработан.
Датчики: задач 54, врезок ПОСТРОЕНО 19, требований 112, решений 127.
Разметка 0 ошибок, правописание протокола 0 жалоб, все ссылки промта ведут к
живым файлам.
Задание 88 закончилось после выката и принесло ту же дату старта, а время
изменения кампании №15 осталось от прошлого задания. Раньше совпадало с концом
каждого задания до секунды — значит строку больше не трогают и счётчик 48 часов
пошёл по-настоящему.
Никого не стёрли: перепись боевого 1031 файл до и после, боевая копия файла до
выката побайтово равна прежней версии, после — новой.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Кампания, застрявшая на модерации дольше 48 часов, обязана уйти в ручной
разбор — иначе висит вечно с замороженными деньгами клиента и молча.
Срок считается по времени изменения строки, а вчерашняя правка «день начала
показов» писала дату при КАЖДОЙ проверке робота массовым обновлением, которое
всегда двигает updated_at. Робот ходит раз в четверть часа — счётчик не
досчитал бы никогда. Защита была цела на вид и мертва на деле.
Улика с боевого: у кампании №15 время изменения строки совпадало с концом
последнего задания робота до секунды, при том что вердикта не было третьи сутки.
Пишем дату, только если она вправду изменилась. Сравнение сырых значений мимо
приведения типов: starts_on это date:Y-m-d, сравнение с Carbon разошлось бы молча.
Сторожа SchyotchikZavisshejModeraciiTest (3 шт) видены красными до правки.
Блок телеграма 381 зелёный на своей базе, статанализ 0, pint чисто.
На боевой НЕ выкачено.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix: письма о недоливе — одна сводка в сутки вместо потока на каждом прогоне
Сверка с поставщиком шла каждые 30 минут и на каждом прогоне слала письмо
на КАЖДУЮ пару (тенант, дата) с недоливом больше 20%. Вчерашняя дата остаётся
в окне сверки, поэтому поток рос день ото дня: 144 письма 27.07 → 510 за 06.08,
около 3000 за 12 дней на ops@liderra.ru.
Теперь пары собираются в один список и уходят одной сводкой раз в сутки
(отсечка по московской дате в redis; гонки нет — handle держит общий lock).
Частоту самой сверки не трогал.
Поштучный TenantBusinessDriftAlertMail с шаблоном удалён — стал мёртвым.
Тесты: сводка собирает все пары в одно письмо, второй прогон в те же сутки
молчит, в новые сутки письмо уходит снова. Красными их видел.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@
Замерено на боевом: вкладка читает журнал сделок, где 364 из 419 строк —
системные «робот залил лид». Данные про входы и про дела клиента лежат в
двух других журналах и на вкладку не попадают вовсе.
Решения владельца: вердикт + полоска 90 дней + лента только дел, пороги
3 и 14 суток, входы только сверху, заодно чиним мёртвую сортировку списка
клиентов по последней активности.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Портальный клиент голосового робота считал опознавательный знак задания от
ОДНИХ ЦИФР телефона, а в тело задания клал номер КАК ЕСТЬ. Один и тот же
человек, записанный `+7 999 000-00-01` и `79990000001`, давал ОДИН знак и
РАЗНЫЕ тела. Приёмник робота сравнивает тело дословно и на такую пару
отвечает отказом — то есть честный повтор получал отказ вместо звонка.
Вторая половина той же беды: знак и телефон приходили в `pozvonit` ДВУМЯ
независимыми доводами, и ничто не заставляло их сойтись. Работник очереди,
посчитавший знак один раз и перебирающий номера, отправил бы роботу
рассогласованную пару, и человеку не позвонили бы молча.
Что сделано:
- заведено ОДНО место приведения номера — `ObzvonClient::odinVidNomera`;
оба потребителя берут номер оттуда: и знак, и тело задания;
- `pozvonit` больше НЕ принимает знак снаружи. Он принимает то, из чего знак
складывается — арендатор, кампания, номер, попытка, — и складывает знак сам.
Рассогласовать нечего: второго номера в клиенте не существует;
- отказ по номеру теперь считается по признаку "в номере ноль цифр", а не по
`trim`. Раньше строка вроде "абв" проходила дальше и давала ОДИН И ТОТ ЖЕ
знак на все такие задания — два разных задания столкнулись бы знаками у
робота, и одному из людей не позвонили бы вовсе;
- в докблоке `pozvonit` вслух записано следствие решения владельца Р124:
номер попытки нельзя прибавлять на исходе, где звонок МОГ состояться, —
иначе знак станет другим и защита не сработает именно в том случае, ради
которого заведена.
Разбор ответа робота, пределы времени, предел одновременных и проверка
шифрования Р123 не тронуты.
Мерки. Сторожа роняли работой, а не текстом: возвращали беду в код и
смотрели на то, что уходит подставному роботу.
- вернул в тело номер "как есть" — 4 красных из 55;
- вернул знак доводом снаружи — 1 красный из 55, сторож читает доводный ряд
метода через `ReflectionMethod`, а не текст файла;
- возврат доказан слепком со снятием невидимых знаков конца строки,
после возврата 55 из 55 зелёных;
- полный прогон на своей базе: 5293 проверки, 5289 зелёных, красных 0,
ошибок 0, пропущено 4; арифметика сходится;
- `composer stan` — 0 ошибок, `pint --test` — passed.
Открытым оставлено и названо в отчёте: `8...` и `+7...` сегодня разные люди
для портала; `dopolnitelno` обязано быть одинаковым у повтора; правило счёта
попыток не назначено.
Отчёт: docs/superpowers/priyomka/stroyka-6/otchyot-pomoshchnika-znak-tela-2026-08-07.md
Проверка на второй заход опровергла мой же разбор. На СТАРОМ коде служба
отступала пять раз подряд, пока окно владельца было открыто, и вернулась к
работе ровно в минуту закрытия — защита работала. Довод «в журнале нет ни
одной строки ПРОПУСК» был замером через полминуты после открытия окна:
пустой журнал доказывал лишь, что окно ещё не дожило до проверки.
Причина закрытия окна НЕ установлена — записано открытым, с рабочим обходом.
Починка сравнения путей остаётся как укрепление: старая проверка держалась
на случайном вспомогательном процессе, слепом первые секунды.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Владелец не мог войти руками: окно закрывалось раньше, чем он успевал.
Защита службы «профиль занят» сравнивала путь буква в букву и искала обратные
косые черты, а браузер подписывается прямыми — совпадения не было никогда.
Улика: за всю жизнь службы в журнале ноль строк «ПРОПУСК: профиль занят».
Починка живёт вне git (C:\liderra\mts-telegram-robot): сравнение вынесено в
etoNashProfil под 5 сторожей, тесты робота 197 -> 202. Проверено живьём:
вход сохранён, сторож ALIVE, задание 52 ok=true, служба дважды отступила
при занятом профиле.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>