Безопасность довёл до ума — Cursor-генерацию переписал руками. IDOR закрыл, CSRF задушил, refresh rotation теперь как надо. HSTS на staging, ENABLE_DOCS=false, install.env recovery протестил. Backend: - jwt_denylist + auth_epoch: мгновенный revoke access JWT (logout/block/reset) - auth/admin/users: bump epoch, logout с Bearer, forgot_password skip для blocked - install_secrets: путь всегда apps/api/data/secrets/ (bootstrap из корня не ломает Docker) - seed: SEED_DEMO_USERS=false на prod/staging - тесты: jwt revoke, integration, coverage gate 90% Frontend: - logout шлёт Bearer, обработка TOKEN_REVOKED - guards TypeScript fix - E2E: blocked user → 401 сразу после block Infra: - staging/prod compose, TLS nginx, deploy-скрипты - k6 §17.2, backup/health/smoke scripts Docs: - docs/ на русском: project, security, deploy, release (старые md слили) - README короткий + план ТЗ + стандартные логины dev Код готов к плаванию. Капитан может идти писать фронт.
3.0 KiB
3.0 KiB
Релиз и QA gates
Перед production не «авось прокатит», а чеклист из ТЗ §17. Если что-то красное — сначала staging, потом prod. Живёт один раз.
Pipeline
flowchart TD
CI[CI green] --> ST[Staging + TLS]
ST --> E2E[E2E 11/11]
ST --> K6[k6 pass]
ST --> ZAP[ZAP 0 High/Crit]
ST --> LH[Lighthouse ≥ 85]
E2E --> PROD[Production deploy]
K6 --> PROD
ZAP --> PROD
LH --> PROD
PROD --> SM[smoke-prod.sh]
SM --> DNS[DNS cutover]
DNS --> MON[24h мониторинг]
Регрессия E2E
Последний локальный прогон: 2026-07-14 — backend 137 / 90.43% cov, frontend 46.
Staging
E2E_BASE_URL=https://staging.example.com E2E_START_API=false pnpm --filter web e2e
| # | Сценарий | Local | Staging |
|---|---|---|---|
| 1 | Landing hero, reduced-motion | ☐ | ☐ |
| 2 | Register → verify → login → profile → logout | ☐ | ☐ |
| 3 | Forgot → reset → login | ☐ | ☐ |
| 4 | Admin publish → public slug | ☐ | ☐ |
| 5 | Block user → login denied | ☐ | ☐ |
| 6 | Pending → нет /profile |
☐ | ☐ |
| 7 | Refresh rotation | ☐ | ☐ |
| 8 | IDOR user A ≠ user B | ☐ | ☐ |
| 9 | Admin не блокирует себя / last admin | ☐ | ☐ |
| 10 | Avatar: bad MIME / size / SVG | ☐ | ☐ |
| 11 | Block → access JWT 401 TOKEN_REVOKED | ☐ | ☐ |
Локально
pnpm --filter web e2e # API :8001, Vite :5175
k6 (§17.2)
k6 run infra/k6/mvp-load-test.js -e BASE_URL=https://staging.example.com
| Параметр | Порог |
|---|---|
| VU / ramp | 50 / 5 min |
| Mix | 35% list, 25% login, 20% me, 10% refresh, 10% slug |
| p95 | < 300 ms |
| Errors | < 1% |
OWASP ZAP
docker run --rm -v "$(pwd):/zap/wrk:rw" -t ghcr.io/zap/zaproxy:stable \
zap-baseline.py -t https://staging.example.com -r zap-report.html
| Severity | Pass |
|---|---|
| High, Critical | 0 |
Medium/Low — review руками. zap-report.html — в архив релиза.
Lighthouse
npx lighthouse https://staging.example.com \
--preset=mobile --only-categories=performance \
--output=json --output-path=./lighthouse-report.json
| Метрика | ≥ |
|---|---|
Performance (mobile, /) |
85 |
Не прошло — hero video preload="none", font swap, меньше JS на лендинге.
Release gates (сводка)
- CI green на
main - E2E 11/11 на staging
- k6 pass
- ZAP 0 High/Critical
- Lighthouse ≥ 85
- security.md staging-пункты
./infra/scripts/smoke-prod.shна prod- DNS cutover + rollback plan (previous tag)
CI локально
pnpm lint && pnpm typecheck
cd apps/api && python -m mypy app
pnpm --filter web test:ci
cd apps/api && python -m pytest --cov=app --cov-fail-under=90
Полный pipeline: .github/workflows/ci.yml.