3 Commits
Author SHA1 Message Date
ksv741 65c8bc14d0 chore: sync develop before complexity PR
Docs security / security (push) Failing after 11s
Docs verify / verify (push) Successful in 15s
Conventional commits / commits (pull_request) Successful in 13s
Docs security / security (pull_request) Failing after 10s
Docs verify / verify (pull_request) Successful in 15s
2026-10-03 17:52:07 +03:00
ksv741 e8e7fbd9fc docs: record minimal runtime coordination
Docs security / security (push) Failing after 9s
Docs verify / verify (push) Successful in 12s
Document accepted cross-tab reauthentication, on-demand candles,
native query/logging ownership and the complete deployment path.

Preserve historical ADR context and link amendments through ADR 018.
Related backend change: WealthLedger/backend@d698ed9
2026-10-03 11:41:24 +03:00
ksv741 2bfe87d0e4 docs: record code quality review and refactoring
Docs security / security (push) Successful in 8s
Docs verify / verify (push) Successful in 13s
Conventional commits / commits (pull_request) Successful in 14s
Docs security / security (pull_request) Successful in 7s
Docs verify / verify (pull_request) Successful in 13s
2026-09-27 17:42:35 +03:00
45 changed files with 782 additions and 1111 deletions
-15
View File
@@ -1,15 +0,0 @@
---
name: Bug Report
about: Неверное поведение
---
# Bug Report
## Проблема и шаги воспроизведения
## Факт / ожидание
## Acceptance Criteria
- [ ]
## Окружение / ссылки (если применимо)
-13
View File
@@ -1,13 +0,0 @@
---
name: Engineering Task
about: CI, тесты, документация, процесс и прочее
---
# Engineering Task
## Контекст и результат
## Scope / связанные Issues (если есть)
## Acceptance Criteria
- [ ]
-15
View File
@@ -1,15 +0,0 @@
---
name: Feature Request
about: Новое наблюдаемое поведение
---
# Feature Request
## Зачем и какой результат
## Ожидаемое поведение
## Acceptance Criteria
- [ ]
## Зависимости / связанные Issues (если есть)
-15
View File
@@ -1,15 +0,0 @@
---
name: Refactoring / Technical Debt
about: Улучшение без нового поведения
---
# Refactoring / Technical Debt
## Проблема и результат
## Сохраняемые инварианты / границы
## Acceptance Criteria
- [ ]
## Зависимости / ссылки (если есть)
-19
View File
@@ -1,19 +0,0 @@
# Pull Request
## Summary
Краткий результат работы описывается здесь.
## Linked Issue
Refs `<owner/repo#number>` (заменить ссылкой на реальную Issue).
## Changes
Затронутые части и изменения.
## Verification
- Команда / результат / ограничения:
## Breaking Changes (если есть)
+1 -9
View File
@@ -4,10 +4,6 @@ on:
pull_request: pull_request:
types: [opened, synchronize, reopened, edited] types: [opened, synchronize, reopened, edited]
concurrency:
group: docs-commits-${{ gitea.event.pull_request.number }}
cancel-in-progress: true
jobs: jobs:
commits: commits:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -27,8 +23,4 @@ jobs:
HEAD_SHA: ${{ github.event.pull_request.head.sha }} HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: | run: |
printf '%s\n' "$PR_TITLE" | npm run commitlint -- --strict printf '%s\n' "$PR_TITLE" | npm run commitlint -- --strict
if [[ "$PR_TITLE" =~ ^chore\(release\):\ [0-9]+\.[0-9]+\.[0-9]+$ ]]; then npm run commitlint -- --strict --from "$BASE_SHA" --to "$HEAD_SHA"
npm run commitlint -- --strict --from "$HEAD_SHA^" --to "$HEAD_SHA"
else
npm run commitlint -- --strict --from "$BASE_SHA" --to "$HEAD_SHA"
fi
+2 -6
View File
@@ -2,16 +2,12 @@ name: Docs security
on: on:
push: push:
branches: [develop, main] branches:
- '**'
pull_request: pull_request:
types: [opened, synchronize, reopened]
schedule: schedule:
- cron: '17 5 * * *' - cron: '17 5 * * *'
concurrency:
group: docs-security-${{ gitea.event_name }}-${{ gitea.event.pull_request.number || gitea.ref }}
cancel-in-progress: true
jobs: jobs:
security: security:
runs-on: ubuntu-latest runs-on: ubuntu-latest
+1 -6
View File
@@ -2,13 +2,8 @@ name: Docs verify
on: on:
push: push:
branches: [develop, main] branches: ['**']
pull_request: pull_request:
types: [opened, synchronize, reopened]
concurrency:
group: docs-verify-${{ gitea.event_name }}-${{ gitea.event.pull_request.number || gitea.ref }}
cancel-in-progress: true
jobs: jobs:
verify: verify:
-6
View File
@@ -1,6 +0,0 @@
{
"extends": ".markdownlint.json",
"MD024": {
"siblings_only": true
}
}
+21
View File
@@ -0,0 +1,21 @@
{
"config": {
"MD013": false
},
"customRules": [
"markdownlint-rule-relative-links"
],
"overrides": [
{
"filter": [
"CHANGELOG.md"
],
"config": {
"MD024": {
"siblings_only": true
}
},
"combine": "merge"
}
]
}
-1
View File
@@ -1 +0,0 @@
module.exports = require('markdownlint-rule-relative-links').default;
-3
View File
@@ -1,3 +0,0 @@
{
"MD013": false
}
+3 -12
View File
@@ -3,9 +3,6 @@
Перед изменением прочитай [README](README.md) и [CONTRIBUTING](CONTRIBUTING.md), Перед изменением прочитай [README](README.md) и [CONTRIBUTING](CONTRIBUTING.md),
затем только затронутую тему. Docs объясняет систему; требования принадлежат затем только затронутую тему. Docs объясняет систему; требования принадлежат
основным спецификациям, реализация и coding conventions — соседним репозиториям. основным спецификациям, реализация и coding conventions — соседним репозиториям.
До edits найди/создай подходящую Gitea Issue, проверь AC и ветку; чтение
допускается без Issue. Общий маршрут — [Engineering Workflow](https://git.personal.ksv741.ru/WealthLedger/main/src/commit/03b3b59e6ff2ba391f9dfc42a039a96d0d84c1c9/.ai/conventions/engineering-workflow.md)
(main#28; после merge — актуальная версия `develop`).
Проверь существенные факты по локальным тематическим страницам и ADR; Проверь существенные факты по локальным тематическим страницам и ADR;
внешние источники используй как справочные свидетельства, когда они доступны. внешние источники используй как справочные свидетельства, когда они доступны.
@@ -23,13 +20,7 @@
## Ветки и релизы ## Ветки и релизы
Работа — в issue-linked ветке от `develop`, затем PR в `develop`; в `main` напрямую Работа — в отдельной ветке от `develop`, затем PR в `develop`; в `main` напрямую
не пушить. PR сливается squash или rebase-merge; merge-коммит не используется. не пушить. Release/hotfix и синхронизация веток — по [README](README.md#релизы).
`main` обновляется только fast-forward из `develop`, поэтому `origin/main` Commits и заголовки PR — Conventional Commits; для спецификаций допустим `spec:`.
всегда предок `origin/develop`:
`git merge-base --is-ancestor origin/main origin/develop`. Срочный fix —
обычная ветка `fix/*` от `develop`; пути hotfix от `main` нет.
Релиз — по [README](README.md#релизы).
Commits и заголовки PR — Conventional Commits со ссылкой на Issue (кроме release);
для спецификаций допустим `spec:`.
Перед подготовкой релиза прочитай местный README; версии вручную не повышать. Перед подготовкой релиза прочитай местный README; версии вручную не повышать.
-55
View File
@@ -1,60 +1,5 @@
# Changelog # Changelog
## [0.1.5](https://git.personal.ksv741.ru/WealthLedger/docs/compare/v0.1.4...v0.1.5) (2026-10-18)
### Documentation
* align current pages with hourly freshness model (WealthLedger/docs[#34](https://git.personal.ksv741.ru/WealthLedger/docs/issues/34)) ([#42](https://git.personal.ksv741.ru/WealthLedger/docs/issues/42)) ([7b61eb8](https://git.personal.ksv741.ru/WealthLedger/docs/commit/7b61eb8e6ba93a765b1ae27ca31d77b5e422e149))
* describe ASCII 8-72 password policy and drop resolved discrepancy (WealthLedger/docs[#46](https://git.personal.ksv741.ru/WealthLedger/docs/issues/46)) ([#47](https://git.personal.ksv741.ru/WealthLedger/docs/issues/47)) ([56885f4](https://git.personal.ksv741.ru/WealthLedger/docs/commit/56885f450bc1298d383d496174d16e591926e047))
* describe events default period and picker bounds (WealthLedger/frontend[#63](https://git.personal.ksv741.ru/WealthLedger/docs/issues/63)) ([8a2a62c](https://git.personal.ksv741.ru/WealthLedger/docs/commit/8a2a62c611e1acc1c1e69048a88b049bbf0d9087))
* describe quiet reading and visible source state ([#51](https://git.personal.ksv741.ru/WealthLedger/docs/issues/51)) ([e8e3ef6](https://git.personal.ksv741.ru/WealthLedger/docs/commit/e8e3ef62854dae8448deaa66ac26e5b15d5c7d6a))
* document income without comparisons and unified payouts (WealthLedger/main[#33](https://git.personal.ksv741.ru/WealthLedger/docs/issues/33)) ([#40](https://git.personal.ksv741.ru/WealthLedger/docs/issues/40)) ([03fb045](https://git.personal.ksv741.ru/WealthLedger/docs/commit/03fb045a9ee8a36775599bda66e7f6a08362bd84))
* document Nexus delivery with shared account (WealthLedger/docs[#18](https://git.personal.ksv741.ru/WealthLedger/docs/issues/18)) ([#48](https://git.personal.ksv741.ru/WealthLedger/docs/issues/48)) ([550143a](https://git.personal.ksv741.ru/WealthLedger/docs/commit/550143aea1f0ac90630f326dd927091b0ec4f38e))
### CI
* rerun commits proof on PR title edit (WealthLedger/docs[#35](https://git.personal.ksv741.ru/WealthLedger/docs/issues/35)) ([03110e9](https://git.personal.ksv741.ru/WealthLedger/docs/commit/03110e99ea58600bacd4c091fda8591d374a5a8d))
### Maintenance
* adopt linear git model in docs instructions ([#49](https://git.personal.ksv741.ru/WealthLedger/docs/issues/49)) ([11f819e](https://git.personal.ksv741.ru/WealthLedger/docs/commit/11f819ed31226a05524ac35a893064525713e41b))
## [0.1.4](https://git.personal.ksv741.ru/WealthLedger/docs/compare/v0.1.3...v0.1.4) (2026-10-12)
### Bug Fixes
* align docs commitlint workflow (WealthLedger/main[#27](https://git.personal.ksv741.ru/WealthLedger/docs/issues/27)) ([602eb43](https://git.personal.ksv741.ru/WealthLedger/docs/commit/602eb43294509274cbd9003be31f726b6e275a1e))
### Documentation
* clarify card summary totals ([df6f010](https://git.personal.ksv741.ru/WealthLedger/docs/commit/df6f0109ed696a529cf2622e23fceeab8133f932))
* document hourly attempt lifecycle semantics (WealthLedger/backend[#56](https://git.personal.ksv741.ru/WealthLedger/docs/issues/56)) ([1378ac9](https://git.personal.ksv741.ru/WealthLedger/docs/commit/1378ac9fe2fcfd43bd71421e62b02f00069be928))
* document hourly investment reads ([f0cab44](https://git.personal.ksv741.ru/WealthLedger/docs/commit/f0cab445d7e7076bf92a61f1023b8bf51e094d33))
* document hourly sync history ([e0bceea](https://git.personal.ksv741.ru/WealthLedger/docs/commit/e0bceea936239c08f4717e264be23d7f60552398))
* document stock screener ([2c34023](https://git.personal.ksv741.ru/WealthLedger/docs/commit/2c34023af8593f305f7c81e672b4e179c0992cf3))
* pin docs workflow reference (WealthLedger/main[#27](https://git.personal.ksv741.ru/WealthLedger/docs/issues/27)) ([c24e4b9](https://git.personal.ksv741.ru/WealthLedger/docs/commit/c24e4b990aaeffd02e05f7de21942579e452cecf))
### CI
* avoid duplicate PR checks ([4caa5e9](https://git.personal.ksv741.ru/WealthLedger/docs/commit/4caa5e92b7d2597e5c7725b2036007b71ece2abd))
* skip checks on PR metadata edits ([e6fd4f6](https://git.personal.ksv741.ru/WealthLedger/docs/commit/e6fd4f64b81dd73b33959b845a1babcfac1891c8))
### Maintenance
* align docs engineering workflow (WealthLedger/main[#27](https://git.personal.ksv741.ru/WealthLedger/docs/issues/27)) ([eaf7ede](https://git.personal.ksv741.ru/WealthLedger/docs/commit/eaf7ede8414f0df972f9ae7ab9e223a6350b5462))
## [0.1.3](https://git.personal.ksv741.ru/WealthLedger/docs/compare/v0.1.2...v0.1.3) (2026-10-07)
### Bug Fixes
* **deps:** refresh docs security baseline ([256d49b](https://git.personal.ksv741.ru/WealthLedger/docs/commit/256d49b04591d55d980a22c49b413432c9bda746))
* **deps:** remove vulnerable docs tooling ([9477e68](https://git.personal.ksv741.ru/WealthLedger/docs/commit/9477e688d180783881abc92f8835517715880279))
### Documentation
* record code quality review and refactoring ([6fa62fb](https://git.personal.ksv741.ru/WealthLedger/docs/commit/6fa62fb8b51aba82766fb50b3e5871572af8be27))
* record minimal runtime coordination ([0ee54ca](https://git.personal.ksv741.ru/WealthLedger/docs/commit/0ee54ca952db6ca7264c0d7fe8cf5759b3187c5b))
## [0.1.2](https://git.personal.ksv741.ru/WealthLedger/docs/compare/v0.1.1...v0.1.2) (2026-09-26) ## [0.1.2](https://git.personal.ksv741.ru/WealthLedger/docs/compare/v0.1.1...v0.1.2) (2026-09-26)
### Documentation ### Documentation
+3 -4
View File
@@ -177,10 +177,9 @@ Markdown-only правки не требуют тестов приложения
`security.yml` при push, PR и ежедневно проверяет всю Git-историю через `security.yml` при push, PR и ежедневно проверяет всю Git-историю через
Gitleaks 8.30.1 с redacted-выводом и все npm dependencies через Gitleaks 8.30.1 с redacted-выводом и все npm dependencies через
`npm audit --include=dev --audit-level=low`. Overrides `smol-toml` и `js-yaml` `npm audit --include=dev --audit-level=low`. Override `smol-toml` исправляет
исправляют DoS в parsers markdownlint-cli; overrides для release-it обновляют DoS в TOML parser markdownlint-cli2; его можно удалить, когда родительский
уязвимые basic-ftp и undici. Их можно удалить, когда родительские пакеты пакет примет исправленную версию.
примут исправленные версии.
Renovate объединяет minor/patch updates; major требует согласования в Renovate объединяет minor/patch updates; major требует согласования в
Dependency Dashboard, automerge не включён. Required checks включаются Dependency Dashboard, automerge не включён. Required checks включаются
+17 -42
View File
@@ -35,7 +35,7 @@ WealthLedger помогает частному инвестору понимат
| [Операции](domains/operations/overview.md) | Исполненные операции, фильтры и пагинация | | [Операции](domains/operations/overview.md) | Исполненные операции, фильтры и пагинация |
| [Доход](domains/income/overview.md) | Полученные купоны и дивиденды, периоды и состав расчёта | | [Доход](domains/income/overview.md) | Полученные купоны и дивиденды, периоды и состав расчёта |
| [Будущие выплаты](domains/income/upcoming-events.md) | Календарь по текущим позициям и ограничения прогноза | | [Будущие выплаты](domains/income/upcoming-events.md) | Календарь по текущим позициям и ограничения прогноза |
| [Скринер акций](domains/instruments/stocks.md) и [облигации](domains/instruments/bonds.md) | Подбор акций по параметрам, поиск выпуска, рыночные показатели и личная история | | [Акции](domains/instruments/stocks.md) и [облигации](domains/instruments/bonds.md) | Поиск выпуска, рыночные показатели и личная история |
## Найти материал по задаче ## Найти материал по задаче
@@ -72,60 +72,35 @@ WealthLedger помогает частному инвестору понимат
## Релизы ## Релизы
Репозиторий выпускается независимо. `develop` — интеграция, `main` — принятые Репозиторий выпускается независимо. `develop` — интеграция, `main` — принятые
релизы. Обычная работа: issue-linked ветка от актуального `develop`, PR в `develop`; релизы. Работа: `codex/<task>` от актуального `develop`, PR в `develop`.
общие правила — в Engineering Workflow по ссылке из AGENTS. Обычные PR допускают squash; release и обратная синхронизация сохраняют историю.
PR сливается squash или rebase-merge; merge-коммит не используется. `main` Commits и PR titles — Conventional Commits, включая `spec:`. Версии вручную не повышай.
обновляется только fast-forward из `develop`, поэтому история линейна:
```text 1. Получи `main`, `develop` и теги: `git fetch origin --tags`. Убедись, что
<type>/<issue>-<slug> --squash--> develop --ff-only--> main --> tag vX.Y.Z `develop` содержит последний релиз. Создай `release/<name>` от проверенного
``` `develop`; `nvm use --silent`, `npm ci`. Hotfix начинается от `main` в
`release/hotfix-<name>` и проходит тот же процесс.
Инвариант: `origin/main` всегда предок `origin/develop`. Проверка:
```bash
git fetch -q origin
git merge-base --is-ancestor origin/main origin/develop && echo ok || echo BROKEN
```
Commits и PR titles — Conventional Commits со ссылкой на Issue (кроме release),
включая `spec:`. Версии вручную не повышай.
1. Получи `main`, `develop` и теги: `git fetch origin --tags`. Проверь
инвариант. Создай `release/X.Y.Z` от проверенного `develop`;
`nvm use --silent`, `npm ci`.
2. На чистой ветке выполни `npm run release:preview`, просмотри версию/changelog, 2. На чистой ветке выполни `npm run release:preview`, просмотри версию/changelog,
затем `npm run release:prepare`. Команда создаёт `chore(release): X.Y.Z`, затем `npm run release:prepare`. Команда создаёт `chore(release): X.Y.Z`,
обновляет manifest/lockfile/CHANGELOG; тег и push не выполняет. Не обходи обновляет manifest/lockfile/CHANGELOG; тег и push не выполняет. Не обходи
guards ветки, чистого дерева, отсутствующих тегов или уже подготовленной версии. guards ветки, чистого дерева, отсутствующих тегов или уже подготовленной версии.
3. Проверь diff и штатные gates этого репозитория. Отправь только release-ветку, 3. Проверь diff и штатные gates этого репозитория. Отправь только release-ветку,
открой PR в **`develop`** с заголовком `chore(release): X.Y.Z`. После зелёных открой PR в `main` с заголовком `chore(release): X.Y.Z`. После зелёных
`verify`, `security`, `commits` выполни fast-forward-only merge. Если `verify`, `security`, `commits` выполни fast-forward-only merge. Если `main`
`develop` изменился, обнови release-ветку и повтори проверки кандидата. изменился, согласуй историю и повтори проверки кандидата.
4. Открой PR `develop → main` с тем же заголовком и слей fast-forward-only. 4. Получи удалённый `main`, сравни его HEAD с проверенным release SHA и версией
5. Получи удалённый `main`, сравни его HEAD с проверенным release SHA и версией
manifest/lockfile. Создай аннотированный `vX.Y.Z` именно на этом SHA: manifest/lockfile. Создай аннотированный `vX.Y.Z` именно на этом SHA:
`git tag -a vX.Y.Z <release-sha> -m "Release X.Y.Z"`, затем отправь только `git tag -a vX.Y.Z <release-sha> -m "Release X.Y.Z"`, затем отправь только
`git push origin refs/tags/vX.Y.Z`. Не запускай release-it повторно на `main`. `git push origin refs/tags/vX.Y.Z`. Не запускай release-it повторно на `main`.
5. Верни release-коммит через PR `main → develop` без squash/rebase; проверь,
Обратная синхронизация не нужна: release-коммит попал в `develop` на шаге 3. что `develop` содержит тег. Опубликованные теги не перемещай. При сбое после
Опубликованные теги не перемещай. При сбое после merge создай отсутствующий тег merge создай отсутствующий тег без нового bump; до merge исправь и проверь
без нового bump; до merge исправь и проверь существующий кандидат без существующий кандидат без повторного повышения версии.
повторного повышения версии.
Freeze релиза не поддерживается: пока открыт релизный PR, `develop` не двигают.
Срочный fix — обычная ветка `fix/*` от `develop` с последующим релизом;
пути от `main` нет, он сделал бы `main` не-предком `develop`. Исключение
требует явного решения: `release/hotfix-<name>` от `main`, PR в `main`, тег,
затем merge `main → develop` для восстановления инварианта.
`feat:` повышает minor, `fix:`/`perf:` и прочие разрешённые типы — patch; `feat:` повышает minor, `fix:`/`perf:` и прочие разрешённые типы — patch;
`!`/`BREAKING CHANGE:` — major, включая версии до 1.0. `chore(release):` `!`/`BREAKING CHANGE:` — major, включая версии до 1.0. `chore(release):`
исключён из рекомендации и changelog. Конфигурация — `.release-it.json`. исключён из рекомендации и changelog. Конфигурация — `.release-it.json`.
В Gitea для `main`/`develop` должны быть запрещены прямой/force push и обход В Gitea для `main`/`develop` должны быть запрещены прямой/force push и обход
администратором, включена блокировка устаревшей ветки, обязательны `verify`, администратором, обязательны `verify`, `security`, `commits`; теги `v*` защищены.
`security`, `commits`; теги `v*` защищены. Линейность обеспечивают merge styles
репозитория: merge-коммиты и rebase `--no-ff` выключены, доступны squash,
rebase-merge и fast-forward-only; стиль по умолчанию — squash.
Workflow сам не включает эти настройки. Отдельное review approval для одного Workflow сам не включает эти настройки. Отдельное review approval для одного
владельца не требуется. Npm publishing, deploy и Gitea Releases не включены. владельца не требуется. Npm publishing, deploy и Gitea Releases не включены.
@@ -1,8 +1,6 @@
# 017. Локальное чтение данных T-Bank и фоновая синхронизация # 017. Локальное чтение данных T-Bank и фоновая синхронизация
**Status:** Accepted — историческое архитектурное направление. Решения ниже о **Status:** Accepted — архитектурное направление согласовано.
request-driven запуске, ручном обновлении и отсутствии периодического обхода
заменены [ADR 019](019-hourly-reads-and-online-instrument-reference.md).
Prefetch policy ниже заменена [ADR 018](018-minimal-runtime-coordination.md): Prefetch policy ниже заменена [ADR 018](018-minimal-runtime-coordination.md):
свечи загружаются по запрошенному диапазону, без предварительной загрузки свечи загружаются по запрошенному диапазону, без предварительной загрузки
@@ -1,37 +0,0 @@
# 019. Часовой обход и онлайн-справочник инструментов
**Status:** Accepted. Заменяет request-driven, manual-refresh и
no-periodic-walk части [ADR 017](017-local-tbank-reads-and-background-sync.md)
и дополняет [ADR 018](018-minimal-runtime-coordination.md).
## Decision
- Личные T-Bank данные текущего подключения обновляются владельцами источников
через durable hourly walk; его результат ограничен этой connection boundary.
Обычный GET и retry читают только сохранённый результат и не запускают
personal work.
- Только первое открытие неподтверждённого исторического интервала operations
и первого неоткрытого диапазона candles может получить соответственно этот
интервал или диапазон. Это не manual sync; повторное чтение не расширяет и не
запускает такую работу заново.
- Portfolio, operations, forecasts и события сохраняют собственные complete
результаты и время последнего успешного чтения. Результат ресурса
(freshness, completeness, `lastSuccessfulAt`, availability) независим от
resource-relevant последней часовой попытки: поздняя partial/failed попытка
не заменяет сохранённый complete result. При его отсутствии `data: null`.
- Detail, company, catalog search и ticker lookup получают ответ онлайн через
существующие integration clients. Их ошибки не заменяют сохранённые
финансовые данные или подписи snapshot.
- Старые справочные строки не являются источником актуальности и удаляются
только отдельной совместимой миграцией после проверки FK и свечей.
- Клиент повторяет открытые инвестиционные GET не чаще одного раза в 30 минут;
retry повторяет чтение и не создаёт screen sync work. Наблюдение последней
попытки, включая Accounts `sync`, не является командой.
## Consequences
Карточки инструментов зависят от доступности публичного upstream в момент
чтения. Финансовые экраны сохраняют последний успешный snapshot, а статус
последнего hourly walk показывается отдельно. OpenAPI backend остаётся
единственным исполняемым источником HTTP-контракта; frontend использует
опубликованный snapshot.
@@ -1,42 +0,0 @@
# 020. Evidence for hourly sync read boundaries
**Status:** Accepted. Evidence for [ADR 019](019-hourly-reads-and-online-instrument-reference.md).
## Measurements
The backend persisted-read budget was measured with `npm run test:performance`
using a synthetic SQLite dataset: three accounts, 100,000 operations per
account, 200 instruments, and 5,000 candles per instrument. The run used Node
24.21.0 on Apple M1 Pro with 32 GiB RAM and a failing synthetic upstream after
population. It measured 100 reads per route family, excluding transport
limiter cost.
The generated evidence is written by the test to its temporary
`persistent-budget.json` file. This is a repeatable measurement, not a
production SLA: it does not include network latency, real T-Bank quotas, or
multi-process SQLite contention.
The successful run produced a 617,263,104-byte SQLite database and measured
zero upstream calls during the persisted-read phase. Backend p50/p95 response
times were approximately 1.3/2.3 ms for account and portfolio reads,
3.9/4.5 ms for account operations, 6.7/7.2 ms for aggregate operations,
40.0/41.5 ms for account income analytics, and 128.1/134.2 ms for aggregate
income analytics. These figures are local synthetic measurements only.
## Durability
The migration durability check passes against a separately built repository at
the pre-history migration prefix. It verifies preservation of users, sessions,
T-Bank connection data, bank-card data, operations, categories, archive state,
and replay behavior across migration and restart.
The executed command was `node test/bank-cards-durability.cjs` with a separately
built pre-history repository. It completed with `PASS durability`.
## Limits
The configured T-Bank start limiter is `TBANK_RATE_LIMIT_PER_SECOND`, default
`5` per backend instance. The hourly scheduler therefore remains a single
durable reservation owner; the measurement does not justify increasing this
limit or changing the hourly cadence. Any production tuning requires current
T-Bank documentation and a new measurement with the deployed topology.
@@ -1,66 +0,0 @@
# 021. Тихое чтение и редкие подтверждения
**Status:** Accepted. Заменяет manual-refresh часть
[ADR 019](019-hourly-reads-and-online-instrument-reference.md) на стороне
интерфейса и уточняет клиентскую часть
[ADR 007](007-react-query-fetch-policy.md). Не меняет владельцев обновления
и границы чтения из ADR 019.
## Context
Экраны просмотра перекладывали на пользователя контроль над получением данных:
кнопки «Обновить» и «Повторить», индикатор фонового обновления и сообщение
о неудачной синхронизации повторялись в разных разделах. Пользователь приходит
смотреть финансы, а не обслуживать загрузку; восстановление после временного
сбоя не должно зависеть от нажатия кнопки.
При этом состояние самих данных остаётся значимым: время последнего успешного
результата источника, устаревание и исход часовой попытки влияют на доверие
к показанным суммам и не могут быть просто скрыты.
## Decision
- Автоматическое чтение клиента не сообщает о себе. Экраны просмотра не
показывают кнопок ручного обновления и повтора, индикаторов фонового
перечитывания и технических предупреждений о собственном запросе.
- Временные сбои GET восстанавливаются сами: ограниченный срок ожидания запроса
и не более двух повторов с возрастающими паузами, затем возврат к штатной
работе при появлении результата. Пользователь не инициирует восстановление.
- Состояние источника из `meta` остаётся видимым. Время последнего успешного
результата, признак устаревания, исход последней часовой попытки и неполнота
показываются текстом либо доступно именованным элементом, а не только цветом.
Это свойство данных, а не отчёт о техническом процессе, и оно не превращается
в действие пользователя.
- Доступный результат сохраняется при обновлении и временной ошибке. Неполнота
и подтверждённая пустота остаются различимыми: неизвестная сумма не становится
нулём, незавершённый расчёт не выдаётся за окончательный, незагруженный список
не выдаётся за отсутствие записей.
- Первичное ожидание ограничено по времени и показывает структуру будущего
содержимого. По его истечении экран переходит в спокойную локальную
недоступность вместо бесконечной загрузки и сам заменяет её содержимым после
восстановления. Сбой одного блока не скрывает остальные доступные блоки.
- Ошибки явных действий пользователя сохраняются и остаются видимыми. Решение
касается только чтения; отправка формы, изменение данных и сброс фильтра
по-прежнему сообщают результат, в том числе неуспешный.
## Consequences
Пользователь не может ускорить обновление вручную: нормальным владельцем
обновления остаётся часовой обход из ADR 019, и отсутствие кнопки не меняет его
срок. Если источник недоступен долго, экран показывает прежние данные с
обозначением устаревания, а не приглашение повторить.
Автоматический повтор ограничен: он покрывает короткие сетевые сбои, но не
длительную недоступность. Ошибки доступа, домена и отмены не повторяются,
mutations не повторяются вовсе.
Состояние источника и состояние клиентского чтения описывают разные вещи и
не подменяют друг друга. `Portfolio.isStale` сохраняет смысл возраста снимка;
ошибка клиентского перечитывания его не меняет.
Детали поведения — в
[ожидании и восстановлении интерфейса](../architecture/cross-cutting/ui-resilience.md)
и [свежести данных](../architecture/cross-cutting/data-freshness.md).
Согласованные требования принадлежат workspace-спецификациям
`frontend-user-feedback`, `frontend-data-resilience` и
`frontend-loading-presentation`.
-3
View File
@@ -23,6 +23,3 @@
- [016 — Версионируемый комплект развёртывания](016-versioned-deployment-bundle.md) - [016 — Версионируемый комплект развёртывания](016-versioned-deployment-bundle.md)
- [017 — Локальное чтение данных T-Bank и фоновая синхронизация](017-local-tbank-reads-and-background-sync.md) - [017 — Локальное чтение данных T-Bank и фоновая синхронизация](017-local-tbank-reads-and-background-sync.md)
- [018 — Минимальная runtime-координация](018-minimal-runtime-coordination.md) - [018 — Минимальная runtime-координация](018-minimal-runtime-coordination.md)
- [019 — Часовой обход и онлайн-справочник инструментов](019-hourly-reads-and-online-instrument-reference.md)
- [020 — Evidence for hourly sync read boundaries](020-hourly-sync-verification-evidence.md)
- [021 — Тихое чтение и редкие подтверждения](021-quiet-reading-and-rare-confirmations.md)
+8 -10
View File
@@ -140,15 +140,12 @@ membership текущей revision перед каждым финансовым
личные данные. Ротация refresh token остаётся атомарной. личные данные. Ротация refresh token остаётся атомарной.
SQLite хранит полный последний portfolio, операции и их подтверждённые SQLite хранит полный последний portfolio, операции и их подтверждённые
диапазоны, private прогнозы, сохранённые точки истории оценки, общие диапазоны, private прогнозы, общие instrument/market sources и intent/staging.
instrument/market sources и intent/staging.
Нормализованные финансовые значения сохраняются decimal-строками. Полнота Нормализованные финансовые значения сохраняются decimal-строками. Полнота
отделена от свежести и пагинации. Ответ не ждёт внешних RPC; обновлением отделена от свежести и пагинации. Ответ не ждёт внешних RPC; запрос при
личных данных владеет часовой обход в том же backend process через необходимости запускает работу в том же backend process через существующий
существующий лимитер, недостающие общие источники он же ставит в фоновую лимитер. На startup импорт не запускается; восстановление требует следующего
очередь. На startup обход начинает следующую попытку по обычному часовому запроса. Подробности — [модель свежести](cross-cutting/data-freshness.md).
сроку; после нового подключения первая попытка начинается сразу.
Подробности — [модель свежести](cross-cutting/data-freshness.md).
PUT подключения сначала проверяет token у T-Bank, затем одной транзакцией PUT подключения сначала проверяет token у T-Bank, затем одной транзакцией
сохраняет encrypted token, новую revision и проверенный membership. сохраняет encrypted token, новую revision и проверенный membership.
@@ -198,8 +195,9 @@ access token и вращаемый refresh token. [Политика query-кэш
Авторизация доступа, недопущение token в публичный ответ и атомарность Авторизация доступа, недопущение token в публичный ответ и атомарность
ротации сессии — отдельные свойства; наличие типизированного клиента ротации сессии — отдельные свойства; наличие типизированного клиента
не доказывает их. Действующий формат ошибок описан в не доказывает их. Известные расхождения регистрации и деталей валидации
[HTTP API](http-api.md#error-contract). отмечены в [аутентификации](../domains/authentication/overview.md#регистрация-и-вход)
и [HTTP API](http-api.md#error-contract).
Поддерживается локальный Compose и ограниченная single-server topology с SQLite. Поддерживается локальный Compose и ограниченная single-server topology с SQLite.
Origin браузера, адрес API и cookie policy должны быть согласованы. Origin браузера, адрес API и cookie policy должны быть согласованы.
+35 -56
View File
@@ -1,72 +1,51 @@
# Свежесть данных и повторное использование # Свежесть данных и повторное использование
WealthLedger читает личные инвестиционные данные из SQLite. Обычный GET и его WealthLedger читает инвестиционные данные из SQLite. Обычный GET возвращает
HTTP retry возвращают сохранённый результат и не запускают личную работу с сохранённый результат и запрашивает фоновые зависимости, если они отсутствуют
T-Bank. Нормальным владельцем обновления служит часовой обход текущего или устарели. HTTP-ответ не ждёт T-Bank. Модель принята в
подключения: после успешного подключения он начинает первую попытку, затем [ADR 017](../../adr/017-local-tbank-reads-and-background-sync.md).
начинает следующую не ранее чем через 60 минут от начала предыдущей. После
простоя выполняется не более одна актуальная попытка, а перекрывающийся срок
пропускается, не ставится в очередь. Общие справочные данные инструментов
запрашиваются онлайн, когда это требуется карточке; финансовый HTTP-ответ не
ждёт T-Bank. Модель принята в
[ADR 019](../../adr/019-hourly-reads-and-online-instrument-reference.md).
## Клиентский и серверный снимки ## Клиентский и серверный снимки
[Client Cached Snapshot](../../glossary.md#client-cached-snapshot) — успешный [Client Cached Snapshot](../../glossary.md#client-cached-snapshot) — успешный
ответ в TanStack Query. Он сохраняет показанное содержимое при временной ошибке ответ в TanStack Query. Он сохраняет показанное содержимое при временной ошибке
HTTP-перечитывания в той же границе доступа. Это не серверный fallback и не запроса в той же границе доступа. SQLite сохраняет источники независимо от
состояние часового обхода. SQLite независимо от браузера хранит membership, браузера: membership, последний полный portfolio, исполненные операции,
последний полный portfolio, исполненные операции, прогнозы, общие сведения об общие сведения об инструментах, котировки, свечи и календари.
инструментах, котировки, свечи и календари.
[Полнота периода](../../glossary.md#полнота-истории-счёта) и свежесть различаются. [Полнота периода](../../glossary.md#полнота-истории-счёта) и свежесть различаются.
Успешно проверенный пустой результат полон и доступен; последняя страница Успешно проверенный пустой интервал полон; последняя страница журнала сама
журнала сама по себе полноту не доказывает. Результат каждого личного ресурса по себе полноту не доказывает. Ошибка сверки сохраняет прежние данные и время.
в `{ data, meta }` описывают пять независимых значений: freshness, Старый полный портфель не удаляется по истечении TTL. `isStale` показывает
completeness, `lastSuccessfulAt`, `lastAttempt` и availability. Первые, возраст portfolio source, а не сам факт неудачного обновления.
полнота и `lastSuccessfulAt` относятся только к возвращённому подтверждённому
результату. `lastAttempt` описывает относящуюся к ресурсу часовую попытку:
`none`, `running`, `succeeded`, `partial` или `failed`.
Итог попытки выводится из фактических результатов её шагов: попытка, не | Источник | Когда обычный запрос требует обновления |
завершившая обязательный шаг, не обозначается полным успехом, а полученные
успешные результаты сохраняются. Ожидание каждого шага ограничено; зависший
шаг учитывается проблемным, а начатое обращение к T-Bank не отменяется.
Попытка, потерявшая выполнение после сбоя сохранения или остановки процесса,
закрывается частичным успехом либо неуспехом и не блокирует следующий обход
дольше обычного часового срока.
Поздняя неуспешная или частичная попытка сохраняет прежний полный результат и
его исходное время. Если полного результата нет, `data` равно `null`: состояние
availability объясняет первоначальную загрузку, временную недоступность либо
недоступность подключения. Пустой массив не означает отсутствие результата.
Старый полный portfolio не удаляется из-за возраста. `Portfolio.isStale`
сохраняет смысл возраста его snapshot, а не обозначает неуспех обхода или
клиентскую ошибку перечитывания.
| Источник | Владелец нормального обновления |
| --- | --- | | --- | --- |
| Membership, portfolio, изменяемый хвост операций и события | Часовой обход текущего подключения | | Portfolio, котировки, изменяемый хвост дневных свечей | Через 60 секунд |
| Котировки и изменяемый хвост дневных свечей | Установленная политика источника; не ordinary personal GET | | Membership и изменяемый хвост операций | Через 5 минут |
| Metadata, поиск каталога и company | Онлайн при чтении | | Metadata, поиск каталога, company и календари | Через сутки |
| Полные прошлые интервалы операций и завершённые свечи | Не обновляются часовым обходом или обычным GET | | Полные прошлые интервалы операций и завершённые свечи | При явном обновлении показанного периода |
Два исключения разрешают только первое получение отсутствующего результата: TTL не создаёт таймер импорта. Запрошенная работа может закончиться после
первое открытие неподтверждённого исторического интервала операций получает ухода со страницы. После перезапуска нет автоматического обхода intent:
только этот интервал и возвращает локальные строки с неполным покрытием; первое следующий подходящий запрос повторяет незавершённый интервал с первой страницы,
открытие диапазона свечей без подтверждённого локального результата получает переиспользуя полные интервалы. Cursor брокера не служит durable checkpoint.
только этот диапазон. Ни одно исключение не обновляет membership, portfolio,
события, другой интервал или диапазон. Их HTTP retry также только перечитывает
локальное состояние. Подтверждённый полный интервал или диапазон остаётся
локальным.
Часовой обход сохраняет успешные разделы независимо, а failed section не Ручное «Обновить» перечитывает зависимости текущего экрана и применённый
заменяет прежний complete result. Отменой query lifecycle владеет TanStack период независимо от TTL. Повторная сверка полного периода публикуется одной
Query. Открытый экран перечитывает ресурс не чаще одного раза в 30 минут; транзакцией после всех страниц, интервалов и счетов, включая `all`.
это локальный HTTP read. Совместимый параметр `sync=observe`, пока он есть в Сбой сохраняет весь прежний результат. Первая загрузка может публиковать
маршруте, также является только чтением и не представляет команду обходу. полные малые интервалы и показывать частичный период без окончательных сумм.
[HTTP API](../http-api.md) описывает исполняемый контракт envelope.
Клиент наблюдает только известную активную работу открытого экрана.
Status GET и data GET с `sync=observe` не запускают работу. После изменения
состояния ответ попадает в ту же query identity; после ухода или завершения
наблюдение прекращается. Поздний ответ не заменяет уже принятый новый.
[HTTP API](../http-api.md) объясняет envelope и экранный refresh.
Отменой query lifecycle владеет TanStack Query. Status не пересобирает
финансовые ответы и не передаёт hash notifications. Свечи нового диапазона
загружаются при его открытии; предзагрузка всей истории портфеля не выполняется.
## Смена подключения ## Смена подключения
+10 -26
View File
@@ -16,17 +16,11 @@
пока неизвестно, доступен public или protected маршрут. При загрузке пока неизвестно, доступен public или protected маршрут. При загрузке
отложенного маршрута уже доступный каркас остаётся видимым. отложенного маршрута уже доступный каркас остаётся видимым.
Фоновое чтение не сообщает о себе: оно сохраняет видимые данные без индикации Фоновое обновление и действие пользователя сохраняют видимые данные или
процесса и без технического предупреждения. В частности, элемент управления с компактной индикацией процесса. В частности,
[клиентский снимок](data-freshness.md#клиентский-и-серверный-снимки) счетов или [клиентский снимок](data-freshness.md#клиентский-и-серверный-снимки) счетов или
портфеля остаётся доступным после ошибки refetch, и эта ошибка не означает портфеля остаётся доступным после ошибки refetch; сообщение и повтор
отсутствие инвестиций. Действие пользователя сохраняет видимые данные или относятся к обновлению, а не к отсутствию инвестиций.
элемент управления и сообщает свой результат, включая неуспешный.
Первичное ожидание ограничено по времени. По его истечении область переходит
в спокойную локальную недоступность вместо бесконечной загрузки и сама
заменяет её содержимым после восстановления. Решение принято в
[ADR 021](../../adr/021-quiet-reading-and-rare-confirmations.md).
Серверная полнота не выводится из query lifecycle. Missing показывает Серверная полнота не выводится из query lifecycle. Missing показывает
skeleton только отсутствующих секций, partial — подтверждённые строки и skeleton только отсутствующих секций, partial — подтверждённые строки и
@@ -34,30 +28,20 @@ skeleton только отсутствующих секций, partial — по
готовятся независимо. Только complete empty означает отсутствие операций. готовятся независимо. Только complete empty означает отсутствие операций.
Время источника не заменяется временем получения HTTP-ответа. Время источника не заменяется временем получения HTTP-ответа.
Экраны просмотра не предлагают ручное обновление или повтор чтения: временные Ручное обновление сохраняет показанный результат и applied filters; кнопка
сбои GET восстанавливаются сами ограниченным числом повторов, сохраняя недоступна во время уже запрошенной работы. Ошибка transport отличается от
показанный результат и applied filters. Ошибка transport отличается от
ошибки source work. Status observation прекращается после terminal outcome ошибки source work. Status observation прекращается после terminal outcome
или ухода; само истечение TTL не запускает импорт. Известный access loss или ухода; само истечение TTL не запускает импорт. Известный access loss
очищает personal view, включая защиту от позднего ответа. очищает personal view, включая защиту от позднего ответа.
Состояние источника при этом видимо: время последнего успешного результата,
устаревание, исход последней часовой попытки и неполнота показаны текстом либо
доступно именованным элементом рядом с данными, к которым относятся. Это
свойство данных, а не отчёт о техническом процессе, и оно не требует действия.
## Известная ошибка сценария ## Известная ошибка сценария
Ошибки счетов, портфеля и подключения T-Bank остаются локальными: затронутый Ошибки счетов, портфеля и подключения T-Bank остаются локальными: рядом
блок показывает собственное состояние и не скрывает успешно отображённые с затронутыми данными доступны предусмотренные сценарием retry и navigation
соседние блоки. Устаревший серверный Portfolio Snapshot показывает сохранённый actions. Устаревший серверный Portfolio Snapshot показывает сохранённый
портфель с обозначением устаревания. Он не превращается в глобальный fallback. портфель с предупреждением. Он не превращается в глобальный fallback.
Ошибка источника не означает нулевой баланс или отсутствие операций. Ошибка источника не означает нулевой баланс или отсутствие операций.
Предусмотренные сценарием navigation actions сохраняются: подтверждённая потеря
доступа предлагает восстановить подключение, а `404` — вернуться к списку
счетов. Это навигация и явные действия пользователя, а не повтор чтения.
## Непойманная ошибка маршрута ## Непойманная ошибка маршрута
Непойманная runtime-ошибка при отображении public или protected route Непойманная runtime-ошибка при отображении public или protected route
+5 -13
View File
@@ -12,7 +12,7 @@ Development-команды ниже выполняются из workspace с che
- Docker Engine с Compose plugin **не ниже 2.24.4** (`!override` в development), - Docker Engine с Compose plugin **не ниже 2.24.4** (`!override` в development),
доступ к registry и достаточно свободного диска для non-root UID 1000. доступ к registry и достаточно свободного диска для non-root UID 1000.
Опубликованные images собраны для Linux amd64 (см. README комплекта). Production images `0.2.3` опубликованы для Linux amd64.
Другую архитектуру проверяют отдельно; multi-platform images не обещаны. Другую архитектуру проверяют отдельно; multi-platform images не обещаны.
- Nexus Docker hosted repository `docker-hosted`, доступный через - Nexus Docker hosted repository `docker-hosted`, доступный через
`nexus.personal.ksv741.ru/docker-hosted/`. Docker Bearer Token Realm включён; `nexus.personal.ksv741.ru/docker-hosted/`. Docker Bearer Token Realm включён;
@@ -26,10 +26,7 @@ Development-команды ниже выполняются из workspace с che
используется сервером для загрузки образов. Для публикации нужны используется сервером для загрузки образов. Для публикации нужны
browse/read/add/edit на `docker-hosted`; фактически выданный набор прав browse/read/add/edit на `docker-hosted`; фактически выданный набор прав
настраивается в Nexus. Built-in настраивается в Nexus. Built-in
workflow token не заменяет эту настройку. Отдельной read-only учётной записи workflow token не заменяет эту настройку.
сервера нет: `wealthledger-ci` работает и как CI writer, и как server reader,
поэтому её пароль на сервере даёт право публикации в `docker-hosted`. Это
принятое владельцем решение; разделение ролей — возможное усиление.
- На сервере Caddy уже завершает TLS и передаёт весь домен на - На сервере Caddy уже завершает TLS и передаёт весь домен на
`127.0.0.1:8080` (или выбранный frontend port). Отдельного `/api` upstream нет. `127.0.0.1:8080` (или выбранный frontend port). Отдельного `/api` upstream нет.
DNS, TLS, firewall и provisioning Caddy выполняются вне этого runbook. DNS, TLS, firewall и provisioning Caddy выполняются вне этого runbook.
@@ -84,8 +81,7 @@ Workflow отказывается перезаписывать существу
Выберите опубликованные version tags, выполните pull и сравните OCI revision Выберите опубликованные version tags, выполните pull и сравните OCI revision
с release commit. Выполните smoke с disposable data/config до первого server с release commit. Выполните smoke с disposable data/config до первого server
rollout. В `release.env` комплекта хранится пара полных image references, например rollout. В `release.env` комплекта хранится пара полных image references, например
`nexus.personal.ksv741.ru/docker-hosted/wealthledger/frontend:1.1.0` и `nexus.personal.ksv741.ru/docker-hosted/wealthledger/frontend:0.2.3`. Сохраните versions/revisions
`.../backend:2.1.0`. Сохраните versions/revisions
в deployment record. Release tags не должны переназначаться другим images. в deployment record. Release tags не должны переназначаться другим images.
## Перенос существующих images из Gitea ## Перенос существующих images из Gitea
@@ -95,9 +91,7 @@ rollout. В `release.env` комплекта хранится пара полн
`REGISTRY_USERNAME`/`REGISTRY_TOKEN` сохраните для отката старого workflow. `REGISTRY_USERNAME`/`REGISTRY_TOKEN` сохраните для отката старого workflow.
Пароли вводятся интерактивно или через `--password-stdin`, не через аргументы. Пароли вводятся интерактивно или через `--password-stdin`, не через аргументы.
Прежние images лежат в [Gitea packages](https://git.personal.ksv741.ru/WealthLedger/-/packages) Скопируйте каждый опубликованный release без пересборки. Например, для `0.2.0`
(`git.personal.ksv741.ru/wealthledger/{frontend,backend}`); новые публикуются
только в Nexus. Скопируйте каждый опубликованный release без пересборки. Например, для `0.2.0`
с установленным Skopeo и доступом чтения Gitea/записи Nexus: с установленным Skopeo и доступом чтения Gitea/записи Nexus:
```bash ```bash
@@ -120,9 +114,7 @@ artifact повторно не публикуйте; несовпадение т
После копирования сравните digests через `skopeo inspect --raw` и SHA-256, После копирования сравните digests через `skopeo inspect --raw` и SHA-256,
выполните `docker pull` обоих images пользователем `wealthledger-ci` на сервере приложения, выполните `docker pull` обоих images пользователем `wealthledger-ci` на сервере приложения,
проверьте OCI revision и container smoke на disposable data. проверьте OCI revision и container smoke на disposable data.
Серверные шаги (login, pull, smoke) в репозитории не подтверждены и Только после этого меняйте production image references и запускайте обычную
выполняются оператором. Только после них меняйте
production image references и запускайте обычную
последовательность обновления ниже. Старые Gitea packages и references сохраните: последовательность обновления ниже. Старые Gitea packages и references сохраните:
откат registry возвращает прежние адреса тех же artifacts, не восстанавливает откат registry возвращает прежние адреса тех же artifacts, не восстанавливает
SQLite и не меняет encryption keys. Не переносите Git release tags и не SQLite и не меняет encryption keys. Не переносите Git release tags и не
+30 -48
View File
@@ -2,8 +2,7 @@
## Источник контракта ## Источник контракта
Backend генерирует OpenAPI из собственного публичного API. Этот сгенерированный Backend генерирует OpenAPI из собственного публичного API. Frontend использует
OpenAPI — исполняемый источник HTTP-контракта. Frontend использует
предоставленный OpenAPI snapshot как актуальный вход и локально генерирует предоставленный OpenAPI snapshot как актуальный вход и локально генерирует
типизированный клиент. Backend владеет контрактом, а подготовка и доставка типизированный клиент. Backend владеет контрактом, а подготовка и доставка
схемы находятся вне frontend. Это разделение принято в схемы находятся вне frontend. Это разделение принято в
@@ -21,57 +20,35 @@ artifact у себя и проверяет соответствие generated ty
API имеет prefix `/api/v1`; Swagger публикуется для локальной разработки по API имеет prefix `/api/v1`; Swagger публикуется для локальной разработки по
environment policy, а не используется frontend в runtime. environment policy, а не используется frontend в runtime.
## Скринер акций
`POST /api/v1/stocks/screener/search` всегда создаёт новый результат по
условиям скринера, включая пустой подтверждённый список. Ответ первой страницы
содержит непрозрачный `searchId`, время получения каталога и признак следующей
страницы. `GET /api/v1/stocks/screener/search/{searchId}?page=...` читает
последующую страницу того же результата, не запускает новый поиск и сохраняет
состав и время списка. Идентификатор результата принадлежит текущему пользователю
и его подключению; недоступный или чужой результат возвращает ошибку, а не
пустой список. Точные поля и статусы определяет backend-generated OpenAPI.
## Инвестиционные данные и синхронизация ## Инвестиционные данные и синхронизация
Затронутые GET инвестиционных ресурсов возвращают сгенерированный contract T-Bank data GET возвращают envelope `{ data, meta }`. `data` может быть `null`
`{ data, meta }`. `data` nullable: `null` означает отсутствие complete result, при missing или неполном финансовом агрегате; partial ledger/candles могут
а подтверждённый пустой массив является available complete result. `meta` содержать подтверждённые строки. `meta` разделяет availability, freshness,
независимо описывает freshness, completeness, `lastSuccessfulAt`, lastSuccessfulAt, sync, coverage и состояния зависимостей. `dataRevision`
resource-relevant `lastAttempt` и availability. `lastAttempt` имеет состояния описывает набор данных; пустое завершённое покрытие явно представлено в coverage.
`none`, `running`, `succeeded`, `partial` и `failed`; его неуспех не заменяет Публичной hash-версии состояния нет. Ни одно поле не раскрывает connection revision.
сохранённый complete result или его время. `Portfolio.isStale` остаётся
специфичным для snapshot полем, а не заменой состояния попытки.
Только Accounts дополнительно возвращает top-level `sync`: `null`, если у Обычный GET планирует missing/stale зависимости после проверки доступа.
текущего подключения ещё не было часовой попытки, иначе connection-wide GET с `sync=observe` только читает. `POST /api/v1/tbank-sync` принимает
наблюдаемый результат. Это наблюдение, не команда. Обычный GET и retry после валидированный screen descriptor и возвращает `202` с handle и нормализованным
проверки доступа читают локальное состояние и не начинают личную работу. scope. `GET /api/v1/tbank-sync/:id` наблюдает разрешённую работу; чужой или
Устаревший `sync=observe`, пока он сохранён для совместимости маршрута, также прежний handle возвращает `404`. Приём работы не означает её успех.
только читает. Request-started screen-sync command/status не является текущей
AS-IS моделью.
Безопасные ошибки завершённой часовой попытки передаются в `lastAttempt` и Status возвращает state/errorCode без данных ресурсов и численных progress
Accounts `sync`; HTTP transport/access/input errors сохраняют Problem Details. counters. Клиент перечитывает показанные resources через `sync=observe` во время
Старые данные при временной source failure сохраняют исходное успешное время. работы и после завершения, сохраняя generated query identity. Sync states ресурса:
Ни одно поле не раскрывает connection revision. Детали поведения — `idle`, `running`, `failed`; отдельного публичного `queued` нет.
Source failure принятой работы передаётся безопасным `meta.sync.errorCode`;
HTTP transport/access/input errors сохраняют Problem Details. Старые данные
при transient failure сохраняют исходное успешное время. Детали поведения —
[свежесть и полнота](cross-cutting/data-freshness.md). [свежесть и полнота](cross-cutting/data-freshness.md).
Envelope несовместим с прежними bare-array ответами: backend/frontend Envelope несовместим с прежними bare-array ответами: backend/frontend
поставляются согласованной парой. Финальный artifact передаётся явно; поставляются согласованной парой. Финальный artifact передаётся явно;
локальная генерация и CI frontend читают только собственный snapshot. локальная генерация и CI frontend читают только собственный snapshot.
## Income analytics
`GET /api/v1/brokerage-accounts/{accountId}/income-analytics` и общий маршрут
`all` возвращают результат только выбранного периода: итоги по исходным
валютам, точные записи выплат, разбиение по инструментам и временные
интервалы. Данных сравнения с предыдущим периодом или годом ранее в ответе
нет — ни в теле, ни среди зависимостей `meta.dependencies`, роль которых
ограничена значением `selected`. Публичную машинную схему задаёт
backend-generated OpenAPI; frontend получает её обновлённый snapshot отдельно.
Точную семантику поведения описывает [доход](../domains/income/overview.md).
## Validation и identity ## Validation и identity
Backend преобразует и проверяет входные данные на общей HTTP-границе. Backend преобразует и проверяет входные данные на общей HTTP-границе.
@@ -93,11 +70,16 @@ message/stack. Типичный ответ:
Для input validation контракт требует `400`, `VALIDATION_ERROR` и `errors` как Для input validation контракт требует `400`, `VALIDATION_ERROR` и `errors` как
массив `{ pointer, detail }`, где pointer адресует поле запроса. Это требование массив `{ pointer, detail }`, где pointer адресует поле запроса. Это требование
проверяется реальным HTTP-ответом после всех стадий обработки запроса. Глобальная не считается проверенным одним описанием OpenAPI: нужна проверка реального
валидация передаёт фильтру структурированные ошибки без исходных значений и DTO, HTTP-ответа после всех стадий обработки запроса.
а фильтр формирует JSON Pointer, включая вложенные поля. HTTP suites подтверждают
детали ошибок регистрации, банковской карты и синхронизации; OpenAPI отдельно **Известное расхождение:** согласованная
фиксирует форму ответа. [спецификация аутентификации](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/user-authentication/spec.md#requirement-стандартные-ответы-об-ошибках)
требует `pointer` и `detail` для невалидных полей. В текущей конфигурации
валидация формирует строковые сообщения, а фильтр ожидает структурированные
ошибки полей; поэтому корректность деталей `errors` не подтверждена.
Исправление требует отдельного изменения реализации и проверки полного
HTTP-потока. OpenAPI сам по себе этого расхождения не устраняет.
Ответ не включает автоматически все поля RFC (`type`, `title`, `instance`). Ответ не включает автоматически все поля RFC (`type`, `title`, `instance`).
Добавление обязательного поля является изменением публичного контракта. Добавление обязательного поля является изменением публичного контракта.
+1 -9
View File
@@ -1,12 +1,4 @@
module.exports = { module.exports = {
extends: ["@commitlint/config-conventional"], extends: ["@commitlint/config-conventional"],
defaultIgnores: false, rules: { "type-enum": [2, "always", ["feat", "fix", "perf", "docs", "spec", "refactor", "build", "ci", "test", "style", "chore", "revert"]] },
ignores: [
message => /^chore\(release\): \d+\.\d+\.\d+$/.test(message.trim()),
message => /^Merge pull request ['"].+['"] \(#\d+\) from .+ into .+$/.test(message.trim()),
],
rules: {
"type-enum": [2, "always", ["feat", "fix", "perf", "docs", "spec", "refactor", "build", "ci", "test", "style", "chore", "revert"]],
"references-empty": [2, "never"],
},
}; };
+5 -6
View File
@@ -62,8 +62,8 @@
- `409 TBANK_NOT_CONNECTED`: список не запрашивается у T-Bank, экран ведёт в настройки. - `409 TBANK_NOT_CONNECTED`: список не запрашивается у T-Bank, экран ведёт в настройки.
- `400 INVALID_TBANK_TOKEN`: сохранённый токен не удаляется, экран ведёт в настройки для замены или удаления. - `400 INVALID_TBANK_TOKEN`: сохранённый токен не удаляется, экран ведёт в настройки для замены или удаления.
- `503 TBANK_UNAVAILABLE`: экран показывает спокойное состояние недоступности; чтение возобновляется автоматически. - `503 TBANK_UNAVAILABLE`: экран предлагает повторить загрузку или открыть настройки.
- Неизвестная ошибка: то же состояние недоступности без действия повтора. - Неизвестная ошибка: экран предлагает только повторить загрузку.
- Успешный пустой ответ: штатное состояние отсутствия инвестиционных счетов. - Успешный пустой ответ: штатное состояние отсутствия инвестиционных счетов.
Карточка ведёт на `/accounts/:accountId` по всей области и доступна с клавиатуры. Карточки расположены в одну колонку при любой ширине. Карточка показывает название, локализованный тип, крупный валютный итог и вторую валюту при наличии, число остальных валют, дату открытия без времени и видимое действие открытия. Отдельные цветные блоки «Акции» и «Облигации» показывают количество наименований бумаг. Недоступная или пустая сводка отображается локально; отсутствие сумм не скрывает известные количества бумаг, а недоступная сводка не подменяет их нулями. Наведение и видимый клавиатурный фокус подчёркивают переход; настройка уменьшения движения отключает анимацию. Карточка ведёт на `/accounts/:accountId` по всей области и доступна с клавиатуры. Карточки расположены в одну колонку при любой ширине. Карточка показывает название, локализованный тип, крупный валютный итог и вторую валюту при наличии, число остальных валют, дату открытия без времени и видимое действие открытия. Отдельные цветные блоки «Акции» и «Облигации» показывают количество наименований бумаг. Недоступная или пустая сводка отображается локально; отсутствие сумм не скрывает известные количества бумаг, а недоступная сводка не подменяет их нулями. Наведение и видимый клавиатурный фокус подчёркивают переход; настройка уменьшения движения отключает анимацию.
@@ -71,10 +71,9 @@
## Свежесть и границы ## Свежесть и границы
Обычное открытие использует query owner и локальный server snapshot. Обычное открытие использует query owner и локальный server snapshot.
«Обновить» перечитывает сохранённые membership и сводки доступных счетов; «Обновить» запрашивает membership и сводки доступных счетов; активная работа
активная работа наблюдается без нового импорта. Истечение TTL само не наблюдается без нового импорта. Истечение TTL само не запускает запрос.
запускает запрос: обновлением личных данных владеет часовой обход. Membership имеет пятиминутную свежесть, portfolio — минутную. Время списка
Membership и portfolio source имеют часовую свежесть. Время списка
не подменяет время каждой сводки. Список не заменяет полный не подменяет время каждой сводки. Список не заменяет полный
[портфель](../portfolio/overview.md), [журнал](../operations/overview.md) [портфель](../portfolio/overview.md), [журнал](../operations/overview.md)
или историю закрытых счетов. Для общего результата доступен или историю закрытых счетов. Для общего результата доступен
+7 -13
View File
@@ -11,20 +11,14 @@
Пользователь регистрируется или входит по email и паролю. Регистрация сразу Пользователь регистрируется или входит по email и паролю. Регистрация сразу
создаёт первую сессию. Внешние пробелы email удаляются, буквы приводятся создаёт первую сессию. Внешние пробелы email удаляются, буквы приводятся
к нижнему регистру. к нижнему регистру. Backend требует пароль не короче 12 символов и не длиннее
72 UTF-8 байтов.
Пароль при регистрации — от 8 до 72 печатных ASCII-символов: латинские **Известное расхождение:** сохранённое требование регистрации и проверка
буквы, цифры, спецсимволы и пробел. Кириллица, emoji, другие не-ASCII и в форме frontend считают нижнюю границу в UTF-8 байтах, а backend — в символах.
управляющие символы отклоняются. Пароль не обрезается и не нормализуется, а Не-ASCII пароль может пройти форму и получить отказ API. Это не принятое
обязательных классов символов нет. Верхняя граница вытекает из bcrypt, который изменение password policy; требуется отдельное согласованное решение
учитывает только первые 72 байта: для ASCII символ равен байту, поэтому о проверке пароля и подтверждение полного HTTP-потока.
длинный пароль не обрезается молча. Frontend и backend проверяют одну и ту же
политику.
При входе проверяется только то, что пароль непустой и не длиннее 72 UTF-8
байтов. Минимум и набор символов не применяются, чтобы пользователь,
зарегистрированный по прежним правилам (в том числе с не-ASCII паролем), мог
войти. Невалидный пароль получает `400` с кодом `VALIDATION_ERROR`.
Access token действует 15 минут и хранится только в памяти браузера. Access token действует 15 минут и хранится только в памяти браузера.
Refresh token имеет серверный срок 7 дней, заменяется при восстановлении Refresh token имеет серверный срок 7 дней, заменяется при восстановлении
+3 -2
View File
@@ -89,8 +89,9 @@ JavaScript Number. Отдельный ввод ограничен signed64: на
следующие страницы. Баланс при выборе прошлого периода остаётся текущим. следующие страницы. Баланс при выборе прошлого периода остаётся текущим.
Сводка учитывает все подходящие записи, включая не показанные на странице. Сводка учитывает все подходящие записи, включая не показанные на странице.
Сводка одной или всех карт отдельно показывает расходы, возвращённую сумму и Сводка одной или всех карт показывает одну итоговую сумму расходов с учётом
итог после возвратов, а также компактные строки категорий по убыванию итоговых расходов. Возврат уменьшает расходы месяца своего возвратов и компактные строки категорий по убыванию итоговых расходов. Отдельные показатели «Возвращено»
и «После возвратов» в интерфейсе скрыты; расчёт и операции возврата сохранены. Возврат уменьшает расходы месяца своего
получения: августовская покупка 1 000 ₽ и сентябрьский возврат 600 ₽ дают получения: августовская покупка 1 000 ₽ и сентябрьский возврат 600 ₽ дают
августовский итог 1 000 ₽ и сентябрьский −600 ₽ при отсутствии других трат. августовский итог 1 000 ₽ и сентябрьский −600 ₽ при отсутствии других трат.
Начальный остаток, пополнения и переводы в эти показатели не входят. Начальный остаток, пополнения и переводы в эти показатели не входят.
+28 -13
View File
@@ -4,7 +4,7 @@
купонами и дивидендами за выбранный период». Пользователь открывает купонами и дивидендами за выбранный период». Пользователь открывает
`/accounts/:accountId/income` из обзора счёта или `/accounts/:accountId/income` из обзора счёта или
[общий доход](../portfolio/all-accounts.md) по доступным счетам. [общий доход](../portfolio/all-accounts.md) по доступным счетам.
Экран показывает итоги по валютам и отдельную карточку каждой Экран показывает итоги по валютам, сравнение и отдельную карточку каждой
включённой выплаты. Источником служат [исполненные операции](../operations/overview.md), включённой выплаты. Источником служат [исполненные операции](../operations/overview.md),
а не изменение цены портфеля и не календарь будущих выплат. а не изменение цены портфеля и не календарь будущих выплат.
@@ -38,12 +38,11 @@
При первой загрузке или новом периоде без сохранённых данных показан При первой загрузке или новом периоде без сохранённых данных показан
каркас страницы. Пустой результат объясняет отсутствие исполненных купонов и каркас страницы. Пустой результат объясняет отсутствие исполненных купонов и
дивидендов, сохраняя фильтры и налоговое пояснение. Ошибка запроса дивидендов, сохраняя фильтры, налоговое пояснение и сравнения. Ошибка запроса
или нарушение принадлежности выплат даёт локальное состояние недоступности или нарушение принадлежности выплат даёт локальное состояние с действием «Повторить».
без действия повтора; чтение восстанавливается автоматически.
При фоновом обновлении того же периода сохранённый ответ и черновик При фоновом обновлении того же периода сохранённый ответ и черновик
остаются видимыми без индикации обновления и технического предупреждения. остаются видимыми: загрузку обозначает индикатор, неуспешное обновление —
Неполнота применённого периода обозначается состоянием самого результата. предупреждение с повторной попыткой.
На узком экране поля и действия идут вертикально; все карточки выплат На узком экране поля и действия идут вертикально; все карточки выплат
остаются в общем потоке страницы без раскрытия или внутренних областей прокрутки. остаются в общем потоке страницы без раскрытия или внутренних областей прокрутки.
@@ -52,15 +51,32 @@
- Backend проверяет доступ локально и читает сохранённые операции. Фоновая загрузка должна завершить все страницы; ошибка страницы или повреждённая цепочка курсоров не подтверждают полноту. - Backend проверяет доступ локально и читает сохранённые операции. Фоновая загрузка должна завершить все страницы; ошибка страницы или повреждённая цепочка курсоров не подтверждают полноту.
- Только точные типы купона и дивиденда источника формируют результат. Суммы остаются точными десятичными строками в исходной валюте; отрицательные корректировки сохраняют знак. Налоги, комиссии, сделки, переводы, погашения и переводы дивидендов не входят в доход. - Только точные типы купона и дивиденда источника формируют результат. Суммы остаются точными десятичными строками в исходной валюте; отрицательные корректировки сохраняют знак. Налоги, комиссии, сделки, переводы, погашения и переводы дивидендов не входят в доход.
- Итоги, временные интервалы и разбиение по инструментам не смешивают валюты и не создают общий конвертированный итог. Система не загружает другие периоды ради сравнения полученного дохода с выбранным. - Итоги, сравнения, временные интервалы и разбиение по инструментам не смешивают валюты и не создают общий конвертированный итог. Страница показывает сравнения с предыдущим периодом и годом ранее, когда календарная дата и история счёта позволяют. Неполное сравнение имеет отдельное сообщение, без нулевой базы.
- Временные интервалы по-прежнему приходят с backend: дни для 1–45 дней, недели для 46–180 и месяцы для более длинного периода, включая пустые интервалы. Страница дохода не строит из них график или текстовую динамику; ответ API не меняется. - Временные интервалы по-прежнему приходят с backend: дни для 1–45 дней, недели для 46–180 и месяцы для более длинного периода, включая пустые интервалы. Страница дохода не строит из них график или текстовую динамику; ответ API не меняется.
- Раздел «Выплаты» показывает единый список карточек выплат в исходном порядке `entries`: одна карточка на каждую выплату. Внешних групп по инструментам или валютам, итогов и количества выплат инструмента нет. Описание, дата, доступные идентификаторы инструмента и сумма со знаком видны в каждой карточке; валюты разделены в итогах. Дополнительный запрос за составом не нужен. - Раздел «Выплаты» показывает единый список карточек выплат в исходном порядке `entries`: одна карточка на каждую выплату. Внешних групп по инструментам или валютам, итогов и количества выплат инструмента нет. Описание, дата, доступные идентификаторы инструмента и сумма со знаком видны в каждой карточке; валюты по-прежнему разделены в итогах и сравнениях. Дополнительный запрос за составом не нужен.
Сравнение с непосредственно предыдущим периодом использует предыдущий полный
месяц для полного выбранного месяца, иначе — непосредственно предшествующий
интервал той же включительной длительности. Оно не подменяет сравнение годом
ранее. Незагруженное сравнение и ошибка его источника отличаются от
недоступности годового сравнения по календарю или дате открытия счёта.
Каждый раздел сравнения показывает цветные панели без общей белой подложки и
внутренних отступов у внешнего блока. Точные периоды показаны в заголовках двух панелей с русским форматом диапазона
и UTC для календарных дат.
Отдельного заголовка с датами и валютного бейджа нет; валюта остаётся у каждой суммы.
Панель «Разница» сохраняет серверную абсолютную разницу и дополняет итог, купоны
и дивиденды процентом: абсолютная разница / сумма годом ранее × 100.
Это округлённый показатель для отображения, а не пересчёт Money или инвестиционная
доходность. Форматирование сохраняет знак и не более одного десятичного разряда.
При нулевой или отрицательной базе либо непредставимом числовом отношении
процентный бейдж не отображается; абсолютная разница остаётся видимой.
## Полнота и принадлежность выплат ## Полнота и принадлежность выплат
Доходы читают сохранённые операции и coverage в согласованной транзакции. Доходы читают сохранённые операции и coverage в согласованной транзакции.
Только выбранный период определяет готовность результата. Неполный период Selected, previous и yearAgo имеют независимую готовность. Неполный selected
не получает окончательных итогов. не получает окончательных итогов; неполное сравнение не создаёт нулевую базу.
Подтверждённый пустой период отличается от ещё не загруженного. При смене Подтверждённый пустой период отличается от ещё не загруженного. При смене
периода прежний snapshot не показывается под новым названием. периода прежний snapshot не показывается под новым названием.
@@ -87,12 +103,11 @@ Backend обеспечивает принадлежность выплат, ва
## Будущие события и ограничения ## Будущие события и ограничения
На странице дохода нет блоков «Динамика» и «Ближайший прогноз», блоков На странице дохода нет блоков «Динамика» и «Ближайший прогноз». Она не запрашивает
сравнения периодов и сообщений об их недоступности. Она не запрашивает
`/events` и не зависит от его доступности. Самостоятельная страница `/events` и не зависит от его доступности. Самостоятельная страница
[«Будущие события»](upcoming-events.md) по-прежнему доступна из обзора [«Будущие события»](upcoming-events.md) по-прежнему доступна из обзора
выбранного счёта и сохраняет прежнее поведение. Будущие выплаты выбранного счёта и сохраняет прежнее поведение. Будущие выплаты
не включаются в итоги полученного дохода. не включаются в итоги полученного дохода или сравнения.
Аналитика читает сохранённую историю операций и не рассчитывает налоги, инвестиционную эффективность, доходность, валютную конвертацию или общий мультивалютный итог. Аналитика читает сохранённую историю операций и не рассчитывает налоги, инвестиционную эффективность, доходность, валютную конвертацию или общий мультивалютный итог.
+25 -42
View File
@@ -39,36 +39,23 @@
## Период и список ## Период и список
Без фильтра страница применяет период по умолчанию из семи календарных Без фильтра период начинается сегодня в UTC и заканчивается через 30 дней
дат включительно: сегодняшней и шести следующих календарных дней. API при после начала, обе даты включены. Поля «С» и «По» позволяют изменить период:
отсутствии параметров использует своё окно (от текущей даты UTC до `from + 30` «Применить» сохраняет выбранные календарные даты в URL и запрашивает результат,
календарных дней), обе даты включены; `from` и `to` принимают только строгие «Сбросить» возвращает период по умолчанию. Обзор счёта сохраняет прогноз
даты `YYYY-MM-DD`. Календарь фильтра разрешает выбор сегодняшней и будущих дат на текущую дату и следующие 30 дней.
и не позволяет выбрать даты раньше сегодняшней. Прямая ссылка с валидным
прошедшим периодом сохраняет указанные даты без замены; некорректный формат,
несуществующая календарная дата или обратный диапазон нормализуются к периоду
по умолчанию с удалением параметров из URL. Поля «С» и «По» сохраняют
выбранные календарные даты в URL, а «Сбросить» возвращает период по умолчанию.
События упорядочены по календарной дате. Купоны и дивиденды показываются События упорядочены по календарной дате. У каждой строки видны тип,
единым предметным представлением выплаты с явной пометкой прогноза — тем же,
что у исполненных выплат в доходе и журнале; неизвестная сумма остаётся
«сумма неизвестна» и не превращается в ноль или полученные деньги.
Дата дивиденда обозначена как дата реестра. Offer и погашение сохраняют своё
значение без суммы выплаты. У каждой строки видны тип,
инструмент, дата и доступная сумма в исходной валюте. Список не имеет инструмент, дата и доступная сумма в исходной валюте. Список не имеет
пагинации и не конвертирует валюты. Пустой результат означает отсутствие пагинации и не конвертирует валюты. Пустой результат означает отсутствие
известных поддерживаемых событий в выбранном окне, а не доказательство известных поддерживаемых событий в выбранном окне, а не доказательство
отсутствия всех будущих выплат. Инструмент без поддерживаемых данных отсутствия всех будущих выплат. Инструмент без поддерживаемых данных
не получает искусственную строку «нет данных». не получает искусственную строку «нет данных».
Сохранённый forecast сохраняет свой `calculatedAt` и исходное включительное API принимает необязательные `from` и `to` в формате `YYYY-MM-DD` для
окно. GET для отдельного счёта или `all` возвращает только пересечение этого отдельного и общего счёта. Без `from` используется сегодня в UTC,
окна и запрошенного UTC-периода, не меняя ни `calculatedAt`, ни исходное окно. без `to` — 30 дней после начала. Неверный формат или несуществующая дата
Пустое пересечение или отсутствие событий в пересечении — успешный пустой дают `400`; обратный период — `400 INVALID_EVENTS_PERIOD`.
список с теми же metadata; это не отсутствие forecast. Неверный формат или
несуществующая дата дают `400`; обратный период — `400 INVALID_EVENTS_PERIOD`
без замены периода и без запуска личной работы.
Фильтрация на сервере предшествует сортировке и вычислению Фильтрация на сервере предшествует сортировке и вычислению
`incomeTotalsByCurrency`, включающего только купоны и дивиденды. `incomeTotalsByCurrency`, включающего только купоны и дивиденды.
Итоги остаются в ответе API; полный экран показывает список без общего итога. Итоги остаются в ответе API; полный экран показывает список без общего итога.
@@ -76,30 +63,26 @@
## Получение и отказы ## Получение и отказы
Запрос требует сессии, подключения и локально подтверждённого membership. Запрос требует сессии, подключения и локально подтверждённого membership.
Он читает сохранённый forecast и не запускает личную синхронизацию. Часовой Прогноз строится из личного сохранённого portfolio и общих календарей: T-Bank для
обход строит forecast из полного личного portfolio и общих календарей: T-Bank дивидендов, MOEX для bond events. Отдельного синхронного получения portfolio
для дивидендов, MOEX для bond events. Личные оценки сохраняются отдельно от нет. Личные оценки сохраняются отдельно от общих календарей.
общих календарей.
Последний полный forecast привязан к scope, исходному окну и membership. Если Последний полный forecast привязан к scope, периоду и membership.
часовой обход не может получить полный новый forecast, прежний полный прогноз, Если новый portfolio требует ещё недоступного календаря, прежний полный
его `calculatedAt` и исходное окно остаются читаемыми, в том числе после прогноз и его source times остаются, в том числе после рестарта. Новый
рестарта; `meta.lastAttempt` отдельно сообщает об ошибке обновления. Без полный результат заменяет его атомарно. Без fallback показывается
полного forecast `data: null` и metadata обозначает initial loading либо missing/partial, а не подтверждённое отсутствие выплат. Другой период или
временную недоступность, а не подтверждённое отсутствие выплат. В режиме `all` изменившийся membership не получают старый прогноз под новым названием.
результат complete только при полном вкладе каждого включённого счёта. Другой Source failure сообщается в metadata и не изменяет успешное время.
scope или изменившийся membership не получают старый forecast под новым
названием.
Успешный ответ MOEX без запрошенного блока означает отсутствие поддерживаемых Успешный ответ MOEX без запрошенного блока означает отсутствие поддерживаемых
событий этого источника; это отличается от transport failure и повреждённого событий этого источника; это отличается от transport failure и повреждённого
формата ответа. формата ответа.
При первом открытии без complete forecast показан каркас заголовка и строк, При первом открытии показан каркас заголовка и строк, без интерактивных
без интерактивных заглушек. Пустой успешный ответ сохраняет возможность заглушек. Пустой ответ сохраняет возможность изменить период.
изменить период. При failed HTTP reread уже полученный Client Cached Snapshot При обычном фоновом обновлении уже полученные данные остаются на экране,
остаётся на экране; это отличается от server-side failed hourly attempt. ошибка получает предупреждение и повторную попытку. Без выбранного счёта
Повторная попытка перечитывает только HTTP-ответ. Без выбранного счёта запрос запрос не выполняется. Смена сессии и подключения следует
не выполняется. Смена сессии и подключения следует
[общим правилам финансовых данных](../../architecture/cross-cutting/data-freshness.md). [общим правилам финансовых данных](../../architecture/cross-cutting/data-freshness.md).
Внешние границы объясняют интеграции [T-Bank](../../integrations/tbank.md) Внешние границы объясняют интеграции [T-Bank](../../integrations/tbank.md)
+4 -15
View File
@@ -89,11 +89,7 @@ HTML-источник, общий кэш и поведение при измен
Календарь охватывает пять прошлых лет и следующий год. Будущие купоны Календарь охватывает пять прошлых лет и следующий год. Будущие купоны
сортируются по дате выплаты, прошедшие и недатированные доступны отдельно. сортируются по дате выплаты, прошедшие и недатированные доступны отдельно.
Будущие и прошедшие события купонного графика показываются единым предметным Показаны сумма на облигацию, номер, тип, длительность периода и дата реестра,
представлением выплаты с пометкой события по графику — тем же, что у
исполненных выплат и прогноза; исполнение подтверждает только личная история.
Показаны сумма на облигацию, отдельная личная оценка, номер, тип, длительность
периода и дата реестра,
если они известны. Неопределённый будущий купон не заменяется нулём. если они известны. Неопределённый будущий купон не заменяется нулём.
Личная оценка доступна только при положительных позициях, известной сумме, Личная оценка доступна только при положительных позициях, известной сумме,
@@ -121,8 +117,8 @@ HTML-источник, общий кэш и поведение при измен
portfolio; они не попадают в общие источники. portfolio; они не попадают в общие источники.
BondBy переиспользуется зависимостями выпуска. Discovery принадлежит query, BondBy переиспользуется зависимостями выпуска. Discovery принадлежит query,
свежесть — конкретному source/range. Missing/stale зависимости ставятся свежесть — конкретному source/range. Missing/stale зависимости запрашиваются
в фоновую очередь через прежний limiter; обычный HTTP-ответ не ждёт брокера. в фоне через прежний limiter; обычный HTTP-ответ не ждёт брокера.
Покрытые завершённые свечи не перечитываются после restart. Открытый хвост Покрытые завершённые свечи не перечитываются после restart. Открытый хвост
остаётся изменяемым; ручной refresh охватывает показанный период. остаётся изменяемым; ручной refresh охватывает показанный период.
@@ -131,8 +127,7 @@ BondBy переиспользуется зависимостями выпуск
выплат. Наблюдение статуса не запускает повторный импорт. Политика источников выплат. Наблюдение статуса не запускает повторный импорт. Политика источников
и границы — в [модели свежести](../../architecture/cross-cutting/data-freshness.md). и границы — в [модели свежести](../../architecture/cross-cutting/data-freshness.md).
Разделы загружаются независимо, показывают каркас загрузки и локальное Разделы загружаются независимо, показывают каркас загрузки и локальную повторную попытку.
спокойное состояние недоступности без действия повтора.
Ошибка фонового обновления сохраняет последние данные. Смена подключения Ошибка фонового обновления сохраняет последние данные. Смена подключения
сбрасывает поддерево клиентских запросов облигаций вместе с остальными финансовыми запросами; сбрасывает поддерево клиентских запросов облигаций вместе с остальными финансовыми запросами;
выход очищает кэш сессии. Общие график, позиция и личные события выход очищает кэш сессии. Общие график, позиция и личные события
@@ -148,9 +143,3 @@ BondBy переиспользуется зависимостями выпуск
и [проверками](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/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.spec.ts).
Границы источников описаны в [разделе интеграций](../../integrations/README.md), Границы источников описаны в [разделе интеграций](../../integrations/README.md),
сходное разделение рыночных и личных данных — в [акциях](stocks.md). сходное разделение рыночных и личных данных — в [акциях](stocks.md).
## Online reference
Страница облигации, поиск и переход по тикеру обращаются к онлайн-справочнику
брокера; сохранённые quotes, свечи, события, ratings и метки портфеля
сохраняют собственные границы свежести и ошибок.
+27 -47
View File
@@ -28,30 +28,24 @@
на frontend. Общее для акций и облигаций восстановление справочных данных на frontend. Общее для акций и облигаций восстановление справочных данных
принадлежит [загрузке портфеля на backend](../portfolio/flows/portfolio-loading.md). принадлежит [загрузке портфеля на backend](../portfolio/flows/portfolio-loading.md).
Раздел «Акции» в боковой и мобильной навигации ведёт на `/stocks`: скринер Раздел «Акции» в боковой и мобильной навигации ведёт на `/stocks`: поиск
запускается кнопкой «Найти» с необязательным названием или тикером, сектором, по названию или тикеру через каталог Т-Банка, начиная с двух символов.
типом акции, валютой расчётов, площадкой, страной риска и максимальным размером Результаты разных выпусков, включая обыкновенные и привилегированные акции,
лота. Он использует полный полученный каталог и показывает только акции, для имеют отдельные UID. Карточка результата показывает логотип, последнюю цену
которых каталог подтверждает доступность покупки и торговли через API. Это не с её временем, тип бумаги, площадку, код режима, ISIN и доступность торгов через
гарантирует, что конкретная заявка пользователя будет исполнена прямо сейчас. API. Поиск показывает одну карточку на нормализованный ISIN. До ограничения
в 30 результатов выбирается представитель выпуска: доступность через API,
Скринер показывает одну карточку на выпуск с нормализованным ISIN, а выпуск точное совпадение тикера, затем режим TQBR; равные варианты упорядочены по UID.
без ISIN остаётся отдельной карточкой точного UID. Обыкновенные и Записи без ISIN сохраняются отдельно по UID. Обыкновенные и привилегированные
привилегированные акции с разными ISIN не объединяются. После отбора карточки акции с разными ISIN не объединяются. Цена и ссылка принадлежат выбранному
упорядочены по тикеру и UID, разделены на страницы по 30 и показывают единое UID; данные разных режимов не смешиваются. Прямые ссылки из портфеля сохраняют
время получения списка. Изменение условий не заменяет текущий список до
следующего нажатия «Найти». Повторный поиск получает новый каталог; страницы
одного результата не пересчитываются. Подтверждённый пустой результат отличается
от ошибки источника: она сообщается рядом с полем поиска, а повторить запрос
пользователь может обычным «Найти» — отдельного действия повтора чтения нет.
Ссылка карточки принадлежит выбранному UID; данные разных режимов не смешиваются.
Прямые ссылки из портфеля сохраняют
исходный UID, даже если поиск выбрал другой режим этого выпуска; такая страница исходный UID, даже если поиск выбрал другой режим этого выпуска; такая страница
сохраняет UUID в адресе. Если один точный тикер относится к разным выпускам, сохраняет UUID в адресе. Если один точный тикер относится к разным выпускам,
сервер возвращает `409 STOCK_TICKER_AMBIGUOUS`. сервер возвращает `409 STOCK_TICKER_AMBIGUOUS`, а страница предлагает поиск.
Поиск тикера нечувствителен к регистру, но использует точное совпадение:
SBER не заменяется на SBERP. Однобуквенные тикеры также поддерживаются.
Прямая ссылка не требует предварительного открытия портфеля. Хлебные крошки Прямая ссылка не требует предварительного открытия портфеля. Хлебные крошки
«Акции → название» возвращают к скринеру. «Акции → название» возвращают к поиску.
## Содержание и состояния ## Содержание и состояния
@@ -64,9 +58,8 @@
рыночные данные и личная история остаются доступны. рыночные данные и личная история остаются доступны.
Каждый раздел загружается независимо. Первичная загрузка показывает каркас Каждый раздел загружается независимо. Первичная загрузка показывает каркас
раздела, затянувшаяся недоступность — локальное спокойное состояние без раздела, ошибка — локальную повторную попытку. При ошибке фонового обновления
повторной попытки. При ошибке фонового обновления сохраняются предыдущие сохраняются предыдущие данные и предупреждение. Цена содержит собственное время
данные без технического предупреждения. Цена содержит собственное время
UTC, которое может быть старше времени получения ответа. Статус торгов не UTC, которое может быть старше времени получения ответа. Статус торгов не
выводится из свежести цены. Отсутствующее значение не заменяется нулём. выводится из свежести цены. Отсутствующее значение не заменяется нулём.
@@ -126,11 +119,7 @@ UID включён в ключ кэша и подпись общего курс
## Дивиденды и финансовые показатели ## Дивиденды и финансовые показатели
События компании отделены от личных зачислений. Будущие и прошедшие события События компании отделены от личных зачислений. Известные будущие выплаты
дивидендного графика показываются единым предметным представлением выплаты
с пометкой «событие по графику» — тем же, что у исполненных выплат и прогноза;
сумма на акцию, отдельная личная оценка и дата реестра сохраняют свои подписи
и не выдаются за личное зачисление. Известные будущие выплаты
определяются по `paymentDate`, при её отсутствии — по `recordDate`; прошедшие определяются по `paymentDate`, при её отсутствии — по `recordDate`; прошедшие
и недатированные события помещены в отдельную историю компании. Отменённые и недатированные события помещены в отдельную историю компании. Отменённые
события исключаются. Личная оценка умножает сумму на акцию на текущее положительное события исключаются. Личная оценка умножает сумму на акцию на текущее положительное
@@ -155,22 +144,19 @@ TTM означает последние 12 месяцев, MRQ — послед
## Получение данных и ограничения ## Получение данных и ограничения
Общие metadata, discovery, company, quotes, свечи и календари сохраняются Общие metadata, discovery, company, quotes, свечи и календари сохраняются
в SQLite. Скринер получает каталог online и показывает время его получения; в SQLite. Поиск хранит подтверждённый набор UID для конкретного query;
временная недоступность каталога не подтверждает отсутствие подходящих акций. успешный SBER не подтверждает пустой GAZP. Отсутствующее enrichment не
Отсутствующее enrichment не скрывает найденную бумагу. Сохранённые общие скрывает найденную бумагу. Сохранённые общие источники переиспользуются
источники переиспользуются между пользователями, но личные позиции и выплаты между пользователями, но личные позиции и выплаты всегда проверяют доступ.
всегда проверяют доступ.
Обычное чтение не ждёт RPC; missing/stale зависимости ставятся в фоновую Обычное чтение не ждёт RPC; missing/stale зависимости запрашиваются в фоне
очередь через существующий T-Bank limiter. Часовая свежесть применяется через существующий T-Bank limiter. Минутная свежесть применяется к quotes
к quotes и открытому хвосту свечей; metadata/company/календари, будучи и открытому хвосту свечей, суточная — к metadata/company/календарям.
сохранёнными, не перечитываются обычным чтением.
Закрытые полные свечные интервалы, включая пустые, сохраняются после restart. Закрытые полные свечные интервалы, включая пустые, сохраняются после restart.
Новый день не делает прежнюю незавершённую свечу полной. Новый день не делает прежнюю незавершённую свечу полной.
Отдельного действия «Обновить» на странице нет: чтение текущего инструмента, «Обновить» передаёт текущий инструмент, выбранный график и personal-history
выбранного графика и personal-history scope выполняется автоматически. scope. Частичные строки видны с предупреждением; неполные личные оценки
Частичные строки видны с обозначением неполноты; неполные личные оценки
не становятся нулями. Статус и `sync=observe` не создают новый импорт. не становятся нулями. Статус и `sync=observe` не создают новый импорт.
Смена подключения и известный access loss очищают личное представление; Смена подключения и известный access loss очищают личное представление;
поздние ответы не возвращают прежние данные. Детали — поздние ответы не возвращают прежние данные. Детали —
@@ -198,9 +184,3 @@ TTM означает последние 12 месяцев, MRQ — послед
[рыночным данным](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-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). [выплатам](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). Сопоставление с облигациями — на [странице облигаций](bonds.md).
## Online reference
Страница акции, поиск и переход по тикеру обращаются к онлайн-справочнику
брокера; сохранённые позиции, quotes, свечи и события остаются независимыми
локальными ресурсами и не подменяются ошибкой справочника.
+2 -4
View File
@@ -84,10 +84,8 @@ Coverage/status без изменения rowset cursor не ломают. Пе
Полный журнал и превью используют одинаковую компактную строку: логотип Полный журнал и превью используют одинаковую компактную строку: логотип
или буквенная заглушка, описание, дата, инструмент и денежное движение. или буквенная заглушка, описание, дата, инструмент и денежное движение.
Исполненные купоны и дивиденды показываются единым предметным представлением Для купона и дивиденда денежная метка называет точное событие; для прочих
выплаты — тем же, что на странице дохода, в прогнозе и графиках инструментов; операций — категорию. Она показывается один раз, без повтора рядом с датой.
денежная метка называет точное исполненное событие. Для прочих
операций метка называет категорию. Она показывается один раз, без повтора рядом с датой.
Знак и цвет обозначают направление денег; отсутствие движения нейтрально. Знак и цвет обозначают направление денег; отсутствие движения нейтрально.
Если описание отсутствует, показывается локализованный тип операции. Если описание отсутствует, показывается локализованный тип операции.
+7 -4
View File
@@ -53,10 +53,13 @@ Backend публикует статические маршруты под `/api/
- Журнал объединяет операции по убыванию времени, при равном времени использует - Журнал объединяет операции по убыванию времени, при равном времени использует
порядок идентификаторов счетов и операций. ID порядок идентификаторов счетов и операций. ID
операций разных счетов не считаются дублями. Переводы не схлопываются. операций разных счетов не считаются дублями. Переводы не схлопываются.
- Доходы собираются из всех страниц операций каждого счёта за выбранный период. - Доходы рассчитываются из согласованной локальной выборки операций за общие периоды.
Итоги, временные интервалы и принадлежность выплат рассчитывает Итоги, сравнения, временные интервалы и принадлежность выплат рассчитывает
сервис дохода. Данные сравнения периодов не загружаются и не возвращаются; сервис дохода.
состав счетов влияет только на фактические выплаты выбранного периода. Для сравнения с годом ранее используется текущий состав счетов; новый счёт
добавляет только реально полученные выплаты. При известных датах открытия всех счетов
доступность сравнения определяется самым ранним открытием в этом составе.
Закрытые счета в историческое сравнение не добавляются.
- Будущие события сортируются по дате и сохраняют отдельную строку каждого счёта. - Будущие события сортируются по дате и сохраняют отдельную строку каждого счёта.
Прогноз не включается в реализованные доходы. Прогноз не включается в реализованные доходы.
+1 -1
View File
@@ -32,7 +32,7 @@
| Cash Balance | Суммы из `GetPositions.money`, объединённые по валюте | Заблокированные деньги не прибавляются; торгуемый инструмент `CURRENCY` остаётся позицией | | Cash Balance | Суммы из `GetPositions.money`, объединённые по валюте | Заблокированные деньги не прибавляются; торгуемый инструмент `CURRENCY` остаётся позицией |
| `totalsByCurrency` | Стоимости позиций + доступные деньги той же валюты | Не включает невычислимую стоимость; не обещает полную оценку портфеля | | `totalsByCurrency` | Стоимости позиций + доступные деньги той же валюты | Не включает невычислимую стоимость; не обещает полную оценку портфеля |
| `lastSuccessfulAt` | Время формирования успешного снимка, UTC | Не является временем каждой котировки; запасной снимок сохраняет исходное время | | `lastSuccessfulAt` | Время формирования успешного снимка, UTC | Не является временем каждой котировки; запасной снимок сохраняет исходное время |
| `isStale` | Истёк часовой срок свежести сохранённого portfolio source | Не описывает состояние клиентского кэша или ошибку фонового запроса | | `isStale` | Истёк минутный срок свежести сохранённого portfolio source | Не описывает состояние клиентского кэша или ошибку фонового запроса |
Ненулевые денежные остатки одной валюты суммируются. Позиции одинаковых Ненулевые денежные остатки одной валюты суммируются. Позиции одинаковых
инструментов в ответе отдельного счёта не объединяются; визуальная группировка инструментов в ответе отдельного счёта не объединяются; визуальная группировка
+31 -32
View File
@@ -1,47 +1,46 @@
# Загрузка портфеля # Загрузка портфеля
Экран `/accounts/:accountId` читает последний полный snapshot из SQLite после Экран `/accounts/:accountId` читает последний полный snapshot из SQLite
проверки сессии, подключения и локального membership. Если список счетов ещё после проверки сессии, подключения и локального membership. Если список
неизвестен, сервер сообщает о загрузке доступа без выдачи личных данных счетов ещё неизвестен, сервер сообщает о загрузке доступа без выдачи личных
неизвестного ID. Известный недоступный счёт возвращает `404`. данных неизвестного ID. Известный недоступный счёт возвращает `404`.
## Основной обмен ## Основной обмен
Обычный GET и retry возвращают read-only локальный `{ data, meta }` envelope: Обычный GET возвращает локальный envelope. Missing/stale зависимости
они не ждут и не вызывают T-Bank и не предлагают ручную синхронизацию. Часовой запрашиваются в фоне; чтение не ждёт T-Bank. Для нового полного snapshot
обход текущего подключения обновляет Portfolio независимо от экрана. Для нового нужны оба финансовых ответа `GetPortfolio` и `GetPositions`. Суммы и позиции
полного snapshot ему нужны оба финансовых ответа `GetPortfolio` и публикуются вместе; metadata дополняет представление независимо, с доступным
`GetPositions`; суммы и позиции публикуются вместе. Metadata инструмента UID/FIGI вместо имени до её загрузки.
дополняет представление независимо, с доступным UID/FIGI вместо имени до её
онлайн-загрузки.
Открытый экран выполняет локальное перечитывание не чаще одного раза в Свежесть portfolio — 60 секунд. Истечение срока не удаляет snapshot и не
30 минут; retry также повторяет только HTTP GET. `data: null` означает, что создаёт таймер. «Обновить» на обзоре перечитывает зависимости текущего экрана:
полного snapshot ещё нет, а не нулевой портфель. Пустой подтверждённый portfolio membership, portfolio, показанные операции, доход и будущие события.
остаётся available complete result. Секции могут быть готовы независимо. Секции могут быть готовы независимо. Подробнее —
Подробнее — [свежесть](../../../architecture/cross-cutting/data-freshness.md). [свежесть](../../../architecture/cross-cutting/data-freshness.md).
## Границы доступа ## Границы доступа
Публикация проверяет connection revision и attempt в той же транзакции. Замена Публикация проверяет connection revision и attempt в той же транзакции.
или удаление подключения не допускает позднюю запись прежней работы. Frontend Замена или удаление подключения не допускает позднюю запись прежней работы.
очищает недоступный personal view и отклоняет запоздалые ответы. В режиме Frontend очищает недоступный personal view и отклоняет запоздалые ответы.
[«Все счета»](../all-accounts.md#полнота-свежесть-и-доступ) состав accounts и В режиме [«Все счета»](../all-accounts.md#полнота-свежесть-и-доступ) состав
snapshots читается согласованно, но их source times могут различаться. accounts и snapshots читается согласованно, но их source times могут различаться.
## Ошибки и результат для пользователя ## Ошибки и результат для пользователя
Нет подключения — `409 TBANK_NOT_CONNECTED`, известный отказ токена — Нет подключения — `409 TBANK_NOT_CONNECTED`, известный отказ токена —
`400 INVALID_TBANK_TOKEN`, недоступный счёт — `404`. Они не маскируются `400 INVALID_TBANK_TOKEN`, недоступный счёт — `404`. Они не маскируются
запасным snapshot. Временная ошибка часового обхода оставляет прежний полный запасным snapshot. Transient source failure оставляет прежний полный портфель
portfolio и его исходное `lastSuccessfulAt`; `meta.lastAttempt` сообщает о и его исходное время с безопасным предупреждением. При отсутствии snapshot
failed или partial попытке отдельно. `Portfolio.isStale` сообщает возраст показывается missing/failed, без нулевых итогов. `503` означает ошибку
snapshot, а Client Cached Snapshot и ошибка браузерного HTTP-перечитывания обслуживания HTTP-запроса, а не принятой фоновой работы.
имеют независимый смысл. При отсутствии полного snapshot `data: null` и
metadata обозначает initial loading либо временную недоступность, без нулевых
итогов. `503` означает ошибку обслуживания самого HTTP-запроса, а не
завершившуюся фоновую работу.
Денежные правила находятся в [справочнике](../data-reference.md), проверка Observer запрашивает статус известной работы, затем `sync=observe` читает
расхождения — в [диагностике](../how-to/investigate-discrepancy.md), RPC/лимиты изменившийся ресурс без запуска синхронизации. При уходе и terminal outcome
— в [интеграции T-Bank](../../../integrations/tbank.md). наблюдение прекращается. Повторное открытие может запросить недостающее;
после рестарта сохранённый snapshot доступен сразу.
Денежные правила находятся в [справочнике](../data-reference.md),
проверка расхождения — в [диагностике](../how-to/investigate-discrepancy.md),
RPC/лимиты — в [интеграции T-Bank](../../../integrations/tbank.md).
@@ -12,12 +12,11 @@
величины. Убедитесь, что сравниваемые значения относятся к одному составу величины. Убедитесь, что сравниваемые значения относятся к одному составу
счетов и инструменту. Название или тикер сами по себе не доказывают счетов и инструменту. Название или тикер сами по себе не доказывают
совпадение выпуска; используйте доступные UID, FIGI и ISIN. совпадение выпуска; используйте доступные UID, FIGI и ISIN.
2. **Сопоставьте время.** Проверьте `lastSuccessfulAt`, `isStale` и показанный 2. **Сопоставьте время.** Проверьте `lastSuccessfulAt`, `isStale` и наличие
исход последней часовой попытки. Серверный снимок не обещает время предупреждения о клиентском обновлении. Серверный снимок не обещает время
каждой котировки, а общий портфель может соединять разные моменты. каждой котировки, а общий портфель может соединять разные моменты.
Действия «Повторить» на экране нет: временный сбой чтения восстанавливается Если запрос завершился ошибкой, доступное действие «Повторить» должно
автоматически, поэтому расхождение проверяйте по показанному состоянию обновить результат или оставить объяснимую ошибку. Перезагрузка backend
источника, а не по результату ручного обновления. Перезагрузка backend
сохраняет последний полный снимок в SQLite, но не создаёт историю стоимости. сохраняет последний полный снимок в SQLite, но не создаёт историю стоимости.
3. **Проверьте состав суммы.** Для одной валюты итог складывается из 3. **Проверьте состав суммы.** Для одной валюты итог складывается из
вычислимых стоимостей позиций и доступных денег. Не добавляйте деньги вычислимых стоимостей позиций и доступных денег. Не добавляйте деньги
+10 -15
View File
@@ -79,23 +79,18 @@ Backend получает данные через серверную
не содержат данных и не получают фокус. При обычном фоновом обновлении не содержат данных и не получают фокус. При обычном фоновом обновлении
сохраняются предыдущие суммы и время успешного получения в UTC. сохраняются предыдущие суммы и время успешного получения в UTC.
Серверный `isStale` обозначает возраст снимка старше часа: обновлением Серверный `isStale` обозначает истечение минутной свежести снимка.
владеет часовой обход, а не запрос экрана. Он показывается рядом с суммами Клиентское предупреждение означает неудачную попытку обновить уже показанный
вместе с временем последнего успешного результата и исходом последней часовой ответ. Эти состояния независимы и могут появиться одновременно.
попытки. Неудачная попытка клиента обновить уже показанный ответ о себе не Успешный повторный запрос обновляет суммы и время, снимает клиентскую ошибку;
сообщает: эти состояния независимы, но пользователю видимо только состояние серверное предупреждение определяется новым `isStale`.
источника. Успешное автоматическое перечитывание обновляет суммы и время;
обозначение устаревания определяется новым `isStale`.
Правила времени и ошибок приведены в [потоке загрузки](flows/portfolio-loading.md). Правила времени и ошибок приведены в [потоке загрузки](flows/portfolio-loading.md).
Отдельного чтения истории стоимости нет, но каждый полный снимок В текущем решении нет долговременной истории снимков, графиков стоимости
сохраняет точку оценки по счёту и валюте для будущих поддерживаемых портфеля, валютной конвертации, учёта виртуальных позиций или заблокированных
представлений; публичного графика в текущем решении нет. Нет также средств. Последний полный снимок сохраняется после перезапуска; выполнение
валютной конвертации, учёта виртуальных позиций или заблокированных синхронизации рассчитано на один экземпляр backend. Действие «Обновить»
средств. Последний полный снимок сохраняется после перезапуска; перечитывает зависимости открытого экрана независимо от их свежести.
обновлением личных данных владеет часовой обход, рассчитанный на один
экземпляр backend. Действие «Обновить» выполняет только локальное
перечитывание открытого экрана и не запускает работу с T-Bank.
Для проверки конкретной суммы используйте Для проверки конкретной суммы используйте
[руководство по расхождениям](how-to/investigate-discrepancy.md). [руководство по расхождениям](how-to/investigate-discrepancy.md).
-17
View File
@@ -128,14 +128,6 @@ Instrument обогащают отображение Position или Event, но
_Не использовать_: бумага, тикер, актив позиции. _Не использовать_: бумага, тикер, актив позиции.
## Скринер акций
Подбор выпусков акций по заданным условиям из каталога T-Bank с указанием
времени получения списка. Скринер отличается от текстового поиска конкретного
инструмента, личного портфеля и подтверждения исполнимости сделки.
_Не использовать_: гарантия покупки, личная проверка доступа к сделке.
## Instrument Logo ## Instrument Logo
Имя файла бренда T-Bank для отображения Instrument в интерфейсе; Имя файла бренда T-Bank для отображения Instrument в интерфейсе;
@@ -160,15 +152,6 @@ _Не использовать_: валютная позиция, заблоки
_Не использовать_: история портфеля, snapshot для графика. _Не использовать_: история портфеля, snapshot для графика.
## Точка истории стоимости портфеля
Полная оценка одного Brokerage Account в одной валюте на момент успешного
обновления Portfolio; последовательность таких точек описывает изменение
оценки во времени, включая неторговые дни. Не является Portfolio Snapshot
или инвестиционной доходностью; отсутствие цены позиции не приравнивается к нулю.
_Не использовать_: снимок состава портфеля, доходность.
## Client Cached Snapshot ## Client Cached Snapshot
Технический термин: последний успешно полученный frontend-ответ React Query, который остаётся Технический термин: последний успешно полученный frontend-ответ React Query, который остаётся
+1 -12
View File
@@ -20,12 +20,6 @@ T-Bank определяет исходные данные и права token. B
сессию, выбирает подключение её пользователя и устанавливает доступ к счёту. сессию, выбирает подключение её пользователя и устанавливает доступ к счёту.
Наличие идентификатора счёта в URL этого доступа не подтверждает. Наличие идентификатора счёта в URL этого доступа не подтверждает.
Скринер акций получает полный online-каталог акций T-Bank для каждого нового
поиска. В него входят только инструменты, для которых каталог подтверждает и
доступность покупки, и торговли через API. Эти признаки описывают полученный
снимок каталога: они не подтверждают личные права пользователя, состояние торгов
или исполнение будущей заявки. Скринер не использует личный портфель как каталог.
[MOEX](moex.md) и [НКР](nkr.md) дополняют публичные сведения об облигациях; они не заменяют [MOEX](moex.md) и [НКР](nkr.md) дополняют публичные сведения об облигациях; они не заменяют
брокера как источник личных позиций и исполненных выплат. Границы источников брокера как источник личных позиций и исполненных выплат. Границы источников
показаны в [контексте](../architecture/context.md), смысл дополнений — в показаны в [контексте](../architecture/context.md), смысл дополнений — в
@@ -97,12 +91,7 @@ sequenceDiagram
`INVALID_TBANK_TOKEN` означает отказ внешней аутентификации или прав доступа. `INVALID_TBANK_TOKEN` означает отказ внешней аутентификации или прав доступа.
`TBANK_UNAVAILABLE` сообщает о временной или иной upstream-ошибке, включая `TBANK_UNAVAILABLE` сообщает о временной или иной upstream-ошибке, включая
исчерпание очереди. Диагностические причины жизненного цикла часовой попытки исчерпание очереди. Ответы и ошибки не содержат token. Удаление подключения
(`INTERRUPTED` — прерывание до нормального завершения, `DEADLINE` — истечение
ограничения ожидания шага) не относятся к ошибкам T-Bank: они остаются
внутренней диагностикой и не раскрываются в hourly-read. Истечение ограничения
ожидания не отменяет уже начатые обращения к T-Bank; их поздние результаты
остаются в строках шагов. Ответы и ошибки не содержат token. Удаление подключения
идемпотентно: удаляются локальный секрет, личные datasets и work; поздняя идемпотентно: удаляются локальный секрет, личные datasets и work; поздняя
публикация прежней revision запрещена. Общие неперсональные источники могут публикация прежней revision запрещена. Общие неперсональные источники могут
сохраняться. Оно не описывает отзыв token на стороне T-Bank. сохраняться. Оно не описывает отзыв token на стороне T-Bank.
+1 -1
View File
@@ -24,7 +24,7 @@ WealthLedger — локальное приложение для частного
| Посчитать полученные выплаты | [Пассивный доход](domains/income/overview.md): исполненные купоны и дивиденды за календарный период | | Посчитать полученные выплаты | [Пассивный доход](domains/income/overview.md): исполненные купоны и дивиденды за календарный период |
| Посмотреть ожидаемые события | [Будущие выплаты](domains/income/upcoming-events.md): купоны, дивиденды, погашения и оферты по текущим позициям | | Посмотреть ожидаемые события | [Будущие выплаты](domains/income/upcoming-events.md): купоны, дивиденды, погашения и оферты по текущим позициям |
| Увидеть общий состав инвестиций | Режим [«Все счета»](domains/portfolio/all-accounts.md), сохраняющий принадлежность строк к счетам | | Увидеть общий состав инвестиций | Режим [«Все счета»](domains/portfolio/all-accounts.md), сохраняющий принадлежность строк к счетам |
| Изучить конкретный инструмент | [Скринер акций](domains/instruments/stocks.md) по параметрам каталога, страницы акций и поиск [облигаций](domains/instruments/bonds.md): рынок, личная позиция, история и события | | Изучить конкретный инструмент | Поиск и страницы [акций](domains/instruments/stocks.md) и [облигаций](domains/instruments/bonds.md): рынок, личная позиция, история и события |
## Как связаны представления ## Как связаны представления
+482 -336
View File
File diff suppressed because it is too large Load Diff
+6 -12
View File
@@ -1,12 +1,12 @@
{ {
"name": "wealth-ledger-docs", "name": "wealth-ledger-docs",
"version": "0.1.5", "version": "0.1.2",
"private": true, "private": true,
"engines": { "engines": {
"node": "24.21.0" "node": "24.21.0"
}, },
"scripts": { "scripts": {
"lint": "markdownlint --dot --rules .markdownlint-rule-relative-links.cjs --ignore node_modules --ignore CHANGELOG.md -- \"**/*.md\" && markdownlint --config .markdownlint-changelog.json --rules .markdownlint-rule-relative-links.cjs -- CHANGELOG.md", "lint": "markdownlint-cli2 \"**/*.md\" \"#node_modules\"",
"verify": "npm run lint && git diff --check", "verify": "npm run lint && git diff --check",
"release:preview": "release-it --dry-run --ci", "release:preview": "release-it --dry-run --ci",
"release:prepare": "release-it --ci", "release:prepare": "release-it --ci",
@@ -17,19 +17,13 @@
"@commitlint/config-conventional": "21.2.2", "@commitlint/config-conventional": "21.2.2",
"@release-it/conventional-changelog": "12.0.0", "@release-it/conventional-changelog": "12.0.0",
"conventional-changelog-conventionalcommits": "10.4.0", "conventional-changelog-conventionalcommits": "10.4.0",
"markdownlint-cli": "0.49.1", "markdownlint-cli2": "0.23.2",
"markdownlint-rule-relative-links": "5.1.3", "markdownlint-rule-relative-links": "5.1.2",
"release-it": "21.0.2" "release-it": "21.0.2"
}, },
"overrides": { "overrides": {
"katex": "0.19.0", "markdownlint-cli2@0.23.2": {
"markdownlint-cli@0.49.1": { "smol-toml": "1.8.0"
"js-yaml": "5.4.2",
"smol-toml": "1.9.0"
},
"release-it": {
"basic-ftp": "6.2.1",
"undici": "7.29.1"
} }
} }
} }