diff --git a/adr/009-concurrent-refresh-rotation-policy.md b/adr/009-concurrent-refresh-rotation-policy.md index 21e85ae..2c303b4 100644 --- a/adr/009-concurrent-refresh-rotation-policy.md +++ b/adr/009-concurrent-refresh-rotation-policy.md @@ -2,6 +2,9 @@ **Status:** Accepted; supersedes the refresh-token reuse rule in ADR-006. +Cookie-clearing policy уточнена в [ADR 018](018-minimal-runtime-coordination.md): +неуспешный refresh больше не удаляет cookie. CAS и отказ superseded token сохранены. + ## Context Два одновременных запроса могут предъявить один действительный refresh token. Отзыв Session для проигравшего запроса делает недействительной сессию успешного запроса, а сохранение отдельной истории token или grace-window для различения replay добавляет непропорциональное состояние локальному MVP. diff --git a/adr/017-local-tbank-reads-and-background-sync.md b/adr/017-local-tbank-reads-and-background-sync.md index fa588b5..15eef69 100644 --- a/adr/017-local-tbank-reads-and-background-sync.md +++ b/adr/017-local-tbank-reads-and-background-sync.md @@ -2,6 +2,10 @@ **Status:** Accepted — архитектурное направление согласовано. +Prefetch policy ниже заменена [ADR 018](018-minimal-runtime-coordination.md): +свечи загружаются по запрошенному диапазону, без предварительной загрузки +всей истории бумаг портфеля. Остальные гарантии локального хранения сохранены. + Реализация `persist-tbank-data` добавлена в рабочие ветки приложения; performance/browser и canonical application gates пройдены. Это не отметка развёртывания в production. Ниже сохранён контекст diff --git a/adr/018-minimal-runtime-coordination.md b/adr/018-minimal-runtime-coordination.md new file mode 100644 index 0000000..40b8499 --- /dev/null +++ b/adr/018-minimal-runtime-coordination.md @@ -0,0 +1,45 @@ +# 018. Минимальная runtime-координация + +**Status:** Accepted. Уточняет cookie policy ADR 009 и prefetch policy ADR 017. + +## Context + +Изоляция владельцев данных, атомарные финансовые изменения и локальное чтение +не требуют отдельного протокола согласования каждого HTTP-ответа. Дополнительная +координация оправдана только наблюдаемым поведением, которое иначе нарушается. + +## Decision + +- Refresh сохраняет атомарную ротацию. Неуспешный refresh не меняет cookie: + поздний отказ не должен удалять cookie успешного запроса. Logout очищает cookie. +- Frontend объединяет refresh и упорядочивает cookie-writing requests только + внутри вкладки. Cross-tab auth synchronization не является требованием; + после изменения сессии в другой вкладке допускаются reload или re-login. +- Отменой, сбросом и публикацией query results владеет TanStack Query. + Смена session/connection очищает соответствующий cache; отдельные registries + HTTP-запросов, access epochs и URL-wide latest-read counters не используются. + Локальная auth boundary предотвращает применение старого auth result и + повтор mutation под другой identity. +- Status GET читает только состояние работы. Открытый экран перечитывает + resources через `sync=observe` во время работы и после завершения. + Сверка hashes между status и data GET, публичные progress counters и + невыдаваемые sync states не нужны. Полнота истории остаётся в resource metadata. +- Свечи загружаются для запрошенного диапазона. Предзагрузки всей истории + портфеля и автоматического расширения короткого периода до `all` нет. +- Backend формирует согласованные entries и aggregates из одних исходных + данных; frontend доверяет этому contract и не выполняет вторую сверку групп. +- Request context и HTTP completion принадлежат `nestjs-pino`/`pino-http`. + Проект сохраняет safe field filtering, redaction и route-template logging. +- Deployment всегда выполняет один stop → backup → migration → readiness + цикл. No-op и frontend-only оптимизации не обещаются; короткий простой принят. + +## Consequences + +Первое открытие нового диапазона свечей может ждать загрузку. Наблюдение активной +работы может перечитать неизменившиеся локальные данные, зато не требует +checksum protocol. Повтор deployment создаёт новый backup и может остановить API. + +DB ownership подключения, conditional publication, coverage, staging, +банковские intent fingerprints и версии записей сохраняются. Отмена HTTP +не считается отменой уже выполняющейся server-side transaction. +Сохранённый полный forecast не заменяется candidate с изменившимися dependencies. diff --git a/adr/README.md b/adr/README.md index 3e49b5d..efe8b86 100644 --- a/adr/README.md +++ b/adr/README.md @@ -22,3 +22,4 @@ - [015 — SQLite в ограниченном single-server deployment](015-single-server-sqlite-deployment.md) - [016 — Версионируемый комплект развёртывания](016-versioned-deployment-bundle.md) - [017 — Локальное чтение данных T-Bank и фоновая синхронизация](017-local-tbank-reads-and-background-sync.md) +- [018 — Минимальная runtime-координация](018-minimal-runtime-coordination.md) diff --git a/architecture/cross-cutting/data-freshness.md b/architecture/cross-cutting/data-freshness.md index 64c262e..27da9b0 100644 --- a/architecture/cross-cutting/data-freshness.md +++ b/architecture/cross-cutting/data-freshness.md @@ -43,6 +43,10 @@ Status GET и data GET с `sync=observe` не запускают работу. наблюдение прекращается. Поздний ответ не заменяет уже принятый новый. [HTTP API](../http-api.md) объясняет envelope и экранный refresh. +Отменой query lifecycle владеет TanStack Query. Status не пересобирает +финансовые ответы и не передаёт hash notifications. Свечи нового диапазона +загружаются при его открытии; предзагрузка всей истории портфеля не выполняется. + ## Смена подключения Каждое чтение проверяет локальную сессию, текущее подключение, известный отказ diff --git a/architecture/deployment.md b/architecture/deployment.md index 7dadddc..e73dc7b 100644 --- a/architecture/deployment.md +++ b/architecture/deployment.md @@ -142,20 +142,15 @@ backups находятся рядом с data directory, вне checkout, и а не удаляются. Незавершённая копия помечается `.partial-` и не подходит для restore. Ключ шифрования хранится отдельно. Локальная копия не защищает от потери диска. -## Независимое обновление frontend +## Единый цикл обновления -Если изменились только image или конфигурация frontend, backend не -пересоздаётся, backup и migration не выполняются. Возможен короткий перерыв -на внешней точке входа; старым browser tabs может потребоваться reload. -При неизменных исправных контейнерах повтор команды не пересоздаёт сервисы. - -## Независимое обновление backend - -Изменение image или конфигурации backend запускает stop → backup → migration → -readiness. Неизменный frontend не пересоздаётся. Во время простоя возможен 502. +Каждое обновление, включая повтор того же тега и изменение только frontend, +выполняет stop → backup → migration → readiness. Сравнение hashes контейнеров +и конфигурации не выполняется. Приняты короткий простой API и дополнительный +backup; во время простоя возможен 502, вкладкам может потребоваться reload. Ошибка migration блокирует старт, ошибка готовности останавливает новый backend; ошибка frontend не останавливает исправный backend. Успех фиксируется только -после проверок запуска затронутых компонентов. +после проверок запуска обоих компонентов. Одна file lock защищает установку от параллельных команд. Маркер незавершённого обновления блокирует повтор до разбора оператором. Он содержит этап, версии diff --git a/architecture/http-api.md b/architecture/http-api.md index da471d3..5ab1082 100644 --- a/architecture/http-api.md +++ b/architecture/http-api.md @@ -26,8 +26,8 @@ T-Bank data GET возвращают envelope `{ data, meta }`. `data` може при missing или неполном финансовом агрегате; partial ledger/candles могут содержать подтверждённые строки. `meta` разделяет availability, freshness, lastSuccessfulAt, sync, coverage и состояния зависимостей. `dataRevision` -описывает набор данных, `stateVersion` — наблюдаемое состояние, включая -пустое завершённое покрытие. Ни одно поле не раскрывает connection revision. +описывает набор данных; пустое завершённое покрытие явно представлено в coverage. +Публичной hash-версии состояния нет. Ни одно поле не раскрывает connection revision. Обычный GET планирует missing/stale зависимости после проверки доступа. GET с `sync=observe` только читает. `POST /api/v1/tbank-sync` принимает @@ -35,6 +35,11 @@ GET с `sync=observe` только читает. `POST /api/v1/tbank-sync` пр scope. `GET /api/v1/tbank-sync/:id` наблюдает разрешённую работу; чужой или прежний handle возвращает `404`. Приём работы не означает её успех. +Status возвращает state/errorCode без данных ресурсов и численных progress +counters. Клиент перечитывает показанные resources через `sync=observe` во время +работы и после завершения, сохраняя generated query identity. Sync states ресурса: +`idle`, `running`, `failed`; отдельного публичного `queued` нет. + Source failure принятой работы передаётся безопасным `meta.sync.errorCode`; HTTP transport/access/input errors сохраняют Problem Details. Старые данные при transient failure сохраняют исходное успешное время. Детали поведения — diff --git a/domains/authentication/overview.md b/domains/authentication/overview.md index 0c00f5b..1fa8628 100644 --- a/domains/authentication/overview.md +++ b/domains/authentication/overview.md @@ -55,15 +55,16 @@ sequenceDiagram B-->>F: Access token, профиль, новая refresh-cookie F->>B: Protected request с Bearer else Cookie нет или refresh отклонён - B-->>F: 401 и очистка cookie + B-->>F: 401 без изменения cookie Note over F: Готовое неавторизованное состояние end ``` -В одном экземпляре frontend одновременные refresh используют один -незавершённый запрос. Между вкладками используется Web Locks, если браузер -его поддерживает. Backend всё равно защищает ротацию атомарно: координация -браузеров не заменяет серверную проверку. +В одной вкладке одновременные refresh используют один незавершённый запрос; +cookie-writing requests упорядочены локально. Межвкладочная синхронизация +не поддерживается: после смены сессии в другой вкладке допустимы reload/re-login. +Backend защищает ротацию атомарно. Неуспешный refresh не удаляет cookie +победившего запроса; недействительный token по-прежнему не восстанавливает сессию. Обычный защищённый запрос с `401 UNAUTHORIZED` проходит refresh и один повтор через общий транспорт; auth endpoints из этого цикла исключены. Само diff --git a/domains/income/overview.md b/domains/income/overview.md index 6045d43..25dbe6d 100644 --- a/domains/income/overview.md +++ b/domains/income/overview.md @@ -91,13 +91,11 @@ Selected, previous и yearAgo имеют независимую готовнос серверным правилом; клиент воспринимает ключ как непрозрачную строку. Это поле принадлежит ответу аналитики; общий журнал операций его не требует. -Frontend проверяет принадлежность выплат по полученному ключу до подготовки -плоского списка выплат. Отсутствующий, пустой или -нестроковый ключ, неоднозначные ключи инструментов, неизвестная группа, другая -валюта или несовпадение числа выплат приводят к существующему локальному -состоянию ошибки аналитики с повторной попыткой. Частичный список выплат не показывается как -полный; формула идентификации в клиенте не восстанавливается. Полностью пустой -ответ допустим. Состав и исходный серверный порядок записей и знак сумм +Backend обеспечивает принадлежность выплат, валюту и receipt count при +построении entries и instrument groups из одного набора данных. Frontend +доверяет типизированному contract, не восстанавливает формулу идентификации +и не выполняет второй consistency validator. Полностью пустой ответ допустим. +Состав и исходный серверный порядок записей и знак сумм сохраняются, frontend не пересчитывает денежные итоги. Стабильный ID в пределах подключения и счёта определяет одну операцию: повторная сверка обновляет её без двойного учёта. Одинаковые ID разных счетов остаются разными выплатами. Изменение источника логотипа позволяет заново diff --git a/integrations/tbank.md b/integrations/tbank.md index b4873e7..f274f81 100644 --- a/integrations/tbank.md +++ b/integrations/tbank.md @@ -130,15 +130,12 @@ Backend хранит token в шифротексте AES-256-GCM с новым n [validateEnv](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/config/env.validation.ts) (справочный источник). Общий FIFO-лимитер ограничивает поток RPC и не допускает более 50 ожидающих. Для `UNAVAILABLE`, `DEADLINE_EXCEEDED` и `RESOURCE_EXHAUSTED` -обычный RPC допускает один повтор через секунду. Deadline каждой попытки — 15 секунд, +каждый RPC допускает один повтор через секунду. Deadline каждой попытки — 15 секунд, а ожидание места в лимитере не является временем этой сетевой попытки. -Для поиска метаданных `GetInstrumentBy` разрешены две повторные попытки -с паузами 2 и 4 секунды после неудачных попыток. Они применяются к тем же -временным кодам и к переполнению локальной очереди; перед каждой попыткой -проверяется её вместимость. Постоянные ошибки и отказ авторизации +Метаданные используют ту же retry policy. Переполненная очередь завершается +ошибкой сразу, без повторов admission. Постоянные ошибки и отказ авторизации не повторяются. Подтверждённый `NOT_FOUND` при поиске метаданных означает -отсутствие инструмента, а не временный сбой. Остальные RPC при полной -очереди завершаются сразу. +отсутствие инструмента, а не временный сбой. Параметры попыток принадлежат [T-Bank client](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/integrations/tbank/tbank.client.ts) (справочный источник); решение об ограниченных повторах — [ADR 005](../adr/005-tbank-upstream-resilience.md).