food-market/docs/sprint12-progress.md
nns 97e26a65d5 docs(s12): ARCHITECTURE/MULTI-TENANCY/RUNBOOK/DEVELOPER-GUIDE + k6 baseline + stage-verify CI
Документация для следующего разработчика (4 файла, ~1500 строк по
существу), реальный нагрузочный baseline на stage, и автоматический
smoke на каждый push.

Доки:
- docs/ARCHITECTURE.md — карта слоёв, модулей, Program.cs composition
  root, полный поток signup→post с трассировщиком ASP.NET pipeline.
- docs/MULTI-TENANCY.md — ITenantEntity + reflection query-filter,
  stamping в SaveChanges, SuperAdmin override (read-only + edit-mode
  с reason), 8 подводных камней, чеклист «как добавить tenant-сущность».
- docs/RUNBOOK.md — health-чеки, backup/restore с примером, смена SDK,
  disaster-recovery на новый сервер, 6 описанных инцидентов
  (включая docker-compose project name), БД-troubleshooting.
- docs/DEVELOPER-GUIDE.md — локальный setup, гочи integration-тестов,
  полные паттерны (controller с permission + tenant-сущность с
  RowVersion + 5 шагов миграции), валидация, structured-логирование,
  «НЕ делать» список.

k6 baseline:
- tests/load/ — 3 скрипта (signup-burst, retail-sales-parallel,
  sales-report-heavy) + README с инструкциями.
- docs/performance-baseline.md — реальные цифры на stage:
  * signup p95 446ms @ 50 RPM (IP-лимит 60/мин держит);
  * retail-sale sequential — 17/sec, p95 71ms;
  * retail-sale @ VU>1 — 53% failure из-за race в
    GenerateNumberAsync (unique-violation 23505 не ловится в
    SaveOrFkErrorAsync) — P0 для следующего рефакторинга;
  * reports на 1500 чеков — p95 50-114ms до VU=5.

CI:
- .forgejo/workflows/stage-verify.yml — on workflow_run после Docker
  API/Web, wait-for-ready → tests/stage-smoke.sh → Telegram пинг.
- tests/stage-smoke.sh — 7-секундный bash-смок (curl+jq+python3),
  5 этапов: health, signup, token, multi-tenant изоляция (B → 404
  на product A, B → пустой список), полный документ-цикл
  (supplier+supply.post → stock=100 → sale.post → stock=99).
  Локальный прогон против stage — все этапы зелёные.

Build чистый, локальный прогон smoke зелёный. Sprint 12 закрывает
автономно-безопасный цикл — дальше нужен вход от user'а.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 03:19:25 +05:00

132 lines
7.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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).