36c8ced41e
Этап 5, Task 2. Схема v9.23 и v9.24. Графа судьбы (миграция 101100): семь колонок у client_sms_messages — delivery_status, delivered_at, delivery_checked_at, delivery_raw, provider_cost, provider_parts, refunded_at, плюс индекс «кого спрашивать дальше». Почему графа, а не переписывание status: по status считаются ДЕНЬГИ и месячный объём клиента (ClientSmsVolumeCounter берёт ровно sent), его же складывает сторож зависших и разбирают два словаря подписей на экране. Заменив sent на delivered, мы обнулили бы клиенту накопление за месяц и удешевили бы цену задним числом. Значит status остаётся фактом ПЕРЕДАЧИ, а судьба живёт рядом (В-202). Цена оператора (provider_cost) хранится КАК ЕСТЬ: единицы поля cost у МТС неизвестны (В-211), в рубли не переводится и ни во что не подставляется. Право служебной роли (миграция 101200): GRANT UPDATE на client_sms_messages. Команда опроса судьбы кросс-клиентская, ходит служебным соединением и ПРАВИТ существующую строку — новых не пишет намеренно (В-204: новые строки делали бы зависшую рассылку «живой» для сторожа, и деньги остались бы замороженными). Без права на бою команда падала бы каждые десять минут; локально не видно никогда — dev и тесты ходят суперпользователем (В-126/В-127). Проверено: - сторож MigrationGrantsTest расширен и проверен ВЫРЕЗОМ: гашение GRANT в миграции красит его; - изоляция цела — запросом к базе после наката: RLS true/true, политика tenant_isolation на месте, права ролей ровно ожидаемые, все семь колонок есть; - весь модуль 327/327 (было 320, +7 читателя судьбы), 13 пачек, все с первой попытки; phpstan ровно 2 чужие давние, pint чисто.