fake()->unique()->words(3,true) fixes quirk #77 deterministic collision
on projects(tenant_id,name) UNIQUE in --parallel runs.
test:parallel alias = pest --parallel --recreate-databases (quirk #62/#73).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sprint 2 Phase A (modernization). Закрытие audit O-stack-01/02/03 + O-perf-07:
- O-stack-01: Pest 4 browser-tests scaffold. testsuite Browser в phpunit.xml,
Pest.php extend(...)->in('Browser'), tests/Browser/SmokeTest.php
(login-flow + deal-create-flow). На Windows native PHP плагин
pest-plugin-browser требует ext-sockets (отсутствует в стандартной
сборке) и при автозагрузке вызывает socket_create_listen() ДО старта
тестов, что ломает весь Pest. Поэтому плагин НЕ в require-dev,
тесты помечены ->skip(...) с инструкцией активации на Linux CI.
Скелет тестов в комментариях — на CI достаточно
composer require pestphp/pest-plugin-browser --dev + npx playwright
install + раскомментировать тела.
- O-stack-02: infection/infection ^0.32.7 в require-dev + app/infection.json
(minMsi=50, source: Http/Models/Services). composer mutation script.
Запуск отдельной задачей в CI (медленный). allow-plugins для
infection/extension-installer.
- O-stack-03: Laravel 13 string-based lazy-loading в routes/web.php —
убран блок use App\Http\Controllers\Api\* (16 импортов), все
ссылки заменены на строки 'App\Http\...\X@method'. Контроллеры
не загружаются автозагрузчиком при boot route'ов; класс резолвится
только при матче маршрута. Использован строковый синтаксис, а не
FQN-class — Pint default preset (fully_qualified_strict_types fixer)
сворачивает FQN-class обратно в use, на строки не действует.
- O-perf-07: Larastan result cache через tmpDir: .phpstan-cache в
phpstan.neon (cache-dir не дублируется флагом — phpstan не принимает
оба источника). lefthook job 6 (larastan) использует этот же
tmpDir автоматически. Ускорение инкрементальных pre-commit прогонов:
на правке одного файла — переанализ только зависящих, не всего
графа классов. .phpstan-cache в .gitignore.
Pest: 416/416 PASS + 2 skipped (browser smoke pending Linux CI).
Larastan: 0 errors above baseline.
Infection: vendor/bin/infection --version → 0.32.7.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- pragmarx/google2fa@^9.0 для TOTP RFC 6238.
- AuthController::login изменён: при totp_enabled=true НЕ делает Auth::login,
сохраняет auth.pending_user_id+pending_remember в session, возвращает
requires_2fa=true. /me=401 пока 2FA не пройдена.
- AuthController::verifyTwoFactor: читает pending_user_id, верифицирует TOTP
через Google2FA::verifyKey($secret, $code, window: 1) (окно ±1 = 30s).
Success → Auth::login + regenerate + clear pending + last_login_at.
- VerifyTwoFactorRequest: regex /^\d{6}$/.
- /api/auth/2fa/verify публичный (нет session-auth до verify).
Frontend:
- auth-store::login: при requires_2fa=true user остаётся null (иначе
isAuthenticated=true и guard пустит на /dashboard минуя 2FA).
- auth-store::verifyTwoFactor action.
- api/auth.ts::verifyTwoFactor(code).
- TwoFactorView: onMounted redirect на /login если нет pending state;
submit → verify → /dashboard; на error - clear code + focus first cell.
userEmail из auth.user?.email.
Pest +6 (всего 67/67 за 6.97s, 194 assertions): login для 2FA НЕ создаёт
session + verify success/неверный код/без login/валидация формата +
после verify /me=200.
Vitest +3 (всего 142/142 за 10.75s): login pending vs success state +
verifyTwoFactor success/reject. TwoFactorView spec получил setActivePinia
+ requires2fa=true для bypass onMounted-redirect.
PHPStan baseline +26 Pest TestCall warnings (накопительно).
Регресс: pint+stan passed; vitest 142/142; vite build 908ms;
story:build 21/28 за 31.28s; Pest 67/67 за 6.97s.
CLAUDE.md v1.33->v1.34, реестр Открытых_вопросов v1.42->v1.43.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Метапакет roave/security-advisories:dev-latest добавлен в require-dev.
Это conflict-only пакет — он не содержит кода, а просто помечает
известные CVE-версии других пакетов как несовместимые. При попытке
установки/обновления таких версий composer падает с conflict-resolution
до того, как уязвимый код попадёт в vendor/.
Текущий снапшот зависимостей чист: «No security vulnerability advisories
found» при установке (95 пакетов).
Закрывает Прил. Н #13. Не требует конфигурации, бинарников, env.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Установлены 3 dev-инструмента из Прил. Н фазы 1:
- laravel/pint v1.29 (форматтер PHP, Прил. Н #11) — Laravel preset
по умолчанию. Команды: composer pint, composer pint:test
- larastan/larastan v3.9.6 + phpstan/phpstan v2.1.54 (статанализ
PHP, Прил. Н #12) — level 5, paths app/bootstrap/config/database/
routes/tests. Команда: composer stan
- barryvdh/laravel-ide-helper v3.7.0 (IDE-stubs, Прил. Н #14) —
команда: composer ide-helper. Артефакт _ide_helper.php в gitignore
Конфиги:
- app/phpstan.neon — base config с includes на larastan extension
и baseline. Включены checkOctaneCompatibility и checkModelProperties
- app/phpstan-baseline.neon — зафиксированы 3 ошибки в default
Laravel scaffold (UserFactory return type covariance + 2× ExampleTest
always-true). Двигаемся вверх с этого baseline'а.
Composer scripts (composer.json):
- pint, pint:test (форматирование / dry-run проверка)
- stan (PHPStan analyse с memory-limit 512M)
- ide-helper (generate + meta)
Smoke-test'ы все 3 прошли: Pint passed, PHPStan passed (с baseline),
ide-helper:generate написал _ide_helper.php.
Фикс окружения: на этой машине composer require падал с "Permission
denied на vendor/composer/tmp-XXX.zip" из-за race condition
параллельных curl'ов. Решение: COMPOSER_MAX_PARALLEL_HTTP=1
(сохранено в memory feedback_environment).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Произошло так: при `composer require pestphp/pest pestphp/pest-plugin-laravel
--dev --with-all-dependencies` я не зафиксировал `^3` — composer подтянул
свежайшую Pest v4.7.0 + pest-plugin-laravel v4.1.0. Smoke-test
(`./vendor/bin/pest`) на default-тестах Laravel 11 прошёл 2/2 за 281 ms —
backward-compat с Pest 3 syntax подтверждён. Заказчик 08.05 (поздний вечер)
принял Pest 4 после live-проверки на стеке.
Бонус Pest 4: browser testing (без Dusk), stress testing, mutation
testing v2. Откат дёшев — `composer require pestphp/pest:^3`.
Что сделано:
- composer remove phpunit/phpunit (был direct dev-dep) — phpunit остался
как транзитивная зависимость Pest
- composer require pestphp/pest:^4.7 pestphp/pest-plugin-laravel:^4.1 --dev
- Pest.php создан через `vendor/bin/pest --init` (`tests/Pest.php`)
- `vendor/bin/pest` smoke-test: 2 passed (Unit/Feature ExampleTest), 281 ms
- `vendor/bin/pest --init` упал на интерактивном промпте «Wanna show Pest
some love?» в non-interactive PowerShell — Pest.php к этому моменту уже
создан, нефатально
Reopen(CTO-12): по правилу «явная фиксация переоткрытий» обновлены
3 источника:
- docs/Открытые_вопросы_v8_3.md v1.15→v1.16: блок «Что изменилось в v1.16»,
таблица §0 (CTO-12 переоткрыт+закрыт), запись §3 (Pest 4 + обоснование),
финальный список §X (Pest 4)
- docs/Tooling_v8_3.md v1.2→v1.3: блок «Что нового в v1.3», §3.1 п.4
(Pest 4 в boost:install), §3.4 строка 18 (Pest 4 + бонусы), §6 п.2
(конфликт Pest↔PHPUnit), §10.1 п.9 (процедура установки + warning про
--init на Windows non-interactive)
- CLAUDE.md v1.4→v1.5: §0 источники (Tooling v1.3, Реестр v1.16),
§3.2 строка 18 (Pest 4), §7 п.5 (Pest 4 в boost:install), футер с
историей версий
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>