# Интеграция с T-Bank Invest T-Bank — источник доступных пользователю инвестиционных счетов, текущих позиций и денег, исполненных операций и рыночных сведений об инструментах. WealthLedger обращается к Invest API на сервере по gRPC over TLS и преобразует ответы в собственный HTTP API. Изображения логотипов загружаются браузером отдельно из публичного CDN; это не доступ к личным брокерским данным. ## Данные и ответственность | Категория | Для чего используется | | --- | --- | | Открытые счета | Определить доступный состав счетов и проверить доступ к конкретному счёту | | Портфель и денежные остатки | Показать текущий состав инвестиций и итоги по исходным валютам | | Исполненные операции | Построить журнал и выделить полученные купоны и дивиденды | | Инструменты и рынок | Имена, идентификаторы, цены, свечи, параметры акций и облигаций | | Купоны и дивиденды | Построить будущие события по текущим позициям и календарь инструмента | T-Bank определяет исходные данные и права token. Backend проверяет локальную сессию, выбирает подключение её пользователя и устанавливает доступ к счёту. Наличие идентификатора счёта в URL этого доступа не подтверждает. [MOEX](moex.md) и [НКР](nkr.md) дополняют публичные сведения об облигациях; они не заменяют брокера как источник личных позиций и исполненных выплат. Границы источников показаны в [контексте](../architecture/context.md), смысл дополнений — в [описании облигаций](../domains/instruments/bonds.md). ## Публичные логотипы T-Bank Brands CDN — отдельная публичная граница для изображений. Backend передаёт имя файла `logoName` из метаданных инструмента; frontend строит URL на `invest-brands.cdn-tinkoff.ru` и загружает изображение браузером. Эти запросы не используют HTTP-клиент приложения, его Bearer token или token Invest API. Лимитер и повторы серверных gRPC-вызовов к ним не относятся. Логотипы используются в портфеле, операциях, будущих событиях, поиске и страницах инструментов. Отсутствующее имя или ошибка изображения оставляют буквенную заглушку и не меняют финансовые значения. Правило построения URL принадлежит [форматированию frontend](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/src/shared/lib/display.ts). ## Проверка и сохранение подключения Авторизованный пользователь управляет одним подключением в настройках. Интерфейс просит read-only T-Bank Invest token: он вводится в поле пароля и отправляется backend. API возвращает только состояние подключения и время его сохранения, не token. Успешное сохранение очищает поле, ошибка оставляет введённое значение для исправления. Удаление требует подтверждения в форме; во время изменения действия секции заблокированы. Схема показывает динамический сценарий создания или замены подключения. SQLite — локальное хранилище backend, а не отдельный сетевой участник. ```mermaid sequenceDiagram participant U as Пользователь participant F as React SPA participant B as Backend API participant T as T-Bank Invest API participant D as Локальная SQLite U->>F: Вводит token и сохраняет подключение F->>B: PUT connection с Bearer и token B->>T: GetAccounts OPEN по gRPC over TLS alt Проверка успешна, в том числе при пустом списке счетов T-->>B: Доступные открытые счета B->>D: Атомарно сохранить token, новую revision и membership B-->>F: Подключено и время сохранения F->>F: Обновить статус и сбросить финансовые выборки F-->>U: Показать подтверждённое подключение else Token отклонён или источник недоступен T-->>B: Ошибка проверки B-->>F: INVALID_TBANK_TOKEN или TBANK_UNAVAILABLE F-->>U: Локальная ошибка, прежнее подключение сохранено end ``` Перед проверкой backend удаляет внешние пробелы token и отклоняет пустое значение. Успешный пустой список счетов подтверждает token: наличие позиций не является условием подключения. Ошибка проверки не заменяет уже сохранённое подключение. Сетевой вызов T-Bank и запись SQLite выполняются последовательно, но не образуют общую транзакцию между системами. ## Состояние и ошибки Состояние без подключения содержит `connected: false` и `connectedAt: null`. После создания или замены `connectedAt` содержит время успешного сохранения в RFC 3339 UTC. Это время подключения, а не отметка свежести портфеля. Чтение статуса показывает сохранённое состояние; оно не выполняет заново проверку token у брокера. Если статус загрузить не удалось, интерфейс показывает ошибку с повтором, а не форму с предполагаемым отсутствием подключения. `INVALID_TBANK_TOKEN` означает отказ внешней аутентификации или прав доступа. `TBANK_UNAVAILABLE` сообщает о временной или иной upstream-ошибке, включая исчерпание очереди. Ответы и ошибки не содержат token. Удаление подключения идемпотентно: удаляются локальный секрет, личные 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). Точные endpoints и формы ответов определяет backend OpenAPI; [HTTP API](../architecture/http-api.md) описывает общую границу. ## Граница финансового кэша Успешное создание, замена или удаление подключения меняет границу повторного использования финансовых данных. Это относится и к сохранению того же самого token: предыдущее подключение не становится источником данных нового. Неуспешная операция сохраняет прежнее подключение и его доступные данные. Механизм внутренней revision, поздние ответы и сброс клиентских выборок объясняет [свежесть данных](../architecture/cross-cutting/data-freshness.md#смена-подключения). ## Конфигурация и ограничения Backend хранит token в шифротексте AES-256-GCM с новым nonce при каждом сохранении. Ключ находится только в серверном окружении. Расшифровка нужна серверной интеграции; ни шифротекст, ни token не входят в публичное API. Причины границы и шифрования сохранены в [ADR 003](../adr/003-tbank-server-side-grpc-boundary.md) и [ADR 004](../adr/004-encrypted-per-user-tbank-token.md). | Настройка | Значение и смысл | | --- | --- | | `TOKEN_ENCRYPTION_KEY` | Обязательный ключ AES-256-GCM: 32 байта, закодированные в base64 | | `TBANK_GRPC_ENDPOINT` | Адрес gRPC; по умолчанию `invest-public-api.tbank.ru:443` | | `TBANK_RATE_LIMIT_PER_SECOND` | Число стартующих T-Bank RPC в секунду на экземпляр backend; по умолчанию `5` | | `TBANK_CA_CERT_PATH` | Необязательный путь к доверенному корневому CA в PEM для сетей с TLS-инспекцией; проверка TLS сохраняется | Параметры окружения определяет backend [validateEnv](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/config/env.validation.ts) (справочный источник). Общий FIFO-лимитер ограничивает поток RPC и не допускает более 50 ожидающих. Для `UNAVAILABLE`, `DEADLINE_EXCEEDED` и `RESOURCE_EXHAUSTED` обычный RPC допускает один повтор через секунду. Deadline каждой попытки — 15 секунд, а ожидание места в лимитере не является временем этой сетевой попытки. Для поиска метаданных `GetInstrumentBy` разрешены две повторные попытки с паузами 2 и 4 секунды после неудачных попыток. Они применяются к тем же временным кодам и к переполнению локальной очереди; перед каждой попыткой проверяется её вместимость. Постоянные ошибки и отказ авторизации не повторяются. Подтверждённый `NOT_FOUND` при поиске метаданных означает отсутствие инструмента, а не временный сбой. Остальные RPC при полной очереди завершаются сразу. Параметры попыток принадлежат [T-Bank client](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/integrations/tbank/tbank.client.ts) (справочный источник); решение об ограниченных повторах — [ADR 005](../adr/005-tbank-upstream-resilience.md). Большое число ранее неизвестных инструментов требует отдельных запросов metadata. При перегруженной очереди необязательные имена могут быть заменены идентификаторами; это не доказывает потерю самой позиции. Изменение локального лимита требует учёта разрешённой квоты T-Bank: оно не увеличивает квоту поставщика. Ключ шифрования не ротируется автоматически. Новый ключ не расшифрует прежнее подключение: нужен исходный ключ либо повторный ввод собственного token через настройки. Правила [резервного копирования](../architecture/containers.md#сохранение-и-восстановление-данных) учитывают отдельное хранение ключа. Лимитер и кэши принадлежат одному экземпляру; изменения подключения не координируются между распределёнными экземплярами backend. Интеграция использует сохранённый protobuf-контракт T-Bank: обновление внешнего протокола и проверка совместимости требуют отдельного изменения интеграции.