docs: reorganize system knowledge by domain

This commit is contained in:
2026-09-12 14:45:42 +03:00
parent 3a99af1a6e
commit f20c3f47bc
83 changed files with 2229 additions and 7335 deletions
+21 -7
View File
@@ -1,9 +1,23 @@
# Архитектура
# Архитектура WealthLedger
Здесь описывается фактическое устройство WealthLedger и границы компонентов.
Архитектурное описание объясняет границы приложений, владение данными и
взаимодействия. Для знакомства с пользовательскими задачами начните с
[обзора продукта](../overview.md), затем прочитайте Context и Container.
- [Обзор системы](system-overview.md) — поставленные возможности, локальные
границы и основные потоки данных.
- [HTTP API и ошибки](http-api.md) — validation, error envelope и границы доказательства.
- [Глоссарий домена](domain-glossary.md) — канонические значения терминов.
- [ADR](../adr/README.md) — долгоживущие решения и их последствия.
| Вопрос | Где искать |
| --- | --- |
| Кто пользуется системой и какие поставщики находятся снаружи? | [C4 System Context](context.md), [внешние интеграции](../integrations/README.md) |
| Из каких приложений и хранилищ состоит система? | [C4 Container](containers.md) |
| Как разделены данные, полномочия и secrets? | [Хранение и доступ](containers.md#хранимые-данные-и-границы-доступа), [аутентификация](../domains/authentication/overview.md), [подключение брокера](../integrations/tbank.md) |
| На каких решениях основано устройство системы? | [Стратегия](containers.md#стратегия-решения-и-её-основания), [ADR](../adr/README.md) |
| Что ограничивает точность, свежесть и эксплуатацию? | [Качество и ограничения](containers.md#качество-и-ограничения) |
| Как взаимодействуют части во время запроса? | [Загрузка портфеля](../domains/portfolio/flows/portfolio-loading.md), [восстановление сессии](../domains/authentication/overview.md#восстановление-сессии), [сохранение подключения](../integrations/tbank.md#проверка-и-сохранение-подключения) |
| Что считается кэшем и когда прежние данные теряют право на повторное использование? | [Свежесть данных](cross-cutting/data-freshness.md) |
| Как обрабатываются ошибки и ожидание? | [HTTP API и ошибки](http-api.md), [состояния интерфейса](cross-cutting/ui-resilience.md) |
| Как связать ошибку с обработкой запроса? | [Наблюдаемость](http-api.md#наблюдаемость) |
| Как устроено подтверждённое локальное окружение? | [Container](containers.md#представление-container), [локальная диагностика](../development/local-workspace.md) |
| Что означают предметные термины? | [Глоссарий](../glossary.md) |
Эта карта покрывает вопросы архитектуры там, где о них есть подтверждённое
содержание. Правила размещения и изменения кода находятся в локальных
инструкциях frontend/backend; системные объяснения остаются здесь.
+138
View File
@@ -0,0 +1,138 @@
# Приложения, хранилище и взаимодействия
Представление C4 Container раскрывает внутреннее устройство WealthLedger.
Контейнер здесь означает приложение или хранилище, а не Docker-контейнер.
Внешние системы совпадают с [System Context](context.md).
## Представление Container
```mermaid
flowchart LR
U["Частный инвестор<br/>Человек"]
subgraph W["WealthLedger · программная система"]
F["React SPA<br/>TypeScript, Vite, Mantine, TanStack Query"]
B["Backend API<br/>NestJS, Node.js"]
D[("Локальная база<br/>Embedded SQLite")]
F -->|"HTTP JSON /api/v1; Bearer и auth cookie"| B
B -->|"Prisma + better-sqlite3; доступ к локальному файлу"| D
end
T["T-Bank Invest API<br/>Внешняя система"]
M["MOEX ISS<br/>Внешняя система"]
N["Реестр НКР<br/>Внешняя система"]
C["T-Bank Brands CDN<br/>Внешняя система"]
U -->|"Просматривает данные и управляет подключением"| F
B -->|"gRPC over TLS; token пользователя"| T
B -->|"HTTPS; публичные данные"| M
B -->|"HTTPS; публичный HTML"| N
F -->|"HTTPS; изображения без авторизации"| C
```
SPA выполняется в браузере пользователя. Backend — отдельное локальное
приложение; SQLite используется внутри его процесса через библиотеку и файл
базы. Между API и SQLite нет сетевого database server. Граница системы на
схеме показывает принадлежность частей, а не их размещение на одном узле.
## Ответственность частей
| Часть | Ответственность |
| --- | --- |
| React SPA | Экраны, навигация, формы, состояние входа и клиентский кэш ответов; форматирование денежных значений |
| Backend API | Локальная идентичность, сессии, проверка доступа, подключение брокера, нормализация внешних данных, точные расчёты и серверные кэши |
| SQLite | Долговременное хранение локальных пользователей, сессий и зашифрованных подключений |
| Внешние системы | Данные брокера, публичные сведения об инструментах и изображения в пределах своей ответственности |
Frontend организован слоями `app → pages → features → entities → shared`:
каркас связывает маршруты, страницы собирают сценарии, а общие средства
обслуживают транспорт и представление. Backend остаётся модульным NestJS API,
где локальная аутентификация отделена от подключения брокера и инвестиционных
сценариев. Это внутренняя организация приложений, не набор микросервисов.
Финансовые HTTP-запросы браузера идут через backend. Пользователь вводит token
T-Bank в форме и отправляет его для сохранения, но API не возвращает token
в ответах; SPA не использует его для прямых брокерских запросов. Прямая
загрузка публичного изображения из CDN не проходит через эту финансовую
границу.
Типизированный frontend-клиент производится из OpenAPI backend во время
разработки. В runtime SPA вызывает API, а не генератор или OpenAPI endpoint.
Форма запросов и ошибок описана в [HTTP API](http-api.md); направление
генерации принято в [ADR 002](../adr/002-nest-generated-openapi-contract.md).
## Хранимые данные и границы доступа
SQLite содержит пользователя с нормализованным email и hash пароля, его
сессии с hash текущего refresh token, сроком действия и признаком отзыва,
а также одно зашифрованное подключение T-Bank с временем подключения.
Пароли хешируются bcrypt; token брокера шифруется AES-256-GCM. Plaintext
credentials не являются сохраняемым представлением этих данных.
Сессии и подключение связаны с владельцем. Access token подтверждает
локального пользователя, но `accountId` из URL сам по себе не даёт права
читать брокерский счёт. Backend получает текущее подключение пользователя
и его generation перед использованием финансового кэша. Проверка доступности
счёта у брокера зависит от сценария: журнал выполняет её до чтения страницы
кэша; свежий портфель одного счёта может быть возвращён без нового запроса
списка счетов. При обновлении портфеля доступ проверяется у брокера.
Ротация сессии атомарно заменяет hash текущего refresh token: два
конкурентных запроса не могут оба успешно выполнить одну ротацию.
Вызов брокера и запись в локальную базу не образуют общую транзакцию:
при сохранении подключения сначала проверяется token, затем записывается
шифротекст. Подробности находятся в
[аутентификации](../domains/authentication/overview.md) и
[интеграции T-Bank](../integrations/tbank.md).
Брокерские счета, сводки, портфель, страницы журнала и будущие события
не являются долговременной историей в SQLite. Они получаются для текущего
подключения и могут временно кэшироваться. Перезапуск backend не
восстанавливает прежние финансовые снимки и cursor continuations из базы;
[модель свежести](cross-cutting/data-freshness.md) отделяет это от хранения
учётных данных.
## Стратегия решения и её основания
Локальная база и Prisma дают типизированный доступ к локальному состоянию
без отдельного сервера базы; исходные причины и пределы выбора сохранены
в [ADR 001](../adr/001-prisma-sqlite-local-mvp.md). Централизованная
[серверная граница T-Bank](../adr/003-tbank-server-side-grpc-boundary.md)
сосредоточивает credentials, лимиты и повторы вне браузера, а
[шифрование подключения](../adr/004-encrypted-per-user-tbank-token.md)
задаёт способ хранения брокерского секрета.
[Политика сессий](../adr/006-auth-session-policy.md) разделяет короткоживущий
access token и вращаемый refresh token. [Политика query-кэша](../adr/007-react-query-fetch-policy.md)
сохраняет доступные данные при обновлении и не запускает refetch при возврате
фокуса. [Ограниченные повторы T-Bank](../adr/005-tbank-upstream-resilience.md)
и [структурированные журналы](../adr/008-pino-backend-logging.md) обслуживают
внешнюю границу. ADR сохраняют контекст принятия решения; текущие детали
уточняются в тематических описаниях.
## Качество и ограничения
Точность представления денег требует сохранять валюту и decimal-значение;
неизвестная сумма не заменяется нулём. Свежесть наблюдения не равна
актуальности рынка в реальном времени. Временные ошибки внешнего поставщика
обрабатываются ограниченными повторами и локальными состояниями интерфейса,
без обещания успешного ответа при недоступном upstream.
Авторизация доступа, недопущение token в публичный ответ и атомарность
ротации сессии — отдельные свойства; наличие типизированного клиента
не доказывает их. Известные расхождения регистрации и деталей валидации
отмечены в [аутентификации](../domains/authentication/overview.md#регистрация-и-вход)
и [HTTP API](http-api.md#error-contract).
Подтверждённая топология — локальная SPA и локальный API с файлом SQLite.
Origin браузера, адрес API и cookie policy должны быть согласованы; условия
и диагностика описаны в [локальном окружении](../development/local-workspace.md).
Наличие локального запуска не задаёт production-топологию или гарантии SLA.
Кэши и лимитер T-Bank действуют на экземпляр backend. Ротация ключа шифрования
подключений и согласование изменений подключения между распределёнными
экземплярами не входят в реализованный механизм. Эти ограничения не
позволяют переносить локальную схему на несколько экземпляров без отдельного
решения о состоянии, credentials и лимитах.
Runtime-сценарии раскрываются у соответствующих владельцев:
[восстановление сессии](../domains/authentication/overview.md#восстановление-сессии),
[сохранение подключения](../integrations/tbank.md#проверка-и-сохранение-подключения)
и [загрузка портфеля](../domains/portfolio/flows/portfolio-loading.md).
+42
View File
@@ -0,0 +1,42 @@
# Контекст WealthLedger
Представление C4 System Context показывает WealthLedger целиком: кому служит
приложение и от каких внешних систем получает сведения. Внутреннее устройство
раскрыто отдельно в [Container](containers.md).
## Система и её окружение
```mermaid
flowchart LR
U["Частный инвестор<br/>Человек"]
W["WealthLedger<br/>Программная система"]
T["T-Bank Invest API<br/>Внешняя система"]
M["MOEX ISS<br/>Внешняя система"]
N["Реестр НКР<br/>Внешняя система"]
C["T-Bank Brands CDN<br/>Внешняя система"]
U -->|"Подключает брокера и изучает инвестиции"| W
W -->|"Получает доступные счета, позиции, операции и рыночные сведения"| T
W -->|"Получает публичные данные облигаций и событий"| M
W -->|"Получает кредитные рейтинги выпусков"| N
W -->|"Загружает изображения инструментов"| C
```
Частный инвестор входит в локальную учётную запись WealthLedger и добавляет
своё подключение к брокеру. Отдельные роли администратора или брокера,
работающего внутри WealthLedger, для описанных пользовательских сценариев
не требуются.
T-Bank определяет доступность счетов и поставляет личные финансовые сведения.
WealthLedger нормализует их для просмотра и сопоставляет с публичной
информацией об инструментах. [Интеграция T-Bank](../integrations/tbank.md)
объясняет полномочия подключения и ограничения внешней зависимости.
[MOEX ISS](../integrations/moex.md) поставляет публичные параметры и календарь выпусков облигаций.
[Реестр НКР](../integrations/nkr.md) используется для поиска кредитного рейтинга конкретного выпуска;
рейтинг эмитента не подставляется вместо него. Отсутствие подходящей записи
и недоступность источника имеют разный смысл. Правила интерпретации этих
сведений находятся в [описании облигаций](../domains/instruments/bonds.md).
Публичные данные выпуска сами по себе не подтверждают личную выплату.
[T-Bank Brands CDN](../integrations/tbank.md#публичные-логотипы) поставляет только изображения для представления
инструментов. Отсутствующий логотип не меняет финансовые данные.
@@ -0,0 +1,72 @@
# Свежесть данных и повторное использование
WealthLedger показывает финансовое наблюдение на момент получения данных.
Временный кэш уменьшает повторные обращения к брокеру, но не превращает
снимки в историю портфеля и не обещает рыночные данные в реальном времени.
## Клиентский и серверный снимки
[Client Cached Snapshot](../../glossary.md#client-cached-snapshot) — последний
успешный ответ, сохранённый TanStack Query в SPA. При обычном фоновом обновлении
того же набора данных уже показанный результат остаётся на экране; ошибка
обновления сообщает о проблеме, не заменяя известные значения пустотой.
Клиентский кэш не является внешней системой или отдельным runtime-приложением.
[Portfolio Snapshot](../../glossary.md#portfolio-snapshot) — полный результат
загрузки портфеля на backend. В пределах разрешённого сценарием срока он может
служить серверным fallback при временной ошибке upstream. Backend отмечает
такой ответ `isStale`; frontend не выводит это поле из состояния собственного
query-кэша. Серверное предупреждение и ошибка клиентского refetch могут
возникать независимо.
Свежесть и допустимость fallback определяются сценарием. Портфельный fallback
не распространяется автоматически на журнал, прогноз событий или страницу
инструмента. Точные правила находятся в
[справочнике портфеля](../../domains/portfolio/data-reference.md),
[журнале операций](../../domains/operations/overview.md) и
[будущих событиях](../../domains/income/upcoming-events.md).
По [ADR 007](../../adr/007-react-query-fetch-policy.md) запрос связан с открытием
экрана, а возврат фокуса окна не запускает refetch. Повторное открытие после
`staleTime` может обновить ответ в фоне. Эта политика не является таймером
периодического обновления. Перезапуск backend уничтожает его in-memory
снимки и continuations журнала; SQLite не восстанавливает их.
## Смена подключения
Кэш финансовых данных относится к пользователю, счёту и текущему
подключению T-Bank. Backend сначала читает подключение и его generation, затем
проверяет возможность повторного использования ответа. Свежий портфель
одного счёта не требует нового запроса списка счетов у брокера; при обновлении
портфеля доступ проверяется заново. Журнал операций проверяет доступ до чтения
кэшированной страницы. Идентификатор счёта и совпадение параметров запроса
сами по себе недостаточны для выдачи кэша.
При каждом успешном сохранении подключения token шифруется с новым
AES-GCM nonce. Из сохранённого шифротекста backend вычисляет непрозрачную
внутреннюю generation. Она не хранится отдельным полем базы и не входит
в API, журналы или ошибки. Portfolio Snapshot и Ledger Page Cache допускают
повторное использование только в пределах соответствующей generation.
Старый кэш или поздно завершившийся запрос прежней generation не обслуживает
новый запрос после замены подключения, включая сохранение того же plaintext
token. Удаление подключения не оставляет доступного контекста для чтения кэша.
После успешного изменения подключения 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](../../../specs/openspec/specs/frontend-query-coherence/spec.md)
и [data resilience](../../../specs/openspec/specs/frontend-data-resilience/spec.md);
серверные правила — в
[portfolio](../../../specs/openspec/specs/portfolio-view/spec.md) и
[ledger](../../../specs/openspec/specs/brokerage-account-ledger/spec.md).
@@ -0,0 +1,49 @@
# Ожидание, ошибки и восстановление интерфейса
Интерфейс различает ожидание данных, известную ошибку запроса и непойманную
ошибку отображения маршрута. Эти состояния требуют разных действий и
не подменяют друг друга.
## Ожидание и доступные данные
При первой загрузке без результата structural skeleton показывает
расположение ожидаемых блоков. При частичной загрузке он занимает только
незагруженную область. Placeholders не содержат правдоподобных финансовых
данных, не становятся действиями и не получают клавиатурный фокус; область
ожидания обозначается как занятая.
При восстановлении сессии SPA показывает нейтральный skeleton каркаса,
пока неизвестно, доступен public или protected маршрут. При загрузке
отложенного маршрута уже доступный каркас остаётся видимым.
Фоновое обновление и действие пользователя сохраняют видимые данные или
элемент управления с компактной индикацией процесса. В частности,
[клиентский снимок](data-freshness.md#клиентский-и-серверный-снимки) счетов или
портфеля остаётся доступным после ошибки refetch; сообщение и повтор
относятся к обновлению, а не к отсутствию инвестиций.
## Известная ошибка сценария
Ошибки счетов, портфеля и подключения T-Bank остаются локальными: рядом
с затронутыми данными доступны предусмотренные сценарием retry и navigation
actions. Устаревший серверный Portfolio Snapshot показывает сохранённый
портфель с предупреждением. Он не превращается в глобальный fallback.
Ошибка источника не означает нулевой баланс или отсутствие операций.
## Непойманная ошибка маршрута
Непойманная runtime-ошибка при отображении public или protected route
заменяет его содержимое нейтральным fallback без технических деталей.
Пользователь может перейти к `/accounts` при активной сессии или к `/login`
без неё. Переход на другой маршрут сбрасывает это состояние; если новый
маршрут отображается успешно, пользователь продолжает работу.
Общий fallback восстанавливает навигацию, но не обещает устранить причину
ошибки. HTTP-коды и безопасная форма серверной ошибки находятся в
[HTTP API](../http-api.md), восстановление сессии — в
[аутентификации](../../domains/authentication/overview.md).
Согласованное поведение раскрыто в workspace-спецификациях
[UI resilience](../../../specs/openspec/specs/frontend-ui-resilience/spec.md),
[loading presentation](../../../specs/openspec/specs/frontend-loading-presentation/spec.md)
и [data resilience](../../../specs/openspec/specs/frontend-data-resilience/spec.md).
-210
View File
@@ -1,210 +0,0 @@
# Глоссарий домена
## User
Локальный владелец данных WealthLedger, идентифицируемый `id` и
нормализованным email. User владеет Session и T-Bank connection и видит только
Brokerage Accounts, доступные через его текущее T-Bank connection.
_Не использовать_: клиент T-Bank, инвестор T-Bank.
## Session
Локальная authenticated-сессия User: короткоживущий access token в браузере и
server-side refresh token rotation через cookie. Session не является
T-Bank connection и не подтверждает доступ к Brokerage Account без текущего
Bearer token.
_Не использовать_: T-Bank сессия, browser session.
## Money
Денежная сумма в одной ISO-валюте, передаваемая как `{ currency, value }`, где
`value` — decimal string без экспоненциальной записи. Backend выполняет точные
операции, frontend форматирует Money для отображения. Неизвестное или
неприменимое денежное значение передаётся как `null`, а не как ноль и не как
раздельные `amount`/`currency`.
_Не использовать_: сумма без валюты, транспортное представление как доменный термин.
## T-Bank connection
Единственное принадлежащее User подключение к T-Bank Invest, содержащее
зашифрованный token для server-side запросов.
_Не использовать_: аккаунт T-Bank, авторизация T-Bank.
## Brokerage Account
Открытый поддерживаемый инвестиционный счёт T-Bank, доступный через текущее
T-Bank connection User: брокерский счёт, ИИС, Инвесткопилка или фонд денежного
рынка. WealthLedger не хранит Brokerage Account как долговременный объект
владения независимо от T-Bank connection.
_Не использовать_: счёт T-Bank, банковский счёт.
## Investment Account Type
Нормализованная категория Brokerage Account, независимая от upstream enum
T-Bank: `BROKERAGE`, `INDIVIDUAL_INVESTMENT`, `INVEST_BOX` или
`MONEY_MARKET_FUND`.
_Не использовать_: тип аккаунта T-Bank, protobuf type.
## Portfolio
Текущее нормализованное состояние инвестиций одного Brokerage Account,
включающее позиции, доступные денежные остатки и итоги по каждой валюте.
_Не использовать_: общий портфель, баланс счёта.
## Portfolio Position
Ненулевой обычный инвестиционный актив в составе Portfolio с количеством и
доступными оценочными показателями. Portfolio Position не является Cash
Balance, даже когда её type равен `CURRENCY`.
_Не использовать_: валютная позиция, виртуальная позиция.
## Portfolio Position Type
Нормализованный display type Portfolio Position, полученный из upstream
instrument type: `STOCK`, `BOND`, `FUND`, `CURRENCY`, `FUTURE`, `OPTION`,
`STRUCTURED_NOTE`, `DIGITAL_FINANCIAL_ASSET` или `OTHER`.
_Не использовать_: upstream instrument type как доменный enum.
## Currency Instrument Position
Portfolio Position с type `CURRENCY`, представляющая торгуемый валютный
Instrument. Она остаётся в `positions` и не объединяется с Cash Balance той же
валюты.
_Не использовать_: денежный остаток, cash balance.
## Instrument
Ценная бумага или иной торгуемый актив T-Bank, на который ссылается Portfolio
Position или Future Cash Event через FIGI или instrument UID. Metadata
Instrument обогащают отображение Position или Event, но не определяют
количество, стоимость или факт события.
_Не использовать_: бумага, тикер, актив позиции.
## Instrument Logo
Имя файла бренда T-Bank для отображения Instrument в интерфейсе;
presentation metadata, которая не влияет на количество или стоимость
позиции. Изображение загружается frontend из публичного брендового CDN
без авторизации; при отсутствии имени или ошибке загрузки используется
фолбэк с первой буквой отображаемого имени.
_Не использовать_: аватар актива, иконка бумаги.
## Cash Balance
Ненулевой доступный денежный остаток Portfolio в одной валюте; не является
Portfolio Position и передаётся отдельно от `positions` в `cashBalances`.
_Не использовать_: валютная позиция, заблокированные средства.
## Portfolio Snapshot
Полный успешно полученный состав Portfolio на определённый момент времени,
который может быть свежим или устаревшим fallback-представлением.
_Не использовать_: история портфеля, snapshot для графика.
Следующие термины описывают runtime/cache-механизмы. Они не являются
самостоятельными бизнес-сущностями.
## Client Cached Snapshot
Последний успешно полученный frontend-ответ React Query, который остаётся
видимым во время фонового обновления или после его ошибки. Не является
server-side fallback и не использует поле `Portfolio.isStale`.
_Не использовать_: устаревший Portfolio Snapshot, fallback портфеля.
## Account Ledger
Упорядоченная read-only история исполненных операций одного Brokerage Account,
отражающая фактические денежные и инструментальные движения без рыночной
переоценки или расчёта остатка после операции.
_Не использовать_: история Portfolio, баланс счёта.
## Ledger Entry
Нормализованное представление одной операции T-Bank в Account Ledger; хранит
исходные суммы и доступные детали операции, не создавая синтетических записей
для комиссии сделки. В публичном API история доступна через маршруты
`/operations`; в доменной документации запись называется Ledger Entry.
_Не использовать_: проводка, рассчитанная операция.
## Operation Category
Нормализованная категория Ledger Entry для фильтрации и отображения:
`trade`, `income`, `tax`, `fee`, `transfer` или `other`.
_Не использовать_: upstream operation type как category.
## Ledger Page Cache
Кэш страницы Account Ledger, отделённый от кэша отдельных операций.
_Не использовать_: operations cache, кэш отдельных операций.
## Ledger Page Cache Key
Каноническая идентичность страницы Ledger Page Cache: opaque generation
текущего T-Bank connection, User, Brokerage Account, нормализованные диапазон
и categories, opaque upstream cursor и limit.
_Не использовать_: сырой URL query, ключ отдельной Ledger Entry.
## Concurrent Refresh Attempt
Два или более перекрывающихся запроса с одним ещё действительным refresh token
одной Session; один становится Refresh Winner, остальные — Refresh Loser и
получают `401`, не отзывая Session победителя.
_Не использовать_: повторное использование refresh token, последовательный
refresh.
## Superseded Refresh Token
Refresh token, который перестал быть действительным после успешной ротации
Session; его предъявление получает `401`, но само по себе не отзывает Session.
_Не использовать_: активный refresh token, признак взлома сессии.
## Future Cash Event
Прогнозируемое на ближайший период денежное или связанное корпоративное
событие одного Brokerage Account, рассчитанное по текущей Portfolio Position;
не является исполненной Ledger Entry. Прогнозная сумма события передаётся как
nullable Money: известные coupon, dividend и maturity содержат Money в исходной
валюте, offer или неизвестные денежные условия передаются как `null`.
В публичном API прогноз доступен через маршруты `/events`; в доменной
документации событие называется Future Cash Event.
_Не использовать_: будущая операция, гарантированная выплата.
## Future Cash Event Type
Нормализованный тип Future Cash Event: `coupon`, `dividend`, `maturity` или
`offer`.
_Не использовать_: upstream dividend type, MOEX row name.
## Realized Income Analytics
Read-only account-scoped result исполненных coupon и dividend Ledger Entries за
включительный календарный period в Europe/Moscow. Он сохраняет исходные
currencies и signed broker-reported payments; это не налоговый расчёт, не
forecast и не investment performance.
_Не использовать_: доходность, общий доход в одной валюте, гарантированный
доход.
+31 -7
View File
@@ -32,10 +32,13 @@ message/stack. Типичный ответ:
не считается проверенным одним описанием OpenAPI: нужна проверка реального
HTTP-ответа после всех стадий обработки запроса.
**Требует ручной проверки:** согласованность деталей ошибок валидации полей
в полном HTTP-потоке пока не подтверждена. Известно расхождение формата ошибок
между стадиями валидации и формирования ответа; наличие описания в OpenAPI
не доказывает передачу ожидаемых `pointer` и `detail`.
**Известное расхождение:** согласованная
[спецификация аутентификации](../../specs/openspec/specs/user-authentication/spec.md#requirement-стандартные-ответы-об-ошибках)
требует `pointer` и `detail` для невалидных полей. В текущей конфигурации
валидация формирует строковые сообщения, а фильтр ожидает структурированные
ошибки полей; поэтому корректность деталей `errors` не подтверждена.
Исправление требует отдельного изменения реализации и проверки полного
HTTP-потока. OpenAPI сам по себе этого расхождения не устраняет.
Ответ не включает автоматически все поля RFC (`type`, `title`, `instance`).
Добавление обязательного поля является изменением публичного контракта.
@@ -45,9 +48,9 @@ HTTP-ответа после всех стадий обработки запро
Специальный code определяется сценарием; общее преобразование ошибок и безопасная
форма ответа принадлежат HTTP-границе. Auth endpoints используют свои status и cookie
operations: [Authentication](../features/authentication.md).
operations: [Authentication](../domains/authentication/overview.md).
T-Bank token failure и временная недоступность имеют разные значения:
[Connection API](../features/tbank-connection.md#api). Raw upstream/Prisma errors
[Connection API](../integrations/tbank.md#состояние-и-ошибки). Raw upstream/Prisma errors
и secrets не передаются frontend.
## Граница доказательства
@@ -57,4 +60,25 @@ T-Bank token failure и временная недоступность имеют
Валидация, доступ, статусы и cookie требуют проверки полного HTTP-потока;
атомарность хранения — проверки с изолированной реальной базой.
Известное ограничение сквозных проверок описано в
[проверке backend](../development/backend-workflow.md#локальная-проверка).
[проверке backend](../development/verification.md).
## Наблюдаемость
Backend пишет структурированные JSON-события в stdout через Pino. Обработка
HTTP-запроса получает серверный request ID, который возвращается заголовком
`X-Request-ID`; допустимый входящий идентификатор сохраняется отдельно как
client request ID. Это позволяет сопоставить публичную ошибку с обработкой
запроса без передачи внутреннего exception в ответ API.
Журнал завершения запроса содержит шаблон маршрута, метод, статус,
длительность и при наличии публичный error code. События внешней границы
добавляют операцию, результат и число попыток. Разрешённые поля ограничены;
полные запросы и ответы, token, cookie и Authorization не являются данными
журнала. Фильтрация полей и redaction не защищают секрет, вручную включённый
в свободный текст сообщения или stack.
Выбор logger и граница ответственности описаны в
[ADR 008](../adr/008-pino-backend-logging.md); согласованное поведение — в
workspace-спецификации
[Structured logging](../../specs/openspec/specs/backend-structured-logging/spec.md).
Сборщик, срок хранения журналов и alerting не определяются приложением.
-169
View File
@@ -1,169 +0,0 @@
# Обзор системы
WealthLedger — локальное приложение для частного инвестора: оно связывает
состав портфеля, операции, полученный доход и будущие выплаты по счетам T-Bank.
Обзор объясняет систему сверху вниз. Детали сценариев и ответственность подсистем находятся
в [карте возможностей](../features/README.md).
## Поставленные возможности
- [Аутентификация](../features/authentication.md): access/refresh-сессии и
защищённые маршруты.
- [Подключение T-Bank](../features/tbank-connection.md): один зашифрованный
token на User.
- [Брокерские счета](../features/brokerage-accounts.md),
[Portfolio](../features/portfolio-view.md),
[Account Ledger](../features/brokerage-account-ledger.md),
[Пассивный доход](../features/brokerage-account-income-analytics.md) и
[Future Cash Events](../features/brokerage-account-upcoming-events.md).
- [Frontend UI resilience](../features/frontend-ui-resilience.md): явные
loading, error, empty и cached-snapshot состояния; initial и частичные
loading-состояния показывают structural skeleton, а уже доступный контекст
остаётся видимым при фоновом обновлении или действии пользователя.
- [Все счета](../features/all-accounts.md): общий портфель, журнал, доходы
и будущие выплаты с сохранением принадлежности к счетам.
- [Акции](../features/stock-detail.md) и [облигации](../features/bond-detail.md):
поиск, рыночные данные, личная позиция и события конкретного инструмента.
Термины этих возможностей определяет [глоссарий домена](domain-glossary.md), а
причины ключевых технических решений — [ADR](../adr/README.md).
## Контекст системы
```mermaid
flowchart LR
U[Частный инвестор] -->|Смотрит инвестиции| W[WealthLedger]
W -->|Счета, позиции, операции и рынок| T[T-Bank Invest API]
W -->|Публичные данные облигаций| M[MOEX ISS]
W -->|Рейтинги выпусков| N[Реестр НКР]
W -->|Изображения логотипов| C[T-Bank Brands CDN]
```
T-Bank — источник личных брокерских данных и рынка. MOEX дополняет будущие
события и показатели облигаций, НКР — рейтинг конкретного выпуска. Публичный
рейтинг или календарь выпуска не подтверждает личное зачисление инвестору.
Отсутствие записи и недоступность источника имеют разный смысл; детали —
[Bond detail](../features/bond-detail.md).
## Исполняемые части
На уровне C4 containers здесь два приложения и локальная база. Каталоги
`docs` и `specs` не являются runtime-компонентами.
```mermaid
flowchart LR
subgraph Browser[Браузер пользователя]
F[React SPA]
end
subgraph Local[Локальное окружение]
B[NestJS API]
D[(SQLite)]
B -->|Prisma: identity, sessions, connection| D
end
F -->|HTTP JSON, Bearer и auth cookie| B
B -->|gRPC over TLS, token пользователя| T[T-Bank Invest]
B -->|HTTPS, публичные данные| M[MOEX ISS]
B -->|HTTPS, публичный HTML| N[НКР]
F -->|HTTPS, изображения| C[Brands CDN]
```
- **Frontend** владеет представлением, навигацией и клиентским query cache.
Access token существует в памяти; refresh-cookie обслуживает browser.
- **Backend** владеет identity, проверкой доступа, credentials брокера,
нормализацией, денежными расчётами и upstream-кэшем.
- **SQLite** хранит локального User, sessions и зашифрованное подключение.
- **Интеграции backend** обращаются к T-Bank, MOEX и НКР, сохраняя различия
личных данных брокера и публичных данных выпуска. Браузер получает
изображения логотипов непосредственно из публичного CDN.
Frontend связывает пользовательские экраны навигацией. Backend объединяет
аутентификацию, подключение брокера и инвестиционные сценарии через публичный API.
Согласование адресов и origin — [локальный запуск](../development/local-workspace.md).
## Хранимые данные
Локальная база содержит идентичность пользователя и hash пароля, состояние
его сессий со сроком действия, признаком отзыва и hash текущего refresh token,
а также одно зашифрованное подключение T-Bank с временем подключения.
Сессии и подключение принадлежат конкретному пользователю; plaintext пароля
или broker token не является сохраняемым представлением credentials.
Изменения связанных локальных данных требуют сохранения их целостности.
Ротация сессии атомарно заменяет текущий refresh token: конкурентные запросы
не могут оба выиграть. Сетевой вызов брокера и изменение локальной базы не
образуют одну общую транзакцию. Финансовые наблюдения хранятся в ограниченном
кэше, а не как долговременная история брокерских данных.
## Локальные границы
- Финансовые API-запросы frontend проходят backend; browser не получает
T-Bank credential или gRPC client. Загрузка публичных логотипов — отдельный поток.
- Backend — NestJS API `/api/v1`, владеющий auth, SQLite-данными и T-Bank
gRPC boundary.
- OpenAPI backend — единственный контракт frontend; committed client
генерируется штатным workflow.
- Поставка остаётся локальной: deployment, Docker, CI/CD и облачные сервисы не
входят в текущие границы.
- Portfolio Snapshot, summary списка Brokerage Account, Future Cash Events и
Ledger pages существуют как read-only наблюдения текущего T-Bank connection;
система не хранит историю портфеля, будущих событий или брокерских счетов как
долговременные агрегаты. Summary списка сохраняет исходные валюты без FX-
агрегации и может быть локально недоступна для отдельного счета.
## Предметная модель
Локальный [User](domain-glossary.md#user) владеет
[Session](domain-glossary.md#session) и одним
[T-Bank connection](domain-glossary.md#t-bank-connection). Через подключение
backend устанавливает доступ к [Brokerage Accounts](domain-glossary.md#brokerage-account).
Идентификатор счёта из URL сам по себе не подтверждает этот доступ.
[Portfolio](domain-glossary.md#portfolio) отвечает на вопрос «что есть сейчас»,
[Account Ledger](domain-glossary.md#account-ledger) — «что исполнено»,
[Realized Income](domain-glossary.md#realized-income-analytics) — «какие выплаты
уже получены», будущие события — «что ожидается». Эти представления нельзя
подменять друг другом. [Money](domain-glossary.md#money) сохраняет исходную
валюту и точность; неизвестное значение отличается от нуля.
Режим [«Все счета»](../features/all-accounts.md) агрегирует доступный состав,
сохраняя происхождение строк; он не создаёт новый брокерский счёт. Обновление
подключения меняет границу финансовых кэшей; перезапуск backend не восстанавливает
прежние in-memory snapshots и cursor continuations из SQLite.
## Поток auth/session
Вход создаёт локальную сессию; последующие запросы используют access token.
После перезагрузки SPA восстанавливает сессию через refresh-cookie. Схема,
конкурентная ротация, logout и ограничения собраны у одного owner:
[Authentication](../features/authentication.md).
## Поток инвестиционных данных
```mermaid
sequenceDiagram
participant U as User
participant F as Frontend
participant B as Backend
participant D as SQLite
participant T as T-Bank gRPC
U->>F: Открывает счета или Portfolio
F->>B: GET /api/v1/brokerage-accounts/* (Bearer)
B->>D: Читает подключение текущего User
B->>T: Проверяет доступ к счёту
B->>T: Запрос через server-side gRPC boundary
T-->>B: Upstream-данные
B-->>F: Нормализованные данные или Problem Details
F-->>U: Экран с данными или локальным состоянием ошибки
```
Схема показывает запрос, которому нужны upstream-данные. Повторные чтения могут
обслуживаться кэшем по правилам конкретной feature. Проверку connection/account
выполняет backend: запрос связывается с пользователем, его текущим подключением
и доступными через него счетами, включая выдачу данных из кэша.
Внешняя ошибка не становится нулевым балансом; допустимость stale snapshot и
локальное восстановление описывает соответствующий сценарий.
Причины server-side границы, шифрования и retry policy —
[ADR 003](../adr/003-tbank-server-side-grpc-boundary.md),
[ADR 004](../adr/004-encrypted-per-user-tbank-token.md) и
[ADR 005](../adr/005-tbank-upstream-resilience.md).