# Sprint 12 — документация, runbook, нагрузочное тестирование

Цель: переложить «то что знаю только я и комментарии в коде» в
читаемые документы для следующего разработчика, замерить реальную
производительность под нагрузкой, и закрыть автоматическую верификацию
stage-стэйджа на каждый push.

Старт: 2026-06-07. Исполнитель: Claude Opus 4.7.

Это **последний автономно-безопасный спринт**. Дальше нужны входы от
user'а: реальные ОФД-ApiKey, MoySklad webhook-token'ы, Windows-машина
для POS WPF, прод-деплой план, казахские переводы, реальный SMTP-провайдер.

## Принципы

- Документация — для человека, не «AI-портянка». Конкретные пути, имена
  типов, причины решений. Без воды и эмоций.
- k6 — реальные числа. Если p95 высокий — пишем как есть.
- НЕ трогать: `global.json`, прод-стек, POS WPF.

## Чек-лист

- [x] **1. docs/ARCHITECTURE.md** — карта слоёв, модулей, потоков
  signup→bootstrap→операции. Реальные имена типов и путей, не маркетинг.
- [x] **2. docs/MULTI-TENANCY.md** — `ITenantEntity` + reflection
  query-filter, stamping в SaveChanges, SuperAdmin override (read-only +
  edit-mode с reason), 8 подводных камней (IgnoreQueryFilters, фоновые
  jobs без HttpContext, raw SQL, и т.д.).
- [x] **3. docs/RUNBOOK.md** — health-чеки, backup/restore (включая
  disaster-recovery), смена SDK, перенос на новый сервер, **6 описанных
  инцидентов** (включая docker-compose project name из ТЗ),
  troubleshooting БД (stock-агрегат расхождения, audit-log
  размер, EFMigrationsHistory).
- [x] **4. docs/DEVELOPER-GUIDE.md** — локальный setup, запуск тестов,
  гочи integration-тестов (Ryuk, rate-limiter eager-config, один
  ApiFactory), полные паттерны: добавить controller с permission +
  добавить tenant-сущность с RowVersion + 5 шагов миграции, валидация
  (DataAnnotations / FluentValidation / бизнес), structured-логирование.
- [x] **5. k6 нагрузочный тест** — `tests/load/` + 3 скрипта
  (signup-burst, retail-sales-parallel, sales-report-heavy) +
  `docs/performance-baseline.md` с **реальными цифрами** на stage'е.
  Главное найденное: race в `GenerateNumberAsync` при VU > 1 на одном
  tenant'е (unique-violation 23505 не ловится → 500). Прогон зарегистрирован
  как P0 для следующего рефакторинга.
- [x] **6. CI workflow `.forgejo/workflows/stage-verify.yml`** —
  `on workflow_run` после `Docker API`/`Docker Web`, ждёт
  `/health/ready` и запускает `tests/stage-smoke.sh` (~7с,
  full-cycle smoke: signup → multi-tenant isolation → supply.post →
  retail-sale.post → stock check). Telegram-нотификация по
  успеху/падению.

## Журнал

### 2026-06-07 старт
Sprint 11 закрыт (7/7 ✓). Поехали по docs-чек-листу.

### 2026-06-07 п.1–п.4 (документация)
Прочитал реальный код: `Program.cs` composition root, `AppDbContext`
reflection-фильтры, `HttpContextTenantContext` с AsyncLocal-override,
`SuperAdminOverrideClaimsTransformer` + `ReadonlyOverrideMiddleware`,
`RequiresPermissionAttribute` + policy-handler, `HangfireJobsConfigurator`
recurring jobs, deploy/Dockerfile + docker-compose, backup-скрипт +
systemd-timer.

Написал 4 документа на основе этого:
- `ARCHITECTURE.md` (372 строки) — слои + модули + composition root +
  поток signup→post с детальным трассировщиком ASP.NET pipeline.
- `MULTI-TENANCY.md` (256 строк) — query-filter, stamping,
  SuperAdmin override, 8 подводных камней + чеклист «как добавить
  tenant-сущность».
- `RUNBOOK.md` (337 строк) — health-чеки, backup/restore с примером,
  смена SDK, disaster-recovery, 6 инцидентов, БД-troubleshooting.
- `DEVELOPER-GUIDE.md` (332 строки) — локальный setup, тесты,
  паттерны (controller + entity + валидация + логирование), "НЕ
  делать" список.

### 2026-06-07 п.5 (k6 baseline)
k6 v0.55.0 standalone в `~/bin/k6`. 3 скрипта в `tests/load/`:

- `signup-burst.js`: 50 RPM → p95 446ms, 0% errors. 100 RPM → 39% 429
  (IP-лимит работает, by design).
- `retail-sales-parallel.js`: VU=1 — 17 sales/sec, p95 71ms, 0%
  failures. VU=5 — **53% failure** из-за race в `GenerateNumberAsync`
  (unique violation на `RetailSale.Number`). Это **реальная находка**,
  P0 для следующего спринта.
- `sales-report-heavy.js`: на tenant'е с 1500 чеков, VU=1 — p95 54ms,
  VU=4 — p95 81ms, VU=5 — p95 114ms (один аномальный прогон показал
  3.8с — autovacuum suspect).

Все цифры в `docs/performance-baseline.md` с воспроизведением.

### 2026-06-07 п.6 (CI workflow)
`.forgejo/workflows/stage-verify.yml` — `on: workflow_run` после
`Docker API` и `Docker Web`, не запускается на failed parent (нет
смысла верифировать незадеплоенное). Шаги: wait-for-ready (60с
retry loop) → запустить `tests/stage-smoke.sh` → Telegram пинг.

`tests/stage-smoke.sh` — bash-скрипт без зависимостей кроме
curl+jq+python3. 5 этапов: health, signup A, token A, multi-tenant
isolation (A создаёт продукт, B получает 404 + список без продукта A),
полный документ-цикл (supplier+supply.post → проверка stock=100 →
sale.post → проверка stock=99). Локальный прогон против stage —
**7 секунд**, всё зелёное.

### Итог

Все 6 пунктов ✓. Документация:
- 4 новых файла в `docs/` (~1300 строк суммарно).
- `docs/performance-baseline.md` — реальные цифры + 1 находка P0.

Тестирование:
- 3 k6 скрипта в `tests/load/`.
- `tests/stage-smoke.sh` — 7-секундный smoke против stage.

CI:
- `.forgejo/workflows/stage-verify.yml` — auto-verify на каждый
  successful deploy.

Следующие шаги, требующие user'а (за пределами автономного режима):
1. Реальный ОФД ApiKey (Webkassa предпочтительно) — Sprint 11-fiscal
   ждёт это для активации.
2. Решение по прод-деплой (домен + cert + DNS).
3. MoySklad webhook-токены для inline-импорта.
4. Windows-машина (или CI runner) для POS WPF сборки.
5. Казахский переводчик для UI (i18n уже подготовлен).
6. Реальный SMTP-провайдер для платформы (Mailgun / Postmark / Yandex).

Plus P0-задача из baseline'а: исправить race в `GenerateNumberAsync`
для `RetailSalesController` и аналогичных контроллеров — это уже
автономно делается, но требует дизайн-решения (per-tenant sequence vs
counter table vs retry-loop).
