From 07de062ebbf043eff50bd7f53b48bff13bb7cb1b Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sat, 26 Sep 2026 21:22:50 +0300 Subject: [PATCH] docs: explain persisted T-Bank reads and synchronization Document local data ownership, coverage and freshness, refresh behavior, failure recovery and additive migration rollback. Record ADR 017. --- ...7-local-tbank-reads-and-background-sync.md | 140 ++++++++++++++ adr/README.md | 1 + architecture/containers.md | 43 ++--- architecture/cross-cutting/data-freshness.md | 112 ++++++----- architecture/cross-cutting/ui-resilience.md | 12 ++ architecture/http-api.md | 24 +++ domains/accounts/overview.md | 50 ++--- domains/income/overview.md | 37 ++-- domains/income/upcoming-events.md | 44 ++--- domains/instruments/bonds.md | 29 +-- domains/instruments/stocks.md | 33 ++-- domains/operations/overview.md | 62 +++--- domains/portfolio/all-accounts.md | 74 +++----- domains/portfolio/data-reference.md | 6 +- domains/portfolio/flows/portfolio-loading.md | 178 ++++-------------- .../how-to/investigate-discrepancy.md | 2 +- domains/portfolio/overview.md | 16 +- glossary.md | 19 +- integrations/tbank.md | 9 +- 19 files changed, 459 insertions(+), 432 deletions(-) create mode 100644 adr/017-local-tbank-reads-and-background-sync.md diff --git a/adr/017-local-tbank-reads-and-background-sync.md b/adr/017-local-tbank-reads-and-background-sync.md new file mode 100644 index 0000000..fa588b5 --- /dev/null +++ b/adr/017-local-tbank-reads-and-background-sync.md @@ -0,0 +1,140 @@ +# 017. Локальное чтение данных T-Bank и фоновая синхронизация + +**Status:** Accepted — архитектурное направление согласовано. + +Реализация `persist-tbank-data` добавлена в рабочие ветки приложения; +performance/browser и canonical application gates пройдены. Это не отметка +развёртывания в production. Ниже сохранён контекст +принятия решения, а текущее устройство описывает +[модель свежести](../architecture/cross-cutting/data-freshness.md). + +## Context + +Инвестиционная история не сохраняется в БД. Серверные кэши находятся в памяти +процесса, а загрузка операций, аналитики дохода и рыночной истории зависит +от повторных обращений к T-Bank. Для журнала операций запрос доступных счетов +выполняется даже перед чтением кэшированной страницы. Сохранение только +ответов без изменения этого пути не устранит ожидание внешнего API. + +Нужно ускорить все экраны, использующие T-Bank, и уменьшить повторную загрузку +данных. Текущие механизмы описаны в [политике свежести](../architecture/cross-cutting/data-freshness.md) +и [журнале операций](../domains/operations/overview.md). + +## Decision + +Для уже загруженных данных обычный запрос frontend обслуживается из постоянного +локального хранилища без ожидания ответа T-Bank. Обновление выполняется в фоне. +Пользователь видит время последнего успешного обновления и может запросить +обновление вручную. Сохранённый портфель может ещё не содержать новую сделку; +эта задержка принята ради быстрого открытия экрана. + +Синхронизация запускается только запросами пользовательских экранов и ручным +действием «Обновить». Периодических запусков, ежедневного обновления неактивных +пользователей и плановой сверки истории нет. Фоновое выполнение означает, +что уже запрошенная работа не задерживает выдачу сохранённых данных; +самостоятельное расписание обновлений оно не подразумевает. + +Обычный запрос запускает обновление, только если нужных данных нет либо +истёк согласованный срок их свежести: + +| Данные | Срок свежести | +| --- | --- | +| Портфель и текущие цены | 1 минута | +| Операции текущего месяца | 5 минут | +| Список доступных счетов | 5 минут | +| Справочник инструментов и календари купонов и дивидендов | 1 сутки | +| Незавершённая дневная свеча | 1 минута | + +Эти сроки проверяются при запросе и не задают расписание фоновых запусков. +Аналитика дохода зависит от полноты и свежести исходных операций. + +Полностью загруженные предыдущие месяцы операций повторно читаются из T-Bank +только по кнопке «Обновить» для показанного периода. Обычный запрос догружает +отсутствующие интервалы и при необходимости обновляет текущий месяц. +Завершённые дневные свечи сохраняются; ручное обновление перечитывает +показанный диапазон. Автоматическое обнаружение поздних операций и исправлений +в уже полностью загруженных прошлых месяцах не обещается. + +Кнопка «Обновить» обходит проверку давности и обновляет данные текущего +экрана: выбранного счёта, доступных счетов в режиме «Все счета» либо +инструмента и показанной личной истории на его странице. Нажатие не означает +повторную загрузку всей накопленной истории всех счетов и инструментов. + +Решение охватывает все экраны с данными T-Bank. Исполненные операции и рыночная +история сохраняются; текущие позиции, остатки и цены представлены последним +успешно полученным состоянием. Будущие выплаты остаются изменяемым прогнозом, +а не исполненными операциями. Это не решение о накоплении исторических снимков +стоимости портфеля. + +Рыночная история бумаг текущего портфеля загружается заранее в рамках +запрошенной пользователем работы. Для остальных бумаг первая загрузка +начинается при открытии инструмента, результат сохраняется для последующего +чтения. Предварительная загрузка истории всего каталога не требуется. + +При первом подключении текущий портфель и недавние операции могут отображаться +до завершения загрузки более ранней истории. Частично загруженный период явно +отличается от полного: неполные итоги нельзя выдавать за окончательные, +а отсутствие загруженных записей — за подтверждённое отсутствие операций. +Смысл полноты определён в [глоссарии](../glossary.md#полнота-истории-счёта). + +Первый запрос задаёт приоритет: сначала период, нужный экрану, включая +периоды сравнения дохода, затем вся доступная история счёта. Уже запущенная +догрузка может завершиться после ухода пользователя со страницы; нового +периодического запуска это не создаёт. + +Полнота относится к успешно загруженным временным интервалам. Смена месяца +сама по себе не делает завершённым последний неполный интервал прошлого +месяца: его оставшаяся часть должна быть догружена. Ошибка промежуточной +страницы не подтверждает полноту и не удаляет ранее сохранённые данные. +Незавершённая свеча также не становится завершённой только из-за смены дня: +её окончательное состояние должен подтвердить источник. + +Целевой ориентир — до 500 мс для обычного серверного запроса к заполненной БД. +Это согласованная цель измерения, а не подтверждённая характеристика системы. +Объём данных и набор запросов для проверки предстоит определить. + +Для этого изменения принято допущение: ID брокерской операции не меняется +со временем. Обнаружение сменившихся ID и регулярная сверка старой истории +не входят в объём работы. Это допущение проекта, а не гарантия T-Bank: +используемый protobuf-контракт допускает изменение ID. Допущение относится +к идентичности операции; оно не требует игнорировать новые значения полей, +если операция с прежним ID снова получена от брокера. + +Подключение T-Bank считается постоянным в рассматриваемом сценарии. +Архив без подключения, специальная политика хранения после отключения +и работа с закрытыми счетами пока не проектируются. Это ограничение объёма +не отменяет существующие проверки доступа и обработку ошибок интеграции. + +## Alternatives + +Ожидание свежего ответа T-Bank при каждом открытии экрана сохраняет зависимость +скорости и доступности интерфейса от брокера. Увеличение времени жизни кэша +в памяти уменьшает часть повторных запросов, но не сохраняет историю после +перезапуска и не обеспечивает локальное чтение всех нужных данных. + +## Consequences + +Интерфейсу нужны различимые состояния первичной загрузки, полноты истории, +свежести и ошибки фонового обновления. Успешная загрузка недавних операций +не подтверждает полноту более ранних периодов или периодов сравнения дохода. + +Проверки локальной сессии и принадлежности личных данных сохраняются. +Способ подтверждения брокерского доступа должен позволять обслужить уже +загруженные данные без синхронного ожидания T-Bank. + +Повторные запросы одного набора данных должны использовать уже выполняющуюся +загрузку. Завершённые интервалы и прогресс сохраняются в БД; после перезапуска +незавершённый интервал повторяется при следующем запросе или ручном обновлении. +Курсор брокера и максимальная дата полученной операции не доказывают полноту. + +Ручное обновление периода согласует состав операций и их поля с полностью +полученным ответом источника. Только успешная загрузка всех страниц позволяет +зафиксировать полное покрытие, успешное обновление и удалить отсутствующие +в этом ответе записи периода. Ошибка не превращает частичный ответ в полный. + +Личные операции и позиции сохраняют принадлежность пользователю и счёту. +Общие данные инструмента не должны содержать персональные позиции и расчёты; +поздний результат прежнего подключения не публикуется для нового подключения. + +Существующие HTTP-контракты пока не поддерживают все необходимые состояния +загрузки и полноты; их изменение входит в будущую реализацию. diff --git a/adr/README.md b/adr/README.md index 420e41b..3e49b5d 100644 --- a/adr/README.md +++ b/adr/README.md @@ -21,3 +21,4 @@ - [014 — Актуальная OpenAPI-схема как вход frontend](014-trusted-openapi-input.md) - [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) diff --git a/architecture/containers.md b/architecture/containers.md index 9f6930b..5884216 100644 --- a/architecture/containers.md +++ b/architecture/containers.md @@ -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. Ротация ключа шифрования подключений и согласование изменений подключения между распределёнными экземплярами не входят в реализованный механизм. Эти ограничения не позволяют переносить локальную схему на несколько экземпляров без отдельного diff --git a/architecture/cross-cutting/data-freshness.md b/architecture/cross-cutting/data-freshness.md index 0615884..64c262e 100644 --- a/architecture/cross-cutting/data-freshness.md +++ b/architecture/cross-cutting/data-freshness.md @@ -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). diff --git a/architecture/cross-cutting/ui-resilience.md b/architecture/cross-cutting/ui-resilience.md index fbdebef..207b5fc 100644 --- a/architecture/cross-cutting/ui-resilience.md +++ b/architecture/cross-cutting/ui-resilience.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 остаются локальными: рядом diff --git a/architecture/http-api.md b/architecture/http-api.md index 8c2b91b..da471d3 100644 --- a/architecture/http-api.md +++ b/architecture/http-api.md @@ -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-границе. diff --git a/domains/accounts/overview.md b/domains/accounts/overview.md index 7c551c5..dc185c0 100644 --- a/domains/accounts/overview.md +++ b/domains/accounts/overview.md @@ -7,30 +7,11 @@ только к backend. Счёт не хранится как долговременное локальное владение независимо от брокерского подключения. -```mermaid -sequenceDiagram - participant U as Пользователь - participant F as Frontend - participant B as Backend - participant D as SQLite - participant T as T-Bank gRPC - U->>F: Открывает /accounts - F->>B: GET /api/v1/brokerage-accounts (Bearer) - B->>D: Читает подключение T-Bank - alt Подключение отсутствует - B-->>F: 409 TBANK_NOT_CONNECTED - F-->>U: Переход к настройкам T-Bank - else Подключение сохранено - B->>T: GetAccounts(OPEN) over TLS - T-->>B: Счета или ошибка - par Для каждого поддерживаемого счета - B->>T: GetPortfolio + GetPositions over TLS - T-->>B: Позиции и деньги либо временная ошибка - end - B-->>F: Счета со сводками либо ошибка - F-->>U: Карточки счетов или объяснение ошибки - end -``` +Экран читает локальный список и сохранённые сводки. Обычный запрос запускает +недостающие или устаревшие зависимости в фоне. Список и сводка каждого счёта +имеют независимые timestamps и состояния. Неподтверждённый список показывает +загрузку доступа, а не отсутствие счетов. Поток и границы описаны в +[модели свежести](../../architecture/cross-cutting/data-freshness.md). ## Счёт и его сводка @@ -69,8 +50,8 @@ sequenceDiagram сводка не подменяется нулями. Временная недоступность сводки одного счёта остаётся локальной для него. -Ошибка подключения, отклонение токена или невозможность получить исходный -список не превращаются в частичный успешный список. +Известный отказ доступа скрывает личные данные. Transient failure сохраняет +последний список и его время; отсутствие первого списка остаётся missing. ## Состояния @@ -85,16 +66,17 @@ sequenceDiagram - Неизвестная ошибка: экран предлагает только повторить загрузку. - Успешный пустой ответ: штатное состояние отсутствия инвестиционных счетов. -Карточка ведёт на `/accounts/:accountId` по всей области и доступна с клавиатуры. На широком экране карточки расположены в две колонки, на узком — в одну. Карточка показывает название, локализованный тип, крупный валютный итог и вторую валюту при наличии, число остальных валют, дату открытия без времени и видимое действие открытия. Отдельные цветные блоки «Акции» и «Облигации» показывают количество наименований бумаг. Недоступная или пустая сводка отображается локально; отсутствие сумм не скрывает известные количества бумаг, а недоступная сводка не подменяет их нулями. Наведение и видимый клавиатурный фокус подчёркивают переход; настройка уменьшения движения отключает анимацию. +Карточка ведёт на `/accounts/:accountId` по всей области и доступна с клавиатуры. Карточки расположены в одну колонку при любой ширине. Карточка показывает название, локализованный тип, крупный валютный итог и вторую валюту при наличии, число остальных валют, дату открытия без времени и видимое действие открытия. Отдельные цветные блоки «Акции» и «Облигации» показывают количество наименований бумаг. Недоступная или пустая сводка отображается локально; отсутствие сумм не скрывает известные количества бумаг, а недоступная сводка не подменяет их нулями. Наведение и видимый клавиатурный фокус подчёркивают переход; настройка уменьшения движения отключает анимацию. ## Свежесть и границы -Список запрашивается при открытии экрана без ручного обновления и без обновления при возврате фокуса. Frontend использует успешный кэш TanStack Query в течение 60 секунд, поэтому повторный вход в этот интервал не выполняет новый GET. Когда уже показанный список обновляется в фоне, он остаётся на экране: индикатор обновления и ошибка с повторной попыткой отображаются рядом с последним успешным ответом, включая пустой список. Это клиентский сохранённый ответ; серверный запасной список не выдаётся. Backend не хранит кэш и не передаёт метку свежести. Список не заменяет полный [портфель](../portfolio/overview.md), -[журнал операций](../operations/overview.md) или историю закрытых счетов. -Для совокупного результата доступен [режим «Все счета»](../portfolio/all-accounts.md). +Обычное открытие использует query owner и локальный server snapshot. +«Обновить» запрашивает membership и сводки доступных счетов; активная работа +наблюдается без нового импорта. Истечение TTL само не запускает запрос. +Membership имеет пятиминутную свежесть, portfolio — минутную. Время списка +не подменяет время каждой сводки. Список не заменяет полный +[портфель](../portfolio/overview.md), [журнал](../operations/overview.md) +или историю закрытых счетов. Для общего результата доступен +[режим «Все счета»](../portfolio/all-accounts.md). Согласованное поведение описывает [спецификация счетов](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-accounts/spec.md). -Её раздел об экране пока требует одноколоночного списка и не описывает -счётчики акций и облигаций. Текущая реализация и её проверки используют -две колонки на широком экране и показывают эти счётчики. Расхождение требует -отдельного согласования требований; в этой документации отражён наблюдаемый экран. diff --git a/domains/income/overview.md b/domains/income/overview.md index 3a1993c..6045d43 100644 --- a/domains/income/overview.md +++ b/domains/income/overview.md @@ -49,21 +49,19 @@ ## Какие выплаты входят в результат -- Backend проверяет доступ к счёту, проходит все страницы исполненных операций и не возвращает частичный результат при ошибке страницы или повреждённой цепочке курсоров. +- Backend проверяет доступ локально и читает сохранённые операции. Фоновая загрузка должна завершить все страницы; ошибка страницы или повреждённая цепочка курсоров не подтверждают полноту. - Только точные типы купона и дивиденда источника формируют результат. Суммы остаются точными десятичными строками в исходной валюте; отрицательные корректировки сохраняют знак. Налоги, комиссии, сделки, переводы, погашения и переводы дивидендов не входят в доход. -- Итоги, сравнения, временные интервалы и разбиение по инструментам не смешивают валюты и не создают общий конвертированный итог. На странице показано только сравнение с годом ранее, когда календарная дата и история счёта позволяют. Предыдущий период остаётся в ответе API, но не отображается. +- Итоги, сравнения, временные интервалы и разбиение по инструментам не смешивают валюты и не создают общий конвертированный итог. Страница показывает сравнения с предыдущим периодом и годом ранее, когда календарная дата и история счёта позволяют. Неполное сравнение имеет отдельное сообщение, без нулевой базы. - Временные интервалы по-прежнему приходят с backend: дни для 1–45 дней, недели для 46–180 и месяцы для более длинного периода, включая пустые интервалы. Страница дохода не строит из них график или текстовую динамику; ответ API не меняется. - Раздел «Выплаты» показывает единый список карточек выплат в исходном порядке `entries`: одна карточка на каждую выплату. Внешних групп по инструментам или валютам, итогов и количества выплат инструмента нет. Описание, дата, доступные идентификаторы инструмента и сумма со знаком видны в каждой карточке; валюты по-прежнему разделены в итогах и сравнениях. Дополнительный запрос за составом не нужен. -Сравнение с непосредственно предыдущим периодом сохраняется в API: -для полного месяца это предыдущий полный месяц, иначе — непосредственно -предшествующий интервал той же включительной длительности. Текущая страница -его не показывает. Это расходится с -[основным требованием сравнений](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-income-analytics/spec.md#requirement-period-comparisons-preserve-calendar-and-currency-meaning), -которое описывает оба сравнения. Требуется отдельно согласовать этот разрыв; -здесь сохранено различие между ответом API и фактическим экраном. +Сравнение с непосредственно предыдущим периодом использует предыдущий полный +месяц для полного выбранного месяца, иначе — непосредственно предшествующий +интервал той же включительной длительности. Оно не подменяет сравнение годом +ранее. Незагруженное сравнение и ошибка его источника отличаются от +недоступности годового сравнения по календарю или дате открытия счёта. -Раздел «Годом ранее» показывает цветные панели без общей белой подложки и +Каждый раздел сравнения показывает цветные панели без общей белой подложки и внутренних отступов у внешнего блока. Точные периоды показаны в заголовках двух панелей с русским форматом диапазона и UTC для календарных дат. Отдельного заголовка с датами и валютного бейджа нет; валюта остаётся у каждой суммы. @@ -76,6 +74,16 @@ ## Полнота и принадлежность выплат +Доходы читают сохранённые операции и coverage в согласованной транзакции. +Selected, previous и yearAgo имеют независимую готовность. Неполный selected +не получает окончательных итогов; неполное сравнение не создаёт нулевую базу. +Подтверждённый пустой период отличается от ещё не загруженного. При смене +периода прежний snapshot не показывается под новым названием. + +Ручная сверка полного периода сохраняет весь прежний результат до успешной +атомарной публикации всех необходимых интервалов и счетов. История счёта +может ещё догружаться после готовности выбранного месяца. + Каждая запись `income-analytics.entries` содержит обязательный `instrumentKey`, равный `key` соответствующей строки `instruments`. Backend вычисляет этот ключ один раз при подготовке записей дохода и использует его же для группировки. @@ -90,9 +98,10 @@ Frontend проверяет принадлежность выплат по по состоянию ошибки аналитики с повторной попыткой. Частичный список выплат не показывается как полный; формула идентификации в клиенте не восстанавливается. Полностью пустой ответ допустим. Состав и исходный серверный порядок записей и знак сумм -сохраняются, frontend не пересчитывает денежные итоги. Повторяющиеся ID -операций остаются отдельными строками; повторные вхождения учитываются раздельно, а изменение источника логотипа -позволяет заново показать изображение после буквенной заглушки. +сохраняются, frontend не пересчитывает денежные итоги. Стабильный ID в пределах подключения и счёта определяет одну операцию: +повторная сверка обновляет её без двойного учёта. Одинаковые ID разных счетов +остаются разными выплатами. Изменение источника логотипа позволяет заново +показать изображение после буквенной заглушки. ## Будущие события и ограничения @@ -102,7 +111,7 @@ Frontend проверяет принадлежность выплат по по выбранного счёта и сохраняет прежнее поведение. Будущие выплаты не включаются в итоги полученного дохода или сравнения. -Аналитика только читает данные, не хранит историю операций и не рассчитывает налоги, инвестиционную эффективность, доходность, валютную конвертацию или общий мультивалютный итог. +Аналитика читает сохранённую историю операций и не рассчитывает налоги, инвестиционную эффективность, доходность, валютную конвертацию или общий мультивалютный итог. Основное проверяемое поведение — в [спецификации дохода](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-income-analytics/spec.md), точная форма ответа — в [HTTP API](../../architecture/http-api.md). diff --git a/domains/income/upcoming-events.md b/domains/income/upcoming-events.md index 288ee49..2f9de3c 100644 --- a/domains/income/upcoming-events.md +++ b/domains/income/upcoming-events.md @@ -60,39 +60,23 @@ API принимает необязательные `from` и `to` в форма `incomeTotalsByCurrency`, включающего только купоны и дивиденды. Итоги остаются в ответе API; полный экран показывает список без общего итога. -Текущие controller, сервис и экран поддерживают выбор периода, тогда как -[основная спецификация](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-upcoming-events/spec.md#requirement-account-page-presents-an-upcoming-events-list) -описывает фиксированные 30 дней и экран без фильтров. Документация отражает -реализованный выбор периода, не изменяя это несогласованное требование. - ## Получение и отказы -```mermaid -sequenceDiagram - participant F as Frontend - participant B as Backend - participant T as T-Bank - participant M as MOEX ISS - F->>B: Счёт и выбранный период событий - B->>B: Проверяет сессию и текущее подключение - B->>T: Получает открытые счета и позиции доступного счёта - par События акций - B->>T: GetDividends за выбранный период - T-->>B: Известные дивиденды - and События облигаций - B->>M: Календарь bondization - M-->>B: Купоны, погашения и оферты - end - B->>B: Применяет период и текущее количество бумаг - B-->>F: Полный список известных событий либо ошибка -``` +Запрос требует сессии, подключения и локально подтверждённого membership. +Прогноз строится из личного сохранённого portfolio и общих календарей: T-Bank для +дивидендов, MOEX для bond events. Отдельного синхронного получения portfolio +нет. Личные оценки сохраняются отдельно от общих календарей. -Запрос требует действующей сессии и подключения T-Bank. Получение -необходимых финансовых данных брокера или календаря MOEX завершается ошибкой -`503`, если источник недоступен: частичный список не выдаётся как полный. -Доступные метаданные инструментов дополняют название и логотип; когда -исходного тикера достаточно для календаря, ошибка дополнительного поиска -метаданных не скрывает события. +Последний полный forecast привязан к scope, периоду и membership. +Если новый portfolio требует ещё недоступного календаря, прежний полный +прогноз и его source times остаются, в том числе после рестарта. Новый +полный результат заменяет его атомарно. Без fallback показывается +missing/partial, а не подтверждённое отсутствие выплат. Другой период или +изменившийся membership не получают старый прогноз под новым названием. +Source failure сообщается в metadata и не изменяет успешное время. +Успешный ответ MOEX без запрошенного блока означает отсутствие поддерживаемых +событий этого источника; это отличается от transport failure и повреждённого +формата ответа. При первом открытии показан каркас заголовка и строк, без интерактивных заглушек. Пустой ответ сохраняет возможность изменить период. diff --git a/domains/instruments/bonds.md b/domains/instruments/bonds.md index 2568f1d..7e030ae 100644 --- a/domains/instruments/bonds.md +++ b/domains/instruments/bonds.md @@ -111,20 +111,21 @@ HTML-источник, общий кэш и поведение при измен транспортного контракта задаёт [HTTP API](../../architecture/http-api.md). `BondBy`, `FindInstrument` и `GetBondCoupons` используют способ -декодирования, сохраняющий отсутствие полей. Рыночные RPC проходят -общие TLS, очередь, ограничение частоты, deadline и повторные попытки. Кэш успешных и совместных -незавершённых запросов ограничен LRU и сроком 30 секунд по пользователю, -поколению подключения и параметрам. Ошибки не кэшируются; ошибки авторизации -не подавляются при дополнительной загрузке данных поиска. -Исходный ответ `BondBy` совместно используется поиском, карточкой, котировкой, -графиком, купонами и сводкой показателей в пределах того же пользователя, поколения и TTL. -Одновременная загрузка секций вызывает один `BondBy` на UID. Календарь -и сводка совместно используют один запрос купонов; котировка переиспользуется -при расчёте купонной доходности. MOEX и НКР загружаются только для открытого -выпуска, независимо от поиска. Для 30 новых -результатов поиск выполняет 32 RPC до повторных попыток: один `FindInstrument`, -30 `BondBy` и один пакетный запрос цен. Общий ограничитель backend не заменяет -квоты Т-Банка; при их исчерпании сохраняется `TBANK_UNAVAILABLE`. +декодирования, сохраняющий отсутствие полей. Общие данные выпуска, quotes, +календари, MOEX metrics и необходимые результаты НКР сохраняются в SQLite. +Личные позиции и оценки выплат используют текущий доступ и сохранённый +portfolio; они не попадают в общие источники. + +BondBy переиспользуется зависимостями выпуска. Discovery принадлежит query, +свежесть — конкретному source/range. Missing/stale зависимости запрашиваются +в фоне через прежний limiter; обычный HTTP-ответ не ждёт брокера. +Покрытые завершённые свечи не перечитываются после restart. Открытый хвост +остаётся изменяемым; ручной refresh охватывает показанный период. + +Последняя полная личная оценка сохраняется при ошибке новых обязательных +зависимостей, вместе с исходным временем. Неполнота не означает отсутствие +выплат. Наблюдение статуса не запускает повторный импорт. Политика источников +и границы — в [модели свежести](../../architecture/cross-cutting/data-freshness.md). Разделы загружаются независимо, показывают каркас загрузки и локальную повторную попытку. Ошибка фонового обновления сохраняет последние данные. Смена подключения diff --git a/domains/instruments/stocks.md b/domains/instruments/stocks.md index fc7ec0f..6fc583f 100644 --- a/domains/instruments/stocks.md +++ b/domains/instruments/stocks.md @@ -73,7 +73,8 @@ Frontend не получает брокерский токен и не обра Личная история использует существующий `GET /api/v1/brokerage-accounts/all/operations?instrumentUid=...`. -Фильтр UID валидируется и передаётся upstream как `instrumentId`; ответ также +Фильтр UID валидируется и применяется к локальной истории. При ручной +сверке бумаги он также передаётся upstream как `instrumentId`; ответ проверяется по UID. Без `from` такой запрос начинается с самой ранней известной даты открытия доступных счетов; если даты неполны, нижняя граница — Unix epoch. Обычный журнал без фильтра инструмента сохраняет начало текущего года. @@ -142,18 +143,24 @@ TTM означает последние 12 месяцев, MRQ — послед ## Получение данных и ограничения -Запросы T-Bank проходят общую серверную интеграцию с TLS, лимитом частоты, -очередью, предельным временем ожидания и повторными попытками. Проверка доступа -и расшифровка token выполняются только backend. Успешные ответы и совместные незавершённые запросы кэшируются -в ограниченном кэше LRU на 30 секунд по пользователю, поколению подключения и параметрам. -Для поиска `FindInstrument` дополняется сведениями `ShareBy` через тот же -кэш карточки инструмента и одним пакетным `GetLastPrices` для списка. Ошибка -дополнительной загрузки или исчезнувшая карточка не скрывает исходную запись -каталога; недоступные логотипы и цены имеют заглушки. Ошибки авторизации -не подавляются. -Ошибка целого запроса не кэшируется; отсутствие дополнительного источника -обозначается в ответе. Поддерево клиентских запросов акций сбрасывается вместе с финансовыми -данными при изменении подключения; выход очищает кэш сессии. +Общие metadata, discovery, company, quotes, свечи и календари сохраняются +в SQLite. Поиск хранит подтверждённый набор UID для конкретного query; +успешный SBER не подтверждает пустой GAZP. Отсутствующее enrichment не +скрывает найденную бумагу. Сохранённые общие источники переиспользуются +между пользователями, но личные позиции и выплаты всегда проверяют доступ. + +Обычное чтение не ждёт RPC; missing/stale зависимости запрашиваются в фоне +через существующий T-Bank limiter. Минутная свежесть применяется к quotes +и открытому хвосту свечей, суточная — к metadata/company/календарям. +Закрытые полные свечные интервалы, включая пустые, сохраняются после restart. +Новый день не делает прежнюю незавершённую свечу полной. + +«Обновить» передаёт текущий инструмент, выбранный график и personal-history +scope. Частичные строки видны с предупреждением; неполные личные оценки +не становятся нулями. Статус и `sync=observe` не создают новый импорт. +Смена подключения и известный access loss очищают личное представление; +поздние ответы не возвращают прежние данные. Детали — +[модель свежести](../../architecture/cross-cutting/data-freshness.md). Полные даты в интерфейсе имеют формат ДД.ММ.ГГГГ; ISO-формат транспорта и временные зоны расчётов не меняются. Личная история использует общий с портфелем формат строки операции, diff --git a/domains/operations/overview.md b/domains/operations/overview.md index e215768..d3e06fe 100644 --- a/domains/operations/overview.md +++ b/domains/operations/overview.md @@ -46,8 +46,9 @@ ID операций разных счетов не означают дублик На [страницах акций](../instruments/stocks.md) и [облигаций](../instruments/bonds.md) этот же журнал становится личной историей -конкретной бумаги. Фильтр `instrumentUid` передаётся брокеру и проверяется -в ответе. Без явного начала периода он охватывает историю с открытия счёта; +конкретной бумаги. Фильтр `instrumentUid` применяется к локальным операциям. Ручная сверка +личной истории может ограничить источниковый запрос этой бумагой, не присваивая +такому subset полноту всех операций счёта. Без явного начала периода он охватывает историю с открытия счёта; в общем режиме — с самой ранней известной даты открытия доступных счетов. Если даты открытия неполны, нижняя граница — Unix epoch. Это позволяет видеть прошлые операции уже проданной бумаги, но не обещает историю @@ -55,43 +56,29 @@ ID операций разных счетов не означают дублик ## Получение и полнота -```mermaid -sequenceDiagram - participant F as Frontend - participant B as Backend - participant T as T-Bank - F->>B: Запрашивает применённый период и страницу операций - B->>B: Проверяет сессию и текущее подключение - B->>T: Получает открытые счета - T-->>B: Подтверждает доступность счёта - alt Подходящая страница есть в кэше - B-->>F: Сохранённая страница - else Нужна загрузка - B->>T: GetOperationsByCursor, только исполненные операции - T-->>B: Страница операций - B->>B: Нормализует записи и применяет фильтры - B-->>F: Записи и продолжение либо ошибка - end -``` +`GET /api/v1/brokerage-accounts/:accountId/operations` читает сохранённые +операции и coverage после локальной проверки доступа. Период, категории, +UID, cursor и limit применяются к этой выборке. Транспорт описан в +[HTTP API](../../architecture/http-api.md). -`GET /api/v1/brokerage-accounts/:accountId/operations` требует Bearer token. -Период, категории, необязательный UID, курсор и размер страницы задают запрос; -ответ содержит записи, время наблюдения и сведения о продолжении. -Точные поля и допустимые значения задаёт [HTTP API](../../architecture/http-api.md). +Стабильность broker operation ID в пределах счёта и подключения принята как +допущение проекта. Повторный ID обновляет ту же запись без двойного учёта. +Страницы источника сначала сохраняются в staging. Полный успешный обход +разрешает upsert, удаление исчезнувших записей в перечитанном predicate +и публикацию coverage одной транзакцией. При повторной сверке полного +показанного периода транзакция охватывает все его окна и счета. -Backend хранит не более 100 страниц в памяти процесса. Свежая страница -живёт до 30 секунд; при переполнении вытесняется давно не использовавшаяся. -Ключ разделяет пользователя, поколение подключения, счёт, нормализованный -период и категории, UID инструмента, курсор и размер страницы. -Отсутствующий конец периода приводится к текущему 30-секундному интервалу. -Текущее подключение и доступ к счёту проверяются до выдачи страницы из кэша. +Полнота относится к UTC-интервалу `[from,to)`, включая подтверждённый пустой. +Категории, размер страницы и `hasNext=false` её не определяют. При первичном +импорте подтверждённые строки видны как partial; итоги ждут полного периода. +Догрузка остальной истории обозначается отдельно. Без известной нижней +границы нельзя заявить полную историю счёта. -После успешного изменения подключения страницы предыдущего поколения, -в том числе поздно завершившиеся запросы, не выдаются журналу или аналитике -дохода. Они удаляются по обычным ограничениям времени и объёма. -Локальной синхронизации и долговременного хранения операций нет. -Продолжения [общего журнала](../portfolio/all-accounts.md#общая-пагинация-операций) -имеют собственные ограничения и восстановление после истечения. +Cursor связан с пользователем, подключением, membership для `all`, фильтрами +и ревизией набора операций. Изменение строк даёт `INVALID_OPERATIONS_CURSOR`; +клиент возвращается на первую страницу, сохраняя применённые фильтры. +Coverage/status без изменения rowset cursor не ломают. Перезапуск сохраняет +опубликованные интервалы; незавершённая работа возобновляется только запросом. ## Представление и ошибки @@ -110,7 +97,8 @@ Backend хранит не более 100 страниц в памяти проц не заменяется каркасом при применении действий или фоновом обновлении. Ошибки `TBANK_NOT_CONNECTED`, `INVALID_TBANK_TOKEN`, недоступный счёт (`404`) и `TBANK_UNAVAILABLE` сохраняют [общую семантику API](../../architecture/http-api.md). -Пустой успешный ответ означает отсутствие подходящих операций, а не сбой. +Только полный пустой ответ означает отсутствие подходящих операций. +Partial empty сообщает о неполноте; source failure сохраняет прежний результат. Согласованное поведение описано в [спецификации журнала](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-ledger/spec.md). Фильтр конкретной бумаги и общий режим расширяют исходное описание отдельного diff --git a/domains/portfolio/all-accounts.md b/domains/portfolio/all-accounts.md index 320bb52..edc843a 100644 --- a/domains/portfolio/all-accounts.md +++ b/domains/portfolio/all-accounts.md @@ -51,9 +51,9 @@ Backend публикует статические маршруты под `/api/ используют прежнюю карточку. Доля относится к позициям той же валюты во всём показанном портфеле, без денежных остатков. - Журнал объединяет операции по убыванию времени, при равном времени использует - порядок идентификаторов счетов, сохраняя порядок внутри одного счёта. ID + порядок идентификаторов счетов и операций. ID операций разных счетов не считаются дублями. Переводы не схлопываются. -- Доходы собираются из всех страниц операций каждого счёта за общие периоды. +- Доходы рассчитываются из согласованной локальной выборки операций за общие периоды. Итоги, сравнения, временные интервалы и принадлежность выплат рассчитывает сервис дохода. Для сравнения с годом ранее используется текущий состав счетов; новый счёт @@ -65,55 +65,39 @@ Backend публикует статические маршруты под `/api/ ## Полнота, свежесть и доступ -Для каждого общего запроса backend определяет состав доступных счетов -и единое поколение подключения. Получение данных использует эту же границу подключения; -проверки сессии, доступа и существующий лимитер внешних запросов сохраняются. -Финансовые данные не сохраняются в БД; снимки и продолжения хранятся -в памяти процесса. +Общий запрос читает membership и разрешённые snapshots из SQLite в одной +согласованной границе. Время источников может различаться: это не обещание +одновременного наблюдения счетов у брокера. Неполный счёт делает aggregate +неполным; отсутствующие суммы не подменяются нулями. `lastSuccessfulAt` +показывает самое раннее время обязательных источников. -Ошибка источника не превращается в частичный успешный ответ раздела. -Портфель может использовать разрешённый существующей политикой запасной снимок -отдельного счёта. Тогда общий `isStale` равен `true`, а `lastSuccessfulAt` -показывает самое раннее время среди включённых снимков. Для пустого состава -возвращается корректный пустой результат. Общий запрос не является атомарным -наблюдением всех счетов в один момент времени. - -Ошибка блока операций, доходов или будущих событий не скрывает основной -портфель. При фоновом обновлении страницы уже полученный результат сохраняется -с индикатором или предупреждением и повторной попыткой. Смена сессии или -подключения сбрасывает общий режим вместе с остальными финансовыми запросами. +Секции портфеля, операций, дохода и событий готовы независимо. Ошибка +обновления сохраняет прежний полный результат того же scope и периода. +Известный отказ доступа и смена подключения очищают personal view. ## Общая пагинация операций -Сервис объединяет потоки страниц каждого счёта и сохраняет непрочитанные строки -между запросами. Фильтрованные пустые страницы не обрывают поток. Повреждённая -цепочка курсоров и нарушенный порядок дат возвращают ошибку без частичной страницы. +Страница строится из локальных строк с порядком occurredAt, accountId и ID +операции. Cursor проверяет пользователя, подключение, membershipRevision, +фильтры, limit и ревизии операций. Изменение rowset возвращает +`400 INVALID_OPERATIONS_CURSOR`; клиент сбрасывает стек страниц, сохраняя +применённые фильтры. Coverage/status без изменения строк не ломают cursor. +Продолжение не зависит от RAM-кэша или перезапуска. -Курсор общего журнала — случайный непрозрачный идентификатор продолжения, -привязанного к пользователю, поколению подключения, составу счетов, фильтрам -и размеру страницы. Верхняя граница периода фиксируется первым запросом. -Состояние ограничено 100 продолжениями на процесс со сроком жизни пять минут; -повторный запрос того же продолжения возвращает тот же успешный результат. - -Неизвестный, вытесненный, истёкший либо несовместимый курсор возвращает -`400 INVALID_OPERATIONS_CURSOR`. Действие «Повторить» сбрасывает пагинацию, -обновляет первую страницу и сохраняет фильтры. Перезапуск backend также -инвалидирует продолжения. Раздел рассчитан на один экземпляр backend, как -существующий финансовый кэш. +Повторная сверка полного показанного периода публикует все счета и интервалы +одной транзакцией; успешное первое окно не смешивается с прежними остальными. +Подробнее — [журнал](../operations/overview.md#получение-и-полнота). ## Совместимость и границы требований -Общий режим добавляет маршруты, сохраняя маршруты отдельных счетов. -При совместном обновлении сначала нужен backend, затем frontend с -соответствующим сгенерированным контрактом; прежний клиент продолжает -работать с отдельными счетами. При откате сначала возвращается клиент. -Этот механизм не требует изменения схемы SQLite и миграции финансовых данных. +Общий режим использует те же persistent sources и envelope, что и отдельный +счёт. Новый envelope несовместим с прежним bare response: backend и frontend +поставляются согласованной парой по [HTTP contract](../../architecture/http-api.md). +Additive migration сохраняет прежние таблицы; rollback приложения не удаляет +новые данные и не заменяет рабочую базу старым backup. Возврат требует заранее +проверенной совместимой пары; произвольный прежний клиент не считается совместимым. +Процедура backup/restore описана в [runbook](../../architecture/deployment.md). -Основные спецификации [портфеля](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md), -[операций](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-ledger/spec.md), -[дохода](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-income-analytics/spec.md) -и [событий](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-upcoming-events/spec.md) -описывают отдельный счёт. Общий режим подтверждён действующими маршрутизаторами, -сервисами и [проверками агрегации](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/all-accounts.spec.ts), -но не выделен в самостоятельную основную спецификацию. Не следует считать -описание общего режима изменением требований к отдельному счёту. +Основные спецификации областей сохраняют исходные понятия отдельного счёта. +Утверждённый change `persist-tbank-data` распространяет полноту, локальный +доступ, атомарную сверку и состояния синхронизации на режим `all`. diff --git a/domains/portfolio/data-reference.md b/domains/portfolio/data-reference.md index c976463..4912199 100644 --- a/domains/portfolio/data-reference.md +++ b/domains/portfolio/data-reference.md @@ -32,7 +32,7 @@ | Cash Balance | Суммы из `GetPositions.money`, объединённые по валюте | Заблокированные деньги не прибавляются; торгуемый инструмент `CURRENCY` остаётся позицией | | `totalsByCurrency` | Стоимости позиций + доступные деньги той же валюты | Не включает невычислимую стоимость; не обещает полную оценку портфеля | | `lastSuccessfulAt` | Время формирования успешного снимка, UTC | Не является временем каждой котировки; запасной снимок сохраняет исходное время | -| `isStale` | Backend вернул предыдущий полный снимок после временной ошибки | Не описывает состояние клиентского кэша или ошибку фонового запроса | +| `isStale` | Истёк минутный срок свежести сохранённого portfolio source | Не описывает состояние клиентского кэша или ошибку фонового запроса | Ненулевые денежные остатки одной валюты суммируются. Позиции одинаковых инструментов в ответе отдельного счёта не объединяются; визуальная группировка @@ -56,8 +56,8 @@ UID определяет конкретный инструмент брокер ISIN, тикер и `logoName` могут быть `null`. `logoName` — имя файла бренда, а не URL: frontend строит ссылку на публичный CDN. Ошибка изображения или метаданных не меняет количество, суммы и свежесть снимка и не скрывает -финансовые данные. Имя ожидается перед завершением нового снимка; -ошибка поиска оставляет доступный идентификатор. +финансовые данные. Metadata обновляется независимо; отсутствие имени не задерживает выдачу +финансового снимка и оставляет доступный идентификатор. ## Структура и доли diff --git a/domains/portfolio/flows/portfolio-loading.md b/domains/portfolio/flows/portfolio-loading.md index fdccd25..5118703 100644 --- a/domains/portfolio/flows/portfolio-loading.md +++ b/domains/portfolio/flows/portfolio-loading.md @@ -1,158 +1,46 @@ # Загрузка портфеля -Поток начинается при открытии `/accounts/:accountId` пользователем -с действующей сессией. Его результат — полный нормализованный снимок -доступного счёта либо объяснимая ошибка. Браузер обращается к backend; -доступ к брокерскому токену и внешние запросы остаются на сервере. +Экран `/accounts/:accountId` читает последний полный snapshot из SQLite +после проверки сессии, подключения и локального membership. Если список +счетов ещё неизвестен, сервер сообщает о загрузке доступа без выдачи личных +данных неизвестного ID. Известный недоступный счёт возвращает `404`. ## Основной обмен -Сначала frontend проверяет свой успешный кэш: в течение 30 секунд повторное -открытие может показать ответ без нового HTTP-запроса. Следующая схема -описывает запрос, который действительно дошёл до backend. +Обычный GET возвращает локальный envelope. Missing/stale зависимости +запрашиваются в фоне; чтение не ждёт T-Bank. Для нового полного snapshot +нужны оба финансовых ответа `GetPortfolio` и `GetPositions`. Суммы и позиции +публикуются вместе; metadata дополняет представление независимо, с доступным +UID/FIGI вместо имени до её загрузки. -```mermaid -sequenceDiagram - participant F as Frontend - participant B as Backend API - participant D as SQLite - participant T as T-Bank - F->>B: Запрос портфеля счёта с Bearer token - B->>B: Проверяет сессию - B->>D: Читает текущее подключение пользователя - D-->>B: Подключение либо отсутствие - alt Подключения нет - B-->>F: 409 TBANK_NOT_CONNECTED - else Подключение доступно - B->>B: Определяет поколение подключения - alt Снимок того же поколения не старше 30 секунд - B-->>F: Полный снимок, isStale=false - else Нужен новый снимок - B->>T: Получает открытые счета - T-->>B: Состав доступных счетов - alt Счёт недоступен - B-->>F: 404 - else Счёт доступен - par Финансовые позиции - B->>T: GetPortfolio - T-->>B: Позиции и цены - and Доступные деньги - B->>T: GetPositions - T-->>B: Денежные остатки - end - B->>T: Метаданные отсутствующих инструментов по UID, затем FIGI - T-->>B: Метаданные, отсутствие или ошибка - B->>B: Нормализует состав и точно вычисляет суммы - B-->>F: Полный новый снимок, isStale=false - end - end - end -``` +Свежесть portfolio — 60 секунд. Истечение срока не удаляет snapshot и не +создаёт таймер. «Обновить» на обзоре перечитывает зависимости текущего экрана: +membership, portfolio, показанные операции, доход и будущие события. +Секции могут быть готовы независимо. Подробнее — +[свежесть](../../../architecture/cross-cutting/data-freshness.md). -Свежий серверный снимок выдаётся после разрешения текущего сохранённого -подключения, но без нового `GetAccounts`. После истечения свежего кэша -проверка доступности счёта предшествует финансовым RPC. SQLite хранит -подключение, а не снимок или право владения счётом вне этого подключения. +## Границы доступа -Для нового снимка требуются оба финансовых ответа. Backend не выдаёт -позиции без денег или деньги без позиций как полный портфель. -Метаданные дополняют имя и идентификаторы, но их временная недоступность -не срывает финансовый результат. Поиск выполняется партиями по пять позиций, -чтобы крупный портфель не переполнял очередь одновременными запросами. -Порядок позиций сохраняется. Backend ищет сведения по UID, затем по FIGI, -если UID не дал пригодного названия или тикера. -Ожидание метаданных ограничено 30 секундами на снимок счёта. После этого -используется доступный кэш либо идентификатор, новые поиски для снимка -не запускаются. Уже начатые запросы могут закончиться позже и обновить общий -справочный кэш, но не меняют выданный финансовый снимок. -Общий серверный клиент обеспечивает TLS, очередь, -ограничение частоты, deadline RPC и разрешённые повторные попытки — -[граница интеграции](../../../integrations/tbank.md). - -## Время и изменение подключения - -| Слой | Что сохраняется | Условие повторного использования | -| --- | --- | --- | -| Frontend | Последний успешный HTTP-ответ для выбранного счёта и сессии | Свежим считается 30 секунд; при обычном обновлении уже показанный ответ остаётся видимым | -| Backend | Полный снимок пары пользователь–счёт в памяти процесса | До 30 секунд, только в том же поколении текущего подключения | -| Backend при временной ошибке | Тот же полный снимок | Не старше пяти минут с момента успешного сохранения, только в том же поколении | -| Метаданные инструмента | Успешный поиск или подтверждённое отсутствие | Свежими считаются 24 часа; общий кэш на 500 ключей UID/FIGI, ошибки не сохраняются | - -Это параметры текущей реализации, а не самостоятельная политика этого -документа: [снимки](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/portfolio/application/portfolio.service.ts), -[метаданные](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/integrations/tbank/instrument-metadata.service.ts) и -[клиентский запрос](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/src/entities/portfolio/api/usePortfolio.ts). - -Пять минут — предельный возраст успешного снимка, а не дополнительные -пять минут после окончания свежих 30 секунд. Запасной ответ сохраняет -исходное `lastSuccessfulAt` и получает `isStale: true`. - -После истечения 24 часов backend обновляет справочные данные. Если обновление -временно недоступно, ранее успешные сведения остаются запасным вариантом, -пока запись есть в ограниченном кэше процесса. Ошибка не продлевает срок -свежести: следующий поиск снова пытается обновить запись. Это относится только -к названию, тикеру, ISIN и логотипу, а не к ценам или денежным значениям. -Перезапуск backend и вытеснение записи из кэша удаляют этот запасной вариант. -Неполный ответ без названия и тикера также не затирает известные сведения -и не обновляет срок их свежести. Подтверждённое отсутствие инструмента -заменяет прежнюю запись отрицательным результатом поиска. - -Время снимка фиксируется при получении финансовых ответов, до ожидания -названий. Сроки серверного кэша отсчитываются от этого времени, поэтому -долгая загрузка справочных данных не делает старые суммы новыми. -Успешные справочные данные общие для всех пользователей; выполняющиеся -запросы объединяются только внутри одного брокерского токена, чтобы ошибка -его авторизации не передавалась другому подключению. - -Успешное создание, замена или удаление подключения меняет границу -повторного использования финансовых данных. Старое или поздно завершившееся -получение снимка не делает его доступным новым запросам другого поколения. -Явная очистка памяти при изменении подключения дополняет эту проверку. -Frontend сбрасывает всё поддерево финансовых запросов при изменении -подключения и общий кэш сессии при выходе; старый портфель не служит -заглушкой загрузки нового подключения. - -В общем режиме сначала определяется состав доступных счетов одного -подключения, затем получаются их снимки. Это -[не атомарное наблюдение нескольких счетов](../all-accounts.md#полнота-свежесть-и-доступ). +Публикация проверяет connection revision и attempt в той же транзакции. +Замена или удаление подключения не допускает позднюю запись прежней работы. +Frontend очищает недоступный personal view и отклоняет запоздалые ответы. +В режиме [«Все счета»](../all-accounts.md#полнота-свежесть-и-доступ) состав +accounts и snapshots читается согласованно, но их source times могут различаться. ## Ошибки и результат для пользователя -| Причина | Ответ backend | Действие на экране | -| --- | --- | --- | -| Нет подключения | `409 TBANK_NOT_CONNECTED` | Открыть настройки T-Bank | -| Брокер отклонил токен | `400 INVALID_TBANK_TOKEN` | Открыть настройки; токен автоматически не удаляется | -| Счёт недоступен через текущее подключение | `404` | Вернуться к списку счетов | -| Временная ошибка, пригодный предыдущий снимок есть | `200`, `isStale: true` | Показать снимок и его исходное время с предупреждением | -| Временная ошибка, пригодного снимка нет | `503 TBANK_UNAVAILABLE` | Повторить загрузку | -| Неизвестная ошибка | Безопасная ошибка API | Повторить загрузку | +Нет подключения — `409 TBANK_NOT_CONNECTED`, известный отказ токена — +`400 INVALID_TBANK_TOKEN`, недоступный счёт — `404`. Они не маскируются +запасным snapshot. Transient source failure оставляет прежний полный портфель +и его исходное время с безопасным предупреждением. При отсутствии snapshot +показывается missing/failed, без нулевых итогов. `503` означает ошибку +обслуживания HTTP-запроса, а не принятой фоновой работы. -`INVALID_TBANK_TOKEN` и `404` не заменяются серверным запасным снимком. -Временная ошибка определения доступных счетов также может использовать -пригодный снимок того же поколения. При временном сбое метаданных сначала -используются ранее успешные сведения, а при их отсутствии — идентификатор -инструмента; это не устанавливает `isStale`. Отказ авторизации при поиске -метаданных не маскируется запасным названием. +Observer запрашивает статус известной работы, затем `sync=observe` читает +изменившийся ресурс без запуска синхронизации. При уходе и terminal outcome +наблюдение прекращается. Повторное открытие может запросить недостающее; +после рестарта сохранённый snapshot доступен сразу. -Для запроса самого портфеля на клиенте нет polling, автоматического повтора или -обновления при возврате фокуса. При обновлении того же уже показанного -результата клиент оставляет его на экране, добавляя индикатор загрузки -или предупреждение с доступной с клавиатуры повторной попыткой. -Это клиентский сохранённый ответ, независимо от серверного `isStale`. - -Frontend отображает название и остальные сведения из ответа портфеля. -Повторные попытки поиска для акций, облигаций и других типов инструментов -принадлежат [общему клиенту T-Bank на backend](../../../integrations/tbank.md#конфигурация-и-ограничения). - -Первое обращение к крупному портфелю может потребовать много метаданных. -Переполнение общей очереди RPC может произойти при конкурирующих запросах; -поиск метаданных в этом случае повторяется с ограниченными паузами. После -успешного поиска метаданные используются повторно до истечения -кэша. Значение `TBANK_RATE_LIMIT_PER_SECOND` следует соотносить с квотой -источника; его увеличение не является гарантированным исправлением -ограничения самого T-Bank. - -Проверяемые правила: [свежесть и запасной снимок](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-свежесть-и-fallback-полного-portfolio-snapshot), -[клиентские состояния](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-состояния-и-ошибки-portfolio-на-клиенте). -Для расследования конкретного значения используйте -[порядок диагностики](../how-to/investigate-discrepancy.md). +Денежные правила находятся в [справочнике](../data-reference.md), +проверка расхождения — в [диагностике](../how-to/investigate-discrepancy.md), +RPC/лимиты — в [интеграции T-Bank](../../../integrations/tbank.md). diff --git a/domains/portfolio/how-to/investigate-discrepancy.md b/domains/portfolio/how-to/investigate-discrepancy.md index 687889b..20da157 100644 --- a/domains/portfolio/how-to/investigate-discrepancy.md +++ b/domains/portfolio/how-to/investigate-discrepancy.md @@ -17,7 +17,7 @@ каждой котировки, а общий портфель может соединять разные моменты. Если запрос завершился ошибкой, доступное действие «Повторить» должно обновить результат или оставить объяснимую ошибку. Перезагрузка backend - удаляет кэш и не восстанавливает историю стоимости. + сохраняет последний полный снимок в SQLite, но не создаёт историю стоимости. 3. **Проверьте состав суммы.** Для одной валюты итог складывается из вычислимых стоимостей позиций и доступных денег. Не добавляйте деньги второй раз, не складывайте валюты и не включайте заблокированные средства. diff --git a/domains/portfolio/overview.md b/domains/portfolio/overview.md index b6c56b3..9f0f791 100644 --- a/domains/portfolio/overview.md +++ b/domains/portfolio/overview.md @@ -17,11 +17,11 @@ [справочнике данных](data-reference.md). Доступность счёта определяется [текущим подключением и списком счетов](../accounts/overview.md). -SQLite не хранит сам портфель: backend получает данные через серверную -[интеграцию T-Bank](../../integrations/tbank.md) и временно сохраняет успешный -снимок в памяти. Frontend получает нормализованный результат через HTTP API. +Backend получает данные через серверную +[интеграцию T-Bank](../../integrations/tbank.md) и постоянно сохраняет последний +полный снимок в SQLite. Frontend получает нормализованный результат через HTTP API. [Поток загрузки](flows/portfolio-loading.md) объясняет границы доступа, -свежий кэш и восстановление после временной ошибки. +свежесть снимка и восстановление после временной ошибки. Обзор счёта также показывает пять последних [операций](../operations/overview.md), [полученный доход](../income/overview.md) текущего месяца и до пяти @@ -79,7 +79,7 @@ SQLite не хранит сам портфель: backend получает да не содержат данных и не получают фокус. При обычном фоновом обновлении сохраняются предыдущие суммы и время успешного получения в UTC. -Серверный `isStale` обозначает разрешённый запасной снимок backend. +Серверный `isStale` обозначает истечение минутной свежести снимка. Клиентское предупреждение означает неудачную попытку обновить уже показанный ответ. Эти состояния независимы и могут появиться одновременно. Успешный повторный запрос обновляет суммы и время, снимает клиентскую ошибку; @@ -88,9 +88,9 @@ SQLite не хранит сам портфель: backend получает да В текущем решении нет долговременной истории снимков, графиков стоимости портфеля, валютной конвертации, учёта виртуальных позиций или заблокированных -средств. Кэш теряется при перезапуске и рассчитан на локальную работу одного -экземпляра backend. Отдельной команды принудительного обновления на экране -нет; повторная попытка доступна после ошибки. +средств. Последний полный снимок сохраняется после перезапуска; выполнение +синхронизации рассчитано на один экземпляр backend. Действие «Обновить» +перечитывает зависимости открытого экрана независимо от их свежести. Для проверки конкретной суммы используйте [руководство по расхождениям](how-to/investigate-discrepancy.md). diff --git a/glossary.md b/glossary.md index 67d24bb..f4eb653 100644 --- a/glossary.md +++ b/glossary.md @@ -177,6 +177,19 @@ _Не использовать_: история Portfolio, баланс счёт _Не использовать_: проводка, рассчитанная операция. +## Полнота истории счёта + +Подтверждённость того, что за указанный период загружены все доступные +исполненные операции счёта на момент успешной сверки с брокером. Полный период +может не содержать операций; полнота не гарантирует отсутствия последующих +исправлений брокера и отличается от свежести данных. + +Понятие принято в [ADR 017](adr/017-local-tbank-reads-and-background-sync.md). +Текущее хранение coverage и независимая свежесть объяснены в +[модели данных](architecture/cross-cutting/data-freshness.md). + +_Не использовать_: свежесть истории, отсутствие операций как признак полноты. + ## Operation Category Нормализованная категория Ledger Entry для фильтрации и отображения: @@ -186,13 +199,15 @@ _Не использовать_: upstream operation type как category. ## Ledger Page Cache -Технический термин: кэш страницы Account Ledger, отделённый от кэша отдельных операций. +Исторический технический термин: RAM-кэш страницы Account Ledger. +Текущее чтение использует сохранённые операции и coverage в SQLite; +страница не является единицей полноты. _Не использовать_: operations cache, кэш отдельных операций. ## Ledger Page Cache Key -Ключ, разделяющий страницы журнала по пользователю, текущему подключению, +Исторический ключ RAM-кэша, разделявший страницы по пользователю, подключению, счёту и параметрам запроса, включая необязательный UID инструмента. Полный состав и правила нормализации описаны в [журнале операций](domains/operations/overview.md#получение-и-полнота). diff --git a/integrations/tbank.md b/integrations/tbank.md index 76c6efb..b4873e7 100644 --- a/integrations/tbank.md +++ b/integrations/tbank.md @@ -62,7 +62,7 @@ sequenceDiagram B->>T: GetAccounts OPEN по gRPC over TLS alt Проверка успешна, в том числе при пустом списке счетов T-->>B: Доступные открытые счета - B->>D: Создать или заменить шифротекст AES-256-GCM + B->>D: Атомарно сохранить token, новую revision и membership B-->>F: Подключено и время сохранения F->>F: Обновить статус и сбросить финансовые выборки F-->>U: Показать подтверждённое подключение @@ -92,8 +92,9 @@ sequenceDiagram `INVALID_TBANK_TOKEN` означает отказ внешней аутентификации или прав доступа. `TBANK_UNAVAILABLE` сообщает о временной или иной upstream-ошибке, включая исчерпание очереди. Ответы и ошибки не содержат token. Удаление подключения -идемпотентно: удаляется локальный секрет и прекращается его использование -для последующих запросов. Оно не описывает отзыв token на стороне T-Bank. +идемпотентно: удаляются локальный секрет, личные datasets и work; поздняя +публикация прежней revision запрещена. Общие неперсональные источники могут +сохраняться. Оно не описывает отзыв token на стороне T-Bank. Согласованное поведение сохранено в workspace-спецификации [T-Bank connection](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/tbank-connection/spec.md). @@ -106,7 +107,7 @@ sequenceDiagram использования финансовых данных. Это относится и к сохранению того же самого token: предыдущее подключение не становится источником данных нового. Неуспешная операция сохраняет прежнее подключение и его доступные данные. -Механизм generation, поздние ответы и сброс клиентских выборок объясняет +Механизм внутренней revision, поздние ответы и сброс клиентских выборок объясняет [свежесть данных](../architecture/cross-cutting/data-freshness.md#смена-подключения). ## Конфигурация и ограничения -- 2.54.0