docs: explain persisted T-Bank reads and synchronization
Conventional commits / commits (pull_request) Successful in 14s
Docs security / security (pull_request) Successful in 8s
Docs verify / verify (pull_request) Successful in 13s
Docs security / security (push) Successful in 8s
Docs verify / verify (push) Successful in 12s
Conventional commits / commits (pull_request) Successful in 14s
Docs security / security (pull_request) Successful in 8s
Docs verify / verify (pull_request) Successful in 13s
Docs security / security (push) Successful in 8s
Docs verify / verify (push) Successful in 12s
Document local data ownership, coverage and freshness, refresh behavior, failure recovery and additive migration rollback. Record ADR 017.
This commit was merged in pull request #22.
This commit is contained in:
+20
-23
@@ -76,8 +76,8 @@ prerequisites: локальный smoke не подтверждает registry/s
|
||||
| Часть | Ответственность |
|
||||
| --- | --- |
|
||||
| React SPA | Экраны, навигация, формы, состояние входа и клиентский кэш ответов; форматирование денежных значений |
|
||||
| Backend API | Локальная идентичность, сессии, проверка доступа, подключение брокера, нормализация внешних данных, точные расчёты и серверные кэши |
|
||||
| SQLite | Долговременное хранение локальных пользователей, сессий, зашифрованных подключений и ручной банковской истории |
|
||||
| Backend API | Локальная идентичность, сессии, проверка доступа, подключение брокера, нормализация внешних данных, точные расчёты и синхронизация сохранённых источников |
|
||||
| SQLite | Долговременное хранение локальных пользователей, сессий, зашифрованных подключений, ручной банковской истории и инвестиционных источников с coverage/work |
|
||||
| Внешние системы | Данные брокера, публичные сведения об инструментах и изображения в пределах своей ответственности |
|
||||
|
||||
Frontend организован слоями `app → pages → features → entities → shared`:
|
||||
@@ -134,28 +134,24 @@ credentials не являются сохраняемым представлен
|
||||
или перезапуска. Архив обратим и сохраняет историю. Подробности —
|
||||
[банковские карты](../domains/cards/overview.md).
|
||||
|
||||
Сессии и подключение связаны с владельцем. Access token подтверждает
|
||||
локального пользователя, но `accountId` из URL сам по себе не даёт права
|
||||
читать брокерский счёт. Backend получает текущее подключение пользователя
|
||||
и его generation перед использованием финансового кэша. Проверка доступности
|
||||
счёта у брокера зависит от сценария: журнал выполняет её до чтения страницы
|
||||
кэша; свежий портфель одного счёта может быть возвращён без нового запроса
|
||||
списка счетов. При обновлении портфеля доступ проверяется у брокера.
|
||||
Сессии и подключение связаны с владельцем. Backend локально проверяет
|
||||
membership текущей revision перед каждым финансовым чтением. Unknown membership
|
||||
не открывает доступ к произвольному счёту; известный отказ токена блокирует
|
||||
личные данные. Ротация refresh token остаётся атомарной.
|
||||
|
||||
Ротация сессии атомарно заменяет hash текущего refresh token: два
|
||||
конкурентных запроса не могут оба успешно выполнить одну ротацию.
|
||||
Вызов брокера и запись в локальную базу не образуют общую транзакцию:
|
||||
при сохранении подключения сначала проверяется token, затем записывается
|
||||
шифротекст. Подробности находятся в
|
||||
[аутентификации](../domains/authentication/overview.md) и
|
||||
[интеграции T-Bank](../integrations/tbank.md).
|
||||
SQLite хранит полный последний portfolio, операции и их подтверждённые
|
||||
диапазоны, private прогнозы, общие instrument/market sources и intent/staging.
|
||||
Нормализованные финансовые значения сохраняются decimal-строками. Полнота
|
||||
отделена от свежести и пагинации. Ответ не ждёт внешних RPC; запрос при
|
||||
необходимости запускает работу в том же backend process через существующий
|
||||
лимитер. На startup импорт не запускается; восстановление требует следующего
|
||||
запроса. Подробности — [модель свежести](cross-cutting/data-freshness.md).
|
||||
|
||||
Брокерские счета, сводки, портфель, страницы журнала и будущие события
|
||||
не являются долговременной историей в SQLite. Они получаются для текущего
|
||||
подключения и могут временно кэшироваться. Перезапуск backend не
|
||||
восстанавливает прежние финансовые снимки и cursor continuations из базы;
|
||||
[модель свежести](cross-cutting/data-freshness.md) отделяет это от хранения
|
||||
учётных данных.
|
||||
PUT подключения сначала проверяет token у T-Bank, затем одной транзакцией
|
||||
сохраняет encrypted token, новую revision и проверенный membership.
|
||||
Сетевая операция не удерживает SQLite-транзакцию. Общие источники не содержат
|
||||
личных позиций, ожидаемых сумм или credentials. Произвольного архива
|
||||
отключённых счетов нет.
|
||||
|
||||
## Сохранение и восстановление данных
|
||||
|
||||
@@ -210,7 +206,8 @@ Origin браузера, адрес API и cookie policy должны быть
|
||||
режимах refresh-cookie имеет Secure flag и требует защищённого соединения.
|
||||
Single-server deployment не задаёт гарантий HA или zero-downtime.
|
||||
|
||||
Кэши и лимитер T-Bank действуют на экземпляр backend. Ротация ключа шифрования
|
||||
Coordinator и лимитер T-Bank действуют на один экземпляр backend. Сохранённые
|
||||
данные переживают перезапуск; processRunId не является distributed lease. Ротация ключа шифрования
|
||||
подключений и согласование изменений подключения между распределёнными
|
||||
экземплярами не входят в реализованный механизм. Эти ограничения не
|
||||
позволяют переносить локальную схему на несколько экземпляров без отдельного
|
||||
|
||||
@@ -1,72 +1,66 @@
|
||||
# Свежесть данных и повторное использование
|
||||
|
||||
WealthLedger показывает финансовое наблюдение на момент получения данных.
|
||||
Временный кэш уменьшает повторные обращения к брокеру, но не превращает
|
||||
снимки в историю портфеля и не обещает рыночные данные в реальном времени.
|
||||
WealthLedger читает инвестиционные данные из SQLite. Обычный GET возвращает
|
||||
сохранённый результат и запрашивает фоновые зависимости, если они отсутствуют
|
||||
или устарели. HTTP-ответ не ждёт T-Bank. Модель принята в
|
||||
[ADR 017](../../adr/017-local-tbank-reads-and-background-sync.md).
|
||||
|
||||
## Клиентский и серверный снимки
|
||||
|
||||
[Client Cached Snapshot](../../glossary.md#client-cached-snapshot) — последний
|
||||
успешный ответ, сохранённый TanStack Query в SPA. При обычном фоновом обновлении
|
||||
того же набора данных уже показанный результат остаётся на экране; ошибка
|
||||
обновления сообщает о проблеме, не заменяя известные значения пустотой.
|
||||
Клиентский кэш не является внешней системой или отдельным runtime-приложением.
|
||||
[Client Cached Snapshot](../../glossary.md#client-cached-snapshot) — успешный
|
||||
ответ в TanStack Query. Он сохраняет показанное содержимое при временной ошибке
|
||||
запроса в той же границе доступа. SQLite сохраняет источники независимо от
|
||||
браузера: membership, последний полный portfolio, исполненные операции,
|
||||
общие сведения об инструментах, котировки, свечи и календари.
|
||||
|
||||
[Portfolio Snapshot](../../glossary.md#portfolio-snapshot) — полный результат
|
||||
загрузки портфеля на backend. В пределах разрешённого сценарием срока он может
|
||||
служить серверным fallback при временной ошибке upstream. Backend отмечает
|
||||
такой ответ `isStale`; frontend не выводит это поле из состояния собственного
|
||||
query-кэша. Серверное предупреждение и ошибка клиентского refetch могут
|
||||
возникать независимо.
|
||||
[Полнота периода](../../glossary.md#полнота-истории-счёта) и свежесть различаются.
|
||||
Успешно проверенный пустой интервал полон; последняя страница журнала сама
|
||||
по себе полноту не доказывает. Ошибка сверки сохраняет прежние данные и время.
|
||||
Старый полный портфель не удаляется по истечении TTL. `isStale` показывает
|
||||
возраст portfolio source, а не сам факт неудачного обновления.
|
||||
|
||||
Свежесть и допустимость fallback определяются сценарием. Портфельный fallback
|
||||
не распространяется автоматически на журнал, прогноз событий или страницу
|
||||
инструмента. Точные правила находятся в
|
||||
[справочнике портфеля](../../domains/portfolio/data-reference.md),
|
||||
[журнале операций](../../domains/operations/overview.md) и
|
||||
[будущих событиях](../../domains/income/upcoming-events.md).
|
||||
| Источник | Когда обычный запрос требует обновления |
|
||||
| --- | --- |
|
||||
| Portfolio, котировки, изменяемый хвост дневных свечей | Через 60 секунд |
|
||||
| Membership и изменяемый хвост операций | Через 5 минут |
|
||||
| Metadata, поиск каталога, company и календари | Через сутки |
|
||||
| Полные прошлые интервалы операций и завершённые свечи | При явном обновлении показанного периода |
|
||||
|
||||
По [ADR 007](../../adr/007-react-query-fetch-policy.md) запрос связан с открытием
|
||||
экрана, а возврат фокуса окна не запускает refetch. Повторное открытие после
|
||||
`staleTime` может обновить ответ в фоне. Эта политика не является таймером
|
||||
периодического обновления. Перезапуск backend уничтожает его in-memory
|
||||
снимки и continuations журнала; SQLite не восстанавливает их.
|
||||
TTL не создаёт таймер импорта. Запрошенная работа может закончиться после
|
||||
ухода со страницы. После перезапуска нет автоматического обхода intent:
|
||||
следующий подходящий запрос повторяет незавершённый интервал с первой страницы,
|
||||
переиспользуя полные интервалы. Cursor брокера не служит durable checkpoint.
|
||||
|
||||
Ручное «Обновить» перечитывает зависимости текущего экрана и применённый
|
||||
период независимо от TTL. Повторная сверка полного периода публикуется одной
|
||||
транзакцией после всех страниц, интервалов и счетов, включая `all`.
|
||||
Сбой сохраняет весь прежний результат. Первая загрузка может публиковать
|
||||
полные малые интервалы и показывать частичный период без окончательных сумм.
|
||||
|
||||
Клиент наблюдает только известную активную работу открытого экрана.
|
||||
Status GET и data GET с `sync=observe` не запускают работу. После изменения
|
||||
состояния ответ попадает в ту же query identity; после ухода или завершения
|
||||
наблюдение прекращается. Поздний ответ не заменяет уже принятый новый.
|
||||
[HTTP API](../http-api.md) объясняет envelope и экранный refresh.
|
||||
|
||||
## Смена подключения
|
||||
|
||||
Кэш финансовых данных относится к пользователю, счёту и текущему
|
||||
подключению T-Bank. Backend сначала читает подключение и его generation, затем
|
||||
проверяет возможность повторного использования ответа. Свежий портфель
|
||||
одного счёта не требует нового запроса списка счетов у брокера; при обновлении
|
||||
портфеля доступ проверяется заново. Журнал операций проверяет доступ до чтения
|
||||
кэшированной страницы. Идентификатор счёта и совпадение параметров запроса
|
||||
сами по себе недостаточны для выдачи кэша.
|
||||
Каждое чтение проверяет локальную сессию, текущее подключение, известный отказ
|
||||
токена и сохранённый состав доступных счетов. Расшифровка token и GetAccounts
|
||||
для локальной выдачи не нужны. Старый membership допускает выдачу и фоновую
|
||||
сверку; это задержка обнаружения отзыва доступа, а не live-проверка брокера.
|
||||
Unknown membership не даёт доступа к произвольному accountId: ответ сообщает
|
||||
о загрузке доступа. Успешный новый список немедленно ограничивает последующие
|
||||
чтения. Известный invalid token блокирует личные данные.
|
||||
|
||||
При каждом успешном сохранении подключения token шифруется с новым
|
||||
AES-GCM nonce. Из сохранённого шифротекста backend вычисляет непрозрачную
|
||||
внутреннюю generation. Она не хранится отдельным полем базы и не входит
|
||||
в API, журналы или ошибки. Portfolio Snapshot и Ledger Page Cache допускают
|
||||
повторное использование только в пределах соответствующей generation.
|
||||
Старый кэш или поздно завершившийся запрос прежней generation не обслуживает
|
||||
новый запрос после замены подключения, включая сохранение того же plaintext
|
||||
token. Удаление подключения не оставляет доступного контекста для чтения кэша.
|
||||
Успешный PUT атомарно сохраняет token, новую случайную внутреннюю revision
|
||||
и membership из проверки token. Прежние личные datasets и work становятся
|
||||
недоступны. DELETE удаляет подключение и личные данные; общие неперсональные
|
||||
источники могут оставаться. Неуспешный PUT сохраняет действующую границу.
|
||||
|
||||
После успешного изменения подключения frontend сначала записывает
|
||||
подтверждённый status, затем сбрасывает generated GET queries в поддеревьях
|
||||
`/api/v1/brokerage-accounts`, `/api/v1/stocks` и `/api/v1/bonds`. Активные
|
||||
выборки запрашиваются заново, неактивные — при следующем использовании.
|
||||
Client Cached Snapshot прежнего подключения больше не служит результатом
|
||||
новой границы.
|
||||
|
||||
Неуспешная операция подключения не меняет статус, generation и доступные
|
||||
финансовые данные. Обычная ошибка refetch также сохраняет клиентский снимок
|
||||
текущей границы. Logout — отдельное событие: оно очищает состояние
|
||||
пользовательской сессии, status подключения и query cache.
|
||||
|
||||
Пользовательский сценарий описан в [интеграции T-Bank](../../integrations/tbank.md).
|
||||
Согласованные правила frontend находятся в workspace-спецификациях
|
||||
[query coherence](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/frontend-query-coherence/spec.md)
|
||||
и [data resilience](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/frontend-data-resilience/spec.md);
|
||||
серверные правила — в
|
||||
[portfolio](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md) и
|
||||
[ledger](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-ledger/spec.md).
|
||||
Публикация проверяет revision и attempt в той же SQLite-транзакции; поздняя
|
||||
работа старого подключения не меняет даже timestamp. Revision не попадает
|
||||
в API, логи или cursor. Frontend очищает personal queries при смене подключения
|
||||
и известном отказе доступа; поздний ответ не восстанавливает скрытые данные.
|
||||
Logout очищает сессию и query cache. Подробнее —
|
||||
[интеграция T-Bank](../../integrations/tbank.md).
|
||||
|
||||
@@ -22,6 +22,18 @@
|
||||
портфеля остаётся доступным после ошибки refetch; сообщение и повтор
|
||||
относятся к обновлению, а не к отсутствию инвестиций.
|
||||
|
||||
Серверная полнота не выводится из query lifecycle. Missing показывает
|
||||
skeleton только отсутствующих секций, partial — подтверждённые строки и
|
||||
сообщение о неполноте. Totals ждут обязательных зависимостей, comparisons
|
||||
готовятся независимо. Только complete empty означает отсутствие операций.
|
||||
Время источника не заменяется временем получения HTTP-ответа.
|
||||
|
||||
Ручное обновление сохраняет показанный результат и applied filters; кнопка
|
||||
недоступна во время уже запрошенной работы. Ошибка transport отличается от
|
||||
ошибки source work. Status observation прекращается после terminal outcome
|
||||
или ухода; само истечение TTL не запускает импорт. Известный access loss
|
||||
очищает personal view, включая защиту от позднего ответа.
|
||||
|
||||
## Известная ошибка сценария
|
||||
|
||||
Ошибки счетов, портфеля и подключения T-Bank остаются локальными: рядом
|
||||
|
||||
@@ -20,6 +20,30 @@ artifact у себя и проверяет соответствие generated ty
|
||||
API имеет prefix `/api/v1`; Swagger публикуется для локальной разработки по
|
||||
environment policy, а не используется frontend в runtime.
|
||||
|
||||
## Инвестиционные данные и синхронизация
|
||||
|
||||
T-Bank data GET возвращают envelope `{ data, meta }`. `data` может быть `null`
|
||||
при missing или неполном финансовом агрегате; partial ledger/candles могут
|
||||
содержать подтверждённые строки. `meta` разделяет availability, freshness,
|
||||
lastSuccessfulAt, sync, coverage и состояния зависимостей. `dataRevision`
|
||||
описывает набор данных, `stateVersion` — наблюдаемое состояние, включая
|
||||
пустое завершённое покрытие. Ни одно поле не раскрывает connection revision.
|
||||
|
||||
Обычный GET планирует missing/stale зависимости после проверки доступа.
|
||||
GET с `sync=observe` только читает. `POST /api/v1/tbank-sync` принимает
|
||||
валидированный screen descriptor и возвращает `202` с handle и нормализованным
|
||||
scope. `GET /api/v1/tbank-sync/:id` наблюдает разрешённую работу; чужой или
|
||||
прежний handle возвращает `404`. Приём работы не означает её успех.
|
||||
|
||||
Source failure принятой работы передаётся безопасным `meta.sync.errorCode`;
|
||||
HTTP transport/access/input errors сохраняют Problem Details. Старые данные
|
||||
при transient failure сохраняют исходное успешное время. Детали поведения —
|
||||
[свежесть и полнота](cross-cutting/data-freshness.md).
|
||||
|
||||
Envelope несовместим с прежними bare-array ответами: backend/frontend
|
||||
поставляются согласованной парой. Финальный artifact передаётся явно;
|
||||
локальная генерация и CI frontend читают только собственный snapshot.
|
||||
|
||||
## Validation и identity
|
||||
|
||||
Backend преобразует и проверяет входные данные на общей HTTP-границе.
|
||||
|
||||
Reference in New Issue
Block a user