docs: make project documentation self-contained

This commit is contained in:
2026-09-12 13:09:37 +03:00
parent aa9afc15e4
commit 3a99af1a6e
36 changed files with 1006 additions and 703 deletions
+5 -7
View File
@@ -25,7 +25,7 @@ _Не использовать_: T-Bank сессия, browser session.
неприменимое денежное значение передаётся как `null`, а не как ноль и не как
раздельные `amount`/`currency`.
_Не использовать_: number amount, MoneyDto как доменный термин.
_Не использовать_: сумма без валюты, транспортное представление как доменный термин.
## T-Bank connection
@@ -138,9 +138,8 @@ _Не использовать_: история Portfolio, баланс счёт
Нормализованное представление одной операции T-Bank в Account Ledger; хранит
исходные суммы и доступные детали операции, не создавая синтетических записей
для комиссии сделки. API/code сохраняют legacy names `BrokerageOperationDto`,
`BrokerageOperationsPageDto` и `/operations`; в доменной документации primary
term — Ledger Entry.
для комиссии сделки. В публичном API история доступна через маршруты
`/operations`; в доменной документации запись называется Ledger Entry.
_Не использовать_: проводка, рассчитанная операция.
@@ -188,9 +187,8 @@ _Не использовать_: активный refresh token, признак
не является исполненной Ledger Entry. Прогнозная сумма события передаётся как
nullable Money: известные coupon, dividend и maturity содержат Money в исходной
валюте, offer или неизвестные денежные условия передаются как `null`.
API/code сохраняют legacy names `UpcomingEventDto`, `UpcomingEventsDto`,
`/events` и frontend `BrokerageEvent`; в доменной документации primary term —
Future Cash Event.
В публичном API прогноз доступен через маршруты `/events`; в доменной
документации событие называется Future Cash Event.
_Не использовать_: будущая операция, гарантированная выплата.
+27 -26
View File
@@ -2,25 +2,24 @@
## Источник контракта
Nest controllers/DTO/Swagger metadata генерируют runtime OpenAPI. Настройка
приложения — [configureApp](../../backend/src/main.ts); frontend хранит derived
snapshot и generated TS. Изменения проходят
Backend генерирует OpenAPI из собственного публичного API. Frontend использует
производное описание контракта и типизированный клиент. Изменения проходят
[contract workflow](../development/runtime-openapi-verification.md).
API имеет prefix `/api/v1`; Swagger публикуется для локальной разработки по
environment policy, а не используется frontend в runtime.
## Validation и identity
Global ValidationPipe использует `transform: true`, `whitelist: true`.
Unknown properties удаляются, а их rejection не обещается без
`forbidNonWhitelisted`. Runtime DTO validators и преобразования задают допустимый
вход; бизнес-ограничения и доступ к account проверяются service/access boundary.
Guard подтверждает identity; accountId из URL сам по себе не доказывает доступ.
Backend преобразует и проверяет входные данные на общей HTTP-границе.
Неизвестные свойства запроса удаляются; сам факт их передачи не обещает отказа.
Проверка формата не заменяет бизнес-ограничения и проверку доступа к счёту.
Аутентификация подтверждает личность пользователя; accountId из URL сам по себе
не доказывает доступ к брокерским данным.
## Error contract
Общий [HttpExceptionFilter](../../backend/src/common/filters/http-exception.filter.ts)
возвращает `application/problem+json` со `status` и публичным `code`.
Общая граница обработки HTTP-ошибок возвращает `application/problem+json`
со `status` и публичным `code`.
Неожиданный exception возвращает `500` / `INTERNAL_SERVER_ERROR`, без исходного
message/stack. Типичный ответ:
@@ -30,21 +29,22 @@ message/stack. Типичный ответ:
Для input validation контракт требует `400`, `VALIDATION_ERROR` и `errors` как
массив `{ pointer, detail }`, где pointer адресует поле запроса. Это требование
не считается проверенным одним Swagger snapshot: direct controller call не
запускает ValidationPipe. Текущий filter преобразует `ValidationError.constraints`,
а default Nest pipe формирует массив строк; корректность field details в
configured HTTP pipeline нужно проверять отдельно и не копировать этот seam
как доказанно полный пример.
не считается проверенным одним описанием OpenAPI: нужна проверка реального
HTTP-ответа после всех стадий обработки запроса.
Документированный DTO — [ProblemDetailsDto](../../backend/src/common/decorators/api-safe-error-response.decorator.ts).
Filter не добавляет автоматически все поля из RFC (`type`, `title`, `instance`).
Не вводи дополнительные обязательные поля без согласованного изменения wire contract.
Caller выбирает поведение по public code, не по внутреннему тексту exception.
**Требует ручной проверки:** согласованность деталей ошибок валидации полей
в полном HTTP-потоке пока не подтверждена. Известно расхождение формата ошибок
между стадиями валидации и формирования ответа; наличие описания в OpenAPI
не доказывает передачу ожидаемых `pointer` и `detail`.
Ответ не включает автоматически все поля RFC (`type`, `title`, `instance`).
Добавление обязательного поля является изменением публичного контракта.
Клиент выбирает поведение по публичному коду, а не по внутреннему тексту ошибки.
## Domain errors и auth
Специальный code задаётся ответственным use case; generic mapping и безопасный
envelope принадлежат filter. Auth endpoints используют свои status и cookie
Специальный code определяется сценарием; общее преобразование ошибок и безопасная
форма ответа принадлежат HTTP-границе. Auth endpoints используют свои status и cookie
operations: [Authentication](../features/authentication.md).
T-Bank token failure и временная недоступность имеют разные значения:
[Connection API](../features/tbank-connection.md#api). Raw upstream/Prisma errors
@@ -52,8 +52,9 @@ T-Bank token failure и временная недоступность имеют
## Граница доказательства
OpenAPI integration проверяет равенство schemas, а не результат каждого request.
Filter unit test проверяет безопасный unexpected error. Для изменения validation,
guard, status/cookie или domain error нужны representative configured HTTP cases;
известный e2e environment blocker не превращает отсутствующую проверку в success.
Выбор уровня и команд — [backend testing](../../backend/.ai/conventions/backend-testing.md).
Сравнение OpenAPI подтверждает согласованность схем, а не результат каждого
запроса. Безопасная обработка неожиданной ошибки проверяется отдельно.
Валидация, доступ, статусы и cookie требуют проверки полного HTTP-потока;
атомарность хранения — проверки с изолированной реальной базой.
Известное ограничение сквозных проверок описано в
[проверке backend](../development/backend-workflow.md#локальная-проверка).
+113 -22
View File
@@ -1,7 +1,9 @@
# Обзор системы
WealthLedger — локальное приложение для просмотра инвестиционных данных через
безопасное server-side подключение к T-Bank Invest.
WealthLedger — локальное приложение для частного инвестора: оно связывает
состав портфеля, операции, полученный доход и будущие выплаты по счетам T-Bank.
Обзор объясняет систему сверху вниз. Детали сценариев и ответственность подсистем находятся
в [карте возможностей](../features/README.md).
## Поставленные возможности
@@ -18,13 +20,84 @@ WealthLedger — локальное приложение для просмотр
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: конкурентные запросы
не могут оба выиграть. Сетевой вызов брокера и изменение локальной базы не
образуют одну общую транзакцию. Финансовые наблюдения хранятся в ограниченном
кэше, а не как долговременная история брокерских данных.
## Локальные границы
- Frontend — React-клиент; он не вызывает T-Bank напрямую.
- Финансовые 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
@@ -37,24 +110,32 @@ WealthLedger — локальное приложение для просмотр
долговременные агрегаты. 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
```mermaid
sequenceDiagram
participant U as User
participant F as Frontend
participant B as Backend
participant D as SQLite
U->>F: Регистрация или вход
F->>B: POST /api/v1/auth/*
B->>D: Создаёт User и Session
B-->>F: Access token + refresh cookie
F->>B: Защищённый запрос с Bearer token
B-->>F: Данные или Problem Details
F->>B: POST /api/v1/auth/refresh при истечении access token
B->>D: Ротирует refresh token
B-->>F: Новый access token + refresh cookie
```
Вход создаёт локальную сессию; последующие запросы используют access token.
После перезагрузки SPA восстанавливает сессию через refresh-cookie. Схема,
конкурентная ротация, logout и ограничения собраны у одного owner:
[Authentication](../features/authentication.md).
## Поток инвестиционных данных
@@ -67,12 +148,22 @@ sequenceDiagram
participant T as T-Bank gRPC
U->>F: Открывает счета или Portfolio
F->>B: GET /api/v1/brokerage-accounts/* (Bearer)
B->>D: Читает T-Bank connection
B->>D: Читает подключение текущего User
B->>T: Проверяет доступ к счёту
B->>T: Запрос через server-side gRPC boundary
T-->>B: Upstream-данные
B-->>F: Нормализованные данные или Problem Details
F-->>U: Экран с данными или локальным состоянием ошибки
```
Подробный статус локальных gates и deferred Prisma e2e находится в
[audit remediation registry](../development/audit-remediation.md).
Схема показывает запрос, которому нужны 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).