Создавать проекты можно всегда; баланс проверяется в момент ЗАПУСКА
(создать-и-запустить / запустить / возобновить одиночно и пачкой / автоподбор)
под замком на клиента (без гонок). Не хватает — проект остаётся на паузе с
меткой preflight_blocked_at, клиенту сообщение в рублях (сколько пополнить).
Групповой запуск «сколько влезло». Нет активного тарифа на дату → запуск
запрещён (fail-closed). Гейт реквизитов первого проекта добавлен и в автоподбор.
- LaunchBalanceGate — единый гейт вместо 3 копий preflight (ProjectController
store/update, AutopodborController), под DB::transaction + lockForUpdate(Tenant).
- ProjectService::create($launch) + новый setActive(); bulk resume «сколько влезло».
- AutopodborProjectCreator: пачка в транзакции через общий ProjectService::create.
- Идемпотентность box/phone_type миграций автоподбора (Schema::hasColumn guard).
- Тест-инфра: afterRefreshingDatabase восстанавливает месячные партиции.
Тесты фичи 40/40 зелёные. Спека и план — docs/superpowers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
B1 из прогона тупой пользователь 24.06: RunwayCalculator считал от расхода
за 30 дней, из-за чего на дашборде показывал 0 дней при полном балансе и
раздувал число на короткой истории. Теперь считает от дневного заказа
активных проектов requiredLeadsForTomorrow, как витрина ёмкости биллинга,
и дашборд совпадает с биллингом. Тесты переписаны под новое поведение.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code-quality review fixups: runway_days клампится в 0 при отрицательном
балансе (overdrawn-тенант не должен показывать «−N дней»); (int)-каст в
wallet() для консистентности; усилены assertJsonPath на type-фильтре.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>