## Context См. мотивацию в `proposal.md` и изменяемое поведение в delta specs. Backend использует Nest 11, Prisma 7 с SQLite adapter, `AuthService` и локальные `Map`-кэши. Публичные auth routes, cookie attributes и success statuses не меняются; backend e2e migration gate остаётся отложенным по WL-P2-05. ## Goals / Non-Goals **Goals:** - Сделать cache страниц журнала ограниченным и полезным для семантически равных запросов. - Устранить race между проверкой уникальности email и его созданием. - Гарантировать единственного победителя refresh rotation без случайного revoke его Session. - Оставить воспроизводимые service-level tests, пока canonical SQLite e2e path недоступен. **Non-Goals:** - Изменять auth response statuses, OpenAPI snapshot или frontend client resilience (WL-P2-04, WL-P2-07 и WL-P2-08). - Добавлять byte-weight cache budget, распределённый cache, cache invalidation или cross-instance coherence. - Добавлять auth framework, новую схему Session, grace window либо отдельную историю использованных refresh token. - Объявлять `test:e2e` или общий workspace verdict восстановленными. ## Decisions ### Bounded cache остаётся domain-local `BrokerageOperationsService` получает прямую production dependency `lru-cache` и хранит в ней максимум 100 response pages с TTL 30 секунд. Это использует готовую, тестируемую LRU policy без глобального Nest cache layer. Cache identity строится отдельно из user/account и нормализованных effective query parameters: categories сортируются и deduplicate-ятся, cursor остаётся opaque, а отсутствующий `to` привязывается к текущему 30-секундному bucket. Cache никогда не заменяет существующую проверку connection и account ownership. Альтернативы: `Map` с ручной eviction повторяет сложную policy; Nest `CacheModule` потребует глобального HTTP keying и не подходит user/account domain cache; byte budget не нужен без подтверждённого memory pressure. ### Database constraint определяет регистрацию `AuthService.register` сохраняет предварительную проверку как UX оптимизацию, но считает `user.create` authoritative. Только ожидаемый Prisma unique-constraint error `P2002` от этой операции преобразуется в `ConflictException` с существующим code `EMAIL_ALREADY_REGISTERED`; прочие ошибки остаются внутренними. Это устраняет check-then-create race, не меняя HTTP filter или API surface. ### Refresh rotation использует compare-and-swap После проверки текущей Session сервис формирует новую token pair и обновляет Session через `updateMany` с predicate `{ id, refreshTokenHash: oldHash }`. `count === 1` означает победителя и разрешает вернуть новую пару. `count === 0` означает superseded token: сервис очищает cookie через существующий controller path и отвечает `401 UNAUTHORIZED`, не вызывая revoke Session. Схема не меняется: current hash уже является достаточным CAS predicate. Альтернативы: transaction добавляет SQLite locking/retry semantics без дополнительной пользы; version column или token history нужны только для отдельного различения late replay и concurrent loser. ADR-009 фиксирует выбранный trade-off. ### Tests моделируют arbitration, не подменяют e2e Новый `AuthService` spec использует stateful Prisma fake и barrier, чтобы две регистрации одновременно дошли до `create`, а два refresh — до CAS. Тесты проверяют наблюдаемые service outcomes и predicate CAS, включая отсутствие revoke у loser. `BrokerageOperationsService` spec использует fake upstream для проверки canonical hit, ownership checks и max-100 eviction; TTL expiry обеспечивается и тестируется библиотекой `lru-cache`, поэтому change не добавляет test-only clock seam. Это детерминированные regression tests; они не выдают себя за HTTP/SQLite e2e и не меняют deferred status WL-P2-05. ## Risks / Trade-offs - [Superseded token может быть поздним replay, а не concurrent loser] → `401` не отзывает Session по ADR-009; более строгая дифференциация требует отдельного security change. - [Одна cached page может быть большой] → максимальное число страниц ограничивает cardinality; byte budget остаётся отдельным будущим решением. - [TTL не создаёт distributed coherence] → cache остаётся process-local, как и текущий local MVP policy. - [Unit tests не доказывают SQLite migration/HTTP wiring] → они покрывают сервисную логику до recovery trigger для WL-P2-05. ## Migration Plan 1. Добавить прямую зависимость `lru-cache` и service-level tests. 2. Внести cache, registration и CAS изменения без Prisma migration. 3. Обновить фактические feature docs и ADR-009; сохранить WL-P2-05/P2-13 deferred в remediation registry до их отдельных trigger. 4. Выполнить backend non-mutating gates, OpenSpec verification и independent review до archive. Rollback заключается в откате приложения и dependency lockfile; schema и публичные route shapes не меняются.