Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4838e1485 | ||
|
|
07de062ebb | ||
|
|
55610ea2b4 | ||
|
|
15d1d820f5 | ||
|
|
6fd39cd467 | ||
|
|
5de85c296f | ||
|
|
34b483018b | ||
|
|
cead9dc176 | ||
|
|
97069d6f6d |
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"main": "2567a755569e6ef6ba12063b62f202fe52884ce7",
|
||||
"frontend": "0244acf429c525b23117f165bbe52ef5403eefd3",
|
||||
"backend": "660b250ecd05e38784690b9a9adb381764c0ca7c",
|
||||
"specs": "9e227a563dda67620f0ad78228aa8a4c18bf35a0"
|
||||
}
|
||||
@@ -8,39 +8,13 @@ on:
|
||||
jobs:
|
||||
verify:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: workspace/docs
|
||||
steps:
|
||||
- uses: https://github.com/actions/checkout@v4
|
||||
with:
|
||||
path: workspace/docs
|
||||
- name: Checkout compatible peer revisions
|
||||
env:
|
||||
SOURCE_BRANCH: ${{ github.head_ref || github.ref_name }}
|
||||
TARGET_BRANCH: ${{ github.base_ref || github.ref_name }}
|
||||
run: |
|
||||
set -eu
|
||||
for repo in main frontend backend specs; do
|
||||
ref=develop
|
||||
case "$SOURCE_BRANCH:$TARGET_BRANCH" in
|
||||
release/*:*|*:main)
|
||||
ref=$(jq -er --arg repo "$repo" '.[$repo]' .ci/refs.json)
|
||||
printf '%s\n' "$ref" | grep -Eq '^[0-9a-f]{40}$'
|
||||
;;
|
||||
esac
|
||||
target="../$repo"
|
||||
if [ "$repo" = main ]; then target=..; fi
|
||||
git init "$target"
|
||||
git -C "$target" remote add origin "https://git.personal.ksv741.ru/WealthLedger/$repo.git"
|
||||
git -C "$target" fetch --depth 1 origin "$ref"
|
||||
git -C "$target" checkout --detach FETCH_HEAD
|
||||
git -C "$target" rev-parse HEAD
|
||||
done
|
||||
git rev-parse HEAD
|
||||
- name: Record checked revision
|
||||
run: git rev-parse HEAD
|
||||
- uses: https://github.com/actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: workspace/docs/.nvmrc
|
||||
node-version-file: .nvmrc
|
||||
- run: npm ci
|
||||
- name: Check Markdown and relative links
|
||||
run: npm run verify
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
затем только затронутую тему. Docs объясняет систему; требования принадлежат
|
||||
основным спецификациям, реализация и coding conventions — соседним репозиториям.
|
||||
|
||||
Проверь существенные факты адресно по спецификациям, ADR и реализации с учётом
|
||||
веток и незакоммиченных изменений. Расхождение обозначь, не выбирая источник
|
||||
Проверь существенные факты по локальным тематическим страницам и ADR;
|
||||
внешние источники используй как справочные свидетельства, когда они доступны.
|
||||
Недостающие факты отмечай явно; инструкции и проверки не требуют других checkout.
|
||||
Расхождение обозначь, не выбирая источник
|
||||
победителем автоматически. Перенос сохраняет полезное содержание и входящие
|
||||
ссылки. Docs описывает текущее устройство системы; история поставок остаётся в Git.
|
||||
Отчёты аудитов и исследований в docs не размещаются. Подтверждённые решения
|
||||
@@ -19,6 +21,6 @@
|
||||
## Ветки и релизы
|
||||
|
||||
Работа — в отдельной ветке от `develop`, затем PR в `develop`; в `main` напрямую
|
||||
не пушить. Release/hotfix и синхронизация веток — по [root README](../README.md#релизы-и-ветки).
|
||||
не пушить. Release/hotfix и синхронизация веток — по [README](README.md#релизы).
|
||||
Commits и заголовки PR — Conventional Commits; для спецификаций допустим `spec:`.
|
||||
Перед подготовкой релиза прочитай root README; версии вручную не повышать.
|
||||
Перед подготовкой релиза прочитай местный README; версии вручную не повышать.
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# Changelog
|
||||
|
||||
## [0.1.2](https://git.personal.ksv741.ru/WealthLedger/docs/compare/v0.1.1...v0.1.2) (2026-09-26)
|
||||
|
||||
### Documentation
|
||||
|
||||
* describe local frontend packages and ownership ([55610ea](https://git.personal.ksv741.ru/WealthLedger/docs/commit/55610ea2b4dc687f76ee9eb37c14d887b1b70100))
|
||||
* document Nexus registry migration [#18](https://git.personal.ksv741.ru/WealthLedger/docs/issues/18) ([5de85c2](https://git.personal.ksv741.ru/WealthLedger/docs/commit/5de85c296f7681882f58476343d3da71f1dee28d))
|
||||
* document versioned deployment bundle ([15d1d82](https://git.personal.ksv741.ru/WealthLedger/docs/commit/15d1d820f5401c25adda23f753b99e894bdcd4d3))
|
||||
* explain persisted T-Bank reads and synchronization ([07de062](https://git.personal.ksv741.ru/WealthLedger/docs/commit/07de062ebbf043eff50bd7f53b48bff13bb7cb1b))
|
||||
* use shared Nexus account [#18](https://git.personal.ksv741.ru/WealthLedger/docs/issues/18) ([6fd39cd](https://git.personal.ksv741.ru/WealthLedger/docs/commit/6fd39cd467566e23cca79a1235125dfb80fa99f7))
|
||||
|
||||
## [0.1.1](https://git.personal.ksv741.ru/WealthLedger/docs/compare/v0.1.0...v0.1.1) (2026-09-16)
|
||||
|
||||
### Documentation
|
||||
|
||||
* document container deployment and recovery ([cead9dc](https://git.personal.ksv741.ru/WealthLedger/docs/commit/cead9dc1765700b7e10f9567e38f6d68d4ec9656))
|
||||
* make documentation checks self-contained ([97069d6](https://git.personal.ksv741.ru/WealthLedger/docs/commit/97069d6f6d0719d6fcee30b1ce8adbda41bd3a6d))
|
||||
|
||||
## 0.1.0 (2026-09-14)
|
||||
|
||||
### Features
|
||||
|
||||
+13
-15
@@ -8,13 +8,13 @@ Docs даёт самостоятельное понимание продукта
|
||||
|
||||
| Вид знания | Где поддерживается |
|
||||
| --- | --- |
|
||||
| Согласованное проверяемое поведение: требования и сценарии | [Основные спецификации](../specs/AI_INDEX.md) в `specs/openspec/specs/` |
|
||||
| Согласованное проверяемое поведение: требования и сценарии | [Основные спецификации](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/AI_INDEX.md) в `specs/openspec/specs/` |
|
||||
| Предложения изменений, их проектирование и задачи | OpenSpec changes в существующем Store `wealth-ledger-specs`, `specs/openspec/changes/` |
|
||||
| Понимание системы, областей и взаимодействий | Тематические страницы docs |
|
||||
| Принятые решения и подтверждённые причины | Единственный журнал [docs/adr](adr/README.md) |
|
||||
| Конкретная реализация и технические контракты | Код, тесты, схемы, конфигурация; публичный OpenAPI формирует backend |
|
||||
| Размещение кода, conventions, команды разработки и проверки | Локальные инструкции frontend/backend |
|
||||
| Выбор репозитория и типа знания | Корневой [AGENTS](../AGENTS.md) |
|
||||
| Работа над docs | Локальные [AGENTS](AGENTS.md) и этот документ |
|
||||
|
||||
История аудитов и проверок остаётся в Git, Issues и PR. `CHANGELOG.md`
|
||||
генерируется release-it из commits; вручную дублировать его на тематических страницах не нужно.
|
||||
@@ -86,9 +86,11 @@ Docs объясняет смысл контракта, не вводя неза
|
||||
Нужные точные значения сопровождай определяющим источником, чтобы не создать
|
||||
конкурирующую норму. Паспорта страниц, Evidence-секции и номера строк не требуются.
|
||||
|
||||
Внутри docs используем относительные Markdown-ссылки. Между репозиториями —
|
||||
относительный путь через соседний корень, например `../specs/AI_INDEX.md` из README.
|
||||
Проверяй регистр, файл и якорь в фактическом workspace. Перенос требует исправить
|
||||
Внутри docs используем относительные Markdown-ссылки. Внешние репозитории —
|
||||
необязательные справочные веб-ссылки на опубликованный источник. Основной текст
|
||||
и инструкции должны быть понятны без открытия этих ссылок. Проверяй локальные
|
||||
файлы/якоря; для удалённых источников не обещай проверку их доступности CI.
|
||||
Перенос требует исправить
|
||||
входящие ссылки активных Markdown-документов; исторические архивы и dated audits
|
||||
сохраняют адреса своего времени. Короткий указатель оставляем только при реальной
|
||||
необходимости совместимости, без второй копии содержания.
|
||||
@@ -153,8 +155,7 @@ npm run verify
|
||||
Существующий gate запускает Markdown lint с проверкой относительных ссылок
|
||||
и якорей, затем `git diff --check`. Он охватывает новые Markdown-файлы;
|
||||
обычный diff их не показывает, поэтому отдельно просмотри untracked-содержание.
|
||||
При изменениях соседних репозиториев выполни там `git diff --check` и проверь
|
||||
затронутые ссылки. Markdown-only правки не требуют тестов всего приложения.
|
||||
Markdown-only правки не требуют тестов приложения или соседнего checkout.
|
||||
|
||||
Правило relative-links не проверяет внешние URL и HTML-ссылки. Mermaid-рендер
|
||||
в gate не входит; используй доступный локальный рендерер без отправки материалов
|
||||
@@ -169,13 +170,10 @@ npm run verify
|
||||
### CI и обновления зависимостей
|
||||
|
||||
`.gitea/workflows/verify.yml` запускает этот gate на push и PR с Node
|
||||
из `.nvmrc` и установкой через `npm ci`. Checkout docs размещается внутри
|
||||
workspace из `WealthLedger/main`, рядом с frontend, backend и specs. Соседние
|
||||
репозитории для разработки берутся из `develop`; для `main`, PR в `main`
|
||||
и `release/*` — из полных SHA в `.ci/refs.json`. SHA всех пяти участников
|
||||
сохраняются в логе.
|
||||
Ошибка checkout, Markdown или относительной ссылки завершает job с FAIL.
|
||||
Это проверка ссылок на выбранные revisions, без Mermaid-render и semantic review.
|
||||
из `.nvmrc` и установкой через `npm ci`. Скачивается только docs;
|
||||
его SHA сохраняется в логе. Ошибка checkout, Markdown или внутренней ссылки
|
||||
завершает job с FAIL. Внешние веб-ссылки — справочные; CI не проверяет их
|
||||
доступность, Mermaid-render или semantic correctness.
|
||||
|
||||
`security.yml` при push, PR и ежедневно проверяет всю Git-историю через
|
||||
Gitleaks 8.30.1 с redacted-выводом и все npm dependencies через
|
||||
@@ -187,4 +185,4 @@ Renovate объединяет minor/patch updates; major требует согл
|
||||
Dependency Dashboard, automerge не включён. Required checks включаются
|
||||
отдельно в Gitea; workflow сам по себе не запрещает merge.
|
||||
|
||||
Версии и ветки: [процесс релиза](../README.md#релизы-и-ветки).
|
||||
Версии и ветки: [процесс релиза](README.md#релизы).
|
||||
|
||||
@@ -47,15 +47,16 @@ WealthLedger помогает частному инвестору понимат
|
||||
| Понять ошибку или старые данные | [Состояния интерфейса](architecture/cross-cutting/ui-resilience.md), [свежесть данных](architecture/cross-cutting/data-freshness.md) |
|
||||
| Понять визуальные роли и состояния | [Визуальные принципы](design/overview.md) |
|
||||
| Узнать причину архитектурного решения | [Журнал ADR](adr/README.md) |
|
||||
| Запустить, обновить или восстановить приложение | [Контейнерная поставка](architecture/deployment.md) |
|
||||
|
||||
## Границы знаний
|
||||
|
||||
[Основные спецификации](../specs/AI_INDEX.md) сохраняют согласованное проверяемое
|
||||
[Основные спецификации](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/AI_INDEX.md) сохраняют согласованное проверяемое
|
||||
поведение, а OpenSpec changes — предложения, дизайн и задачи изменений.
|
||||
Фактический Store находится в `specs/openspec/`, основные требования — в
|
||||
`specs/openspec/specs/`. Docs объясняет систему; код, тесты, схемы и конфигурация
|
||||
задают конкретный механизм, а [ADR](adr/README.md) сохраняют принятые решения.
|
||||
Локальные инструкции [frontend](../frontend/AGENTS.md) и [backend](../backend/AGENTS.md)
|
||||
Локальные инструкции [frontend](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/AGENTS.md) и [backend](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/AGENTS.md)
|
||||
описывают изменение реализации и её проверки.
|
||||
|
||||
Эти источники дополняют друг друга. Найденный незавершённый change не доказывает
|
||||
@@ -67,3 +68,39 @@ WealthLedger помогает частному инвестору понимат
|
||||
|
||||
Правила изменения текста, диаграмм, ссылок и проверки docs-impact находятся
|
||||
в [CONTRIBUTING](CONTRIBUTING.md).
|
||||
|
||||
## Релизы
|
||||
|
||||
Репозиторий выпускается независимо. `develop` — интеграция, `main` — принятые
|
||||
релизы. Работа: `codex/<task>` от актуального `develop`, PR в `develop`.
|
||||
Обычные PR допускают squash; release и обратная синхронизация сохраняют историю.
|
||||
Commits и PR titles — Conventional Commits, включая `spec:`. Версии вручную не повышай.
|
||||
|
||||
1. Получи `main`, `develop` и теги: `git fetch origin --tags`. Убедись, что
|
||||
`develop` содержит последний релиз. Создай `release/<name>` от проверенного
|
||||
`develop`; `nvm use --silent`, `npm ci`. Hotfix начинается от `main` в
|
||||
`release/hotfix-<name>` и проходит тот же процесс.
|
||||
2. На чистой ветке выполни `npm run release:preview`, просмотри версию/changelog,
|
||||
затем `npm run release:prepare`. Команда создаёт `chore(release): X.Y.Z`,
|
||||
обновляет manifest/lockfile/CHANGELOG; тег и push не выполняет. Не обходи
|
||||
guards ветки, чистого дерева, отсутствующих тегов или уже подготовленной версии.
|
||||
3. Проверь diff и штатные gates этого репозитория. Отправь только release-ветку,
|
||||
открой PR в `main` с заголовком `chore(release): X.Y.Z`. После зелёных
|
||||
`verify`, `security`, `commits` выполни fast-forward-only merge. Если `main`
|
||||
изменился, согласуй историю и повтори проверки кандидата.
|
||||
4. Получи удалённый `main`, сравни его HEAD с проверенным release SHA и версией
|
||||
manifest/lockfile. Создай аннотированный `vX.Y.Z` именно на этом SHA:
|
||||
`git tag -a vX.Y.Z <release-sha> -m "Release X.Y.Z"`, затем отправь только
|
||||
`git push origin refs/tags/vX.Y.Z`. Не запускай release-it повторно на `main`.
|
||||
5. Верни release-коммит через PR `main → develop` без squash/rebase; проверь,
|
||||
что `develop` содержит тег. Опубликованные теги не перемещай. При сбое после
|
||||
merge создай отсутствующий тег без нового bump; до merge исправь и проверь
|
||||
существующий кандидат без повторного повышения версии.
|
||||
|
||||
`feat:` повышает minor, `fix:`/`perf:` и прочие разрешённые типы — patch;
|
||||
`!`/`BREAKING CHANGE:` — major, включая версии до 1.0. `chore(release):`
|
||||
исключён из рекомендации и changelog. Конфигурация — `.release-it.json`.
|
||||
В Gitea для `main`/`develop` должны быть запрещены прямой/force push и обход
|
||||
администратором, обязательны `verify`, `security`, `commits`; теги `v*` защищены.
|
||||
Workflow сам не включает эти настройки. Отдельное review approval для одного
|
||||
владельца не требуется. Npm publishing, deploy и Gitea Releases не включены.
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
**Status:** Accepted, baseline decision
|
||||
|
||||
Deployment boundary расширена [ADR 015](015-single-server-sqlite-deployment.md)
|
||||
для одного сервера и одного backend process. Ниже сохранён исходный контекст.
|
||||
|
||||
## Context
|
||||
|
||||
MVP нужен воспроизводимый локальный доступ к данным без инфраструктуры развёртывания. Доменные таблицы и миграции на baseline отсутствуют.
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# 015. SQLite в ограниченном single-server deployment
|
||||
|
||||
**Status:** Accepted
|
||||
|
||||
## Context
|
||||
|
||||
[ADR 001](001-prisma-sqlite-local-mvp.md) ограничивал SQLite локальным MVP.
|
||||
Контейнерная поставка требует постоянного хранилища на одном сервере,
|
||||
но не требует высокой доступности или нескольких экземпляров API.
|
||||
Пользователи, сессии, зашифрованные подключения и банковская история уже
|
||||
хранятся в SQLite. Кэши, limiter и generation подключений локальны процессу.
|
||||
|
||||
## Decision
|
||||
|
||||
Разрешён один backend process на одном Docker host с SQLite в постоянном
|
||||
host directory. Development использует отдельный named volume. SQLite
|
||||
остаётся embedded library без сетевого порта. Это расширяет deployment
|
||||
boundary ADR 001; его исторический контекст сохраняется.
|
||||
|
||||
Prisma migrations выполняются отдельным одноразовым service из того же image,
|
||||
что и backend. Перед изменением схемы backend останавливается, всё data
|
||||
directory копируется вместе с возможными WAL/SHM files. Ключ шифрования
|
||||
сохраняется отдельно. Неуспешная migration блокирует запуск новой версии.
|
||||
|
||||
Откат image требует совместимости с текущей схемой и не восстанавливает
|
||||
старую базу. Restore требует отдельного решения о потере данных после backup.
|
||||
Frontend и backend имеют независимые версии; production фиксирует version tags.
|
||||
Краткий простой допустим.
|
||||
|
||||
## Alternatives
|
||||
|
||||
PostgreSQL потребовал бы миграции данных и отдельного database lifecycle;
|
||||
текущие требования этого не оправдывают. Kubernetes, replicas и blue-green
|
||||
не устраняют ограничения process-local состояния и не входят в решение.
|
||||
|
||||
## Consequences
|
||||
|
||||
Сервер не обещает HA или zero-downtime. Один backend instance — обязательное
|
||||
ограничение, а не рекомендуемая настройка масштабирования. Нужны контроль
|
||||
свободного места, согласованные backups и проверяемое восстановление.
|
||||
|
||||
Решение пересматривается при необходимости нескольких replicas, другом
|
||||
availability target, заметном write contention или потребности в отдельном
|
||||
database service. Добавление реплик до такого решения недопустимо.
|
||||
|
||||
Топология описана в [архитектуре](../architecture/containers.md#контейнерная-поставка),
|
||||
операции — в [runbook](../architecture/deployment.md).
|
||||
@@ -0,0 +1,72 @@
|
||||
# 016. Версионируемый комплект развёртывания
|
||||
|
||||
**Status:** Accepted
|
||||
|
||||
Frontend и backend выпускаются независимо. Выбор их версий непосредственно
|
||||
на сервере перекладывает подбор совместимой пары на оператора при каждом
|
||||
обновлении. Принято решение выпускать комплект в репозитории `deploy`:
|
||||
его релиз закрепляет проверенную пару конкретных версий обоих образов.
|
||||
Независимые релизы компонентов сохраняются; выпуск компонента сам по себе
|
||||
не означает готовность нового релиза развёртывания.
|
||||
|
||||
Оператор подключается к серверу по SSH и одной командой запускает обновление
|
||||
до выбранного релиза развёртывания. При обновлении backend команда должна
|
||||
скачать образы до его остановки, сохранить резервную копию остановленной базы,
|
||||
выполнить миграции и проверить запуск. Ограничения
|
||||
[ADR 015](015-single-server-sqlite-deployment.md)
|
||||
сохраняются: один backend, краткий простой, неуспешная миграция блокирует запуск;
|
||||
откат образа не восстанавливает базу автоматически.
|
||||
|
||||
## Доставка и подготовка сервера
|
||||
|
||||
На сервере хранится Git checkout `deploy`. Команда обновления получает
|
||||
выбранный тег комплекта и загружает закреплённые образы из Nexus;
|
||||
сборка приложения на сервере не требуется.
|
||||
|
||||
Docker с Compose, внешний HTTPS и доступ к registry считаются подготовленными.
|
||||
README комплекта должен описывать последовательность этой разовой подготовки
|
||||
и подключения существующей установки. Автоматическое provisioning сервера
|
||||
в комплект не входит.
|
||||
|
||||
Рабочие данные, сертификат доверия T-Bank и секреты остаются вне обновляемого
|
||||
checkout. При подключении существующей установки сохраняются её БД,
|
||||
JWT secret и ключ шифрования. Сертификат `certs/tbank-ca.pem` монтируется
|
||||
в контейнер только для чтения; путь внутри контейнера задаётся через
|
||||
`TBANK_CA_CERT_PATH`. Этот сертификат не обслуживает входящий HTTPS.
|
||||
|
||||
## Резервные копии
|
||||
|
||||
Перед обновлением backend создаётся согласованная копия всего каталога данных
|
||||
при остановленном backend. Копии хранятся в локальном `backups/` вне checkout,
|
||||
без автоматического удаления; очистку выполняет оператор вручную.
|
||||
Такие копии позволяют восстановиться после неудачного обновления, но сами
|
||||
по себе не защищают от потери диска сервера.
|
||||
|
||||
## Обновление и сбои
|
||||
|
||||
Если релиз меняет только frontend, обновляется только его контейнер:
|
||||
backend не останавливается, миграции и резервное копирование БД для этого
|
||||
обновления не выполняются. Этот путь применим только при неизменных образе
|
||||
и конфигурации backend.
|
||||
|
||||
Если миграции прошли, но новый backend не прошёл проверку запуска, обновление
|
||||
завершается с ошибкой, неисправный backend останавливается. Оператор получает
|
||||
диагностику, сведения о предыдущей версии и путь к резервной копии.
|
||||
Автоматического отката нет: прежний образ может быть несовместим с новой
|
||||
схемой. Откат образа и восстановление базы остаются отдельными явными действиями
|
||||
по правилам ADR 015.
|
||||
|
||||
## Выпуск комплекта
|
||||
|
||||
После публикации образов компонентов отдельно готовится релиз `deploy`:
|
||||
фиксируется совместимая пара версий, выполняется её проверка и выпускается
|
||||
тег комплекта. Публикация компонента не создаёт релиз комплекта автоматически
|
||||
и не запускает обновление сервера. Оператор выбирает выпущенный тег при
|
||||
запуске команды развёртывания.
|
||||
|
||||
## Состояние реализации
|
||||
|
||||
Комплект и команда реализованы в отдельном репозитории `deploy`.
|
||||
Публикация первого тега и обновление рабочего сервера — отдельные действия.
|
||||
[Контейнерная поставка](../architecture/deployment.md) описывает границы,
|
||||
пошаговая инструкция находится в README комплекта.
|
||||
@@ -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-контракты пока не поддерживают все необходимые состояния
|
||||
загрузки и полноты; их изменение входит в будущую реализацию.
|
||||
@@ -19,3 +19,6 @@
|
||||
- [009 — Concurrent refresh rotation policy](009-concurrent-refresh-rotation-policy.md)
|
||||
- [013 — НКР как первый источник кредитных рейтингов облигаций](013-nkr-bond-credit-ratings.md)
|
||||
- [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)
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
| Как обрабатываются ошибки и ожидание? | [HTTP API и ошибки](http-api.md), [состояния интерфейса](cross-cutting/ui-resilience.md) |
|
||||
| Как связать ошибку с обработкой запроса? | [Наблюдаемость](http-api.md#наблюдаемость) |
|
||||
| Как устроено подтверждённое локальное окружение? | [Container](containers.md#представление-container), [качество и ограничения](containers.md#качество-и-ограничения) |
|
||||
| Как запустить, обновить или восстановить приложение? | [Развёртывание](deployment.md) |
|
||||
| Что означают предметные термины? | [Глоссарий](../glossary.md) |
|
||||
|
||||
Эта карта покрывает вопросы архитектуры там, где о них есть подтверждённое
|
||||
|
||||
+80
-30
@@ -27,18 +27,57 @@ flowchart LR
|
||||
F -->|"HTTPS; изображения без авторизации"| C
|
||||
```
|
||||
|
||||
SPA выполняется в браузере пользователя. Backend — отдельное локальное
|
||||
SPA выполняется в браузере пользователя. Backend — отдельное
|
||||
приложение; SQLite используется внутри его процесса через библиотеку и файл
|
||||
базы. Между API и SQLite нет сетевого database server. Граница системы на
|
||||
схеме показывает принадлежность частей, а не их размещение на одном узле.
|
||||
|
||||
## Контейнерная поставка
|
||||
|
||||
Deployment view для одного сервера. Caddy принадлежит серверу; Compose
|
||||
содержит два постоянных application containers и одноразовую migration.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Browser["Браузер · React SPA"] -->|HTTPS| Caddy["Серверный Caddy · TLS"]
|
||||
subgraph Host["Один Docker host"]
|
||||
Caddy -->|"HTTP · 127.0.0.1:8080"| Frontend["frontend · nginx · SPA + /api/"]
|
||||
Frontend -->|"HTTP /api/v1 · internal network"| Backend["backend · NestJS · один process"]
|
||||
Migration["migration · Prisma · one-shot"] -->|"До старта backend"| Data[("Host directory · SQLite + WAL")]
|
||||
Backend -->|"Чтение и запись /data"| Data
|
||||
Secrets["Restricted environment · ключи вне images"] --> Backend
|
||||
end
|
||||
```
|
||||
|
||||
Только frontend публикует host port, привязанный к loopback. Backend не
|
||||
публикует порт, но имеет исходящий доступ к внешним API. nginx сохраняет
|
||||
`/api/` prefix, forwarding headers и request ID; Docker DNS разрешается
|
||||
повторно после backend recreation. HTML требует revalidation, hashed assets
|
||||
имеют immutable cache. Caddy не содержит отдельного маршрута к API.
|
||||
|
||||
Development заменяет nginx на Vite с HMR, backend — на Nest watch, а host data
|
||||
path — на отдельный named volume. Browser origin остаётся единым. Linux
|
||||
node_modules не монтируются с хоста. Production SPA собирается с relative API
|
||||
base; production cookies остаются Secure, публичный origin использует HTTPS.
|
||||
|
||||
Компоненты имеют независимые images и версии. Production фиксирует image
|
||||
version tags; migration использует тот же backend image. Успех migration предшествует
|
||||
backend healthcheck на существующем auth route; frontend ждёт healthy backend.
|
||||
Runtime работает non-root, SQLite сохраняется вне image, ключ шифрования и JWT
|
||||
secret поступают из restricted environment и сохраняются между обновлениями.
|
||||
|
||||
Ограничение SQLite расширено в [ADR 015](../adr/015-single-server-sqlite-deployment.md).
|
||||
[Runbook](deployment.md) описывает установку, independent
|
||||
updates, backup, rollback и explicit restore. Он также перечисляет внешние
|
||||
prerequisites: локальный smoke не подтверждает registry/server rollout.
|
||||
|
||||
## Ответственность частей
|
||||
|
||||
| Часть | Ответственность |
|
||||
| --- | --- |
|
||||
| React SPA | Экраны, навигация, формы, состояние входа и клиентский кэш ответов; форматирование денежных значений |
|
||||
| Backend API | Локальная идентичность, сессии, проверка доступа, подключение брокера, нормализация внешних данных, точные расчёты и серверные кэши |
|
||||
| SQLite | Долговременное хранение локальных пользователей, сессий, зашифрованных подключений и ручной банковской истории |
|
||||
| Backend API | Локальная идентичность, сессии, проверка доступа, подключение брокера, нормализация внешних данных, точные расчёты и синхронизация сохранённых источников |
|
||||
| SQLite | Долговременное хранение локальных пользователей, сессий, зашифрованных подключений, ручной банковской истории и инвестиционных источников с coverage/work |
|
||||
| Внешние системы | Данные брокера, публичные сведения об инструментах и изображения в пределах своей ответственности |
|
||||
|
||||
Frontend организован слоями `app → pages → features → entities → shared`:
|
||||
@@ -47,6 +86,20 @@ Frontend организован слоями `app → pages → features → ent
|
||||
где локальная аутентификация отделена от подключения брокера и инвестиционных
|
||||
сценариев. Это внутренняя организация приложений, не набор микросервисов.
|
||||
|
||||
Маршруты и AppShell принадлежат `app`. Page controller/VM управляет запросами,
|
||||
формой и восстановлением; presentation получает данные и callbacks, поля
|
||||
могут использовать контекст React Hook Form. В OperationForm controller
|
||||
сохраняет intent операции, черновик и явное принятие актуальной версии после
|
||||
конфликта. В account-income чистые преобразования периода принадлежат модели
|
||||
страницы и не связывают форму с её запросами.
|
||||
|
||||
Локальные packages tokens/ui/icons образуют [визуальную основу](../design/overview.md#общая-основа-и-каталог)
|
||||
внутри frontend и входят в сборку SPA. Это библиотеки, а не дополнительные
|
||||
runtime-контейнеры. Провайдеры темы принадлежат приложению и Storybook;
|
||||
предметные компоненты и API-клиент остаются в приложении. Отдельный API-пакет
|
||||
и registry-поставка визуальных пакетов не реализованы. Существующие Docker
|
||||
images приложения не означают независимого npm-выпуска этих библиотек.
|
||||
|
||||
Финансовые HTTP-запросы браузера идут через backend. Пользователь вводит token
|
||||
T-Bank в форме и отправляет его для сохранения, но API не возвращает token
|
||||
в ответах; SPA не использует его для прямых брокерских запросов. Прямая
|
||||
@@ -81,35 +134,31 @@ 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. Произвольного архива
|
||||
отключённых счетов нет.
|
||||
|
||||
## Сохранение и восстановление данных
|
||||
|
||||
Резервная копия SQLite должна быть согласованной: для работающей базы
|
||||
используется SQLite backup API с учётом WAL. Несогласованное копирование
|
||||
файлов базы и WAL не сохраняет целостный снимок. Восстановление проверяется
|
||||
на отдельной копии. Ключ шифрования подключения хранится отдельно от базы;
|
||||
Поддерживаемая процедура останавливает backend и копирует весь data directory
|
||||
с возможными WAL/SHM files до migration. Копирование отдельных файлов
|
||||
работающей базы не сохраняет целостный снимок; для live backup потребовался бы
|
||||
SQLite backup API. Восстановление проверяется на отдельной копии. Ключ шифрования подключения хранится отдельно от базы;
|
||||
его потеря делает сохранённый T-Bank token нечитаемым.
|
||||
|
||||
Откат приложения требует совместимости предыдущей версии с сохранённой
|
||||
@@ -150,14 +199,15 @@ access token и вращаемый refresh token. [Политика query-кэш
|
||||
отмечены в [аутентификации](../domains/authentication/overview.md#регистрация-и-вход)
|
||||
и [HTTP API](http-api.md#error-contract).
|
||||
|
||||
Подтверждённая топология — локальная SPA и локальный API с файлом SQLite.
|
||||
Поддерживается локальный Compose и ограниченная single-server topology с SQLite.
|
||||
Origin браузера, адрес API и cookie policy должны быть согласованы.
|
||||
`localhost` и `127.0.0.1` задают разные browser-границы для cookie и CORS.
|
||||
Локальный HTTP поддерживается в development-режиме backend; в остальных
|
||||
режимах refresh-cookie имеет Secure flag и требует защищённого соединения.
|
||||
Наличие локального запуска не задаёт production-топологию или гарантии SLA.
|
||||
Single-server deployment не задаёт гарантий HA или zero-downtime.
|
||||
|
||||
Кэши и лимитер T-Bank действуют на экземпляр backend. Ротация ключа шифрования
|
||||
Coordinator и лимитер T-Bank действуют на один экземпляр backend. Сохранённые
|
||||
данные переживают перезапуск; processRunId не является distributed lease. Ротация ключа шифрования
|
||||
подключений и согласование изменений подключения между распределёнными
|
||||
экземплярами не входят в реализованный механизм. Эти ограничения не
|
||||
позволяют переносить локальную схему на несколько экземпляров без отдельного
|
||||
|
||||
@@ -1,72 +1,66 @@
|
||||
# Свежесть данных и повторное использование
|
||||
|
||||
WealthLedger показывает финансовое наблюдение на момент получения данных.
|
||||
Временный кэш уменьшает повторные обращения к брокеру, но не превращает
|
||||
снимки в историю портфеля и не обещает рыночные данные в реальном времени.
|
||||
WealthLedger читает инвестиционные данные из SQLite. Обычный GET возвращает
|
||||
сохранённый результат и запрашивает фоновые зависимости, если они отсутствуют
|
||||
или устарели. HTTP-ответ не ждёт T-Bank. Модель принята в
|
||||
[ADR 017](../../adr/017-local-tbank-reads-and-background-sync.md).
|
||||
|
||||
## Клиентский и серверный снимки
|
||||
|
||||
[Client Cached Snapshot](../../glossary.md#client-cached-snapshot) — последний
|
||||
успешный ответ, сохранённый TanStack Query в SPA. При обычном фоновом обновлении
|
||||
того же набора данных уже показанный результат остаётся на экране; ошибка
|
||||
обновления сообщает о проблеме, не заменяя известные значения пустотой.
|
||||
Клиентский кэш не является внешней системой или отдельным runtime-приложением.
|
||||
[Client Cached Snapshot](../../glossary.md#client-cached-snapshot) — успешный
|
||||
ответ в TanStack Query. Он сохраняет показанное содержимое при временной ошибке
|
||||
запроса в той же границе доступа. SQLite сохраняет источники независимо от
|
||||
браузера: membership, последний полный portfolio, исполненные операции,
|
||||
общие сведения об инструментах, котировки, свечи и календари.
|
||||
|
||||
[Portfolio Snapshot](../../glossary.md#portfolio-snapshot) — полный результат
|
||||
загрузки портфеля на backend. В пределах разрешённого сценарием срока он может
|
||||
служить серверным fallback при временной ошибке upstream. Backend отмечает
|
||||
такой ответ `isStale`; frontend не выводит это поле из состояния собственного
|
||||
query-кэша. Серверное предупреждение и ошибка клиентского refetch могут
|
||||
возникать независимо.
|
||||
[Полнота периода](../../glossary.md#полнота-истории-счёта) и свежесть различаются.
|
||||
Успешно проверенный пустой интервал полон; последняя страница журнала сама
|
||||
по себе полноту не доказывает. Ошибка сверки сохраняет прежние данные и время.
|
||||
Старый полный портфель не удаляется по истечении TTL. `isStale` показывает
|
||||
возраст portfolio source, а не сам факт неудачного обновления.
|
||||
|
||||
Свежесть и допустимость fallback определяются сценарием. Портфельный fallback
|
||||
не распространяется автоматически на журнал, прогноз событий или страницу
|
||||
инструмента. Точные правила находятся в
|
||||
[справочнике портфеля](../../domains/portfolio/data-reference.md),
|
||||
[журнале операций](../../domains/operations/overview.md) и
|
||||
[будущих событиях](../../domains/income/upcoming-events.md).
|
||||
| Источник | Когда обычный запрос требует обновления |
|
||||
| --- | --- |
|
||||
| Portfolio, котировки, изменяемый хвост дневных свечей | Через 60 секунд |
|
||||
| Membership и изменяемый хвост операций | Через 5 минут |
|
||||
| Metadata, поиск каталога, company и календари | Через сутки |
|
||||
| Полные прошлые интервалы операций и завершённые свечи | При явном обновлении показанного периода |
|
||||
|
||||
По [ADR 007](../../adr/007-react-query-fetch-policy.md) запрос связан с открытием
|
||||
экрана, а возврат фокуса окна не запускает refetch. Повторное открытие после
|
||||
`staleTime` может обновить ответ в фоне. Эта политика не является таймером
|
||||
периодического обновления. Перезапуск backend уничтожает его in-memory
|
||||
снимки и continuations журнала; SQLite не восстанавливает их.
|
||||
TTL не создаёт таймер импорта. Запрошенная работа может закончиться после
|
||||
ухода со страницы. После перезапуска нет автоматического обхода intent:
|
||||
следующий подходящий запрос повторяет незавершённый интервал с первой страницы,
|
||||
переиспользуя полные интервалы. Cursor брокера не служит durable checkpoint.
|
||||
|
||||
Ручное «Обновить» перечитывает зависимости текущего экрана и применённый
|
||||
период независимо от TTL. Повторная сверка полного периода публикуется одной
|
||||
транзакцией после всех страниц, интервалов и счетов, включая `all`.
|
||||
Сбой сохраняет весь прежний результат. Первая загрузка может публиковать
|
||||
полные малые интервалы и показывать частичный период без окончательных сумм.
|
||||
|
||||
Клиент наблюдает только известную активную работу открытого экрана.
|
||||
Status GET и data GET с `sync=observe` не запускают работу. После изменения
|
||||
состояния ответ попадает в ту же query identity; после ухода или завершения
|
||||
наблюдение прекращается. Поздний ответ не заменяет уже принятый новый.
|
||||
[HTTP API](../http-api.md) объясняет envelope и экранный refresh.
|
||||
|
||||
## Смена подключения
|
||||
|
||||
Кэш финансовых данных относится к пользователю, счёту и текущему
|
||||
подключению T-Bank. Backend сначала читает подключение и его generation, затем
|
||||
проверяет возможность повторного использования ответа. Свежий портфель
|
||||
одного счёта не требует нового запроса списка счетов у брокера; при обновлении
|
||||
портфеля доступ проверяется заново. Журнал операций проверяет доступ до чтения
|
||||
кэшированной страницы. Идентификатор счёта и совпадение параметров запроса
|
||||
сами по себе недостаточны для выдачи кэша.
|
||||
Каждое чтение проверяет локальную сессию, текущее подключение, известный отказ
|
||||
токена и сохранённый состав доступных счетов. Расшифровка token и GetAccounts
|
||||
для локальной выдачи не нужны. Старый membership допускает выдачу и фоновую
|
||||
сверку; это задержка обнаружения отзыва доступа, а не live-проверка брокера.
|
||||
Unknown membership не даёт доступа к произвольному accountId: ответ сообщает
|
||||
о загрузке доступа. Успешный новый список немедленно ограничивает последующие
|
||||
чтения. Известный invalid token блокирует личные данные.
|
||||
|
||||
При каждом успешном сохранении подключения token шифруется с новым
|
||||
AES-GCM nonce. Из сохранённого шифротекста backend вычисляет непрозрачную
|
||||
внутреннюю generation. Она не хранится отдельным полем базы и не входит
|
||||
в API, журналы или ошибки. Portfolio Snapshot и Ledger Page Cache допускают
|
||||
повторное использование только в пределах соответствующей generation.
|
||||
Старый кэш или поздно завершившийся запрос прежней generation не обслуживает
|
||||
новый запрос после замены подключения, включая сохранение того же plaintext
|
||||
token. Удаление подключения не оставляет доступного контекста для чтения кэша.
|
||||
Успешный PUT атомарно сохраняет token, новую случайную внутреннюю revision
|
||||
и membership из проверки token. Прежние личные datasets и work становятся
|
||||
недоступны. DELETE удаляет подключение и личные данные; общие неперсональные
|
||||
источники могут оставаться. Неуспешный PUT сохраняет действующую границу.
|
||||
|
||||
После успешного изменения подключения frontend сначала записывает
|
||||
подтверждённый status, затем сбрасывает generated GET queries в поддеревьях
|
||||
`/api/v1/brokerage-accounts`, `/api/v1/stocks` и `/api/v1/bonds`. Активные
|
||||
выборки запрашиваются заново, неактивные — при следующем использовании.
|
||||
Client Cached Snapshot прежнего подключения больше не служит результатом
|
||||
новой границы.
|
||||
|
||||
Неуспешная операция подключения не меняет статус, generation и доступные
|
||||
финансовые данные. Обычная ошибка refetch также сохраняет клиентский снимок
|
||||
текущей границы. Logout — отдельное событие: оно очищает состояние
|
||||
пользовательской сессии, status подключения и query cache.
|
||||
|
||||
Пользовательский сценарий описан в [интеграции T-Bank](../../integrations/tbank.md).
|
||||
Согласованные правила frontend находятся в workspace-спецификациях
|
||||
[query coherence](../../../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).
|
||||
Публикация проверяет revision и attempt в той же SQLite-транзакции; поздняя
|
||||
работа старого подключения не меняет даже timestamp. Revision не попадает
|
||||
в API, логи или cursor. Frontend очищает personal queries при смене подключения
|
||||
и известном отказе доступа; поздний ответ не восстанавливает скрытые данные.
|
||||
Logout очищает сессию и query cache. Подробнее —
|
||||
[интеграция T-Bank](../../integrations/tbank.md).
|
||||
|
||||
@@ -22,6 +22,18 @@
|
||||
портфеля остаётся доступным после ошибки refetch; сообщение и повтор
|
||||
относятся к обновлению, а не к отсутствию инвестиций.
|
||||
|
||||
Серверная полнота не выводится из query lifecycle. Missing показывает
|
||||
skeleton только отсутствующих секций, partial — подтверждённые строки и
|
||||
сообщение о неполноте. Totals ждут обязательных зависимостей, comparisons
|
||||
готовятся независимо. Только complete empty означает отсутствие операций.
|
||||
Время источника не заменяется временем получения HTTP-ответа.
|
||||
|
||||
Ручное обновление сохраняет показанный результат и applied filters; кнопка
|
||||
недоступна во время уже запрошенной работы. Ошибка transport отличается от
|
||||
ошибки source work. Status observation прекращается после terminal outcome
|
||||
или ухода; само истечение TTL не запускает импорт. Известный access loss
|
||||
очищает personal view, включая защиту от позднего ответа.
|
||||
|
||||
## Известная ошибка сценария
|
||||
|
||||
Ошибки счетов, портфеля и подключения T-Bank остаются локальными: рядом
|
||||
@@ -44,6 +56,6 @@ actions. Устаревший серверный Portfolio Snapshot показы
|
||||
[аутентификации](../../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).
|
||||
[UI resilience](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/frontend-ui-resilience/spec.md),
|
||||
[loading presentation](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/frontend-loading-presentation/spec.md)
|
||||
и [data resilience](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/frontend-data-resilience/spec.md).
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
# Контейнерная поставка
|
||||
|
||||
Поставка для одного Docker host, одного backend и краткого простоя.
|
||||
[Топология](containers.md#контейнерная-поставка) и
|
||||
[ADR 015](../adr/015-single-server-sqlite-deployment.md) объясняют границы.
|
||||
Production-комплект находится в отдельном [репозитории deploy](https://git.personal.ksv741.ru/WealthLedger/deploy).
|
||||
Его README — единственная пошаговая инструкция установки, обновления и recovery;
|
||||
[ADR 016](../adr/016-versioned-deployment-bundle.md) объясняет выбор релиза комплекта.
|
||||
Development-команды ниже выполняются из workspace с checkout `frontend/` и `backend/`.
|
||||
|
||||
## Предпосылки
|
||||
|
||||
- Docker Engine с Compose plugin **не ниже 2.24.4** (`!override` в development),
|
||||
доступ к registry и достаточно свободного диска для non-root UID 1000.
|
||||
Production images `0.2.3` опубликованы для Linux amd64.
|
||||
Другую архитектуру проверяют отдельно; multi-platform images не обещаны.
|
||||
- Nexus Docker hosted repository `docker-hosted`, доступный через
|
||||
`nexus.personal.ksv741.ru/docker-hosted/`. Docker Bearer Token Realm включён;
|
||||
reverse proxy передаёт исходные host и HTTPS scheme. Адрес token realm
|
||||
в `WWW-Authenticate` также должен начинаться с `https://`.
|
||||
Deployment policy запрещает redeploy существующих release tags.
|
||||
Runner label — `ubuntu-latest`, runner должен иметь Docker CLI, buildx и daemon.
|
||||
Наличие label само по себе не доказывает Docker capability.
|
||||
- В Actions каждого component repo нужны `NEXUS_USERNAME` и
|
||||
`NEXUS_PASSWORD` пользователя `wealthledger-ci`. Эта же учётная запись
|
||||
используется сервером для загрузки образов. Для публикации нужны
|
||||
browse/read/add/edit на `docker-hosted`; фактически выданный набор прав
|
||||
настраивается в Nexus. Built-in
|
||||
workflow token не заменяет эту настройку.
|
||||
- На сервере Caddy уже завершает TLS и передаёт весь домен на
|
||||
`127.0.0.1:8080` (или выбранный frontend port). Отдельного `/api` upstream нет.
|
||||
DNS, TLS, firewall и provisioning Caddy выполняются вне этого runbook.
|
||||
|
||||
Серверный login и доступ к images проверяются на целевом сервере.
|
||||
Локальный smoke не доказывает серверные registry permissions.
|
||||
|
||||
## Development
|
||||
|
||||
Из workspace с исходниками обоих компонентов:
|
||||
|
||||
```bash
|
||||
docker version
|
||||
docker compose version
|
||||
docker compose --env-file .env.development.example \
|
||||
-f compose.yaml -f compose.development.yaml config --quiet
|
||||
docker compose --env-file .env.development.example \
|
||||
-f compose.yaml -f compose.development.yaml up -d --build --wait --wait-timeout 180
|
||||
```
|
||||
|
||||
Откройте `http://localhost:5173`. Vite отдаёт HMR и проксирует `/api/` без
|
||||
rewrite; Nest работает в watch mode. Монтируются только source/public,
|
||||
`node_modules` и generated files остаются в container filesystem. `.env`
|
||||
с хоста не копируется. После изменения manifest, lockfile, tsconfig или Vite
|
||||
config пересоберите соответствующий image. `up --build` обновляет Linux
|
||||
dependencies без stale named volume для `node_modules`.
|
||||
|
||||
Для другого порта скопируйте `.env.development.example` в игнорируемый
|
||||
`.env.development` и согласованно поменяйте `FRONTEND_PORT`/`FRONTEND_ORIGIN`.
|
||||
Пример содержит только вымышленные local secrets; server secrets сюда не входят.
|
||||
Поля image/data directory нужны для интерполяции base Compose, но полностью
|
||||
заменяются development override. В итоговом config нет production data path.
|
||||
|
||||
Project `wealthledger-dev` хранит SQLite в `development-data`. Обычные
|
||||
`restart` и `down` сохраняют его. Не применяйте `down -v` к данным, которые
|
||||
хотите сохранить. Не используйте production project name для development.
|
||||
|
||||
## Публикация компонентных images
|
||||
|
||||
Штатный release process каждого repo создаёт защищённый `vX.Y.Z` после
|
||||
проверенного release commit. `image.yml` проверяет tag против package/lock
|
||||
version, затем выполняет `npm ci`, `npm run verify`; backend дополнительно
|
||||
выполняет `npm run test:e2e`. У каждого image OCI version/revision labels.
|
||||
|
||||
Images публикуются как `nexus.personal.ksv741.ru/docker-hosted/wealthledger/frontend:X.Y.Z`
|
||||
и `.../backend:X.Y.Z`. `latest` не создаётся.
|
||||
Workflow отказывается перезаписывать существующие release tags;
|
||||
защита release tags и запрет параллельных publishers обязательны. Если одна
|
||||
публикация уже состоялась, не удаляйте tag для повторного запуска: проверьте
|
||||
опубликованный image и завершите проверку существующего artifact отдельно.
|
||||
|
||||
Выберите опубликованные version tags, выполните pull и сравните OCI revision
|
||||
с release commit. Выполните smoke с disposable data/config до первого server
|
||||
rollout. В `release.env` комплекта хранится пара полных image references, например
|
||||
`nexus.personal.ksv741.ru/docker-hosted/wealthledger/frontend:0.2.3`. Сохраните versions/revisions
|
||||
в deployment record. Release tags не должны переназначаться другим images.
|
||||
|
||||
## Перенос существующих images из Gitea
|
||||
|
||||
До переключения CI и server references проверьте HTTPS token realm и настройте
|
||||
пользователя `wealthledger-ci`. В Actions добавьте `NEXUS_USERNAME`/`NEXUS_PASSWORD`; прежние
|
||||
`REGISTRY_USERNAME`/`REGISTRY_TOKEN` сохраните для отката старого workflow.
|
||||
Пароли вводятся интерактивно или через `--password-stdin`, не через аргументы.
|
||||
|
||||
Скопируйте каждый опубликованный release без пересборки. Например, для `0.2.0`
|
||||
с установленным Skopeo и доступом чтения Gitea/записи Nexus:
|
||||
|
||||
```bash
|
||||
skopeo login git.personal.ksv741.ru
|
||||
skopeo login nexus.personal.ksv741.ru
|
||||
for component in frontend backend; do
|
||||
skopeo copy --all --preserve-digests \
|
||||
"docker://git.personal.ksv741.ru/wealthledger/$component:0.2.0" \
|
||||
"docker://nexus.personal.ksv741.ru/docker-hosted/wealthledger/$component:0.2.0" || exit 1
|
||||
done
|
||||
```
|
||||
|
||||
`--all` переносит весь image index, включая платформы и вложенные attestation
|
||||
manifests; `--preserve-digests` прерывает перенос при изменении digest.
|
||||
До копирования проверьте наличие destination tag: если он уже существует,
|
||||
сравните исходный и целевой index digest и вложенные manifests. Совпадающий
|
||||
artifact повторно не публикуйте; несовпадение требует расследования, не удаления
|
||||
или перезаписи tag. Частичный перенос не считается завершённым.
|
||||
|
||||
После копирования сравните digests через `skopeo inspect --raw` и SHA-256,
|
||||
выполните `docker pull` обоих images пользователем `wealthledger-ci` на сервере приложения,
|
||||
проверьте OCI revision и container smoke на disposable data.
|
||||
Только после этого меняйте production image references и запускайте обычную
|
||||
последовательность обновления ниже. Старые Gitea packages и references сохраните:
|
||||
откат registry возвращает прежние адреса тех же artifacts, не восстанавливает
|
||||
SQLite и не меняет encryption keys. Не переносите Git release tags и не
|
||||
запускайте заново старый tag workflow для копирования существующего image.
|
||||
|
||||
## Первая установка
|
||||
|
||||
На подготовленном сервере оператор клонирует `deploy`, настраивает внешний
|
||||
HTTPS и registry login, размещает server env, сертификат T-Bank и data directory
|
||||
вне checkout. Для новой пустой базы команда вызывается с `--init`.
|
||||
Для действующей установки сохраняются прежние данные и ключи, а первый переход
|
||||
выполняется как полное обновление с backup. Отсутствующая БД без `--init` — ошибка.
|
||||
Точные команды и разрешения каталогов приведены в README комплекта.
|
||||
|
||||
Git-тег `deploy` закрепляет пару images через `release.env`. Версия комплекта
|
||||
не обязана совпадать с версиями компонентов. Публикация image не выпускает
|
||||
комплект и не обновляет сервер автоматически. Команда получает выбранный тег,
|
||||
загружает images до простоя и использует весь snapshot этого commit.
|
||||
|
||||
## Backup при остановленном backend
|
||||
|
||||
Сначала останавливаются писатели, затем копируется весь каталог данных,
|
||||
включая возможные WAL/SHM. Только успешная копия допускает миграции. Локальные
|
||||
backups находятся рядом с data directory, вне checkout, и автоматически
|
||||
не удаляются. Незавершённая копия помечается `.partial-` и не подходит для restore.
|
||||
Ключ шифрования хранится отдельно. Локальная копия не защищает от потери диска.
|
||||
|
||||
## Независимое обновление frontend
|
||||
|
||||
Если изменились только image или конфигурация frontend, backend не
|
||||
пересоздаётся, backup и migration не выполняются. Возможен короткий перерыв
|
||||
на внешней точке входа; старым browser tabs может потребоваться reload.
|
||||
При неизменных исправных контейнерах повтор команды не пересоздаёт сервисы.
|
||||
|
||||
## Независимое обновление backend
|
||||
|
||||
Изменение image или конфигурации backend запускает stop → backup → migration →
|
||||
readiness. Неизменный frontend не пересоздаётся. Во время простоя возможен 502.
|
||||
Ошибка migration блокирует старт, ошибка готовности останавливает новый backend;
|
||||
ошибка frontend не останавливает исправный backend. Успех фиксируется только
|
||||
после проверок запуска затронутых компонентов.
|
||||
|
||||
Одна file lock защищает установку от параллельных команд. Маркер незавершённого
|
||||
обновления блокирует повтор до разбора оператором. Он содержит этап, версии
|
||||
и путь к backup, но не секреты и не копию данных. После обновления нужен smoke
|
||||
через HTTPS: healthcheck сам по себе не доказывает работу Secure refresh-cookie.
|
||||
|
||||
## Откат image
|
||||
|
||||
Откат версии и восстановление базы — разные операции. Прежний backend можно
|
||||
запускать только после проверки совместимости с текущей схемой. Автоматического
|
||||
rollback нет; migration service при ручном image rollback не запускается.
|
||||
Порядок ручных Compose-команд и проверки recovery находится в README комплекта.
|
||||
|
||||
## Explicit restore
|
||||
|
||||
Оператор выбирает проверенную копию и явно принимает потерю всех более новых
|
||||
записей. Сначала восстановление проверяется на isolated данных с прежним ключом
|
||||
и совместимым image. На целевой установке останавливаются писатели, текущий
|
||||
каталог сохраняется под новым именем, затем восстанавливается выбранная копия.
|
||||
Backend пересоздаётся, чтобы заново подключить заменённый bind-каталог:
|
||||
простой stop/start может сохранить mount прежних данных.
|
||||
Проверяются целостность БД, auth/cards и расшифровка подключения. Только после
|
||||
подтверждённого recovery можно снять маркер незавершённой операции.
|
||||
@@ -8,9 +8,42 @@ Backend генерирует OpenAPI из собственного публич
|
||||
схемы находятся вне frontend. Это разделение принято в
|
||||
[ADR 002](../adr/002-nest-generated-openapi-contract.md) и
|
||||
[ADR 014](../adr/014-trusted-openapi-input.md).
|
||||
|
||||
Backend командой `npm run openapi:generate` создаёт `openapi.json` без запуска
|
||||
сервера и не хранит результат в Git. Contract test сравнивает вывод генератора
|
||||
с Nest runtime schema настроенного приложения. Frontend хранит полученный
|
||||
artifact у себя и проверяет соответствие generated types этой копии. CI не
|
||||
скачивает соседние репозитории и не сравнивает их текущие revisions. Обновление
|
||||
входного artifact потребителя выполняется отдельно с review; совместимость
|
||||
развёрнутых версий не доказывается одним зелёным локальным CI.
|
||||
|
||||
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-границе.
|
||||
@@ -36,7 +69,7 @@ message/stack. Типичный ответ:
|
||||
HTTP-ответа после всех стадий обработки запроса.
|
||||
|
||||
**Известное расхождение:** согласованная
|
||||
[спецификация аутентификации](../../specs/openspec/specs/user-authentication/spec.md#requirement-стандартные-ответы-об-ошибках)
|
||||
[спецификация аутентификации](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/user-authentication/spec.md#requirement-стандартные-ответы-об-ошибках)
|
||||
требует `pointer` и `detail` для невалидных полей. В текущей конфигурации
|
||||
валидация формирует строковые сообщения, а фильтр ожидает структурированные
|
||||
ошибки полей; поэтому корректность деталей `errors` не подтверждена.
|
||||
@@ -74,5 +107,5 @@ client request ID. Это позволяет сопоставить публич
|
||||
Выбор logger и граница ответственности описаны в
|
||||
[ADR 008](../adr/008-pino-backend-logging.md); согласованное поведение — в
|
||||
workspace-спецификации
|
||||
[Structured logging](../../specs/openspec/specs/backend-structured-logging/spec.md).
|
||||
[Structured logging](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/backend-structured-logging/spec.md).
|
||||
Сборщик, срок хранения журналов и alerting не определяются приложением.
|
||||
|
||||
+31
-3
@@ -4,6 +4,34 @@
|
||||
типографику, интервалы, радиусы, тени и адаптивные размеры.
|
||||
Поддерживается светлая схема; тёмная тема не заявлена.
|
||||
|
||||
## Общая основа и каталог
|
||||
|
||||
Внутри frontend используются три private npm workspace-пакета:
|
||||
|
||||
| Пакет | Ответственность |
|
||||
| --- | --- |
|
||||
| `@wealthledger/tokens` | JSON со значениями палитры, типографики, интервалов и других визуальных параметров |
|
||||
| `@wealthledger/ui` | Mantine-тема, общие стили, StateView, LoadingSkeleton и EntryCard |
|
||||
| `@wealthledger/icons` | React-иконки добавления, редактирования и удаления |
|
||||
|
||||
EntryCard отвечает за расположение переданных подписей, значения и действий.
|
||||
Предметный компонент определяет смысл операции, форматирует данные и выбирает
|
||||
цветовую роль. Денежные расчёты и запросы не входят в общий UI-пакет.
|
||||
|
||||
Иконки наследуют цвет через `currentColor`. По умолчанию они декоративные:
|
||||
доступное имя принадлежит кнопке. Самостоятельная смысловая иконка получает
|
||||
явные роль изображения и доступное имя. Предметные рисунки и анимации
|
||||
остаются в приложении; поставки raw SVG и мобильного UI нет.
|
||||
|
||||
Storybook объединяет примеры приложения и пакетов. Он показывает синтетические
|
||||
состояния без API, авторизации и query providers; формы получают локальные
|
||||
callbacks. Каталог помогает проверять представление, но не заменяет проверки
|
||||
запросов, денежных ограничений и восстановления после ошибок.
|
||||
|
||||
Пакеты используются локально и не опубликованы в registry. Независимый выпуск
|
||||
не настроен. Потребители импортируют публичные exports пакетов напрямую; прежние
|
||||
compatibility re-exports `shared/theme` и `shared/ui` удалены.
|
||||
|
||||
## Семантические роли
|
||||
|
||||
| Роль | Цветовая основа | Применение |
|
||||
@@ -16,8 +44,8 @@
|
||||
| border | gray.3 | Граница поверхности |
|
||||
| primary | blue filled | Основное действие и активная навигация |
|
||||
| info | blue.6 | Информация |
|
||||
| positive | green.6 | Положительный результат с текстом или знаком |
|
||||
| negative | red.6 | Ошибка или отрицательный результат с текстом или знаком |
|
||||
| positive | green.9 | Положительный результат с текстом или знаком |
|
||||
| negative | red.9 | Ошибка или отрицательный результат с текстом или знаком |
|
||||
| warning | yellow.6 | Предупреждение; не мелкий текст на белом |
|
||||
|
||||
Цвет не заменяет подпись или знак. Палитра не гарантирует доступный контраст
|
||||
@@ -44,4 +72,4 @@
|
||||
и [банковских картах](../domains/cards/overview.md).
|
||||
|
||||
Согласованная общая основа описана в
|
||||
[спецификации дизайн-системы](../../specs/openspec/specs/frontend-design-system/spec.md).
|
||||
[спецификации дизайн-системы](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/frontend-design-system/spec.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).
|
||||
|
||||
Согласованное поведение описывает [спецификация счетов](../../../specs/openspec/specs/brokerage-accounts/spec.md).
|
||||
Её раздел об экране пока требует одноколоночного списка и не описывает
|
||||
счётчики акций и облигаций. Текущая реализация и её проверки используют
|
||||
две колонки на широком экране и показывают эти счётчики. Расхождение требует
|
||||
отдельного согласования требований; в этой документации отражён наблюдаемый экран.
|
||||
Согласованное поведение описывает [спецификация счетов](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-accounts/spec.md).
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
Access token действует 15 минут и хранится только в памяти браузера.
|
||||
Refresh token имеет серверный срок 7 дней, заменяется при восстановлении
|
||||
сессии и передаётся в cookie `refresh_token` с `httpOnly` и `SameSite=Lax`.
|
||||
Сроки определены в [спецификации сессий](../../../specs/openspec/specs/user-authentication/spec.md#requirement-access-и-refresh-сессии)
|
||||
Сроки определены в [спецификации сессий](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/user-authentication/spec.md#requirement-access-и-refresh-сессии)
|
||||
и [ADR 006](../../adr/006-auth-session-policy.md).
|
||||
|
||||
После перезагрузки SPA получает новый access token через refresh-cookie.
|
||||
@@ -117,6 +117,6 @@ Backend проверяет учётные данные и tokens, хранит
|
||||
и проверку атомарности с реальной изолированной базой.
|
||||
|
||||
Согласованное поведение находится в workspace-спецификации
|
||||
[User authentication](../../../specs/openspec/specs/user-authentication/spec.md).
|
||||
[User authentication](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/user-authentication/spec.md).
|
||||
Правила начального ожидания — в
|
||||
[состояниях интерфейса](../../architecture/cross-cutting/ui-resilience.md).
|
||||
|
||||
@@ -140,6 +140,6 @@ JavaScript Number. Отдельный ввод ограничен signed64: на
|
||||
сохраняет новую финансовую историю и при откате приложения. Устройство хранения
|
||||
описано в [контейнерах](../../architecture/containers.md#хранимые-данные-и-границы-доступа).
|
||||
Согласованный контракт банковского учёта находится в
|
||||
[manual-bank-cards](../../../specs/openspec/changes/add-manual-bank-cards/specs/manual-bank-cards/spec.md).
|
||||
[manual-bank-cards](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/changes/add-manual-bank-cards/specs/manual-bank-cards/spec.md).
|
||||
Автоматическая загрузка банковских данных, кредитные карты, другие валюты,
|
||||
бюджеты и общий баланс банковских денег с инвестициями в эту версию не входят.
|
||||
|
||||
+24
-15
@@ -49,21 +49,19 @@
|
||||
|
||||
## Какие выплаты входят в результат
|
||||
|
||||
- Backend проверяет доступ к счёту, проходит все страницы исполненных операций и не возвращает частичный результат при ошибке страницы или повреждённой цепочке курсоров.
|
||||
- Backend проверяет доступ локально и читает сохранённые операции. Фоновая загрузка должна завершить все страницы; ошибка страницы или повреждённая цепочка курсоров не подтверждают полноту.
|
||||
- Только точные типы купона и дивиденда источника формируют результат. Суммы остаются точными десятичными строками в исходной валюте; отрицательные корректировки сохраняют знак. Налоги, комиссии, сделки, переводы, погашения и переводы дивидендов не входят в доход.
|
||||
- Итоги, сравнения, временные интервалы и разбиение по инструментам не смешивают валюты и не создают общий конвертированный итог. На странице показано только сравнение с годом ранее, когда календарная дата и история счёта позволяют. Предыдущий период остаётся в ответе API, но не отображается.
|
||||
- Итоги, сравнения, временные интервалы и разбиение по инструментам не смешивают валюты и не создают общий конвертированный итог. Страница показывает сравнения с предыдущим периодом и годом ранее, когда календарная дата и история счёта позволяют. Неполное сравнение имеет отдельное сообщение, без нулевой базы.
|
||||
- Временные интервалы по-прежнему приходят с backend: дни для 1–45 дней, недели для 46–180 и месяцы для более длинного периода, включая пустые интервалы. Страница дохода не строит из них график или текстовую динамику; ответ API не меняется.
|
||||
- Раздел «Выплаты» показывает единый список карточек выплат в исходном порядке `entries`: одна карточка на каждую выплату. Внешних групп по инструментам или валютам, итогов и количества выплат инструмента нет. Описание, дата, доступные идентификаторы инструмента и сумма со знаком видны в каждой карточке; валюты по-прежнему разделены в итогах и сравнениях. Дополнительный запрос за составом не нужен.
|
||||
|
||||
Сравнение с непосредственно предыдущим периодом сохраняется в API:
|
||||
для полного месяца это предыдущий полный месяц, иначе — непосредственно
|
||||
предшествующий интервал той же включительной длительности. Текущая страница
|
||||
его не показывает. Это расходится с
|
||||
[основным требованием сравнений](../../../specs/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,9 +111,9 @@ Frontend проверяет принадлежность выплат по по
|
||||
выбранного счёта и сохраняет прежнее поведение. Будущие выплаты
|
||||
не включаются в итоги полученного дохода или сравнения.
|
||||
|
||||
Аналитика только читает данные, не хранит историю операций и не рассчитывает налоги, инвестиционную эффективность, доходность, валютную конвертацию или общий мультивалютный итог.
|
||||
Аналитика читает сохранённую историю операций и не рассчитывает налоги, инвестиционную эффективность, доходность, валютную конвертацию или общий мультивалютный итог.
|
||||
|
||||
Основное проверяемое поведение — в [спецификации дохода](../../../specs/openspec/specs/brokerage-account-income-analytics/spec.md),
|
||||
Основное проверяемое поведение — в [спецификации дохода](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-income-analytics/spec.md),
|
||||
точная форма ответа — в [HTTP API](../../architecture/http-api.md).
|
||||
Не следует использовать календарь будущих событий для восстановления
|
||||
состава уже полученного дохода.
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
Суммы рассчитаны по количеству на `calculatedAt`. Они не учитывают будущие
|
||||
сделки, право владения на дату реестра и персональный налоговый расчёт.
|
||||
Есть неразрешённое ограничение интерпретации:
|
||||
[основная спецификация](../../../specs/openspec/specs/brokerage-account-upcoming-events/spec.md#requirement-forecasts-preserve-event-meaning-and-uncertainty)
|
||||
[основная спецификация](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-upcoming-events/spec.md#requirement-forecasts-preserve-event-meaning-and-uncertainty)
|
||||
и текущая подсказка экрана называют прогноз суммой «до налогов», но для
|
||||
дивиденда реализация только умножает `dividend_net` на количество.
|
||||
Налоговый смысл этого поля не подтверждён локальными материалами; на
|
||||
@@ -60,39 +60,23 @@ API принимает необязательные `from` и `to` в форма
|
||||
`incomeTotalsByCurrency`, включающего только купоны и дивиденды.
|
||||
Итоги остаются в ответе API; полный экран показывает список без общего итога.
|
||||
|
||||
Текущие controller, сервис и экран поддерживают выбор периода, тогда как
|
||||
[основная спецификация](../../../specs/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 и повреждённого
|
||||
формата ответа.
|
||||
|
||||
При первом открытии показан каркас заголовка и строк, без интерактивных
|
||||
заглушек. Пустой ответ сохраняет возможность изменить период.
|
||||
|
||||
@@ -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).
|
||||
|
||||
Разделы загружаются независимо, показывают каркас загрузки и локальную повторную попытку.
|
||||
Ошибка фонового обновления сохраняет последние данные. Смена подключения
|
||||
@@ -132,13 +133,13 @@ HTML-источник, общий кэш и поведение при измен
|
||||
выход очищает кэш сессии. Общие график, позиция и личные события
|
||||
имеют общее представление на страницах акций и облигаций.
|
||||
|
||||
Основные спецификации [портфеля](../../../specs/openspec/specs/portfolio-view/spec.md)
|
||||
и [журнала](../../../specs/openspec/specs/brokerage-account-ledger/spec.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)
|
||||
описывают исходные финансовые понятия. Отдельная основная спецификация
|
||||
страницы облигации в текущем OpenSpec Store не найдена; условия вычислений
|
||||
подтверждены [каталогом](../../../backend/src/modules/investments/instruments/bonds/application/bond-catalog.service.ts),
|
||||
[рыночными данными](../../../backend/src/modules/investments/instruments/bonds/application/bond-market.service.ts),
|
||||
[выплатами](../../../backend/src/modules/investments/instruments/bonds/application/bond-income.service.ts)
|
||||
и [проверками](../../../backend/src/modules/investments/instruments/bonds/application/bond.spec.ts).
|
||||
подтверждены [каталогом](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/bonds/application/bond-catalog.service.ts),
|
||||
[рыночными данными](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/bonds/application/bond-market.service.ts),
|
||||
[выплатами](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/bonds/application/bond-income.service.ts)
|
||||
и [проверками](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/bonds/application/bond.spec.ts).
|
||||
Границы источников описаны в [разделе интеграций](../../integrations/README.md),
|
||||
сходное разделение рыночных и личных данных — в [акциях](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-формат транспорта и
|
||||
временные зоны расчётов не меняются. Личная история использует общий с портфелем формат строки операции,
|
||||
@@ -169,11 +176,11 @@ TTM означает последние 12 месяцев, MRQ — послед
|
||||
История открытых счетов не обещает полную историю владения, включая закрытые
|
||||
счета, переводы и универсальные корпоративные события.
|
||||
|
||||
Основные спецификации [портфеля](../../../specs/openspec/specs/portfolio-view/spec.md)
|
||||
и [журнала](../../../specs/openspec/specs/brokerage-account-ledger/spec.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)
|
||||
задают исходные финансовые понятия. Отдельная основная спецификация страницы
|
||||
акции в текущем OpenSpec Store не найдена; описанные механизмы проверяются
|
||||
по [каталогу](../../../backend/src/modules/investments/instruments/stocks/application/stock-catalog.service.ts),
|
||||
[рыночным данным](../../../backend/src/modules/investments/instruments/stocks/application/stock-market.service.ts),
|
||||
[выплатам](../../../backend/src/modules/investments/instruments/stocks/application/stock-income.service.ts) и [проверкам](../../../backend/src/modules/investments/instruments/stocks/application/stock.spec.ts).
|
||||
по [каталогу](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/stocks/application/stock-catalog.service.ts),
|
||||
[рыночным данным](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/stocks/application/stock-market.service.ts),
|
||||
[выплатам](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/stocks/application/stock-income.service.ts) и [проверкам](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/stocks/application/stock.spec.ts).
|
||||
Сопоставление с облигациями — на [странице облигаций](bonds.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,8 +97,9 @@ Backend хранит не более 100 страниц в памяти проц
|
||||
не заменяется каркасом при применении действий или фоновом обновлении.
|
||||
Ошибки `TBANK_NOT_CONNECTED`, `INVALID_TBANK_TOKEN`, недоступный счёт (`404`)
|
||||
и `TBANK_UNAVAILABLE` сохраняют [общую семантику API](../../architecture/http-api.md).
|
||||
Пустой успешный ответ означает отсутствие подходящих операций, а не сбой.
|
||||
Только полный пустой ответ означает отсутствие подходящих операций.
|
||||
Partial empty сообщает о неполноте; source failure сохраняет прежний результат.
|
||||
|
||||
Согласованное поведение описано в [спецификации журнала](../../../specs/openspec/specs/brokerage-account-ledger/spec.md).
|
||||
Согласованное поведение описано в [спецификации журнала](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-ledger/spec.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).
|
||||
|
||||
Основные спецификации [портфеля](../../../specs/openspec/specs/portfolio-view/spec.md),
|
||||
[операций](../../../specs/openspec/specs/brokerage-account-ledger/spec.md),
|
||||
[дохода](../../../specs/openspec/specs/brokerage-account-income-analytics/spec.md)
|
||||
и [событий](../../../specs/openspec/specs/brokerage-account-upcoming-events/spec.md)
|
||||
описывают отдельный счёт. Общий режим подтверждён действующими маршрутизаторами,
|
||||
сервисами и [проверками агрегации](../../../backend/src/modules/investments/all-accounts.spec.ts),
|
||||
но не выделен в самостоятельную основную спецификацию. Не следует считать
|
||||
описание общего режима изменением требований к отдельному счёту.
|
||||
Основные спецификации областей сохраняют исходные понятия отдельного счёта.
|
||||
Утверждённый change `persist-tbank-data` распространяет полноту, локальный
|
||||
доступ, атомарную сверку и состояния синхронизации на режим `all`.
|
||||
|
||||
@@ -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 обновляется независимо; отсутствие имени не задерживает выдачу
|
||||
финансового снимка и оставляет доступный идентификатор.
|
||||
|
||||
## Структура и доли
|
||||
|
||||
@@ -90,8 +90,8 @@ frontend показывает до сотых. Положительное зна
|
||||
поэтому ограничение долей действует на все валюты. В общем портфеле проверка
|
||||
отрицательных денег выполняется по остаткам счетов до взаимозачёта.
|
||||
|
||||
Нормативные основы: [нормализованный состав](../../../specs/openspec/specs/portfolio-view/spec.md#requirement-нормализованный-состав-portfolio)
|
||||
и [точные значения и валютные агрегаты](../../../specs/openspec/specs/portfolio-view/spec.md#requirement-точные-значения-и-валютные-агрегаты).
|
||||
Нормативные основы: [нормализованный состав](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-нормализованный-состав-portfolio)
|
||||
и [точные значения и валютные агрегаты](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-точные-значения-и-валютные-агрегаты).
|
||||
Структура и общий режим подтверждены реализацией и её проверками;
|
||||
отдельных требований к ним в основной спецификации портфеля пока нет.
|
||||
Поля транспорта следует сверять с [HTTP API](../../architecture/http-api.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, ошибки не сохраняются |
|
||||
|
||||
Это параметры текущей реализации, а не самостоятельная политика этого
|
||||
документа: [снимки](../../../../backend/src/modules/investments/portfolio/application/portfolio.service.ts),
|
||||
[метаданные](../../../../backend/src/integrations/tbank/instrument-metadata.service.ts) и
|
||||
[клиентский запрос](../../../../frontend/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.
|
||||
|
||||
Проверяемые правила: [свежесть и запасной снимок](../../../../specs/openspec/specs/portfolio-view/spec.md#requirement-свежесть-и-fallback-полного-portfolio-snapshot),
|
||||
[клиентские состояния](../../../../specs/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).
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
каждой котировки, а общий портфель может соединять разные моменты.
|
||||
Если запрос завершился ошибкой, доступное действие «Повторить» должно
|
||||
обновить результат или оставить объяснимую ошибку. Перезагрузка backend
|
||||
удаляет кэш и не восстанавливает историю стоимости.
|
||||
сохраняет последний полный снимок в SQLite, но не создаёт историю стоимости.
|
||||
3. **Проверьте состав суммы.** Для одной валюты итог складывается из
|
||||
вычислимых стоимостей позиций и доступных денег. Не добавляйте деньги
|
||||
второй раз, не складывайте валюты и не включайте заблокированные средства.
|
||||
@@ -37,16 +37,16 @@
|
||||
теряет цифры, валюту или показатель, исследуйте frontend.
|
||||
Если исходные безопасные данные и нормализованный ответ расходятся,
|
||||
исследуйте backend. Если не совпадают ожидаемое и описанное поведение,
|
||||
сначала сопоставьте [спецификацию портфеля](../../../../specs/openspec/specs/portfolio-view/spec.md)
|
||||
сначала сопоставьте [спецификацию портфеля](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md)
|
||||
и ограничения [потока загрузки](../flows/portfolio-loading.md).
|
||||
Исправление документации само по себе не разрешает конфликт требований
|
||||
и реализации.
|
||||
|
||||
Для адресной проверки исходников владельцами служат
|
||||
[сервис снимков](../../../../backend/src/modules/investments/portfolio/application/portfolio.service.ts),
|
||||
[валютные итоги](../../../../backend/src/modules/investments/portfolio/infrastructure/tbank-portfolio.mapper.ts),
|
||||
[структура](../../../../backend/src/modules/investments/portfolio/domain/portfolio-structure.ts) и
|
||||
[подготовка экрана](../../../../frontend/src/pages/account/ui/AccountPage.vm.ts).
|
||||
[сервис снимков](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/modules/investments/portfolio/infrastructure/tbank-portfolio.mapper.ts),
|
||||
[структура](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/portfolio/domain/portfolio-structure.ts) и
|
||||
[подготовка экрана](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/src/pages/account/ui/AccountPage.vm.ts).
|
||||
Это точки продолжения расследования; смысл вычислений изложен выше.
|
||||
|
||||
Разбор завершён, когда различие объяснено составом, временем или определением
|
||||
|
||||
@@ -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) текущего месяца и до пяти
|
||||
@@ -64,7 +64,7 @@ SQLite не хранит сам портфель: backend получает да
|
||||
без логотипа и с подписью результата. Отсутствующие показатели скрываются,
|
||||
а не подменяются нулём или заглушкой.
|
||||
|
||||
Существует расхождение: [согласованная спецификация представления позиций](../../../specs/openspec/specs/portfolio-view/spec.md#requirement-представление-карточек-позиций-на-клиенте)
|
||||
Существует расхождение: [согласованная спецификация представления позиций](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-представление-карточек-позиций-на-клиенте)
|
||||
требует доступности всех иных типов в отдельной секции, а текущие frontend
|
||||
и его проверка исключают `CURRENCY` только из списка карточек. Такие позиции
|
||||
остаются в ответе, валютных итогах, структуре и знаменателе долей остальных
|
||||
@@ -79,7 +79,7 @@ SQLite не хранит сам портфель: backend получает да
|
||||
не содержат данных и не получают фокус. При обычном фоновом обновлении
|
||||
сохраняются предыдущие суммы и время успешного получения в UTC.
|
||||
|
||||
Серверный `isStale` обозначает разрешённый запасной снимок backend.
|
||||
Серверный `isStale` обозначает истечение минутной свежести снимка.
|
||||
Клиентское предупреждение означает неудачную попытку обновить уже показанный
|
||||
ответ. Эти состояния независимы и могут появиться одновременно.
|
||||
Успешный повторный запрос обновляет суммы и время, снимает клиентскую ошибку;
|
||||
@@ -88,12 +88,12 @@ SQLite не хранит сам портфель: backend получает да
|
||||
|
||||
В текущем решении нет долговременной истории снимков, графиков стоимости
|
||||
портфеля, валютной конвертации, учёта виртуальных позиций или заблокированных
|
||||
средств. Кэш теряется при перезапуске и рассчитан на локальную работу одного
|
||||
экземпляра backend. Отдельной команды принудительного обновления на экране
|
||||
нет; повторная попытка доступна после ошибки.
|
||||
средств. Последний полный снимок сохраняется после перезапуска; выполнение
|
||||
синхронизации рассчитано на один экземпляр backend. Действие «Обновить»
|
||||
перечитывает зависимости открытого экрана независимо от их свежести.
|
||||
|
||||
Для проверки конкретной суммы используйте
|
||||
[руководство по расхождениям](how-to/investigate-discrepancy.md).
|
||||
Проверяемое поведение отдельного счёта описано в
|
||||
[спецификации портфеля](../../../specs/openspec/specs/portfolio-view/spec.md);
|
||||
[спецификации портфеля](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md);
|
||||
точную форму HTTP-ответа задаёт [OpenAPI backend](../../architecture/http-api.md).
|
||||
|
||||
+25
-2
@@ -4,6 +4,14 @@
|
||||
Связи между ними объясняет [обзор продукта](overview.md), подробные правила —
|
||||
[предметные области](README.md#предметные-области).
|
||||
|
||||
## Релиз развёртывания WealthLedger
|
||||
|
||||
Версия комплекта установки WealthLedger, закрепляющая проверенную совместимую
|
||||
пару релизов frontend и backend. Понятие принято в
|
||||
[ADR 016](adr/016-versioned-deployment-bundle.md).
|
||||
|
||||
_Не использовать_: релиз frontend или backend как синоним релиза всей установки.
|
||||
|
||||
## User
|
||||
|
||||
Локальный владелец данных WealthLedger, идентифицируемый `id` и
|
||||
@@ -169,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 для фильтрации и отображения:
|
||||
@@ -178,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#получение-и-полнота).
|
||||
|
||||
@@ -48,6 +48,6 @@ HTTP-запрос ограничен таймаутом. Сеть, неуспе
|
||||
Наличие успешного HTTP-ответа не гарантирует котировку без задержки.
|
||||
|
||||
Условия обработки источника в прогнозе закреплены в
|
||||
[спецификации будущих событий](../../specs/openspec/specs/brokerage-account-upcoming-events/spec.md#requirement-upcoming-events-use-complete-source-results).
|
||||
[спецификации будущих событий](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-upcoming-events/spec.md#requirement-upcoming-events-use-complete-source-results).
|
||||
Точный механизм транспорта, разбора и сопоставления реализован в
|
||||
[клиенте MOEX](../../backend/src/integrations/moex/moex.client.ts).
|
||||
[клиенте MOEX](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/integrations/moex/moex.client.ts).
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ ISIN, даты и ссылки на выпуск. Изменение табли
|
||||
После ошибки новая загрузка ограничена минутным интервалом; внутри одного
|
||||
HTTP-запроса повторных попыток нет. Обновление запускается следующим обращением
|
||||
после истечения кэша, без фонового задания. Точный механизм и сроки принадлежат
|
||||
[клиенту НКР](../../backend/src/integrations/ncr/ncr-ratings.client.ts).
|
||||
[клиенту НКР](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/integrations/ncr/ncr-ratings.client.ts).
|
||||
|
||||
При отказе реестра показатели облигации отмечают недоступность рейтинга,
|
||||
сохраняя остальные доступные показатели. Готовый ответ метрик имеет
|
||||
|
||||
@@ -36,7 +36,7 @@ Backend передаёт имя файла `logoName` из метаданных
|
||||
Логотипы используются в портфеле, операциях, будущих событиях, поиске и
|
||||
страницах инструментов. Отсутствующее имя или ошибка изображения оставляют
|
||||
буквенную заглушку и не меняют финансовые значения. Правило построения URL
|
||||
принадлежит [форматированию frontend](../../frontend/src/shared/lib/display.ts).
|
||||
принадлежит [форматированию frontend](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/src/shared/lib/display.ts).
|
||||
|
||||
## Проверка и сохранение подключения
|
||||
|
||||
@@ -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,11 +92,12 @@ sequenceDiagram
|
||||
`INVALID_TBANK_TOKEN` означает отказ внешней аутентификации или прав доступа.
|
||||
`TBANK_UNAVAILABLE` сообщает о временной или иной upstream-ошибке, включая
|
||||
исчерпание очереди. Ответы и ошибки не содержат token. Удаление подключения
|
||||
идемпотентно: удаляется локальный секрет и прекращается его использование
|
||||
для последующих запросов. Оно не описывает отзыв token на стороне T-Bank.
|
||||
идемпотентно: удаляются локальный секрет, личные datasets и work; поздняя
|
||||
публикация прежней revision запрещена. Общие неперсональные источники могут
|
||||
сохраняться. Оно не описывает отзыв token на стороне T-Bank.
|
||||
|
||||
Согласованное поведение сохранено в workspace-спецификации
|
||||
[T-Bank connection](../../specs/openspec/specs/tbank-connection/spec.md).
|
||||
[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) описывает общую границу.
|
||||
|
||||
@@ -106,7 +107,7 @@ sequenceDiagram
|
||||
использования финансовых данных. Это относится и к сохранению того же самого
|
||||
token: предыдущее подключение не становится источником данных нового.
|
||||
Неуспешная операция сохраняет прежнее подключение и его доступные данные.
|
||||
Механизм generation, поздние ответы и сброс клиентских выборок объясняет
|
||||
Механизм внутренней revision, поздние ответы и сброс клиентских выборок объясняет
|
||||
[свежесть данных](../architecture/cross-cutting/data-freshness.md#смена-подключения).
|
||||
|
||||
## Конфигурация и ограничения
|
||||
@@ -126,7 +127,7 @@ Backend хранит token в шифротексте AES-256-GCM с новым n
|
||||
| `TBANK_CA_CERT_PATH` | Необязательный путь к доверенному корневому CA в PEM для сетей с TLS-инспекцией; проверка TLS сохраняется |
|
||||
|
||||
Параметры окружения определяет backend
|
||||
[validateEnv](../../backend/src/config/env.validation.ts) (workspace-ссылка).
|
||||
[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 секунд,
|
||||
@@ -139,7 +140,7 @@ Backend хранит token в шифротексте AES-256-GCM с новым n
|
||||
отсутствие инструмента, а не временный сбой. Остальные RPC при полной
|
||||
очереди завершаются сразу.
|
||||
Параметры попыток принадлежат
|
||||
[T-Bank client](../../backend/src/integrations/tbank/tbank.client.ts) (workspace-ссылка);
|
||||
[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).
|
||||
|
||||
Большое число ранее неизвестных инструментов требует отдельных запросов
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "wealth-ledger-docs",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "wealth-ledger-docs",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.2",
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "21.2.2",
|
||||
"@commitlint/config-conventional": "21.2.2",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wealth-ledger-docs",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.2",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": "24.21.0"
|
||||
|
||||
Reference in New Issue
Block a user