diff --git a/adr/009-concurrent-refresh-rotation-policy.md b/adr/009-concurrent-refresh-rotation-policy.md new file mode 100644 index 0000000..21e85ae --- /dev/null +++ b/adr/009-concurrent-refresh-rotation-policy.md @@ -0,0 +1,15 @@ +# 009. Политика конкурентной ротации refresh token + +**Status:** Accepted; supersedes the refresh-token reuse rule in ADR-006. + +## Context + +Два одновременных запроса могут предъявить один действительный refresh token. Отзыв Session для проигравшего запроса делает недействительной сессию успешного запроса, а сохранение отдельной истории token или grace-window для различения replay добавляет непропорциональное состояние локальному MVP. + +## Decision + +Backend атомарно ротирует refresh token через compare-and-swap: один запрос становится победителем. Запрос с superseded refresh token, включая проигравший конкурентный запрос, получает `401` и удаление cookie, но сам по себе не отзывает Session. Публичный auth contract и feature documentation должны отражать эту семантику. + +## Consequences + +Повторное предъявление superseded token больше не служит автоматическим сигналом для отзыва Session. Если понадобится различать конкурентный запрос и подтверждённый replay, потребуется отдельный change с дополнительным server-side состоянием и новой security policy. diff --git a/features/authentication.md b/features/authentication.md index b0b29a2..7cccc21 100644 --- a/features/authentication.md +++ b/features/authentication.md @@ -16,4 +16,6 @@ Access token живёт 15 минут только в памяти браузе ## Limitations -MVP не включает подтверждение email, восстановление или смену пароля, смену email, удаление аккаунта, список устройств, logout всех устройств или rate limit попыток входа. Access token может оставаться действительным до 15 минут после logout. Параллельный refresh между независимыми API-клиентами не поддерживается. +MVP не включает подтверждение email, восстановление или смену пароля, смену email, удаление аккаунта, список устройств, logout всех устройств или rate limit попыток входа. Access token может оставаться действительным до 15 минут после logout. + +Если несколько клиентов одновременно предъявляют один действительный refresh token, сервер завершает одну ротацию успешно, а остальные запросы получают `401`; сессия победившего запроса остаётся действительной. Уже заменённый refresh token также даёт `401`, но сам по себе не отзывает сессию: MVP не хранит историю token или grace-window, чтобы отличить конкурентный запрос от replay. diff --git a/features/brokerage-account-ledger.md b/features/brokerage-account-ledger.md index 8b4b854..13092f3 100644 --- a/features/brokerage-account-ledger.md +++ b/features/brokerage-account-ledger.md @@ -31,6 +31,6 @@ sequenceDiagram - Запрашиваются только исполненные операции T-Bank. - Полный журнал фильтруется по периоду и категориям, а изменение фильтра сбрасывает cursor-пагинацию. - Полный журнал не показывает цену, количество, НКД и комиссию записи. -- Backend кэширует точный запрос 30 секунд в памяти процесса; операции не сохраняются и не синхронизируются локально. +- Backend хранит в памяти процесса до 100 страниц Account Ledger: свежая страница живёт до 30 секунд, а при переполнении вытесняется давно не использовавшаяся страница (LRU). Ключ включает пользователя, счёт, нормализованные период и categories, cursor и limit; отсутствующий `to` приводится к текущему 30-секундному bucket. Проверка подключения и доступа к счёту выполняется до выдачи кэшированной страницы. Операции не сохраняются и не синхронизируются локально. - Журнал не рассчитывает остаток после операции, суммарные доходы или налоги, историческую стоимость Portfolio, конвертацию валют и рыночные изменения цены. - `409 TBANK_NOT_CONNECTED`, `400 INVALID_TBANK_TOKEN`, `404` недоступного счёта и `503 TBANK_UNAVAILABLE` используют существующую семантику ошибок T-Bank.