105 lines
6.6 KiB
Markdown
105 lines
6.6 KiB
Markdown
## 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 не меняются.
|