# Спринт 6 — технический долг + 2FA Автономная работа. После каждого пункта: `dotnet build` (SDK 8.0.126), unit + integration тесты, коммит порцией, отметка `[x]`, коммит прогресса. Не ломать auth. НЕ трогать global.json/POS/nginx/ОФД. ## Чек-лист 1. [x] **TD-6 Concurrency-токены на документах** — `RowVersion` (PostgreSQL `xmin` через `IsRowVersion`) на Supply/Demand/RetailSale/Transfer/Inventory. Миграция. EF concurrency check. 409 при конфликте. Тест: два параллельных PUT → один 200, другой 409. ✅ Postgres `xmin` (system column, без миграции) через `UseXminAsConcurrencyToken` для 5 документов + `IVersionedEntity`-marker. SuppliesController PUT принимает `Xmin`, сверяет, 409 при mismatch. Bonus: Supply.Update перешёл на ExecuteDelete+AddRange (тот же fix что в RetailSale). 2 интеграционных теста. 2. [x] **TD-2 FluentValidation** — `FluentValidation.AspNetCore`, validator'ы для SupplyInputDto/RetailSaleInputDto/ProductInputDto/CounterpartyInputDto/ EmployeeInputDto. Auto-register. Тесты на каждый. ✅ Не используем deprecated `FluentValidation.AspNetCore` — текущая рекомендация: `AddValidatorsFromAssemblyContaining()` + кастомный `ValidationFilter` (IAsyncActionFilter). На неуспех → 400 ValidationProblemDetails. 5 validator'ов, 16 unit-тестов, 70 integration зелёных без регрессий. 3. [x] **TD-4 Структурные log-fields в Serilog** — `LogContext.PushProperty` в middleware: OrgId, UserId, CorrelationId. Бизнес-логи (Supply.Post, Sale.Post) — структурно. `docs/logging.md`. ✅ `LogEnrichmentMiddleware` после Authentication. CorrelationId из заголовка `X-Correlation-ID` или генерируется. Business-логи на Supply.Post / RetailSale.Post с именованными плейсхолдерами. `docs/logging.md` с паттерном + анти-паттернами (string interpolation, PII в логах). 4. [x] **TD-1 CQRS partial (MediatR)** — `CreateSupplyCommand`, `PostRetailSaleCommand`, `GetSalesReportQuery`. Показать паттерн, не полный рефакторинг. Тесты на handlers. ✅ MediatR подключён в `food-market.api` с авторегистрацией из `food-market.application`. 3 handler-образца с абстракциями (`ISupplyWriter`, `IRetailSalePoster`) — testable без EF/БД. Контроллеры остались на прежнем flow (поэтапная миграция). 6 unit-тестов. 5. [x] **P2-4 2FA для админов (TOTP)** — `AuthenticatorTokenProvider`, endpoints `/api/me/2fa/enroll`, `/api/me/2fa/verify`, `/api/me/2fa/disable`. Опционально для Admin+SuperAdmin. При логине с включённым 2FA — два шага. ✅ Identity `AuthenticatorTokenProvider` (RFC 6238). Endpoints: /api/me/2fa/{status, enroll, verify, disable} с QR-URI. Password-grant на `/connect/token` принимает custom param `otp_code`; при включённом 2FA без него — `invalid_grant` с `error_description=2fa_required`. 4 интеграционных теста (тест сам генерит TOTP через RFC 6238). ## Итог **Все 5 пунктов выполнены.** Спринт 6 завершён 2026-05-28. Сводка: - **TD-6 RowVersion** — Postgres `xmin` через `UseXminAsConcurrencyToken` на 5 документах, 409 при conflict. Bonus: исправил Supply.Update. - **TD-2 FluentValidation** — `ValidationFilter` + 5 validator'ов (Supply, RetailSale, Product, Counterparty, Employee). - **TD-4 Structured logging** — `LogEnrichmentMiddleware` (OrgId/UserId/ CorrelationId в LogContext), business-логи на Post-операциях. - **TD-1 CQRS partial** — MediatR + 3 handler-образца с testable-абстракциями. - **P2-4 TOTP 2FA** — endpoints + интеграция в password-grant. **Сборка:** зелёная. **Тесты:** 57 unit + 74 integration = **131 зелёных**. ### Что осталось вне scope автономной работы - ОФД-оператор (`Транском`/`Касса24`), - MoySklad webhook-токены прод, - WPF/POS UI на Windows, - Стейдж→прод-деплой, - Реальный SMTP-провайдер прод (Mailgun/Sendgrid), - Backup-коды для 2FA recovery (отдельная задача). ## Лог - Каждый пункт: build + тесты + коммит порцией + отметка [x] + коммит прогресса. - Все правки на `main` (origin Forgejo), без коммита `global.json`.