docs: record minimal runtime coordination
Docs security / security (push) Failing after 9s
Docs verify / verify (push) Successful in 12s

Document accepted cross-tab reauthentication, on-demand candles,
native query/logging ownership and the complete deployment path.

Preserve historical ADR context and link amendments through ADR 018.
Related backend change: WealthLedger/backend@d698ed9
This commit is contained in:
2026-10-03 11:41:24 +03:00
parent 2bfe87d0e4
commit e8e7fbd9fc
10 changed files with 85 additions and 32 deletions
@@ -2,6 +2,9 @@
**Status:** Accepted; supersedes the refresh-token reuse rule in ADR-006. **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 ## Context
Два одновременных запроса могут предъявить один действительный refresh token. Отзыв Session для проигравшего запроса делает недействительной сессию успешного запроса, а сохранение отдельной истории token или grace-window для различения replay добавляет непропорциональное состояние локальному MVP. Два одновременных запроса могут предъявить один действительный refresh token. Отзыв Session для проигравшего запроса делает недействительной сессию успешного запроса, а сохранение отдельной истории token или grace-window для различения replay добавляет непропорциональное состояние локальному MVP.
@@ -2,6 +2,10 @@
**Status:** Accepted — архитектурное направление согласовано. **Status:** Accepted — архитектурное направление согласовано.
Prefetch policy ниже заменена [ADR 018](018-minimal-runtime-coordination.md):
свечи загружаются по запрошенному диапазону, без предварительной загрузки
всей истории бумаг портфеля. Остальные гарантии локального хранения сохранены.
Реализация `persist-tbank-data` добавлена в рабочие ветки приложения; Реализация `persist-tbank-data` добавлена в рабочие ветки приложения;
performance/browser и canonical application gates пройдены. Это не отметка performance/browser и canonical application gates пройдены. Это не отметка
развёртывания в production. Ниже сохранён контекст развёртывания в production. Ниже сохранён контекст
+45
View File
@@ -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.
+1
View File
@@ -22,3 +22,4 @@
- [015 — SQLite в ограниченном single-server deployment](015-single-server-sqlite-deployment.md) - [015 — SQLite в ограниченном single-server deployment](015-single-server-sqlite-deployment.md)
- [016 — Версионируемый комплект развёртывания](016-versioned-deployment-bundle.md) - [016 — Версионируемый комплект развёртывания](016-versioned-deployment-bundle.md)
- [017 — Локальное чтение данных T-Bank и фоновая синхронизация](017-local-tbank-reads-and-background-sync.md) - [017 — Локальное чтение данных T-Bank и фоновая синхронизация](017-local-tbank-reads-and-background-sync.md)
- [018 — Минимальная runtime-координация](018-minimal-runtime-coordination.md)
@@ -43,6 +43,10 @@ Status GET и data GET с `sync=observe` не запускают работу.
наблюдение прекращается. Поздний ответ не заменяет уже принятый новый. наблюдение прекращается. Поздний ответ не заменяет уже принятый новый.
[HTTP API](../http-api.md) объясняет envelope и экранный refresh. [HTTP API](../http-api.md) объясняет envelope и экранный refresh.
Отменой query lifecycle владеет TanStack Query. Status не пересобирает
финансовые ответы и не передаёт hash notifications. Свечи нового диапазона
загружаются при его открытии; предзагрузка всей истории портфеля не выполняется.
## Смена подключения ## Смена подключения
Каждое чтение проверяет локальную сессию, текущее подключение, известный отказ Каждое чтение проверяет локальную сессию, текущее подключение, известный отказ
+6 -11
View File
@@ -142,20 +142,15 @@ backups находятся рядом с data directory, вне checkout, и а
не удаляются. Незавершённая копия помечается `.partial-` и не подходит для restore. не удаляются. Незавершённая копия помечается `.partial-` и не подходит для restore.
Ключ шифрования хранится отдельно. Локальная копия не защищает от потери диска. Ключ шифрования хранится отдельно. Локальная копия не защищает от потери диска.
## Независимое обновление frontend ## Единый цикл обновления
Если изменились только image или конфигурация frontend, backend не Каждое обновление, включая повтор того же тега и изменение только frontend,
пересоздаётся, backup и migration не выполняются. Возможен короткий перерыв выполняет stop → backup → migration → readiness. Сравнение hashes контейнеров
на внешней точке входа; старым browser tabs может потребоваться reload. и конфигурации не выполняется. Приняты короткий простой API и дополнительный
При неизменных исправных контейнерах повтор команды не пересоздаёт сервисы. backup; во время простоя возможен 502, вкладкам может потребоваться reload.
## Независимое обновление backend
Изменение image или конфигурации backend запускает stop → backup → migration →
readiness. Неизменный frontend не пересоздаётся. Во время простоя возможен 502.
Ошибка migration блокирует старт, ошибка готовности останавливает новый backend; Ошибка migration блокирует старт, ошибка готовности останавливает новый backend;
ошибка frontend не останавливает исправный backend. Успех фиксируется только ошибка frontend не останавливает исправный backend. Успех фиксируется только
после проверок запуска затронутых компонентов. после проверок запуска обоих компонентов.
Одна file lock защищает установку от параллельных команд. Маркер незавершённого Одна file lock защищает установку от параллельных команд. Маркер незавершённого
обновления блокирует повтор до разбора оператором. Он содержит этап, версии обновления блокирует повтор до разбора оператором. Он содержит этап, версии
+7 -2
View File
@@ -26,8 +26,8 @@ T-Bank data GET возвращают envelope `{ data, meta }`. `data` може
при missing или неполном финансовом агрегате; partial ledger/candles могут при missing или неполном финансовом агрегате; partial ledger/candles могут
содержать подтверждённые строки. `meta` разделяет availability, freshness, содержать подтверждённые строки. `meta` разделяет availability, freshness,
lastSuccessfulAt, sync, coverage и состояния зависимостей. `dataRevision` lastSuccessfulAt, sync, coverage и состояния зависимостей. `dataRevision`
описывает набор данных, `stateVersion` — наблюдаемое состояние, включая описывает набор данных; пустое завершённое покрытие явно представлено в coverage.
пустое завершённое покрытие. Ни одно поле не раскрывает connection revision. Публичной hash-версии состояния нет. Ни одно поле не раскрывает connection revision.
Обычный GET планирует missing/stale зависимости после проверки доступа. Обычный GET планирует missing/stale зависимости после проверки доступа.
GET с `sync=observe` только читает. `POST /api/v1/tbank-sync` принимает 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` наблюдает разрешённую работу; чужой или scope. `GET /api/v1/tbank-sync/:id` наблюдает разрешённую работу; чужой или
прежний handle возвращает `404`. Приём работы не означает её успех. прежний handle возвращает `404`. Приём работы не означает её успех.
Status возвращает state/errorCode без данных ресурсов и численных progress
counters. Клиент перечитывает показанные resources через `sync=observe` во время
работы и после завершения, сохраняя generated query identity. Sync states ресурса:
`idle`, `running`, `failed`; отдельного публичного `queued` нет.
Source failure принятой работы передаётся безопасным `meta.sync.errorCode`; Source failure принятой работы передаётся безопасным `meta.sync.errorCode`;
HTTP transport/access/input errors сохраняют Problem Details. Старые данные HTTP transport/access/input errors сохраняют Problem Details. Старые данные
при transient failure сохраняют исходное успешное время. Детали поведения — при transient failure сохраняют исходное успешное время. Детали поведения —
+6 -5
View File
@@ -55,15 +55,16 @@ sequenceDiagram
B-->>F: Access token, профиль, новая refresh-cookie B-->>F: Access token, профиль, новая refresh-cookie
F->>B: Protected request с Bearer F->>B: Protected request с Bearer
else Cookie нет или refresh отклонён else Cookie нет или refresh отклонён
B-->>F: 401 и очистка cookie B-->>F: 401 без изменения cookie
Note over F: Готовое неавторизованное состояние Note over F: Готовое неавторизованное состояние
end end
``` ```
В одном экземпляре frontend одновременные refresh используют один В одной вкладке одновременные refresh используют один незавершённый запрос;
незавершённый запрос. Между вкладками используется Web Locks, если браузер cookie-writing requests упорядочены локально. Межвкладочная синхронизация
его поддерживает. Backend всё равно защищает ротацию атомарно: координация не поддерживается: после смены сессии в другой вкладке допустимы reload/re-login.
браузеров не заменяет серверную проверку. Backend защищает ротацию атомарно. Неуспешный refresh не удаляет cookie
победившего запроса; недействительный token по-прежнему не восстанавливает сессию.
Обычный защищённый запрос с `401 UNAUTHORIZED` проходит refresh и один повтор Обычный защищённый запрос с `401 UNAUTHORIZED` проходит refresh и один повтор
через общий транспорт; auth endpoints из этого цикла исключены. Само через общий транспорт; auth endpoints из этого цикла исключены. Само
+5 -7
View File
@@ -91,13 +91,11 @@ Selected, previous и yearAgo имеют независимую готовнос
серверным правилом; клиент воспринимает ключ как непрозрачную строку. серверным правилом; клиент воспринимает ключ как непрозрачную строку.
Это поле принадлежит ответу аналитики; общий журнал операций его не требует. Это поле принадлежит ответу аналитики; общий журнал операций его не требует.
Frontend проверяет принадлежность выплат по полученному ключу до подготовки Backend обеспечивает принадлежность выплат, валюту и receipt count при
плоского списка выплат. Отсутствующий, пустой или построении entries и instrument groups из одного набора данных. Frontend
нестроковый ключ, неоднозначные ключи инструментов, неизвестная группа, другая доверяет типизированному contract, не восстанавливает формулу идентификации
валюта или несовпадение числа выплат приводят к существующему локальному и не выполняет второй consistency validator. Полностью пустой ответ допустим.
состоянию ошибки аналитики с повторной попыткой. Частичный список выплат не показывается как Состав и исходный серверный порядок записей и знак сумм
полный; формула идентификации в клиенте не восстанавливается. Полностью пустой
ответ допустим. Состав и исходный серверный порядок записей и знак сумм
сохраняются, frontend не пересчитывает денежные итоги. Стабильный ID в пределах подключения и счёта определяет одну операцию: сохраняются, frontend не пересчитывает денежные итоги. Стабильный ID в пределах подключения и счёта определяет одну операцию:
повторная сверка обновляет её без двойного учёта. Одинаковые ID разных счетов повторная сверка обновляет её без двойного учёта. Одинаковые ID разных счетов
остаются разными выплатами. Изменение источника логотипа позволяет заново остаются разными выплатами. Изменение источника логотипа позволяет заново
+4 -7
View File
@@ -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) (справочный источник). [validateEnv](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/config/env.validation.ts) (справочный источник).
Общий FIFO-лимитер ограничивает поток RPC и не допускает более 50 Общий FIFO-лимитер ограничивает поток RPC и не допускает более 50
ожидающих. Для `UNAVAILABLE`, `DEADLINE_EXCEEDED` и `RESOURCE_EXHAUSTED` ожидающих. Для `UNAVAILABLE`, `DEADLINE_EXCEEDED` и `RESOURCE_EXHAUSTED`
обычный RPC допускает один повтор через секунду. Deadline каждой попытки — 15 секунд, каждый RPC допускает один повтор через секунду. Deadline каждой попытки — 15 секунд,
а ожидание места в лимитере не является временем этой сетевой попытки. а ожидание места в лимитере не является временем этой сетевой попытки.
Для поиска метаданных `GetInstrumentBy` разрешены две повторные попытки Метаданные используют ту же retry policy. Переполненная очередь завершается
с паузами 2 и 4 секунды после неудачных попыток. Они применяются к тем же ошибкой сразу, без повторов admission. Постоянные ошибки и отказ авторизации
временным кодам и к переполнению локальной очереди; перед каждой попыткой
проверяется её вместимость. Постоянные ошибки и отказ авторизации
не повторяются. Подтверждённый `NOT_FOUND` при поиске метаданных означает не повторяются. Подтверждённый `NOT_FOUND` при поиске метаданных означает
отсутствие инструмента, а не временный сбой. Остальные RPC при полной отсутствие инструмента, а не временный сбой.
очереди завершаются сразу.
Параметры попыток принадлежат Параметры попыток принадлежат
[T-Bank client](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/integrations/tbank/tbank.client.ts) (справочный источник); [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). решение об ограниченных повторах — [ADR 005](../adr/005-tbank-upstream-resilience.md).