1 Commits
Author SHA1 Message Date
renovate-bot 2a91f9f211 Add renovate.json 2026-09-13 14:02:30 +00:00
78 changed files with 3340 additions and 6641 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 (если есть)
-34
View File
@@ -1,34 +0,0 @@
name: Conventional commits
on:
pull_request:
types: [opened, synchronize, reopened, edited]
concurrency:
group: docs-commits-${{ gitea.event.pull_request.number }}
cancel-in-progress: true
jobs:
commits:
runs-on: ubuntu-latest
steps:
- uses: https://github.com/actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- uses: https://github.com/actions/setup-node@v4
with:
node-version-file: .nvmrc
- run: npm ci --ignore-scripts
- name: Check PR title and commits
env:
PR_TITLE: ${{ github.event.pull_request.title }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
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 "$HEAD_SHA^" --to "$HEAD_SHA"
else
npm run commitlint -- --strict --from "$BASE_SHA" --to "$HEAD_SHA"
fi
-35
View File
@@ -1,35 +0,0 @@
name: Docs security
on:
push:
branches: [develop, main]
pull_request:
types: [opened, synchronize, reopened]
schedule:
- cron: '17 5 * * *'
concurrency:
group: docs-security-${{ gitea.event_name }}-${{ gitea.event.pull_request.number || gitea.ref }}
cancel-in-progress: true
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: https://github.com/actions/checkout@v4
with:
fetch-depth: 0
- name: Record checked revision
run: git rev-parse HEAD
- name: Scan history with Gitleaks 8.30.1
run: |
curl --fail --location --silent --show-error https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz -o /tmp/gitleaks.tar.gz
echo '551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb /tmp/gitleaks.tar.gz' | sha256sum --check
mkdir -p /tmp/gitleaks-bin
tar -xzf /tmp/gitleaks.tar.gz -C /tmp/gitleaks-bin gitleaks
/tmp/gitleaks-bin/gitleaks git --log-opts=--all --redact=100 --no-banner
- uses: https://github.com/actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Audit runtime and development dependencies
run: npm audit --include=dev --audit-level=low
-25
View File
@@ -1,25 +0,0 @@
name: Docs verify
on:
push:
branches: [develop, main]
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:
verify:
runs-on: ubuntu-latest
steps:
- uses: https://github.com/actions/checkout@v4
- name: Record checked revision
run: git rev-parse HEAD
- uses: https://github.com/actions/setup-node@v4
with:
node-version-file: .nvmrc
- run: npm ci
- name: Check Markdown and relative links
run: npm run verify
-6
View File
@@ -1,6 +0,0 @@
{
"extends": ".markdownlint.json",
"MD024": {
"siblings_only": true
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"config": {
"MD013": false
},
"customRules": ["markdownlint-rule-relative-links"],
"overrides": [
{
// Historical evidence records keep original source paths, outside the standalone book.
"filter": ["development/completion-evidence.md", "development/audit-remediation.md"],
"config": { "relative-links": false },
"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
}
+1 -1
View File
@@ -1 +1 @@
24.22.1 24.21.0
-106
View File
@@ -1,106 +0,0 @@
{
"git": {
"requireBranch": "release/*",
"requireCleanWorkingDir": true,
"requireUpstream": false,
"requireCommits": true,
"commitMessage": "chore(release): ${version}",
"tagName": "v${version}",
"tag": false,
"push": false
},
"npm": {
"publish": false
},
"github": {
"release": false
},
"gitlab": {
"release": false
},
"hooks": {
"after:git:init": "node scripts/check-release.cjs"
},
"plugins": {
"@release-it/conventional-changelog": {
"infile": "CHANGELOG.md",
"preset": {
"name": "conventionalcommits",
"types": [
{
"type": "chore",
"scope": "release",
"effect": "hidden"
},
{
"type": "feat",
"section": "Features",
"effect": "bump"
},
{
"type": "fix",
"section": "Bug Fixes",
"effect": "bump"
},
{
"type": "perf",
"section": "Performance",
"effect": "bump"
},
{
"type": "docs",
"section": "Documentation",
"effect": "bump"
},
{
"type": "spec",
"section": "Specifications",
"effect": "bump"
},
{
"type": "refactor",
"section": "Refactoring",
"effect": "bump"
},
{
"type": "build",
"section": "Build",
"effect": "bump"
},
{
"type": "ci",
"section": "CI",
"effect": "bump"
},
{
"type": "test",
"section": "Tests",
"effect": "bump"
},
{
"type": "style",
"section": "Styles",
"effect": "bump"
},
{
"type": "chore",
"section": "Maintenance",
"effect": "bump"
},
{
"type": "revert",
"section": "Reverts",
"effect": "bump"
}
]
},
"context": {
"host": "https://git.personal.ksv741.ru",
"owner": "WealthLedger",
"repository": "docs",
"linkReferences": true,
"commit": "commit"
}
}
}
}
+3 -21
View File
@@ -3,16 +3,11 @@
Перед изменением прочитай [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 и реализации с учётом
внешние источники используй как справочные свидетельства, когда они доступны. веток и незакоммиченных изменений. Расхождение обозначь, не выбирая источник
Недостающие факты отмечай явно; инструкции и проверки не требуют других checkout.
Расхождение обозначь, не выбирая источник
победителем автоматически. Перенос сохраняет полезное содержание и входящие победителем автоматически. Перенос сохраняет полезное содержание и входящие
ссылки. Docs описывает текущее устройство системы; история поставок остаётся в Git. ссылки; существующие исторические свидетельства остаются неизменными.
Отчёты аудитов и исследований в docs не размещаются. Подтверждённые решения Отчёты аудитов и исследований в docs не размещаются. Подтверждённые решения
из исследований оформляются в ADR, системные знания — в тематических страницах. из исследований оформляются в ADR, системные знания — в тематических страницах.
@@ -20,16 +15,3 @@
[доступные проверки](CONTRIBUTING.md#проверки), включая новые файлы. [доступные проверки](CONTRIBUTING.md#проверки), включая новые файлы.
Сохрани границы задачи и чужие изменения. Успех Markdown-проверки не заменяет Сохрани границы задачи и чужие изменения. Успех Markdown-проверки не заменяет
смысловую сверку и не доказывает рендер диаграмм. смысловую сверку и не доказывает рендер диаграмм.
## Ветки и релизы
Работа — в issue-linked ветке от `develop`, затем PR в `develop`; в `main` напрямую
не пушить. PR сливается squash или rebase-merge; merge-коммит не используется.
`main` обновляется только fast-forward из `develop`, поэтому `origin/main`
всегда предок `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; версии вручную не повышать.
-213
View File
@@ -1,213 +0,0 @@
# Changelog
## [0.1.7](https://git.personal.ksv741.ru/WealthLedger/docs/compare/v0.1.6...v0.1.7) (2026-11-01)
### Documentation
* describe all-account row grouping (WealthLedger/main[#47](https://git.personal.ksv741.ru/WealthLedger/docs/issues/47)) ([#59](https://git.personal.ksv741.ru/WealthLedger/docs/issues/59)) ([dcb528f](https://git.personal.ksv741.ru/WealthLedger/docs/commit/dcb528fa9f76f0d7aa98bd827cc7ee39024dbb13))
* describe concentration tab and stored instrument classification (WealthLedger/main[#48](https://git.personal.ksv741.ru/WealthLedger/docs/issues/48)) ([#60](https://git.personal.ksv741.ru/WealthLedger/docs/issues/60)) ([37430da](https://git.personal.ksv741.ru/WealthLedger/docs/commit/37430da4c99cb571ef5bc0676cad7b3a47bcb6f0))
### Maintenance
* **deps:** resolve markdownlint audit advisory (WealthLedger/main[#49](https://git.personal.ksv741.ru/WealthLedger/docs/issues/49)) ([#61](https://git.personal.ksv741.ru/WealthLedger/docs/issues/61)) ([abe5b16](https://git.personal.ksv741.ru/WealthLedger/docs/commit/abe5b16241d3002baa5d39c2a12949f23a75b968))
## [0.1.6](https://git.personal.ksv741.ru/WealthLedger/docs/compare/v0.1.5...v0.1.6) (2026-10-24)
### Maintenance
* bump Node to 24.22.1 (WealthLedger/backend[#122](https://git.personal.ksv741.ru/WealthLedger/docs/issues/122)) ([4216eb8](https://git.personal.ksv741.ru/WealthLedger/docs/commit/4216eb815f59b7a284e679ccb1b725438da07882))
* **deps:** update non-major dependencies (WealthLedger/docs[#56](https://git.personal.ksv741.ru/WealthLedger/docs/issues/56)) ([f9098d8](https://git.personal.ksv741.ru/WealthLedger/docs/commit/f9098d84098467e41143bad5044085d6329099d9))
## [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)
### Documentation
* describe local frontend packages and ownership ([55610ea](https://git.personal.ksv741.ru/WealthLedger/docs/commit/55610ea2b4dc687f76ee9eb37c14d887b1b70100))
* document Nexus registry migration [#18](https://git.personal.ksv741.ru/WealthLedger/docs/issues/18) ([5de85c2](https://git.personal.ksv741.ru/WealthLedger/docs/commit/5de85c296f7681882f58476343d3da71f1dee28d))
* document versioned deployment bundle ([15d1d82](https://git.personal.ksv741.ru/WealthLedger/docs/commit/15d1d820f5401c25adda23f753b99e894bdcd4d3))
* explain persisted T-Bank reads and synchronization ([07de062](https://git.personal.ksv741.ru/WealthLedger/docs/commit/07de062ebbf043eff50bd7f53b48bff13bb7cb1b))
* use shared Nexus account [#18](https://git.personal.ksv741.ru/WealthLedger/docs/issues/18) ([6fd39cd](https://git.personal.ksv741.ru/WealthLedger/docs/commit/6fd39cd467566e23cca79a1235125dfb80fa99f7))
## [0.1.1](https://git.personal.ksv741.ru/WealthLedger/docs/compare/v0.1.0...v0.1.1) (2026-09-16)
### Documentation
* document container deployment and recovery ([cead9dc](https://git.personal.ksv741.ru/WealthLedger/docs/commit/cead9dc1765700b7e10f9567e38f6d68d4ec9656))
* make documentation checks self-contained ([97069d6](https://git.personal.ksv741.ru/WealthLedger/docs/commit/97069d6f6d0719d6fcee30b1ce8adbda41bd3a6d))
## 0.1.0 (2026-09-14)
### Features
* add authorization ([6ca175b](https://git.personal.ksv741.ru/WealthLedger/docs/commit/6ca175b541a9f03e08448bef7593e560e928a15c))
* add brocker account list ([ebf34e0](https://git.personal.ksv741.ru/WealthLedger/docs/commit/ebf34e01ca8c8dab533bb76aff3d5b03177383f8))
* add logging ([016b624](https://git.personal.ksv741.ru/WealthLedger/docs/commit/016b624185d24a4e5ff07d4d164f2a3942b12358))
* add operations list ([d825126](https://git.personal.ksv741.ru/WealthLedger/docs/commit/d8251269207b6f163889f04637862d9126cd89ca))
* add portfolio ([4ffc374](https://git.personal.ksv741.ru/WealthLedger/docs/commit/4ffc374bd8ae4cca5a05ac806bf8d9a423d1153f))
* add tbank tokens ([026b531](https://git.personal.ksv741.ru/WealthLedger/docs/commit/026b5318af0d1d2b8e4622fc82cc454c23222bd8))
* add агегку events ([c513c47](https://git.personal.ksv741.ru/WealthLedger/docs/commit/c513c478404c6d00475ad093f70fce54aa30d1c2))
### Documentation
* activate frontend quality guardrails remediation ([fa5db07](https://git.personal.ksv741.ru/WealthLedger/docs/commit/fa5db0722915dd21fff49dbf49b8ddfc42eb4733))
* activate token-efficient agent workflow ([42fd894](https://git.personal.ksv741.ru/WealthLedger/docs/commit/42fd8940dcd2fcfba5ed7f34d6265f289b049659))
* add account income analytics ([cc3b5b0](https://git.personal.ksv741.ru/WealthLedger/docs/commit/cc3b5b0ae5b0e9507e2267c47fb55ea969f2765b))
* add audit remediation registry ([793d201](https://git.personal.ksv741.ru/WealthLedger/docs/commit/793d2012a39e0b204d1dc768c386e60cb987b05a))
* add baseline design ([e610c83](https://git.personal.ksv741.ru/WealthLedger/docs/commit/e610c83366045e5e8dab71ea5edbe7796bb78364))
* add completion evidence for extend-instrument-logo-to-bonds ([c68c978](https://git.personal.ksv741.ru/WealthLedger/docs/commit/c68c978c91d3fc821b8a31a9e52c7406a7121bb6))
* add old audit file ([5bf5c4e](https://git.personal.ksv741.ru/WealthLedger/docs/commit/5bf5c4e774f3dae8610a8ea9cba1204b1f3532ae))
* **ai:** adopt task-driven scoped context ([b7a56b1](https://git.personal.ksv741.ru/WealthLedger/docs/commit/b7a56b1eb53bae0a00e18be4ed4209df70fc193b))
* align investment domain terminology ([68d975b](https://git.personal.ksv741.ru/WealthLedger/docs/commit/68d975befda90c198a0287bb8d43ac5d6e386eb5))
* **brokerage-account-ledger:** describe explicit filters ([820cf55](https://git.personal.ksv741.ru/WealthLedger/docs/commit/820cf5510318e0868eba46a6ff0b556053cb9806))
* **brokerage-account-ledger:** describe operation type label and no category duplication ([2abb591](https://git.personal.ksv741.ru/WealthLedger/docs/commit/2abb5912b2db23d4984ca3b99578310596d66555))
* **cards:** describe compact expense summaries ([d3a9e91](https://git.personal.ksv741.ru/WealthLedger/docs/commit/d3a9e91d475414f92b01f53848b58bd20263a56b))
* **cards:** describe date range filtering ([a86d1eb](https://git.personal.ksv741.ru/WealthLedger/docs/commit/a86d1eb0a3d34992e650075c9f94cec1bda252e8))
* clarify domain and agent authority ([0c53c50](https://git.personal.ksv741.ru/WealthLedger/docs/commit/0c53c50c624c9ab8d9c812c60a7cab56661fcbbd))
* close SQLite migration and e2e blocker ([c74c573](https://git.personal.ksv741.ru/WealthLedger/docs/commit/c74c57373329e4accc46dbc13fdbe57ebf7ea3fd))
* describe all-accounts behavior ([0e8c8c8](https://git.personal.ksv741.ru/WealthLedger/docs/commit/0e8c8c803f8ce8b49cdb7cbf2d5d1b0018736ee9))
* describe bank operation icons and date format ([1f65b91](https://git.personal.ksv741.ru/WealthLedger/docs/commit/1f65b91a16a3ab90de17a493d011a8cf3fdc3c1c))
* describe bond pages and rating sources ([35711a5](https://git.personal.ksv741.ru/WealthLedger/docs/commit/35711a52ec79d05887b39adb5a64b52578b4da21))
* describe brokerage account summaries ([76d4de4](https://git.personal.ksv741.ru/WealthLedger/docs/commit/76d4de4428e5f902d82782ade745487d015994a1))
* describe compact portfolio totals ([2985def](https://git.personal.ksv741.ru/WealthLedger/docs/commit/2985def574f1c22f9c41f6755b802342fafb30cd))
* describe direct local token adaptation ([b0e747f](https://git.personal.ksv741.ru/WealthLedger/docs/commit/b0e747fdee2f726f18a4222f4bfc249c8f6d845b))
* describe improved ledger event rows ([d39a9d3](https://git.personal.ksv741.ru/WealthLedger/docs/commit/d39a9d3df08d068b0c5fba97cfb7bb0d5755f329))
* describe manual bank card accounting ([c0dfb80](https://git.personal.ksv741.ru/WealthLedger/docs/commit/c0dfb8061721bfd48d16e2a978348be4bec38c50))
* describe polished stock cards ([0c59ce7](https://git.personal.ksv741.ru/WealthLedger/docs/commit/0c59ce788690ba6d17f33e0b57e122dd67e74904))
* describe portable design token contract ([436251e](https://git.personal.ksv741.ru/WealthLedger/docs/commit/436251e83e07fe9726a1576dd081d5bd41655ff9))
* describe shared design system and catalog blocks ([30683b8](https://git.personal.ksv741.ru/WealthLedger/docs/commit/30683b8b73cdad838feac6c1cac255aea3783ae4))
* describe simplified income page ([7166ee5](https://git.personal.ksv741.ru/WealthLedger/docs/commit/7166ee51d181b04f3892ff6757a56b578ee9df64))
* describe stock pages and portfolio navigation ([ead965a](https://git.personal.ksv741.ru/WealthLedger/docs/commit/ead965a67402789245c267a4c32b52176d3df7a2))
* describe year-over-year income comparison ([2c98989](https://git.personal.ksv741.ru/WealthLedger/docs/commit/2c98989f5eeafd9baca8dab2659619473b78702d))
* document auth and client refresh resilience ([b91280c](https://git.personal.ksv741.ru/WealthLedger/docs/commit/b91280cbaace4d0ed6d66186c62465abeeda6032))
* document frontend audit remediation delivery ([55d35c7](https://git.personal.ksv741.ru/WealthLedger/docs/commit/55d35c7f789478230c683a79e5c80563c75487b6))
* document instrument logos in portfolio ([30e5699](https://git.personal.ksv741.ru/WealthLedger/docs/commit/30e56997f9bbceee09ea87b218a6042ab17112f0))
* document local verification workflow ([293df9c](https://git.personal.ksv741.ru/WealthLedger/docs/commit/293df9c59dfb8788e2c7e044c8855146b3c5d03e))
* document OpenSpec authority lifecycle ([0bc8d1c](https://git.personal.ksv741.ru/WealthLedger/docs/commit/0bc8d1c9aed984cc16b6efd1dab76e2a7a9ca307))
* document skeleton loading states ([95fa815](https://git.personal.ksv741.ru/WealthLedger/docs/commit/95fa8153286d549f7a58229bd92ef327b75b7e58))
* document T-Bank cache generation boundary ([3d2bc1e](https://git.personal.ksv741.ru/WealthLedger/docs/commit/3d2bc1eac4add7cc3316c97d4d02a78fe91620d5))
* establish architecture records ([2a09ef7](https://git.personal.ksv741.ru/WealthLedger/docs/commit/2a09ef74bb25feca65efdace61c2686199f614a5))
* **events:** describe date range filtering ([aa9afc1](https://git.personal.ksv741.ru/WealthLedger/docs/commit/aa9afc15e46888c4d6cabbe8e17ff87e383f5731))
* explain server-side portfolio metadata recovery ([05bfe8d](https://git.personal.ksv741.ru/WealthLedger/docs/commit/05bfe8df1cb0fde9b97d2075d9f571198295fea1))
* link remediation evidence commit ([9752234](https://git.personal.ksv741.ru/WealthLedger/docs/commit/9752234f23c8dd400667ba354b0ed1eec248db99))
* make project documentation self-contained ([3a99af1](https://git.personal.ksv741.ru/WealthLedger/docs/commit/3a99af1a6e49836ae6ea2378dd2e4037092d08fc))
* mark frontend audit remediation active ([ba15e52](https://git.personal.ksv741.ru/WealthLedger/docs/commit/ba15e529b44b39f819c3666ad4787f2f566d1da4))
* **portfolio-view:** update as-built for bond parity ([2e9e403](https://git.personal.ksv741.ru/WealthLedger/docs/commit/2e9e403db207c5fc4a21827b118407a51c7a70cc))
* **portfolio:** describe asset allocation ([174bab3](https://git.personal.ksv741.ru/WealthLedger/docs/commit/174bab3566be2d302b51a8a86e10b11271f9835f))
* **portfolio:** document ticker display ([7f4d0b2](https://git.personal.ksv741.ru/WealthLedger/docs/commit/7f4d0b255eb4ef5a73139bfd499eb88118105a6d))
* publish token workflow pilot result ([21ccad3](https://git.personal.ksv741.ru/WealthLedger/docs/commit/21ccad3639714bcc364af329ea884eda57259aab))
* record account-ledger-explicit-filters evidence ([2bc56db](https://git.personal.ksv741.ru/WealthLedger/docs/commit/2bc56db68076b243dccaa36ef850e4b28823e2fc))
* record add-instrument-logos completion evidence ([d2a98f3](https://git.personal.ksv741.ru/WealthLedger/docs/commit/d2a98f31577cdd0730efa6fad73e132572e017f2))
* record AI governance audit ([1944f2f](https://git.personal.ksv741.ru/WealthLedger/docs/commit/1944f2fc970db2bdc292981e01b1aca549f9ac52))
* record api contract resilience remediation ([b623805](https://git.personal.ksv741.ru/WealthLedger/docs/commit/b623805cbe9cd8c57cfc83a35b6c8af3018fea1e))
* record auth and cache concurrency policy ([8568e7b](https://git.personal.ksv741.ru/WealthLedger/docs/commit/8568e7b317a3c96c1e4ca2cbabce41d57a11181e))
* record auth cache remediation evidence ([6c37e58](https://git.personal.ksv741.ru/WealthLedger/docs/commit/6c37e58fbe670ef79ec774280171bc8b28d88737))
* record backend refactor and close P1-12 ([b940d39](https://git.personal.ksv741.ru/WealthLedger/docs/commit/b940d39946baae78ab14a10b9f71ef15153e9604))
* record backend strict typing completion ([f4cefd5](https://git.personal.ksv741.ru/WealthLedger/docs/commit/f4cefd5c401d009e1d3332e76f30913c97a480b7))
* record dependency hygiene remediation evidence ([60a1c33](https://git.personal.ksv741.ru/WealthLedger/docs/commit/60a1c33003bd35f7ff71e8c4b8f30d6a6f511e12))
* record documentation remediation evidence ([b91dfe6](https://git.personal.ksv741.ru/WealthLedger/docs/commit/b91dfe66e0d8c55c9000687895dfc2954f4d1c44))
* record domain money contract evidence ([4dbff85](https://git.personal.ksv741.ru/WealthLedger/docs/commit/4dbff85654656473dfa0b68f69b7841ac27d8efe))
* record frontend audit remediation evidence ([8721e93](https://git.personal.ksv741.ru/WealthLedger/docs/commit/8721e93849f72413ca065f9e738948f0ca10132e))
* record frontend auth query gating evidence ([68f1d8f](https://git.personal.ksv741.ru/WealthLedger/docs/commit/68f1d8f398e6606fb2a0db694c452ba235a29434))
* record frontend quality guardrails evidence ([eb69300](https://git.personal.ksv741.ru/WealthLedger/docs/commit/eb69300ebed41bc19e78909195387dce4e2c0ec7))
* record frontend remediation completion ([4cc8565](https://git.personal.ksv741.ru/WealthLedger/docs/commit/4cc856552498821f0dbaaa192b0fcc9a45d7b097))
* record frontend remediation evidence ([ef2bf97](https://git.personal.ksv741.ru/WealthLedger/docs/commit/ef2bf977bf6fd55580c878b0b3aec3e37a3d944e))
* record frontend view model boundary evidence ([373c18a](https://git.personal.ksv741.ru/WealthLedger/docs/commit/373c18a2f170f6ae2df1bda0b294b37e4c31d18f))
* record governance completion evidence ([1a214dd](https://git.personal.ksv741.ru/WealthLedger/docs/commit/1a214dd7649cc34ff86ec1483e68d099b69ff2e7))
* record improved ledger row evidence ([35c5302](https://git.personal.ksv741.ru/WealthLedger/docs/commit/35c5302e78545170ded1f9aa9109e3faa0ebea2a))
* record income analytics completion evidence ([09f5f9e](https://git.personal.ksv741.ru/WealthLedger/docs/commit/09f5f9e4f9ac8709e1fa66b24e485deecf5f0655))
* record ledger entry type label evidence ([d749bf5](https://git.personal.ksv741.ru/WealthLedger/docs/commit/d749bf5b2829b3127d6b8f6129edd262db37348f))
* record local verification remediation evidence ([82159e3](https://git.personal.ksv741.ru/WealthLedger/docs/commit/82159e3b073ff0b4b5a0ceb5033a623b9d39f2e2))
* record Mantine breadcrumbs evidence ([5fd5e79](https://git.personal.ksv741.ru/WealthLedger/docs/commit/5fd5e79590986ecc9787fd26b548ec8bd5b08fc3))
* record OpenSpec lifecycle remediation ([9bb76e3](https://git.personal.ksv741.ru/WealthLedger/docs/commit/9bb76e35f9496e9414b678d325f6995bf413f144))
* record OpenSpec resilience evidence ([79727de](https://git.personal.ksv741.ru/WealthLedger/docs/commit/79727de9ffe61f36fbaca866e2993bde6243eace))
* record P1-11 SQLite verification ([66721ee](https://git.personal.ksv741.ru/WealthLedger/docs/commit/66721eee6295f1c432aaeb8631cfd3b0234d11d6))
* record React component style evidence ([6812fde](https://git.personal.ksv741.ru/WealthLedger/docs/commit/6812fdeae36faf93e682d1ec089bd2e839ea6fdc))
* record SDD audit remediation ([72c3a8a](https://git.personal.ksv741.ru/WealthLedger/docs/commit/72c3a8a6683b95f7e734044fdabe568668097c33))
* record SDD remediation evidence ([5233726](https://git.personal.ksv741.ru/WealthLedger/docs/commit/5233726855b9f3a8f1b8e0f64991e17f9e8be299))
* record second OpenSpec schema review ([d80e2e3](https://git.personal.ksv741.ru/WealthLedger/docs/commit/d80e2e32b2ebadcd93a53105e1ac337fe2c1f4f6))
* record session workflow evidence ([9f8280c](https://git.personal.ksv741.ru/WealthLedger/docs/commit/9f8280c567e6e3e659c899980e916805cdf46968))
* record stock card polish evidence ([6006400](https://git.personal.ksv741.ru/WealthLedger/docs/commit/60064003ede0afb99c1fa299b813c0be5f889ad7))
* record T-Bank cache boundary evidence ([598dfb2](https://git.personal.ksv741.ru/WealthLedger/docs/commit/598dfb2bdb2c0d5da41495f01edbea83f6e5c0d3))
* record verification simplification evidence ([1f030af](https://git.personal.ksv741.ru/WealthLedger/docs/commit/1f030af2df32cb392e272ce90f6fb0138a9d13b4))
* refresh project onboarding entrypoints ([88c0cd3](https://git.personal.ksv741.ru/WealthLedger/docs/commit/88c0cd3281f1b409779f0ec9365692291c651ffa))
* remove misplaced baseline design ([34577dc](https://git.personal.ksv741.ru/WealthLedger/docs/commit/34577dc7956f90f73c50ba067ff8ecc7e751853c))
* remove useless ([adc369b](https://git.personal.ksv741.ru/WealthLedger/docs/commit/adc369be79d1b4b4aad36168a8d32deab6568932))
* reorganize system knowledge by domain ([f20c3f4](https://git.personal.ksv741.ru/WealthLedger/docs/commit/f20c3f47bcacc4e65444c602c261c80691cab66f))
* simplify OpenSpec agent entrypoints ([eb0a96e](https://git.personal.ksv741.ru/WealthLedger/docs/commit/eb0a96e27c072e53f5c01db5a810502c104e8e6c))
* simplify system documentation ([9b19a5c](https://git.personal.ksv741.ru/WealthLedger/docs/commit/9b19a5cf4df5116c5f603f7189751d37ae9f0ffd))
* simplify verification workflow ([4af0a9c](https://git.personal.ksv741.ru/WealthLedger/docs/commit/4af0a9c05590d7d47135fc708719bf83d5dd3fb9))
* trust supplied OpenAPI snapshot ([2ef3759](https://git.personal.ksv741.ru/WealthLedger/docs/commit/2ef375919b84d9367791e9b22d761e506a5da930))
* update income analytics period constraints ([28a01a4](https://git.personal.ksv741.ru/WealthLedger/docs/commit/28a01a449acd907a93b6f45c86cd40543237395b))
* update portfolio view feature ([c628535](https://git.personal.ksv741.ru/WealthLedger/docs/commit/c6285352b11907b3542ba8fc502608828a834fa3))
### Refactoring
* little structure refactor ([1383949](https://git.personal.ksv741.ru/WealthLedger/docs/commit/1383949dc4c295d1b4e89abc372b780442ad91b7))
* refactor foundation ([5d5497a](https://git.personal.ksv741.ru/WealthLedger/docs/commit/5d5497a4e4fc52cd03326080ba082c6dec369ce5))
* refactor future event list ([680fc03](https://git.personal.ksv741.ru/WealthLedger/docs/commit/680fc03bec7ff5fdbac7c81c6daf8e5dc119abb5))
### CI
* verify docs and secure dependency updates [#1](https://git.personal.ksv741.ru/WealthLedger/docs/issues/1) [#5](https://git.personal.ksv741.ru/WealthLedger/docs/issues/5) [#6](https://git.personal.ksv741.ru/WealthLedger/docs/issues/6) ([c0a8cdb](https://git.personal.ksv741.ru/WealthLedger/docs/commit/c0a8cdb683953e515757071c288bfdf9e7b4b40a))
### Maintenance
* configure releases and develop workflow ([261f1ca](https://git.personal.ksv741.ru/WealthLedger/docs/commit/261f1ca6d15a894de470b94f2f0ad6fce3ae7917))
* **deps:** update node.js to v24.21.0 ([67f152d](https://git.personal.ksv741.ru/WealthLedger/docs/commit/67f152d3654b15c8d51b5bde95ac4e9e0e6fb253))
* document and adopt Node 24 LTS ([7bc8792](https://git.personal.ksv741.ru/WealthLedger/docs/commit/7bc8792b9853768734066c31a323b70886d73a23))
* save pending changes ([14dd879](https://git.personal.ksv741.ru/WealthLedger/docs/commit/14dd8790996b3461da8b1e3ec9a49d9526100047))
+10 -34
View File
@@ -8,22 +8,18 @@ Docs даёт самостоятельное понимание продукта
| Вид знания | Где поддерживается | | Вид знания | Где поддерживается |
| --- | --- | | --- | --- |
| Согласованное проверяемое поведение: требования и сценарии | [Основные спецификации](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/AI_INDEX.md) в `specs/openspec/specs/` | | Согласованное проверяемое поведение: требования и сценарии | [Основные спецификации](../specs/AI_INDEX.md) в `specs/openspec/specs/` |
| Предложения изменений, их проектирование и задачи | OpenSpec changes в существующем Store `wealth-ledger-specs`, `specs/openspec/changes/` | | Предложения изменений, их проектирование и задачи | OpenSpec changes в существующем Store `wealth-ledger-specs`, `specs/openspec/changes/` |
| Понимание системы, областей и взаимодействий | Тематические страницы docs | | Понимание системы, областей и взаимодействий | Тематические страницы docs |
| Принятые решения и подтверждённые причины | Единственный журнал [docs/adr](adr/README.md) | | Принятые решения и подтверждённые причины | Единственный журнал [docs/adr](adr/README.md) |
| Конкретная реализация и технические контракты | Код, тесты, схемы, конфигурация; публичный OpenAPI формирует backend | | Конкретная реализация и технические контракты | Код, тесты, схемы, конфигурация; публичный OpenAPI формирует backend |
| Размещение кода, conventions, команды разработки и проверки | Локальные инструкции frontend/backend | | Размещение кода, conventions, команды разработки и проверки | Локальные инструкции frontend/backend |
| Работа над docs | Локальные [AGENTS](AGENTS.md) и этот документ | | Выбор репозитория и типа знания | Корневой [AGENTS](../AGENTS.md) |
История аудитов и проверок остаётся в Git, Issues и PR. `CHANGELOG.md`
генерируется release-it из commits; вручную дублировать его на тематических страницах не нужно.
Процесс разработки и инструкции агентов принадлежат локальным инструкциям
репозиториев. ADR фиксируют решения самой системы, а не организацию работы.
Основные спецификации описывают и согласованное текущее поведение. Основные спецификации описывают и согласованное текущее поведение.
Различие specs и docs не является разделением на будущее и настоящее. Различие specs и docs не является разделением на будущее и настоящее.
Docs объясняет смысл контракта, не вводя независимо редактируемые требования. Docs объясняет смысл контракта, не вводя независимо редактируемые требования.
OpenSpec применяется по явному запросу согласно [принятому процессу](development/openspec-authority-and-lifecycle.md).
## Потребность читателя и структура ## Потребность читателя и структура
@@ -86,11 +82,9 @@ Docs объясняет смысл контракта, не вводя неза
Нужные точные значения сопровождай определяющим источником, чтобы не создать Нужные точные значения сопровождай определяющим источником, чтобы не создать
конкурирующую норму. Паспорта страниц, Evidence-секции и номера строк не требуются. конкурирующую норму. Паспорта страниц, Evidence-секции и номера строк не требуются.
Внутри docs используем относительные Markdown-ссылки. Внешние репозитории — Внутри docs используем относительные Markdown-ссылки. Между репозиториями —
необязательные справочные веб-ссылки на опубликованный источник. Основной текст относительный путь через соседний корень, например `../specs/AI_INDEX.md` из README.
и инструкции должны быть понятны без открытия этих ссылок. Проверяй локальные Проверяй регистр, файл и якорь в фактическом workspace. Перенос требует исправить
файлы/якоря; для удалённых источников не обещай проверку их доступности CI.
Перенос требует исправить
входящие ссылки активных Markdown-документов; исторические архивы и dated audits входящие ссылки активных Markdown-документов; исторические архивы и dated audits
сохраняют адреса своего времени. Короткий указатель оставляем только при реальной сохраняют адреса своего времени. Короткий указатель оставляем только при реальной
необходимости совместимости, без второй копии содержания. необходимости совместимости, без второй копии содержания.
@@ -155,8 +149,11 @@ npm run verify
Существующий gate запускает Markdown lint с проверкой относительных ссылок Существующий gate запускает Markdown lint с проверкой относительных ссылок
и якорей, затем `git diff --check`. Он охватывает новые Markdown-файлы; и якорей, затем `git diff --check`. Он охватывает новые Markdown-файлы;
обычный diff их не показывает, поэтому отдельно просмотри untracked-содержание. обычный diff их не показывает, поэтому отдельно просмотри untracked-содержание.
Markdown-only правки не требуют тестов приложения или соседнего checkout. При изменениях соседних репозиториев выполни там `git diff --check` и проверь
затронутые ссылки. Markdown-only правки не требуют тестов всего приложения.
Точный scope исключений задан существующими `package.json` и конфигурацией lint:
исторические свидетельства не обязаны разрешать прежние адреса.
Правило relative-links не проверяет внешние URL и HTML-ссылки. Mermaid-рендер Правило relative-links не проверяет внешние URL и HTML-ссылки. Mermaid-рендер
в gate не входит; используй доступный локальный рендерер без отправки материалов в gate не входит; используй доступный локальный рендерер без отправки материалов
наружу, установки стека или добавления новых валидаторов. Публикационного наружу, установки стека или добавления новых валидаторов. Публикационного
@@ -166,24 +163,3 @@ Markdown-only правки не требуют тестов приложения
разработчик от вопроса к объяснению и требованию; агент от типа задачи к нужным разработчик от вопроса к объяснению и требованию; агент от типа задачи к нужным
ограничениям, ADR и репозиторию. Отчёт различает синтаксис, ссылки, рендер, ограничениям, ADR и репозиторию. Отчёт различает синтаксис, ссылки, рендер,
смысловую сверку и оставшиеся ограничения. смысловую сверку и оставшиеся ограничения.
### CI и обновления зависимостей
`.gitea/workflows/verify.yml` запускает этот gate на push и PR с Node
из `.nvmrc` и установкой через `npm ci`. Скачивается только docs;
его SHA сохраняется в логе. Ошибка checkout, Markdown или внутренней ссылки
завершает job с FAIL. Внешние веб-ссылки — справочные; CI не проверяет их
доступность, Mermaid-render или semantic correctness.
`security.yml` при push, PR и ежедневно проверяет всю Git-историю через
Gitleaks 8.30.1 с redacted-выводом и все npm dependencies через
`npm audit --include=dev --audit-level=low`. Overrides `smol-toml` и `js-yaml`
исправляют DoS в parsers markdownlint-cli; overrides для release-it обновляют
уязвимые basic-ftp и undici. Их можно удалить, когда родительские пакеты
примут исправленные версии.
Renovate объединяет minor/patch updates; major требует согласования в
Dependency Dashboard, automerge не включён. Required checks включаются
отдельно в Gitea; workflow сам по себе не запрещает merge.
Версии и ветки: [процесс релиза](README.md#релизы).
+13 -69
View File
@@ -30,12 +30,12 @@ WealthLedger помогает частному инвестору понимат
| [Аутентификация](domains/authentication/overview.md) | Регистрация, вход, восстановление и завершение сессии | | [Аутентификация](domains/authentication/overview.md) | Регистрация, вход, восстановление и завершение сессии |
| [Счета](domains/accounts/overview.md) | Доступные счета, сводки и состояния каждого счёта | | [Счета](domains/accounts/overview.md) | Доступные счета, сводки и состояния каждого счёта |
| [Банковские карты](domains/cards/overview.md) | Ручные остатки, операции, категории, возвраты, месячные сводки и исправления | | [Банковские карты](domains/cards/overview.md) | Ручные остатки, операции, категории, возвраты, месячные сводки и исправления |
| [Портфель](domains/portfolio/overview.md) | Позиции, остатки, суммы, структура, [концентрация](domains/portfolio/concentration.md) и свежесть | | [Портфель](domains/portfolio/overview.md) | Позиции, остатки, суммы, структура и свежесть |
| [Все счета](domains/portfolio/all-accounts.md) | Общий портфель, происхождение строк и сводные разделы | | [Все счета](domains/portfolio/all-accounts.md) | Общий портфель, происхождение строк и сводные разделы |
| [Операции](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) | Поиск выпуска, рыночные показатели и личная история |
## Найти материал по задаче ## Найти материал по задаче
@@ -44,88 +44,32 @@ WealthLedger помогает частному инвестору понимат
| Разобраться в системе | [Архитектура](architecture/README.md), нужная область выше, [внешние интеграции](integrations/README.md) | | Разобраться в системе | [Архитектура](architecture/README.md), нужная область выше, [внешние интеграции](integrations/README.md) |
| Найти точные сведения | [Глоссарий](glossary.md), [показатели портфеля](domains/portfolio/data-reference.md), [HTTP API](architecture/http-api.md) | | Найти точные сведения | [Глоссарий](glossary.md), [показатели портфеля](domains/portfolio/data-reference.md), [HTTP API](architecture/http-api.md) |
| Разобрать расхождение в суммах | [Диагностика портфеля](domains/portfolio/how-to/investigate-discrepancy.md) | | Разобрать расхождение в суммах | [Диагностика портфеля](domains/portfolio/how-to/investigate-discrepancy.md) |
| Проверить условия локальной работы | [Окружение и восстановление](development/local-workspace.md) |
| Понять ошибку или старые данные | [Состояния интерфейса](architecture/cross-cutting/ui-resilience.md), [свежесть данных](architecture/cross-cutting/data-freshness.md) | | Понять ошибку или старые данные | [Состояния интерфейса](architecture/cross-cutting/ui-resilience.md), [свежесть данных](architecture/cross-cutting/data-freshness.md) |
| Понять визуальные роли и состояния | [Визуальные принципы](design/overview.md) | | Найти визуальные роли и примеры | [Дизайн-система](development/design-system.md), [UI-каталог](development/ui-catalog.md) |
| Узнать причину архитектурного решения | [Журнал ADR](adr/README.md) | | Узнать причину архитектурного решения | [Журнал ADR](adr/README.md) |
| Запустить, обновить или восстановить приложение | [Контейнерная поставка](architecture/deployment.md) | | Изменить или проверить систему | [Сопровождение](development/README.md), затем инструкции нужного репозитория |
Практический tutorial первого инвестиционного сценария пока не подготовлен:
он требует подключения брокера. Маршрут первого знакомства выше — чтение, не учебный запуск.
UI-каталог содержит безопасные вымышленные примеры и поясняет границы их использования.
## Границы знаний ## Границы знаний
[Основные спецификации](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/AI_INDEX.md) сохраняют согласованное проверяемое [Основные спецификации](../specs/AI_INDEX.md) сохраняют согласованное проверяемое
поведение, а OpenSpec changes — предложения, дизайн и задачи изменений. поведение, а OpenSpec changes — предложения, дизайн и задачи изменений.
Фактический Store находится в `specs/openspec/`, основные требования — в Фактический Store находится в `specs/openspec/`, основные требования — в
`specs/openspec/specs/`. Docs объясняет систему; код, тесты, схемы и конфигурация `specs/openspec/specs/`. Docs объясняет систему; код, тесты, схемы и конфигурация
задают конкретный механизм, а [ADR](adr/README.md) сохраняют принятые решения. задают конкретный механизм, а [ADR](adr/README.md) сохраняют принятые решения.
Локальные инструкции [frontend](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/AGENTS.md) и [backend](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/AGENTS.md) Локальные инструкции [frontend](../frontend/AGENTS.md) и [backend](../backend/AGENTS.md)
описывают изменение реализации и её проверки. описывают изменение реализации и её проверки.
Эти источники дополняют друг друга. Найденный незавершённый change не доказывает Эти источники дополняют друг друга. Найденный незавершённый change не доказывает
реализованную функцию; расхождение кода и требования требует разбора. реализованную функцию; расхождение кода и требования требует разбора.
Итоги исследований входят в ADR, когда принято значимое решение, и в тематические Итоги исследований входят в ADR, когда принято значимое решение, и в тематические
страницы, когда уточняют знания о системе. Исходные исследовательские отчёты страницы, когда уточняют знания о системе. Исходные исследовательские отчёты
в docs не хранятся. История изменений остаётся в Git; документация описывает в docs не хранятся. [Исторические свидетельства](development/README.md#исторические-свидетельства)
текущее устройство системы и причины архитектурных решений. сохраняют результаты своего времени и не заменяют текущее описание системы.
Правила изменения текста, диаграмм, ссылок и проверки docs-impact находятся Правила изменения текста, диаграмм, ссылок и проверки docs-impact находятся
в [CONTRIBUTING](CONTRIBUTING.md). в [CONTRIBUTING](CONTRIBUTING.md).
## Релизы
Репозиторий выпускается независимо. `develop` — интеграция, `main` — принятые
релизы. Обычная работа: issue-linked ветка от актуального `develop`, PR в `develop`;
общие правила — в Engineering Workflow по ссылке из AGENTS.
PR сливается squash или rebase-merge; merge-коммит не используется. `main`
обновляется только fast-forward из `develop`, поэтому история линейна:
```text
<type>/<issue>-<slug> --squash--> develop --ff-only--> main --> tag vX.Y.Z
```
Инвариант: `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,
затем `npm run release:prepare`. Команда создаёт `chore(release): X.Y.Z`,
обновляет manifest/lockfile/CHANGELOG; тег и push не выполняет. Не обходи
guards ветки, чистого дерева, отсутствующих тегов или уже подготовленной версии.
3. Проверь diff и штатные gates этого репозитория. Отправь только release-ветку,
открой PR в **`develop`** с заголовком `chore(release): X.Y.Z`. После зелёных
`verify`, `security`, `commits` выполни fast-forward-only merge. Если
`develop` изменился, обнови release-ветку и повтори проверки кандидата.
4. Открой PR `develop → main` с тем же заголовком и слей fast-forward-only.
5. Получи удалённый `main`, сравни его HEAD с проверенным release SHA и версией
manifest/lockfile. Создай аннотированный `vX.Y.Z` именно на этом SHA:
`git tag -a vX.Y.Z <release-sha> -m "Release X.Y.Z"`, затем отправь только
`git push origin refs/tags/vX.Y.Z`. Не запускай release-it повторно на `main`.
Обратная синхронизация не нужна: release-коммит попал в `develop` на шаге 3.
Опубликованные теги не перемещай. При сбое после 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;
`!`/`BREAKING CHANGE:` — major, включая версии до 1.0. `chore(release):`
исключён из рекомендации и changelog. Конфигурация — `.release-it.json`.
В Gitea для `main`/`develop` должны быть запрещены прямой/force push и обход
администратором, включена блокировка устаревшей ветки, обязательны `verify`,
`security`, `commits`; теги `v*` защищены. Линейность обеспечивают merge styles
репозитория: merge-коммиты и rebase `--no-ff` выключены, доступны squash,
rebase-merge и fast-forward-only; стиль по умолчанию — squash.
Workflow сам не включает эти настройки. Отдельное review approval для одного
владельца не требуется. Npm publishing, deploy и Gitea Releases не включены.
-3
View File
@@ -2,9 +2,6 @@
**Status:** Accepted, baseline decision **Status:** Accepted, baseline decision
Deployment boundary расширена [ADR 015](015-single-server-sqlite-deployment.md)
для одного сервера и одного backend process. Ниже сохранён исходный контекст.
## Context ## Context
MVP нужен воспроизводимый локальный доступ к данным без инфраструктуры развёртывания. Доменные таблицы и миграции на baseline отсутствуют. MVP нужен воспроизводимый локальный доступ к данным без инфраструктуры развёртывания. Доменные таблицы и миграции на baseline отсутствуют.
@@ -2,9 +2,6 @@
**Status:** Accepted; supersedes the refresh-token reuse rule in ADR-006. **Status:** Accepted; supersedes the refresh-token reuse rule in ADR-006.
Cookie-clearing policy уточнена в [ADR 018](018-minimal-runtime-coordination.md):
неуспешный refresh больше не удаляет cookie. CAS и отказ superseded token сохранены.
## Context ## Context
Два одновременных запроса могут предъявить один действительный refresh token. Отзыв Session для проигравшего запроса делает недействительной сессию успешного запроса, а сохранение отдельной истории token или grace-window для различения replay добавляет непропорциональное состояние локальному MVP. Два одновременных запроса могут предъявить один действительный refresh token. Отзыв Session для проигравшего запроса делает недействительной сессию успешного запроса, а сохранение отдельной истории token или grace-window для различения replay добавляет непропорциональное состояние локальному MVP.
@@ -0,0 +1,55 @@
---
status: accepted
---
# Native OpenSpec SDD и минимальная local orchestration
## Статус: историческое решение
[ADR 012](012-task-driven-development-with-scoped-context.md) отменяет обязательный
SDD lifecycle. Native OpenSpec остаётся optional инструментом; repo gates сохраняются.
### Прежняя частичная замена
[ADR 011](011-native-openspec-lifecycle-with-repository-gates.md) заменяет части
этого решения о schema v3, custom verifiers и dispatcher. Выбор native OpenSpec,
независимых repository gates и исходное rationale сохраняются. Ниже оставлено
историческое описание прежней реализации; текущие принципы работы описаны
в ADR 012. Version contract этим уточнением не меняется.
## Исторический контекст и решение
На момент решения WealthLedger использовал проектную schema `wealth-ledger-sdd` version 3 с
последовательностью `Proposal → Delta Specs → Design → Tasks → Apply`, один exact
OpenSpec CLI contract и проверки без изменения проверяемых материалов. Это
сохраняло единый источник правил жизненного цикла в хранилище спецификаций, выражало approval
boundaries нативными extension points и не вводит второй workflow engine или
monorepo control plane для четырёх независимых Git-репозиториев.
## Рассмотренные варианты
- Встроенная `spec-driven` schema отклонена: она не выражает обязательный переход Specs перед Design и не задаёт Delivery Map или проектные роли артефактов.
- Отдельный workflow engine или custom generator отклонён: он создал бы конкурирующий формат и lifecycle authority.
- Nx, Turborepo, Bazel, Make или just отклонены для текущего масштаба: workspace не имеет общего dependency graph, build cache или CI, а репозитории уже владеют canonical gates.
- Versioned source tree, patch-only, programmatic generator и symlink для root
agent entrypoints отклонены для текущего масштаба: они добавляют отдельный
projection lifecycle поверх уже достаточных schema, docs и scripts.
## Исторические последствия
OpenSpec CLI закреплён exact version contract; новая stable release не включает
auto-upgrade, а смена contract является отдельным SDD change со staged
проверками совместимости. Локальная точка входа оставалась навигацией,
а обязательные ограничения проверялись отдельно. Planning
verifier до approval проверяет только детерминированную структуру: capability
set, Delivery Map, rollback, task area и Requirement coverage. Он различает
artifact-validation, неверный invocation и failure окружения, чтобы agent не
переписывал planning artifact из-за сломанного вызова или prerequisite.
Dispatcher знает четыре workspace area, но не реализует build или test logic:
он делегирует repo-owned commands. Approval identity остаётся человеческим
workflow constraint, потому что schema моделирует порядок артефактов, но не
личность утвердившего. Semantic review по-прежнему не заменяется structural
checks.
Решение пересматривается, если OpenSpec перестанет поддерживать нужный artifact graph, появится обязательный общий CI/build graph или стоимость последовательной локальной orchestration станет существенной. В этих случаях новый ADR сравнивает native evolution с внешним engine либо monorepo platform.
@@ -0,0 +1,58 @@
---
status: superseded
---
# Native OpenSpec lifecycle и repository gates
Обязательность описанной ниже процедуры заменена
[ADR 012](012-task-driven-development-with-scoped-context.md). Текст сохраняет
историческое решение; current policy — прямая работа по задаче.
## Контекст
[ADR 010](010-native-openspec-sdd-and-local-orchestration.md) сохраняет rationale
выбора native OpenSpec и независимых repository gates. Его описание schema v3,
custom authority/planning verifiers и dispatcher относится к прежней реализации.
На момент решения проектная schema `wealth-ledger-sdd`
имела version 5 и граф `Proposal → Delta Specs → Design → Tasks → Apply`.
Порядок работы определялся средствами OpenSpec и общими правилами проекта;
дополнительные точки входа служили навигацией по единой процедуре.
Версия OpenSpec CLI была зафиксирована как `1.10.0`.
Версия schema и CLI contract — разные сведения. Фиксация версии
не доказывает запуск прежних проверок; это решение не меняет CLI pin,
version contract, schema или требования к tooling.
## Решение
Частично заменить ADR 010 в части прежней schema version и orchestration.
Единым хранилищем согласования оставался `wealth-ledger-specs`.
Процедура разделяла следующие ответственности:
- Согласование ролей, подтверждений, объёма поставки и обязательных проверок.
- Планирование и применение изменений, структурную проверку и смысловое согласование требований.
- Выбор и повторную сверку исходных знаний, восстановление контекста и повторное использование свидетельств.
Native OpenSpec получает structure и contextFiles текущего artifact или Apply.
Каждое изменяемое приложение владело своей проверкой качества. Выполнение
процедуры не зависит от custom verifier, dispatcher, skill или session adapter;
optional entrypoints остаются navigation, без второго workflow engine.
## Последствия
Люди и автоматизированные инструменты следовали единой процедуре, а каждый
репозиторий отвечал за собственные проверки. ADR фиксировал причину этого
разделения ответственности. Исходные альтернативы и rationale остаются
в ADR 010; historical archives, audits и evidence сохраняют описание своего времени.
Structural success не подтверждает решение Change Owner, semantic compliance
или независимый PASS. Approval bindings, полный применимый context/Design
package, независимая Validation и отдельная Finalization сохраняются.
Замена orchestration не ослабляет gates и не разрешает изменение незатронутых
main-spec requirements о tooling.
Текущие принципы сопровождения описаны в
[engineering governance](../development/engineering-governance.md), роль
сохранённых требований — в [OpenSpec](../development/openspec-authority-and-lifecycle.md).
Эти материалы отражают последующее упрощение процесса, а не восстанавливают
обязательность исторической процедуры.
@@ -0,0 +1,33 @@
# 012. Прямая разработка с контекстом по задаче
**Status:** Accepted; supersedes обязательный lifecycle из ADR 010/011.
## Context
Обязательная цепочка OpenSpec artifacts не обеспечила соответствие исходному
намерению: суженный change мог пройти все проверки, оставив backend и основные
рекомендации исследований вне реализации. Владелец проекта выбрал прямую
разработку без обязательного OpenSpec/SDD.
## Decision
Задача пользователя определяет результат. Рабочий контекст выбирается по
предмету изменения: локальные правила разработки отделены от проектных фактов,
а тематические источники объясняют соответствующую область. Общие принципы
и согласование контракта не дублируются в независимых описаниях.
[Engineering governance](../development/engineering-governance.md) и
[context discipline](../development/agent-token-efficiency.md) описывают текущую
работу. Repo gates, trust-boundary checks, migration safety и semantic review
сохраняются. OpenSpec используется только по явному запросу; history сохраняется.
## Consequences
Обычная задача не требует planning artifacts, role handoffs, approval fingerprints
и archive. Сложные public/security/data решения уточняются по существу до опасной
операции. Правильность routing проверяется на задачах и counterexamples; качество
агента оценивается по наблюдаемым outcomes, а не по числу файлов или checkboxes.
Рекомендации исследований адаптируются к принятой архитектуре и существующим
проверкам WealthLedger. Backend остаётся источником OpenAPI. Новые сервисы,
общая инфраструктура и проверки не вводятся только ради организации контекста.
+5
View File
@@ -15,6 +15,11 @@
Это давало частичное покрытие одного агентства без покупки доступа, Это давало частичное покрытие одного агентства без покупки доступа,
но не рейтинг любой российской облигации и не стабильный контракт API. но не рейтинг любой российской облигации и не стабильный контракт API.
Основания выбора сохранены из исследования, а применение решения подтверждено
backend-коммитом `235c179` (`feat: add bond details, yields and credit ratings`).
Исходное исследование содержало также рекомендации; эта запись фиксирует
выбранный подход, не объявляя все рекомендации реализованными.
## Решение ## Решение
Использовать публичный [реестр выпусков НКР](https://ratings.ru/ratings/issues/) Использовать публичный [реестр выпусков НКР](https://ratings.ru/ratings/issues/)
-27
View File
@@ -1,27 +0,0 @@
# 014. Актуальная OpenAPI-схема как вход frontend
**Статус:** принято 2026-09-13.
## Контекст
Frontend и backend — независимые репозитории. Зависимость frontend от путей
и команд backend связывала бы их внутреннее устройство. Предоставленная
OpenAPI-схема служит границей между владельцем API и его потребителем.
## Решение
Backend остаётся источником HTTP-контракта согласно
[ADR 002](002-nest-generated-openapi-contract.md).
Frontend принимает предоставленную OpenAPI-схему как актуальную и генерирует
из неё типы локально, без доступа к соседнему backend checkout.
Подготовка и доставка схемы находятся вне frontend.
## Последствия
Frontend зависит от публичного контракта, а не от внутреннего устройства
backend. Соответствие типов входной схеме не гарантирует её актуальность:
рассинхронизацию предоставленной схемы и работающего API frontend не выявляет
сравнением схем. Актуальность входа остаётся принятым допущением.
OpenAPI endpoint не является runtime-зависимостью SPA.
Текущее взаимодействие описано в [HTTP API](../architecture/http-api.md).
@@ -1,47 +0,0 @@
# 015. SQLite в ограниченном single-server deployment
**Status:** Accepted
## Context
[ADR 001](001-prisma-sqlite-local-mvp.md) ограничивал SQLite локальным MVP.
Контейнерная поставка требует постоянного хранилища на одном сервере,
но не требует высокой доступности или нескольких экземпляров API.
Пользователи, сессии, зашифрованные подключения и банковская история уже
хранятся в SQLite. Кэши, limiter и generation подключений локальны процессу.
## Decision
Разрешён один backend process на одном Docker host с SQLite в постоянном
host directory. Development использует отдельный named volume. SQLite
остаётся embedded library без сетевого порта. Это расширяет deployment
boundary ADR 001; его исторический контекст сохраняется.
Prisma migrations выполняются отдельным одноразовым service из того же image,
что и backend. Перед изменением схемы backend останавливается, всё data
directory копируется вместе с возможными WAL/SHM files. Ключ шифрования
сохраняется отдельно. Неуспешная migration блокирует запуск новой версии.
Откат image требует совместимости с текущей схемой и не восстанавливает
старую базу. Restore требует отдельного решения о потере данных после backup.
Frontend и backend имеют независимые версии; production фиксирует version tags.
Краткий простой допустим.
## Alternatives
PostgreSQL потребовал бы миграции данных и отдельного database lifecycle;
текущие требования этого не оправдывают. Kubernetes, replicas и blue-green
не устраняют ограничения process-local состояния и не входят в решение.
## Consequences
Сервер не обещает HA или zero-downtime. Один backend instance — обязательное
ограничение, а не рекомендуемая настройка масштабирования. Нужны контроль
свободного места, согласованные backups и проверяемое восстановление.
Решение пересматривается при необходимости нескольких replicas, другом
availability target, заметном write contention или потребности в отдельном
database service. Добавление реплик до такого решения недопустимо.
Топология описана в [архитектуре](../architecture/containers.md#контейнерная-поставка),
операции — в [runbook](../architecture/deployment.md).
-72
View File
@@ -1,72 +0,0 @@
# 016. Версионируемый комплект развёртывания
**Status:** Accepted
Frontend и backend выпускаются независимо. Выбор их версий непосредственно
на сервере перекладывает подбор совместимой пары на оператора при каждом
обновлении. Принято решение выпускать комплект в репозитории `deploy`:
его релиз закрепляет проверенную пару конкретных версий обоих образов.
Независимые релизы компонентов сохраняются; выпуск компонента сам по себе
не означает готовность нового релиза развёртывания.
Оператор подключается к серверу по SSH и одной командой запускает обновление
до выбранного релиза развёртывания. При обновлении backend команда должна
скачать образы до его остановки, сохранить резервную копию остановленной базы,
выполнить миграции и проверить запуск. Ограничения
[ADR 015](015-single-server-sqlite-deployment.md)
сохраняются: один backend, краткий простой, неуспешная миграция блокирует запуск;
откат образа не восстанавливает базу автоматически.
## Доставка и подготовка сервера
На сервере хранится Git checkout `deploy`. Команда обновления получает
выбранный тег комплекта и загружает закреплённые образы из Nexus;
сборка приложения на сервере не требуется.
Docker с Compose, внешний HTTPS и доступ к registry считаются подготовленными.
README комплекта должен описывать последовательность этой разовой подготовки
и подключения существующей установки. Автоматическое provisioning сервера
в комплект не входит.
Рабочие данные, сертификат доверия T-Bank и секреты остаются вне обновляемого
checkout. При подключении существующей установки сохраняются её БД,
JWT secret и ключ шифрования. Сертификат `certs/tbank-ca.pem` монтируется
в контейнер только для чтения; путь внутри контейнера задаётся через
`TBANK_CA_CERT_PATH`. Этот сертификат не обслуживает входящий HTTPS.
## Резервные копии
Перед обновлением backend создаётся согласованная копия всего каталога данных
при остановленном backend. Копии хранятся в локальном `backups/` вне checkout,
без автоматического удаления; очистку выполняет оператор вручную.
Такие копии позволяют восстановиться после неудачного обновления, но сами
по себе не защищают от потери диска сервера.
## Обновление и сбои
Если релиз меняет только frontend, обновляется только его контейнер:
backend не останавливается, миграции и резервное копирование БД для этого
обновления не выполняются. Этот путь применим только при неизменных образе
и конфигурации backend.
Если миграции прошли, но новый backend не прошёл проверку запуска, обновление
завершается с ошибкой, неисправный backend останавливается. Оператор получает
диагностику, сведения о предыдущей версии и путь к резервной копии.
Автоматического отката нет: прежний образ может быть несовместим с новой
схемой. Откат образа и восстановление базы остаются отдельными явными действиями
по правилам ADR 015.
## Выпуск комплекта
После публикации образов компонентов отдельно готовится релиз `deploy`:
фиксируется совместимая пара версий, выполняется её проверка и выпускается
тег комплекта. Публикация компонента не создаёт релиз комплекта автоматически
и не запускает обновление сервера. Оператор выбирает выпущенный тег при
запуске команды развёртывания.
## Состояние реализации
Комплект и команда реализованы в отдельном репозитории `deploy`.
Публикация первого тега и обновление рабочего сервера — отдельные действия.
[Контейнерная поставка](../architecture/deployment.md) описывает границы,
пошаговая инструкция находится в README комплекта.
@@ -1,146 +0,0 @@
# 017. Локальное чтение данных T-Bank и фоновая синхронизация
**Status:** Accepted — историческое архитектурное направление. Решения ниже о
request-driven запуске, ручном обновлении и отсутствии периодического обхода
заменены [ADR 019](019-hourly-reads-and-online-instrument-reference.md).
Prefetch policy ниже заменена [ADR 018](018-minimal-runtime-coordination.md):
свечи загружаются по запрошенному диапазону, без предварительной загрузки
всей истории бумаг портфеля. Остальные гарантии локального хранения сохранены.
Реализация `persist-tbank-data` добавлена в рабочие ветки приложения;
performance/browser и canonical application gates пройдены. Это не отметка
развёртывания в production. Ниже сохранён контекст
принятия решения, а текущее устройство описывает
[модель свежести](../architecture/cross-cutting/data-freshness.md).
## Context
Инвестиционная история не сохраняется в БД. Серверные кэши находятся в памяти
процесса, а загрузка операций, аналитики дохода и рыночной истории зависит
от повторных обращений к T-Bank. Для журнала операций запрос доступных счетов
выполняется даже перед чтением кэшированной страницы. Сохранение только
ответов без изменения этого пути не устранит ожидание внешнего API.
Нужно ускорить все экраны, использующие T-Bank, и уменьшить повторную загрузку
данных. Текущие механизмы описаны в [политике свежести](../architecture/cross-cutting/data-freshness.md)
и [журнале операций](../domains/operations/overview.md).
## Decision
Для уже загруженных данных обычный запрос frontend обслуживается из постоянного
локального хранилища без ожидания ответа T-Bank. Обновление выполняется в фоне.
Пользователь видит время последнего успешного обновления и может запросить
обновление вручную. Сохранённый портфель может ещё не содержать новую сделку;
эта задержка принята ради быстрого открытия экрана.
Синхронизация запускается только запросами пользовательских экранов и ручным
действием «Обновить». Периодических запусков, ежедневного обновления неактивных
пользователей и плановой сверки истории нет. Фоновое выполнение означает,
что уже запрошенная работа не задерживает выдачу сохранённых данных;
самостоятельное расписание обновлений оно не подразумевает.
Обычный запрос запускает обновление, только если нужных данных нет либо
истёк согласованный срок их свежести:
| Данные | Срок свежести |
| --- | --- |
| Портфель и текущие цены | 1 минута |
| Операции текущего месяца | 5 минут |
| Список доступных счетов | 5 минут |
| Справочник инструментов и календари купонов и дивидендов | 1 сутки |
| Незавершённая дневная свеча | 1 минута |
Эти сроки проверяются при запросе и не задают расписание фоновых запусков.
Аналитика дохода зависит от полноты и свежести исходных операций.
Полностью загруженные предыдущие месяцы операций повторно читаются из T-Bank
только по кнопке «Обновить» для показанного периода. Обычный запрос догружает
отсутствующие интервалы и при необходимости обновляет текущий месяц.
Завершённые дневные свечи сохраняются; ручное обновление перечитывает
показанный диапазон. Автоматическое обнаружение поздних операций и исправлений
в уже полностью загруженных прошлых месяцах не обещается.
Кнопка «Обновить» обходит проверку давности и обновляет данные текущего
экрана: выбранного счёта, доступных счетов в режиме «Все счета» либо
инструмента и показанной личной истории на его странице. Нажатие не означает
повторную загрузку всей накопленной истории всех счетов и инструментов.
Решение охватывает все экраны с данными T-Bank. Исполненные операции и рыночная
история сохраняются; текущие позиции, остатки и цены представлены последним
успешно полученным состоянием. Будущие выплаты остаются изменяемым прогнозом,
а не исполненными операциями. Это не решение о накоплении исторических снимков
стоимости портфеля.
Рыночная история бумаг текущего портфеля загружается заранее в рамках
запрошенной пользователем работы. Для остальных бумаг первая загрузка
начинается при открытии инструмента, результат сохраняется для последующего
чтения. Предварительная загрузка истории всего каталога не требуется.
При первом подключении текущий портфель и недавние операции могут отображаться
до завершения загрузки более ранней истории. Частично загруженный период явно
отличается от полного: неполные итоги нельзя выдавать за окончательные,
а отсутствие загруженных записей — за подтверждённое отсутствие операций.
Смысл полноты определён в [глоссарии](../glossary.md#полнота-истории-счёта).
Первый запрос задаёт приоритет: сначала период, нужный экрану, включая
периоды сравнения дохода, затем вся доступная история счёта. Уже запущенная
догрузка может завершиться после ухода пользователя со страницы; нового
периодического запуска это не создаёт.
Полнота относится к успешно загруженным временным интервалам. Смена месяца
сама по себе не делает завершённым последний неполный интервал прошлого
месяца: его оставшаяся часть должна быть догружена. Ошибка промежуточной
страницы не подтверждает полноту и не удаляет ранее сохранённые данные.
Незавершённая свеча также не становится завершённой только из-за смены дня:
её окончательное состояние должен подтвердить источник.
Целевой ориентир — до 500 мс для обычного серверного запроса к заполненной БД.
Это согласованная цель измерения, а не подтверждённая характеристика системы.
Объём данных и набор запросов для проверки предстоит определить.
Для этого изменения принято допущение: ID брокерской операции не меняется
со временем. Обнаружение сменившихся ID и регулярная сверка старой истории
не входят в объём работы. Это допущение проекта, а не гарантия T-Bank:
используемый protobuf-контракт допускает изменение ID. Допущение относится
к идентичности операции; оно не требует игнорировать новые значения полей,
если операция с прежним ID снова получена от брокера.
Подключение T-Bank считается постоянным в рассматриваемом сценарии.
Архив без подключения, специальная политика хранения после отключения
и работа с закрытыми счетами пока не проектируются. Это ограничение объёма
не отменяет существующие проверки доступа и обработку ошибок интеграции.
## Alternatives
Ожидание свежего ответа T-Bank при каждом открытии экрана сохраняет зависимость
скорости и доступности интерфейса от брокера. Увеличение времени жизни кэша
в памяти уменьшает часть повторных запросов, но не сохраняет историю после
перезапуска и не обеспечивает локальное чтение всех нужных данных.
## Consequences
Интерфейсу нужны различимые состояния первичной загрузки, полноты истории,
свежести и ошибки фонового обновления. Успешная загрузка недавних операций
не подтверждает полноту более ранних периодов или периодов сравнения дохода.
Проверки локальной сессии и принадлежности личных данных сохраняются.
Способ подтверждения брокерского доступа должен позволять обслужить уже
загруженные данные без синхронного ожидания T-Bank.
Повторные запросы одного набора данных должны использовать уже выполняющуюся
загрузку. Завершённые интервалы и прогресс сохраняются в БД; после перезапуска
незавершённый интервал повторяется при следующем запросе или ручном обновлении.
Курсор брокера и максимальная дата полученной операции не доказывают полноту.
Ручное обновление периода согласует состав операций и их поля с полностью
полученным ответом источника. Только успешная загрузка всех страниц позволяет
зафиксировать полное покрытие, успешное обновление и удалить отсутствующие
в этом ответе записи периода. Ошибка не превращает частичный ответ в полный.
Личные операции и позиции сохраняют принадлежность пользователю и счёту.
Общие данные инструмента не должны содержать персональные позиции и расчёты;
поздний результат прежнего подключения не публикуется для нового подключения.
Существующие HTTP-контракты пока не поддерживают все необходимые состояния
загрузки и полноты; их изменение входит в будущую реализацию.
-45
View File
@@ -1,45 +0,0 @@
# 018. Минимальная runtime-координация
**Status:** Accepted. Уточняет cookie policy ADR 009 и prefetch policy ADR 017.
## Context
Изоляция владельцев данных, атомарные финансовые изменения и локальное чтение
не требуют отдельного протокола согласования каждого HTTP-ответа. Дополнительная
координация оправдана только наблюдаемым поведением, которое иначе нарушается.
## Decision
- Refresh сохраняет атомарную ротацию. Неуспешный refresh не меняет cookie:
поздний отказ не должен удалять cookie успешного запроса. Logout очищает cookie.
- Frontend объединяет refresh и упорядочивает cookie-writing requests только
внутри вкладки. Cross-tab auth synchronization не является требованием;
после изменения сессии в другой вкладке допускаются reload или re-login.
- Отменой, сбросом и публикацией query results владеет TanStack Query.
Смена session/connection очищает соответствующий cache; отдельные registries
HTTP-запросов, access epochs и URL-wide latest-read counters не используются.
Локальная auth boundary предотвращает применение старого auth result и
повтор mutation под другой identity.
- Status GET читает только состояние работы. Открытый экран перечитывает
resources через `sync=observe` во время работы и после завершения.
Сверка hashes между status и data GET, публичные progress counters и
невыдаваемые sync states не нужны. Полнота истории остаётся в resource metadata.
- Свечи загружаются для запрошенного диапазона. Предзагрузки всей истории
портфеля и автоматического расширения короткого периода до `all` нет.
- Backend формирует согласованные entries и aggregates из одних исходных
данных; frontend доверяет этому contract и не выполняет вторую сверку групп.
- Request context и HTTP completion принадлежат `nestjs-pino`/`pino-http`.
Проект сохраняет safe field filtering, redaction и route-template logging.
- Deployment всегда выполняет один stop → backup → migration → readiness
цикл. No-op и frontend-only оптимизации не обещаются; короткий простой принят.
## Consequences
Первое открытие нового диапазона свечей может ждать загрузку. Наблюдение активной
работы может перечитать неизменившиеся локальные данные, зато не требует
checksum protocol. Повтор deployment создаёт новый backup и может остановить API.
DB ownership подключения, conditional publication, coverage, staging,
банковские intent fingerprints и версии записей сохраняются. Отмена HTTP
не считается отменой уже выполняющейся server-side transaction.
Сохранённый полный forecast не заменяется candidate с изменившимися dependencies.
@@ -1,40 +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). Правило «справочные
сведения запрашиваются онлайн» дополнено исключением для сектора и бренда
актива позиции — [ADR
022](022-stored-instrument-classification.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`.
@@ -1,56 +0,0 @@
# 022. Хранимая классификация инструментов T-Bank
**Status:** Accepted. Дополняет [ADR
019](019-hourly-reads-and-online-instrument-reference.md) исключением из
правила «справочные сведения запрашиваются онлайн». Решение утверждено
владельцем в [Issue
WealthLedger/main#48](https://git.personal.ksv741.ru/WealthLedger/main/issues/48).
## Context
ADR 019 установил, что справочные сведения инструментов (detail, lookup,
каталоги) запрашиваются онлайн и не сохраняются как долговременный справочник:
«старые справочные строки не являются источником актуальности». Вкладка
«Концентрация» требует для каждой позиции её сектора и эмитента — бренда
актива по данным T-Bank. Сектор есть только в описаниях отдельных типов
инструментов (`share`, `bond`, `etf`), а эмитент — в описании актива по
`asset_uid` инструмента; из названия, тикера, ISIN или `logoName` его вывести
нельзя. Запрашивать эти поля онлайн при каждом открытии экрана нельзя:
ответ Portfolio не ждёт T-Bank, а разрезы должны считаться по уже сохранённому
снимку.
Существующий metadata-кэш (`TbankInstrument`) не подходит: общий ответ
`GetInstrumentBy` не содержит `sector`, а его `brand` — данные логотипа,
а не `{uid, name}` бренда актива.
## Decision
- Сектор и бренд актива позиции хранятся как последнее известное значение в
глобальной таблице `TbankInstrumentClassification` (провайдер, ключ
инструмента, сектор и время его получения, uid актива и uid/название бренда
с временем получения). Данные публичные, без привязки к пользователю или
подключению; ограждение по connection revision к ним не применяется, публикация
Snapshot ограждается как прежде.
- Каждое поле обновляется в часовом обходе при его первой попытке, начавшейся
не ранее чем через 24 часа после последнего успешного получения этого поля;
пока значение не получено, попытка повторяется в каждом обходе. Сектор и
бренд обновляются независимо.
- Неуспешное получение не мешает публикации Portfolio Snapshot и не влияет на
его свежесть; сохранённое значение остаётся доступным. Отказ token
обрабатывается как отказ token при обходе Portfolio. Ответ «инструмент не
найден» сохраняет прежнее значение и перезапускает отсчёт 24 часов.
- Классификация не входит в `meta.dependencies` Portfolio и не меняет
`isStale` и `lastSuccessfulAt`.
## Consequences
- Это первый долговременный справочник T-Bank в системе: ADR 019 теряет
характер «только онлайн» для этой области и дополняется, а не заменяется.
- Разрезы концентрации считаются по снимку без обращений к T-Bank; число
вызовов растёт ограниченно — не более одной попытки на поле в сутки на
позицию, при первом обходе после выката возможен разовый всплеск, сглаженный
общим лимитером и очередью.
- Значение может быть устаревшим до 24 часов; это приемлемо для группировки
позиций, но классификация не является источником актуальных данных брокера.
- Второй потребитель хранимой классификации — условие пересмотра решения
(выделение независимого процесса обновления, отдельные сроки).
+3 -9
View File
@@ -17,13 +17,7 @@
- [007 — React Query fetch policy](007-react-query-fetch-policy.md) - [007 — React Query fetch policy](007-react-query-fetch-policy.md)
- [008 — Pino backend logging](008-pino-backend-logging.md) - [008 — Pino backend logging](008-pino-backend-logging.md)
- [009 — Concurrent refresh rotation policy](009-concurrent-refresh-rotation-policy.md) - [009 — Concurrent refresh rotation policy](009-concurrent-refresh-rotation-policy.md)
- [010 — Native OpenSpec SDD и local orchestration](010-native-openspec-sdd-and-local-orchestration.md) — исторический; частично заменён ADR 011, обязательность SDD отменена ADR 012.
- [011 — Native OpenSpec lifecycle и repository gates](011-native-openspec-lifecycle-with-repository-gates.md) — заменён ADR 012 в части обязательного lifecycle.
- [012 — Прямая разработка с контекстом по задаче](012-task-driven-development-with-scoped-context.md)
- [013 — НКР как первый источник кредитных рейтингов облигаций](013-nkr-bond-credit-ratings.md) - [013 — НКР как первый источник кредитных рейтингов облигаций](013-nkr-bond-credit-ratings.md)
- [014 — Актуальная OpenAPI-схема как вход frontend](014-trusted-openapi-input.md)
- [015 — SQLite в ограниченном single-server deployment](015-single-server-sqlite-deployment.md)
- [016 — Версионируемый комплект развёртывания](016-versioned-deployment-bundle.md)
- [017 — Локальное чтение данных T-Bank и фоновая синхронизация](017-local-tbank-reads-and-background-sync.md)
- [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)
- [022 — Хранимая классификация инструментов T-Bank](022-stored-instrument-classification.md)
+1 -2
View File
@@ -15,8 +15,7 @@
| Что считается кэшем и когда прежние данные теряют право на повторное использование? | [Свежесть данных](cross-cutting/data-freshness.md) | | Что считается кэшем и когда прежние данные теряют право на повторное использование? | [Свежесть данных](cross-cutting/data-freshness.md) |
| Как обрабатываются ошибки и ожидание? | [HTTP API и ошибки](http-api.md), [состояния интерфейса](cross-cutting/ui-resilience.md) | | Как обрабатываются ошибки и ожидание? | [HTTP API и ошибки](http-api.md), [состояния интерфейса](cross-cutting/ui-resilience.md) |
| Как связать ошибку с обработкой запроса? | [Наблюдаемость](http-api.md#наблюдаемость) | | Как связать ошибку с обработкой запроса? | [Наблюдаемость](http-api.md#наблюдаемость) |
| Как устроено подтверждённое локальное окружение? | [Container](containers.md#представление-container), [качество и ограничения](containers.md#качество-и-ограничения) | | Как устроено подтверждённое локальное окружение? | [Container](containers.md#представление-container), [локальная диагностика](../development/local-workspace.md) |
| Как запустить, обновить или восстановить приложение? | [Развёртывание](deployment.md) |
| Что означают предметные термины? | [Глоссарий](../glossary.md) | | Что означают предметные термины? | [Глоссарий](../glossary.md) |
Эта карта покрывает вопросы архитектуры там, где о них есть подтверждённое Эта карта покрывает вопросы архитектуры там, где о них есть подтверждённое
+35 -110
View File
@@ -27,57 +27,18 @@ flowchart LR
F -->|"HTTPS; изображения без авторизации"| C F -->|"HTTPS; изображения без авторизации"| C
``` ```
SPA выполняется в браузере пользователя. Backend — отдельное SPA выполняется в браузере пользователя. Backend — отдельное локальное
приложение; SQLite используется внутри его процесса через библиотеку и файл приложение; SQLite используется внутри его процесса через библиотеку и файл
базы. Между API и SQLite нет сетевого database server. Граница системы на базы. Между API и SQLite нет сетевого database server. Граница системы на
схеме показывает принадлежность частей, а не их размещение на одном узле. схеме показывает принадлежность частей, а не их размещение на одном узле.
## Контейнерная поставка
Deployment view для одного сервера. Caddy принадлежит серверу; Compose
содержит два постоянных application containers и одноразовую migration.
```mermaid
flowchart LR
Browser["Браузер · React SPA"] -->|HTTPS| Caddy["Серверный Caddy · TLS"]
subgraph Host["Один Docker host"]
Caddy -->|"HTTP · 127.0.0.1:8080"| Frontend["frontend · nginx · SPA + /api/"]
Frontend -->|"HTTP /api/v1 · internal network"| Backend["backend · NestJS · один process"]
Migration["migration · Prisma · one-shot"] -->|"До старта backend"| Data[("Host directory · SQLite + WAL")]
Backend -->|"Чтение и запись /data"| Data
Secrets["Restricted environment · ключи вне images"] --> Backend
end
```
Только frontend публикует host port, привязанный к loopback. Backend не
публикует порт, но имеет исходящий доступ к внешним API. nginx сохраняет
`/api/` prefix, forwarding headers и request ID; Docker DNS разрешается
повторно после backend recreation. HTML требует revalidation, hashed assets
имеют immutable cache. Caddy не содержит отдельного маршрута к API.
Development заменяет nginx на Vite с HMR, backend — на Nest watch, а host data
path — на отдельный named volume. Browser origin остаётся единым. Linux
node_modules не монтируются с хоста. Production SPA собирается с relative API
base; production cookies остаются Secure, публичный origin использует HTTPS.
Компоненты имеют независимые images и версии. Production фиксирует image
version tags; migration использует тот же backend image. Успех migration предшествует
backend healthcheck на существующем auth route; frontend ждёт healthy backend.
Runtime работает non-root, SQLite сохраняется вне image, ключ шифрования и JWT
secret поступают из restricted environment и сохраняются между обновлениями.
Ограничение SQLite расширено в [ADR 015](../adr/015-single-server-sqlite-deployment.md).
[Runbook](deployment.md) описывает установку, independent
updates, backup, rollback и explicit restore. Он также перечисляет внешние
prerequisites: локальный smoke не подтверждает registry/server rollout.
## Ответственность частей ## Ответственность частей
| Часть | Ответственность | | Часть | Ответственность |
| --- | --- | | --- | --- |
| React SPA | Экраны, навигация, формы, состояние входа и клиентский кэш ответов; форматирование денежных значений | | React SPA | Экраны, навигация, формы, состояние входа и клиентский кэш ответов; форматирование денежных значений |
| Backend API | Локальная идентичность, сессии, проверка доступа, подключение брокера, нормализация внешних данных, точные расчёты и синхронизация сохранённых источников | | Backend API | Локальная идентичность, сессии, проверка доступа, подключение брокера, нормализация внешних данных, точные расчёты и серверные кэши |
| SQLite | Долговременное хранение локальных пользователей, сессий, зашифрованных подключений, ручной банковской истории и инвестиционных источников с coverage/work | | SQLite | Долговременное хранение локальных пользователей, сессий, зашифрованных подключений и ручной банковской истории |
| Внешние системы | Данные брокера, публичные сведения об инструментах и изображения в пределах своей ответственности | | Внешние системы | Данные брокера, публичные сведения об инструментах и изображения в пределах своей ответственности |
Frontend организован слоями `app → pages → features → entities → shared`: Frontend организован слоями `app → pages → features → entities → shared`:
@@ -86,32 +47,16 @@ Frontend организован слоями `app → pages → features → ent
где локальная аутентификация отделена от подключения брокера и инвестиционных где локальная аутентификация отделена от подключения брокера и инвестиционных
сценариев. Это внутренняя организация приложений, не набор микросервисов. сценариев. Это внутренняя организация приложений, не набор микросервисов.
Маршруты и AppShell принадлежат `app`. Page controller/VM управляет запросами,
формой и восстановлением; presentation получает данные и callbacks, поля
могут использовать контекст React Hook Form. В OperationForm controller
сохраняет intent операции, черновик и явное принятие актуальной версии после
конфликта. В account-income чистые преобразования периода принадлежат модели
страницы и не связывают форму с её запросами.
Локальные packages tokens/ui/icons образуют [визуальную основу](../design/overview.md#общая-основа-и-каталог)
внутри frontend и входят в сборку SPA. Это библиотеки, а не дополнительные
runtime-контейнеры. Провайдеры темы принадлежат приложению и Storybook;
предметные компоненты и API-клиент остаются в приложении. Отдельный API-пакет
и registry-поставка визуальных пакетов не реализованы. Существующие Docker
images приложения не означают независимого npm-выпуска этих библиотек.
Финансовые HTTP-запросы браузера идут через backend. Пользователь вводит token Финансовые HTTP-запросы браузера идут через backend. Пользователь вводит token
T-Bank в форме и отправляет его для сохранения, но API не возвращает token T-Bank в форме и отправляет его для сохранения, но API не возвращает token
в ответах; SPA не использует его для прямых брокерских запросов. Прямая в ответах; SPA не использует его для прямых брокерских запросов. Прямая
загрузка публичного изображения из CDN не проходит через эту финансовую загрузка публичного изображения из CDN не проходит через эту финансовую
границу. границу.
Типизированный frontend-клиент производится из предоставленного OpenAPI snapshot Типизированный frontend-клиент производится из OpenAPI backend во время
во время разработки. В runtime SPA вызывает API, а не генератор или OpenAPI разработки. В runtime SPA вызывает API, а не генератор или OpenAPI endpoint.
endpoint. Форма запросов и ошибок описана в [HTTP API](http-api.md); направление Форма запросов и ошибок описана в [HTTP API](http-api.md); направление
генерации принято в [ADR 002](../adr/002-nest-generated-openapi-contract.md), генерации принято в [ADR 002](../adr/002-nest-generated-openapi-contract.md).
а доверие frontend к предоставленному snapshot — в
[ADR 014](../adr/014-trusted-openapi-input.md).
## Хранимые данные и границы доступа ## Хранимые данные и границы доступа
@@ -134,46 +79,28 @@ credentials не являются сохраняемым представлен
или перезапуска. Архив обратим и сохраняет историю. Подробности — или перезапуска. Архив обратим и сохраняет историю. Подробности —
[банковские карты](../domains/cards/overview.md). [банковские карты](../domains/cards/overview.md).
Сессии и подключение связаны с владельцем. Backend локально проверяет Сессии и подключение связаны с владельцем. Access token подтверждает
membership текущей revision перед каждым финансовым чтением. Unknown membership локального пользователя, но `accountId` из URL сам по себе не даёт права
не открывает доступ к произвольному счёту; известный отказ токена блокирует читать брокерский счёт. Backend получает текущее подключение пользователя
личные данные. Ротация refresh token остаётся атомарной. и его generation перед использованием финансового кэша. Проверка доступности
счёта у брокера зависит от сценария: журнал выполняет её до чтения страницы
кэша; свежий портфель одного счёта может быть возвращён без нового запроса
списка счетов. При обновлении портфеля доступ проверяется у брокера.
SQLite хранит полный последний portfolio, операции и их подтверждённые Ротация сессии атомарно заменяет hash текущего refresh token: два
диапазоны, private прогнозы, сохранённые точки истории оценки, общие конкурентных запроса не могут оба успешно выполнить одну ротацию.
instrument/market sources, классификацию инструментов (сектор и бренд актива Вызов брокера и запись в локальную базу не образуют общую транзакцию:
как последнее известное значение с временем каждого получения, при сохранении подключения сначала проверяется token, затем записывается
`TbankInstrumentClassification`) и intent/staging. шифротекст. Подробности находятся в
Нормализованные финансовые значения сохраняются decimal-строками. Полнота [аутентификации](../domains/authentication/overview.md) и
отделена от свежести и пагинации. Ответ не ждёт внешних RPC; обновлением [интеграции T-Bank](../integrations/tbank.md).
личных данных владеет часовой обход в том же backend process через
существующий лимитер, недостающие общие источники он же ставит в фоновую
очередь. На startup обход начинает следующую попытку по обычному часовому
сроку; после нового подключения первая попытка начинается сразу.
Классификация — публичные данные без привязки к пользователю и подключению;
её поля обновляются в том же обходе не чаще раза в сутки
([ADR 022](../adr/022-stored-instrument-classification.md)).
Подробности — [модель свежести](cross-cutting/data-freshness.md).
PUT подключения сначала проверяет token у T-Bank, затем одной транзакцией Брокерские счета, сводки, портфель, страницы журнала и будущие события
сохраняет encrypted token, новую revision и проверенный membership. не являются долговременной историей в SQLite. Они получаются для текущего
Сетевая операция не удерживает SQLite-транзакцию. Общие источники не содержат подключения и могут временно кэшироваться. Перезапуск backend не
личных позиций, ожидаемых сумм или credentials. Произвольного архива восстанавливает прежние финансовые снимки и cursor continuations из базы;
отключённых счетов нет. [модель свежести](cross-cutting/data-freshness.md) отделяет это от хранения
учётных данных.
## Сохранение и восстановление данных
Поддерживаемая процедура останавливает backend и копирует весь data directory
с возможными WAL/SHM files до migration. Копирование отдельных файлов
работающей базы не сохраняет целостный снимок; для live backup потребовался бы
SQLite backup API. Восстановление проверяется на отдельной копии. Ключ шифрования подключения хранится отдельно от базы;
его потеря делает сохранённый T-Bank token нечитаемым.
Откат приложения требует совместимости предыдущей версии с сохранённой
схемой. Расширенная банковскими таблицами схема сохраняет пользователей,
сессии и подключения. Возврат старого backup поверх новой истории удалил бы
введённые банковские операции; откат приложения не равнозначен откату данных.
Совместимость произвольных будущих версий этим не гарантируется.
## Стратегия решения и её основания ## Стратегия решения и её основания
@@ -203,18 +130,16 @@ 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. Подтверждённая топология — локальная SPA и локальный API с файлом SQLite.
Origin браузера, адрес API и cookie policy должны быть согласованы. Origin браузера, адрес API и cookie policy должны быть согласованы; условия
`localhost` и `127.0.0.1` задают разные browser-границы для cookie и CORS. и диагностика описаны в [локальном окружении](../development/local-workspace.md).
Локальный HTTP поддерживается в development-режиме backend; в остальных Наличие локального запуска не задаёт production-топологию или гарантии SLA.
режимах refresh-cookie имеет Secure flag и требует защищённого соединения.
Single-server deployment не задаёт гарантий HA или zero-downtime.
Coordinator и лимитер T-Bank действуют на один экземпляр backend. Сохранённые Кэши и лимитер T-Bank действуют на экземпляр backend. Ротация ключа шифрования
данные переживают перезапуск; processRunId не является distributed lease. Ротация ключа шифрования
подключений и согласование изменений подключения между распределёнными подключений и согласование изменений подключения между распределёнными
экземплярами не входят в реализованный механизм. Эти ограничения не экземплярами не входят в реализованный механизм. Эти ограничения не
позволяют переносить локальную схему на несколько экземпляров без отдельного позволяют переносить локальную схему на несколько экземпляров без отдельного
+59 -85
View File
@@ -1,98 +1,72 @@
# Свежесть данных и повторное использование # Свежесть данных и повторное использование
WealthLedger читает личные инвестиционные данные из SQLite. Обычный GET и его WealthLedger показывает финансовое наблюдение на момент получения данных.
HTTP retry возвращают сохранённый результат и не запускают личную работу с Временный кэш уменьшает повторные обращения к брокеру, но не превращает
T-Bank. Нормальным владельцем обновления служит часовой обход текущего снимки в историю портфеля и не обещает рыночные данные в реальном времени.
подключения: после успешного подключения он начинает первую попытку, затем
начинает следующую не ранее чем через 60 минут от начала предыдущей. После
простоя выполняется не более одна актуальная попытка, а перекрывающийся срок
пропускается, не ставится в очередь. Общие справочные данные инструментов
запрашиваются онлайн, когда это требуется карточке; финансовый HTTP-ответ не
ждёт T-Bank. Единственное исключение — сектор и бренд актива позиции, которые
хранятся как последнее известное значение с обновлением не чаще раза в сутки;
свежесть и полнота Portfolio от них не зависят. Модель принята в
[ADR 019](../../adr/019-hourly-reads-and-online-instrument-reference.md),
исключение — в
[ADR 022](../../adr/022-stored-instrument-classification.md).
## Клиентский и серверный снимки ## Клиентский и серверный снимки
[Client Cached Snapshot](../../glossary.md#client-cached-snapshot) — успешный [Client Cached Snapshot](../../glossary.md#client-cached-snapshot) — последний
ответ в TanStack Query. Он сохраняет показанное содержимое при временной ошибке успешный ответ, сохранённый TanStack Query в SPA. При обычном фоновом обновлении
HTTP-перечитывания в той же границе доступа. Это не серверный fallback и не того же набора данных уже показанный результат остаётся на экране; ошибка
состояние часового обхода. SQLite независимо от браузера хранит membership, обновления сообщает о проблеме, не заменяя известные значения пустотой.
последний полный portfolio, исполненные операции, прогнозы, общие сведения об Клиентский кэш не является внешней системой или отдельным runtime-приложением.
инструментах и классификацию инструментов (сектор и бренд актива как
последнее известное значение, [ADR
022](../../adr/022-stored-instrument-classification.md)), котировки, свечи и
календари.
[Полнота периода](../../glossary.md#полнота-истории-счёта) и свежесть различаются. [Portfolio Snapshot](../../glossary.md#portfolio-snapshot) — полный результат
Успешно проверенный пустой результат полон и доступен; последняя страница загрузки портфеля на backend. В пределах разрешённого сценарием срока он может
журнала сама по себе полноту не доказывает. Результат каждого личного ресурса служить серверным fallback при временной ошибке upstream. Backend отмечает
в `{ data, meta }` описывают пять независимых значений: freshness, такой ответ `isStale`; frontend не выводит это поле из состояния собственного
completeness, `lastSuccessfulAt`, `lastAttempt` и availability. Первые, query-кэша. Серверное предупреждение и ошибка клиентского refetch могут
полнота и `lastSuccessfulAt` относятся только к возвращённому подтверждённому возникать независимо.
результату. `lastAttempt` описывает относящуюся к ресурсу часовую попытку:
`none`, `running`, `succeeded`, `partial` или `failed`.
Итог попытки выводится из фактических результатов её шагов: попытка, не Свежесть и допустимость fallback определяются сценарием. Портфельный fallback
завершившая обязательный шаг, не обозначается полным успехом, а полученные не распространяется автоматически на журнал, прогноз событий или страницу
успешные результаты сохраняются. Ожидание каждого шага ограничено; зависший инструмента. Точные правила находятся в
шаг учитывается проблемным, а начатое обращение к T-Bank не отменяется. [справочнике портфеля](../../domains/portfolio/data-reference.md),
Попытка, потерявшая выполнение после сбоя сохранения или остановки процесса, [журнале операций](../../domains/operations/overview.md) и
закрывается частичным успехом либо неуспехом и не блокирует следующий обход [будущих событиях](../../domains/income/upcoming-events.md).
дольше обычного часового срока.
Поздняя неуспешная или частичная попытка сохраняет прежний полный результат и По [ADR 007](../../adr/007-react-query-fetch-policy.md) запрос связан с открытием
его исходное время. Если полного результата нет, `data` равно `null`: состояние экрана, а возврат фокуса окна не запускает refetch. Повторное открытие после
availability объясняет первоначальную загрузку, временную недоступность либо `staleTime` может обновить ответ в фоне. Эта политика не является таймером
недоступность подключения. Пустой массив не означает отсутствие результата. периодического обновления. Перезапуск backend уничтожает его in-memory
Старый полный portfolio не удаляется из-за возраста. `Portfolio.isStale` снимки и continuations журнала; SQLite не восстанавливает их.
сохраняет смысл возраста его snapshot, а не обозначает неуспех обхода или
клиентскую ошибку перечитывания.
| Источник | Владелец нормального обновления |
| --- | --- |
| Membership, portfolio, изменяемый хвост операций и события | Часовой обход текущего подключения |
| Котировки и изменяемый хвост дневных свечей | Установленная политика источника; не ordinary personal GET |
| Metadata, поиск каталога и company | Онлайн при чтении |
| Полные прошлые интервалы операций и завершённые свечи | Не обновляются часовым обходом или обычным GET |
Два исключения разрешают только первое получение отсутствующего результата:
первое открытие неподтверждённого исторического интервала операций получает
только этот интервал и возвращает локальные строки с неполным покрытием; первое
открытие диапазона свечей без подтверждённого локального результата получает
только этот диапазон. Ни одно исключение не обновляет membership, portfolio,
события, другой интервал или диапазон. Их HTTP retry также только перечитывает
локальное состояние. Подтверждённый полный интервал или диапазон остаётся
локальным.
Часовой обход сохраняет успешные разделы независимо, а failed section не
заменяет прежний complete result. Отменой query lifecycle владеет TanStack
Query. Открытый экран перечитывает ресурс не чаще одного раза в 30 минут;
это локальный HTTP read. Совместимый параметр `sync=observe`, пока он есть в
маршруте, также является только чтением и не представляет команду обходу.
[HTTP API](../http-api.md) описывает исполняемый контракт envelope.
## Смена подключения ## Смена подключения
Каждое чтение проверяет локальную сессию, текущее подключение, известный отказ Кэш финансовых данных относится к пользователю, счёту и текущему
токена и сохранённый состав доступных счетов. Расшифровка token и GetAccounts подключению T-Bank. Backend сначала читает подключение и его generation, затем
для локальной выдачи не нужны. Старый membership допускает выдачу и фоновую проверяет возможность повторного использования ответа. Свежий портфель
сверку; это задержка обнаружения отзыва доступа, а не live-проверка брокера. одного счёта не требует нового запроса списка счетов у брокера; при обновлении
Unknown membership не даёт доступа к произвольному accountId: ответ сообщает портфеля доступ проверяется заново. Журнал операций проверяет доступ до чтения
о загрузке доступа. Успешный новый список немедленно ограничивает последующие кэшированной страницы. Идентификатор счёта и совпадение параметров запроса
чтения. Известный invalid token блокирует личные данные. сами по себе недостаточны для выдачи кэша.
Успешный PUT атомарно сохраняет token, новую случайную внутреннюю revision При каждом успешном сохранении подключения token шифруется с новым
и membership из проверки token. Прежние личные datasets и work становятся AES-GCM nonce. Из сохранённого шифротекста backend вычисляет непрозрачную
недоступны. DELETE удаляет подключение и личные данные; общие неперсональные внутреннюю generation. Она не хранится отдельным полем базы и не входит
источники могут оставаться. Неуспешный PUT сохраняет действующую границу. в API, журналы или ошибки. Portfolio Snapshot и Ledger Page Cache допускают
повторное использование только в пределах соответствующей generation.
Старый кэш или поздно завершившийся запрос прежней generation не обслуживает
новый запрос после замены подключения, включая сохранение того же plaintext
token. Удаление подключения не оставляет доступного контекста для чтения кэша.
Публикация проверяет revision и attempt в той же SQLite-транзакции; поздняя После успешного изменения подключения frontend сначала записывает
работа старого подключения не меняет даже timestamp. Revision не попадает подтверждённый status, затем сбрасывает generated GET queries в поддеревьях
в API, логи или cursor. Frontend очищает personal queries при смене подключения `/api/v1/brokerage-accounts`, `/api/v1/stocks` и `/api/v1/bonds`. Активные
и известном отказе доступа; поздний ответ не восстанавливает скрытые данные. выборки запрашиваются заново, неактивные — при следующем использовании.
Logout очищает сессию и query cache. Подробнее — Client Cached Snapshot прежнего подключения больше не служит результатом
[интеграция T-Bank](../../integrations/tbank.md). новой границы.
Неуспешная операция подключения не меняет статус, generation и доступные
финансовые данные. Обычная ошибка refetch также сохраняет клиентский снимок
текущей границы. Logout — отдельное событие: оно очищает состояние
пользовательской сессии, status подключения и query cache.
Пользовательский сценарий описан в [интеграции T-Bank](../../integrations/tbank.md).
Согласованные правила frontend находятся в workspace-спецификациях
[query coherence](../../../specs/openspec/specs/frontend-query-coherence/spec.md)
и [data resilience](../../../specs/openspec/specs/frontend-data-resilience/spec.md);
серверные правила — в
[portfolio](../../../specs/openspec/specs/portfolio-view/spec.md) и
[ledger](../../../specs/openspec/specs/brokerage-account-ledger/spec.md).
+11 -39
View File
@@ -16,48 +16,20 @@
пока неизвестно, доступен 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 показывает
skeleton только отсутствующих секций, partial — подтверждённые строки и
сообщение о неполноте. Totals ждут обязательных зависимостей, comparisons
готовятся независимо. Только complete empty означает отсутствие операций.
Время источника не заменяется временем получения HTTP-ответа.
Экраны просмотра не предлагают ручное обновление или повтор чтения: временные
сбои GET восстанавливаются сами ограниченным числом повторов, сохраняя
показанный результат и applied filters. Ошибка transport отличается от
ошибки source work. Status observation прекращается после terminal outcome
или ухода; само истечение TTL не запускает импорт. Известный access loss
очищает 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
@@ -72,6 +44,6 @@ skeleton только отсутствующих секций, partial — по
[аутентификации](../../domains/authentication/overview.md). [аутентификации](../../domains/authentication/overview.md).
Согласованное поведение раскрыто в workspace-спецификациях Согласованное поведение раскрыто в workspace-спецификациях
[UI resilience](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/frontend-ui-resilience/spec.md), [UI resilience](../../../specs/openspec/specs/frontend-ui-resilience/spec.md),
[loading presentation](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/frontend-loading-presentation/spec.md) [loading presentation](../../../specs/openspec/specs/frontend-loading-presentation/spec.md)
и [data resilience](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/frontend-data-resilience/spec.md). и [data resilience](../../../specs/openspec/specs/frontend-data-resilience/spec.md).
-184
View File
@@ -1,184 +0,0 @@
# Контейнерная поставка
Поставка для одного Docker host, одного backend и краткого простоя.
[Топология](containers.md#контейнерная-поставка) и
[ADR 015](../adr/015-single-server-sqlite-deployment.md) объясняют границы.
Production-комплект находится в отдельном [репозитории deploy](https://git.personal.ksv741.ru/WealthLedger/deploy).
Его README — единственная пошаговая инструкция установки, обновления и recovery;
[ADR 016](../adr/016-versioned-deployment-bundle.md) объясняет выбор релиза комплекта.
Development-команды ниже выполняются из workspace с checkout `frontend/` и `backend/`.
## Предпосылки
- Docker Engine с Compose plugin **не ниже 2.24.4** (`!override` в development),
доступ к registry и достаточно свободного диска для non-root UID 1000.
Опубликованные images собраны для Linux amd64 (см. README комплекта).
Другую архитектуру проверяют отдельно; multi-platform images не обещаны.
- Nexus Docker hosted repository `docker-hosted`, доступный через
`nexus.personal.ksv741.ru/docker-hosted/`. Docker Bearer Token Realm включён;
reverse proxy передаёт исходные host и HTTPS scheme. Адрес token realm
в `WWW-Authenticate` также должен начинаться с `https://`.
Deployment policy запрещает redeploy существующих release tags.
Runner label — `ubuntu-latest`, runner должен иметь Docker CLI, buildx и daemon.
Наличие label само по себе не доказывает Docker capability.
- В Actions каждого component repo нужны `NEXUS_USERNAME` и
`NEXUS_PASSWORD` пользователя `wealthledger-ci`. Эта же учётная запись
используется сервером для загрузки образов. Для публикации нужны
browse/read/add/edit на `docker-hosted`; фактически выданный набор прав
настраивается в Nexus. Built-in
workflow token не заменяет эту настройку. Отдельной read-only учётной записи
сервера нет: `wealthledger-ci` работает и как CI writer, и как server reader,
поэтому её пароль на сервере даёт право публикации в `docker-hosted`. Это
принятое владельцем решение; разделение ролей — возможное усиление.
- На сервере Caddy уже завершает TLS и передаёт весь домен на
`127.0.0.1:8080` (или выбранный frontend port). Отдельного `/api` upstream нет.
DNS, TLS, firewall и provisioning Caddy выполняются вне этого runbook.
Серверный login и доступ к images проверяются на целевом сервере.
Локальный smoke не доказывает серверные registry permissions.
## Development
Из workspace с исходниками обоих компонентов:
```bash
docker version
docker compose version
docker compose --env-file .env.development.example \
-f compose.yaml -f compose.development.yaml config --quiet
docker compose --env-file .env.development.example \
-f compose.yaml -f compose.development.yaml up -d --build --wait --wait-timeout 180
```
Откройте `http://localhost:5173`. Vite отдаёт HMR и проксирует `/api/` без
rewrite; Nest работает в watch mode. Монтируются только source/public,
`node_modules` и generated files остаются в container filesystem. `.env`
с хоста не копируется. После изменения manifest, lockfile, tsconfig или Vite
config пересоберите соответствующий image. `up --build` обновляет Linux
dependencies без stale named volume для `node_modules`.
Для другого порта скопируйте `.env.development.example` в игнорируемый
`.env.development` и согласованно поменяйте `FRONTEND_PORT`/`FRONTEND_ORIGIN`.
Пример содержит только вымышленные local secrets; server secrets сюда не входят.
Поля image/data directory нужны для интерполяции base Compose, но полностью
заменяются development override. В итоговом config нет production data path.
Project `wealthledger-dev` хранит SQLite в `development-data`. Обычные
`restart` и `down` сохраняют его. Не применяйте `down -v` к данным, которые
хотите сохранить. Не используйте production project name для development.
## Публикация компонентных images
Штатный release process каждого repo создаёт защищённый `vX.Y.Z` после
проверенного release commit. `image.yml` проверяет tag против package/lock
version, затем выполняет `npm ci`, `npm run verify`; backend дополнительно
выполняет `npm run test:e2e`. У каждого image OCI version/revision labels.
Images публикуются как `nexus.personal.ksv741.ru/docker-hosted/wealthledger/frontend:X.Y.Z`
и `.../backend:X.Y.Z`. `latest` не создаётся.
Workflow отказывается перезаписывать существующие release tags;
защита release tags и запрет параллельных publishers обязательны. Если одна
публикация уже состоялась, не удаляйте tag для повторного запуска: проверьте
опубликованный image и завершите проверку существующего artifact отдельно.
Выберите опубликованные version tags, выполните pull и сравните OCI revision
с release commit. Выполните smoke с disposable data/config до первого server
rollout. В `release.env` комплекта хранится пара полных image references, например
`nexus.personal.ksv741.ru/docker-hosted/wealthledger/frontend:1.1.0` и
`.../backend:2.1.0`. Сохраните versions/revisions
в deployment record. Release tags не должны переназначаться другим images.
## Перенос существующих images из Gitea
До переключения CI и server references проверьте HTTPS token realm и настройте
пользователя `wealthledger-ci`. В Actions добавьте `NEXUS_USERNAME`/`NEXUS_PASSWORD`; прежние
`REGISTRY_USERNAME`/`REGISTRY_TOKEN` сохраните для отката старого workflow.
Пароли вводятся интерактивно или через `--password-stdin`, не через аргументы.
Прежние images лежат в [Gitea packages](https://git.personal.ksv741.ru/WealthLedger/-/packages)
(`git.personal.ksv741.ru/wealthledger/{frontend,backend}`); новые публикуются
только в Nexus. Скопируйте каждый опубликованный release без пересборки. Например, для `0.2.0`
с установленным Skopeo и доступом чтения Gitea/записи Nexus:
```bash
skopeo login git.personal.ksv741.ru
skopeo login nexus.personal.ksv741.ru
for component in frontend backend; do
skopeo copy --all --preserve-digests \
"docker://git.personal.ksv741.ru/wealthledger/$component:0.2.0" \
"docker://nexus.personal.ksv741.ru/docker-hosted/wealthledger/$component:0.2.0" || exit 1
done
```
`--all` переносит весь image index, включая платформы и вложенные attestation
manifests; `--preserve-digests` прерывает перенос при изменении digest.
До копирования проверьте наличие destination tag: если он уже существует,
сравните исходный и целевой index digest и вложенные manifests. Совпадающий
artifact повторно не публикуйте; несовпадение требует расследования, не удаления
или перезаписи tag. Частичный перенос не считается завершённым.
После копирования сравните digests через `skopeo inspect --raw` и SHA-256,
выполните `docker pull` обоих images пользователем `wealthledger-ci` на сервере приложения,
проверьте OCI revision и container smoke на disposable data.
Серверные шаги (login, pull, smoke) в репозитории не подтверждены и
выполняются оператором. Только после них меняйте
production image references и запускайте обычную
последовательность обновления ниже. Старые Gitea packages и references сохраните:
откат registry возвращает прежние адреса тех же artifacts, не восстанавливает
SQLite и не меняет encryption keys. Не переносите Git release tags и не
запускайте заново старый tag workflow для копирования существующего image.
## Первая установка
На подготовленном сервере оператор клонирует `deploy`, настраивает внешний
HTTPS и registry login, размещает server env, сертификат T-Bank и data directory
вне checkout. Для новой пустой базы команда вызывается с `--init`.
Для действующей установки сохраняются прежние данные и ключи, а первый переход
выполняется как полное обновление с backup. Отсутствующая БД без `--init` — ошибка.
Точные команды и разрешения каталогов приведены в README комплекта.
Git-тег `deploy` закрепляет пару images через `release.env`. Версия комплекта
не обязана совпадать с версиями компонентов. Публикация image не выпускает
комплект и не обновляет сервер автоматически. Команда получает выбранный тег,
загружает images до простоя и использует весь snapshot этого commit.
## Backup при остановленном backend
Сначала останавливаются писатели, затем копируется весь каталог данных,
включая возможные WAL/SHM. Только успешная копия допускает миграции. Локальные
backups находятся рядом с data directory, вне checkout, и автоматически
не удаляются. Незавершённая копия помечается `.partial-` и не подходит для restore.
Ключ шифрования хранится отдельно. Локальная копия не защищает от потери диска.
## Единый цикл обновления
Каждое обновление, включая повтор того же тега и изменение только frontend,
выполняет stop → backup → migration → readiness. Сравнение hashes контейнеров
и конфигурации не выполняется. Приняты короткий простой API и дополнительный
backup; во время простоя возможен 502, вкладкам может потребоваться reload.
Ошибка migration блокирует старт, ошибка готовности останавливает новый backend;
ошибка frontend не останавливает исправный backend. Успех фиксируется только
после проверок запуска обоих компонентов.
Одна file lock защищает установку от параллельных команд. Маркер незавершённого
обновления блокирует повтор до разбора оператором. Он содержит этап, версии
и путь к backup, но не секреты и не копию данных. После обновления нужен smoke
через HTTPS: healthcheck сам по себе не доказывает работу Secure refresh-cookie.
## Откат image
Откат версии и восстановление базы — разные операции. Прежний backend можно
запускать только после проверки совместимости с текущей схемой. Автоматического
rollback нет; migration service при ручном image rollback не запускается.
Порядок ручных Compose-команд и проверки recovery находится в README комплекта.
## Explicit restore
Оператор выбирает проверенную копию и явно принимает потерю всех более новых
записей. Сначала восстановление проверяется на isolated данных с прежним ключом
и совместимым image. На целевой установке останавливаются писатели, текущий
каталог сохраняется под новым именем, затем восстанавливается выбранная копия.
Backend пересоздаётся, чтобы заново подключить заменённый bind-каталог:
простой stop/start может сохранить mount прежних данных.
Проверяются целостность БД, auth/cards и расшифровка подключения. Только после
подтверждённого recovery можно снять маркер незавершённой операции.
+23 -73
View File
@@ -2,76 +2,12 @@
## Источник контракта ## Источник контракта
Backend генерирует OpenAPI из собственного публичного API. Этот сгенерированный Backend генерирует OpenAPI из собственного публичного API. Frontend использует
OpenAPI — исполняемый источник HTTP-контракта. Frontend использует производное описание контракта и типизированный клиент. Изменения проходят
предоставленный OpenAPI snapshot как актуальный вход и локально генерирует [contract workflow](../development/runtime-openapi-verification.md).
типизированный клиент. Backend владеет контрактом, а подготовка и доставка
схемы находятся вне frontend. Это разделение принято в
[ADR 002](../adr/002-nest-generated-openapi-contract.md) и
[ADR 014](../adr/014-trusted-openapi-input.md).
Backend командой `npm run openapi:generate` создаёт `openapi.json` без запуска
сервера и не хранит результат в Git. Contract test сравнивает вывод генератора
с Nest runtime schema настроенного приложения. Frontend хранит полученный
artifact у себя и проверяет соответствие generated types этой копии. CI не
скачивает соседние репозитории и не сравнивает их текущие revisions. Обновление
входного artifact потребителя выполняется отдельно с review; совместимость
развёрнутых версий не доказывается одним зелёным локальным CI.
API имеет prefix `/api/v1`; Swagger публикуется для локальной разработки по 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
`{ data, meta }`. `data` nullable: `null` означает отсутствие complete result,
а подтверждённый пустой массив является available complete result. `meta`
независимо описывает freshness, completeness, `lastSuccessfulAt`,
resource-relevant `lastAttempt` и availability. `lastAttempt` имеет состояния
`none`, `running`, `succeeded`, `partial` и `failed`; его неуспех не заменяет
сохранённый complete result или его время. `Portfolio.isStale` остаётся
специфичным для snapshot полем, а не заменой состояния попытки.
Только Accounts дополнительно возвращает top-level `sync`: `null`, если у
текущего подключения ещё не было часовой попытки, иначе connection-wide
наблюдаемый результат. Это наблюдение, не команда. Обычный GET и retry после
проверки доступа читают локальное состояние и не начинают личную работу.
Устаревший `sync=observe`, пока он сохранён для совместимости маршрута, также
только читает. Request-started screen-sync command/status не является текущей
AS-IS моделью.
Безопасные ошибки завершённой часовой попытки передаются в `lastAttempt` и
Accounts `sync`; HTTP transport/access/input errors сохраняют Problem Details.
Старые данные при временной source failure сохраняют исходное успешное время.
Ни одно поле не раскрывает connection revision. Детали поведения —
[свежесть и полнота](cross-cutting/data-freshness.md).
Envelope несовместим с прежними bare-array ответами: backend/frontend
поставляются согласованной парой. Финальный artifact передаётся явно;
локальная генерация и 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 +29,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 отдельно **Известное расхождение:** согласованная
фиксирует форму ответа. [спецификация аутентификации](../../specs/openspec/specs/user-authentication/spec.md#requirement-стандартные-ответы-об-ошибках)
требует `pointer` и `detail` для невалидных полей. В текущей конфигурации
валидация формирует строковые сообщения, а фильтр ожидает структурированные
ошибки полей; поэтому корректность деталей `errors` не подтверждена.
Исправление требует отдельного изменения реализации и проверки полного
HTTP-потока. OpenAPI сам по себе этого расхождения не устраняет.
Ответ не включает автоматически все поля RFC (`type`, `title`, `instance`). Ответ не включает автоматически все поля RFC (`type`, `title`, `instance`).
Добавление обязательного поля является изменением публичного контракта. Добавление обязательного поля является изменением публичного контракта.
@@ -112,6 +53,15 @@ T-Bank token failure и временная недоступность имеют
[Connection API](../integrations/tbank.md#состояние-и-ошибки). Raw upstream/Prisma errors [Connection API](../integrations/tbank.md#состояние-и-ошибки). Raw upstream/Prisma errors
и secrets не передаются frontend. и secrets не передаются frontend.
## Граница доказательства
Сравнение OpenAPI подтверждает согласованность схем, а не результат каждого
запроса. Безопасная обработка неожиданной ошибки проверяется отдельно.
Валидация, доступ, статусы и cookie требуют проверки полного HTTP-потока;
атомарность хранения — проверки с изолированной реальной базой.
Известное ограничение сквозных проверок описано в
[проверке backend](../development/verification.md).
## Наблюдаемость ## Наблюдаемость
Backend пишет структурированные JSON-события в stdout через Pino. Обработка Backend пишет структурированные JSON-события в stdout через Pino. Обработка
@@ -130,5 +80,5 @@ client request ID. Это позволяет сопоставить публич
Выбор logger и граница ответственности описаны в Выбор logger и граница ответственности описаны в
[ADR 008](../adr/008-pino-backend-logging.md); согласованное поведение — в [ADR 008](../adr/008-pino-backend-logging.md); согласованное поведение — в
workspace-спецификации workspace-спецификации
[Structured logging](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/backend-structured-logging/spec.md). [Structured logging](../../specs/openspec/specs/backend-structured-logging/spec.md).
Сборщик, срок хранения журналов и alerting не определяются приложением. Сборщик, срок хранения журналов и alerting не определяются приложением.
File diff suppressed because it is too large Load Diff
-12
View File
@@ -1,12 +0,0 @@
module.exports = {
extends: ["@commitlint/config-conventional"],
defaultIgnores: false,
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"],
},
};
-75
View File
@@ -1,75 +0,0 @@
# Визуальные принципы WealthLedger
Интерфейс использует общую визуальную основу на Mantine: цветовые роли,
типографику, интервалы, радиусы, тени и адаптивные размеры.
Поддерживается светлая схема; тёмная тема не заявлена.
## Общая основа и каталог
Внутри frontend используются три private npm workspace-пакета:
| Пакет | Ответственность |
| --- | --- |
| `@wealthledger/tokens` | JSON со значениями палитры, типографики, интервалов и других визуальных параметров |
| `@wealthledger/ui` | Mantine-тема, общие стили, StateView, LoadingSkeleton и EntryCard |
| `@wealthledger/icons` | React-иконки добавления, редактирования и удаления |
EntryCard отвечает за расположение переданных подписей, значения и действий.
Предметный компонент определяет смысл операции, форматирует данные и выбирает
цветовую роль. Денежные расчёты и запросы не входят в общий UI-пакет.
Иконки наследуют цвет через `currentColor`. По умолчанию они декоративные:
доступное имя принадлежит кнопке. Самостоятельная смысловая иконка получает
явные роль изображения и доступное имя. Предметные рисунки и анимации
остаются в приложении; поставки raw SVG и мобильного UI нет.
Storybook объединяет примеры приложения и пакетов. Он показывает синтетические
состояния без API, авторизации и query providers; формы получают локальные
callbacks. Каталог помогает проверять представление, но не заменяет проверки
запросов, денежных ограничений и восстановления после ошибок.
Пакеты используются локально и не опубликованы в registry. Независимый выпуск
не настроен. Потребители импортируют публичные exports пакетов напрямую; прежние
compatibility re-exports `shared/theme` и `shared/ui` удалены.
## Семантические роли
| Роль | Цветовая основа | Применение |
| --- | --- | --- |
| canvas | gray.2 | Фон приложения |
| surface | white | Поверхности и формы |
| subtle | gray.0 | Приглушённая поверхность |
| text | dark.7 | Основной текст |
| muted | gray.6 | Вторичный текст |
| border | gray.3 | Граница поверхности |
| primary | blue filled | Основное действие и активная навигация |
| info | blue.6 | Информация |
| positive | green.9 | Положительный результат с текстом или знаком |
| negative | red.9 | Ошибка или отрицательный результат с текстом или знаком |
| warning | yellow.6 | Предупреждение; не мелкий текст на белом |
Цвет не заменяет подпись или знак. Палитра не гарантирует доступный контраст
произвольного сочетания текста и фона. Дополнительные grape/orange и отдельные
оформления карточек сохраняют предметный смысл соответствующего экрана;
они не образуют универсальную финансовую классификацию всей системы.
## Состояния и действия
Основное, вторичное и вспомогательное действия различаются по визуальному
приоритету. Выполнение действия и его недоступность — разные состояния.
Пустой результат и ошибка содержат заголовок, пояснение и действие, когда
оно предусмотрено сценарием. Ошибка объявляется вспомогательным технологиям,
а кнопка имеет доступное название.
Первая загрузка показывает структуру ожидаемого содержимого, без вымышленных
сумм; область обозначается как занятая, декоративные фигуры не получают фокус.
Фоновое обновление сохраняет доступные данные с локальным индикатором.
Подробные правила — в [состояниях интерфейса](../architecture/cross-cutting/ui-resilience.md).
Финансовые значения сохраняют валюту, знак и различие между нулём и
неизвестной суммой. Предметные представления раскрыты в
[портфеле](../domains/portfolio/overview.md), [операциях](../domains/operations/overview.md)
и [банковских картах](../domains/cards/overview.md).
Согласованная общая основа описана в
[спецификации дизайн-системы](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/frontend-design-system/spec.md).
+21
View File
@@ -0,0 +1,21 @@
# Сопровождение системы
Здесь описаны локальное окружение, согласованность компонентов, границы
проверки и поддержание проектных знаний. Команды разработки и coding conventions
принадлежат [frontend](../../frontend/AGENTS.md) и [backend](../../backend/AGENTS.md).
- [Локальное окружение](local-workspace.md) — предпосылки работы, первый пользовательский сценарий и диагностика.
- [Проверки системы](verification.md) — что доказывают проверки интерфейса, API и хранения данных, известные ограничения.
- [HTTP contract](runtime-openapi-verification.md) — направление генерации OpenAPI и проверка совместимости.
- [Дизайн-система](design-system.md) / [UI-каталог](ui-catalog.md) — визуальные роли, реальные состояния и ограничения примеров.
- [Принципы изменений](engineering-governance.md) — связность системы, риск и доказательства результата.
- [Ведение документации](../CONTRIBUTING.md) — самостоятельные объяснения и их обновление вместе с системой.
- [Контекст знаний](agent-token-efficiency.md) / [проверочные вопросы](agent-context-evals.md) — поиск ответа и обнаружение пробелов документации.
- [OpenSpec](openspec-authority-and-lifecycle.md) — роль сохранённых требований и статус прежнего процесса.
## Исторические свидетельства
[Remediation registry](audit-remediation.md) и [Completion Evidence](completion-evidence.md)
сохраняют статусы, исходные адреса и результаты прежних проверок.
Эти материалы не входят в самостоятельное описание текущей системы; их внутренние ссылки
могут требовать архивного окружения и не являются условием чтения документации.
+36
View File
@@ -0,0 +1,36 @@
# Проверка полноты проектных знаний
Эти вопросы помогают оценить, объясняет ли документация систему без доступа
к исходникам. Для каждого вопроса важны правильный ответ, существенное
ограничение и отсутствие ошибочного вывода из похожего сценария.
## Cases
| Вопрос | Объяснение | Существенная граница |
| --- | --- | --- |
| Кто подтверждает доступ к брокерскому счёту? | [Обзор](../overview.md#как-связаны-представления) | Сессия подтверждает пользователя, но идентификатор счёта не доказывает доступ |
| Как меняются финансовые данные при замене token? | [Подключение](../integrations/tbank.md#граница-финансового-кэша) | Данные прежнего поколения не становятся сохранённым снимком нового подключения |
| Что происходит при конкурентном refresh? | [Authentication](../domains/authentication/overview.md), [ADR 009](../adr/009-concurrent-refresh-rotation-policy.md) | Один победитель; заменённый token не отзывает его сессию сам по себе |
| Как восстанавливается интерфейс после ошибки обновления? | [UI resilience](../architecture/cross-cutting/ui-resilience.md) | Фоновая ошибка отличается от первой загрузки и смены пользователя |
| Чем будущая выплата отличается от дохода? | [События](../domains/income/upcoming-events.md), [доходы](../domains/income/overview.md) | Прогноз не подтверждает личное зачисление |
| Можно ли складывать разные валюты или заменить неизвестное нулём? | [Money](../glossary.md#money) | Точность, валюта и отсутствие значения сохраняются отдельно |
| Откуда рейтинг и доходность облигации? | [Bond detail](../domains/instruments/bonds.md) | Рейтинг эмитента не заменяет рейтинг выпуска; рыночная доходность не равна личному результату |
| Доказывает ли OpenAPI правильность ошибки запроса? | [HTTP API](../architecture/http-api.md#error-contract) | Равенство схем не доказывает полный HTTP-поток |
| Что гарантирует проверка хранения данных? | [Backend](verification.md#api-и-хранение-данных) | Проверка с подставными ответами не доказывает атомарность реальной базы |
| Как ограничиваются обращения к внешнему сервису? | [T-Bank](../integrations/tbank.md#конфигурация-и-ограничения) | Повторы, очередь и лимит ограничены; TLS остаётся включённым |
| Защищает ли фильтрация логов любое сообщение? | [ADR 008](../adr/008-pino-backend-logging.md) | Секрет в свободном тексте требует отдельной проверки |
| Есть ли восстановление пароля по письму? | [Ограничения auth](../domains/authentication/overview.md#ограничения) | Пример будущего сценария не является поставленной возможностью |
## Что измерять
Полезное объяснение позволяет найти ответ, назвать ограничение и связать его
с другими сценариями. Оцениваются неверные выводы о доступе, деньгах, полноте
данных и источниках, а также число уточнений до правильного понимания.
Время поиска и объём чтения имеют смысл только вместе с точностью ответа.
## Изменение правил
Если знание есть, но его трудно найти, уточняется навигация. Если документ
найден, но не объясняет различие понятий или ограничение, уточняется объяснение.
При конфликте источников сохраняется отметка о ручном решении; догадка не
подменяет установленный факт.
+42
View File
@@ -0,0 +1,42 @@
# Контекст знаний о системе
Цель навигации — дать читателю знания, необходимые для конкретного вопроса,
без предварительного чтения всей документации. Самостоятельное объяснение
связывает понятия, сценарии и ограничения, а не воспроизводит устройство кода.
## Routing context
[Тематическая карта](../README.md#предметные-области) направляет к нужному предмету:
аутентификация связана с безопасностью и сессиями, денежный показатель —
с его происхождением и валютой, ошибка интерфейса — с восстановлением данных.
Вопрос о внешнем виде не требует автоматически изучать хранение данных.
При обнаружении новой существенной границы расширяется и нужный контекст.
## Источники и конфликты
Публичный HTTP-контракт описывается OpenAPI backend; документация объясняет
систему, ADR сохраняют причины решений, исследования предлагают варианты.
Существующее поведение не отменяет согласованное требование, а рекомендация
из исследования не становится принятым решением автоматически.
При расхождении возможны устаревшее объяснение, дефект реализации или
незавершённое изменение. Неустановленный смысл, особенно в безопасности,
данных и публичном контракте, явно отмечается для ручного решения.
Карта ответственности за знания — [ведение документации](../CONTRIBUTING.md).
## Recovery и handoff
Продолжение работы опирается на ожидаемый результат, выполненные изменения,
оставшиеся вопросы, результаты проверок и ограничения. Дублирование требований
в нескольких независимых пересказах создаёт риск расхождения. Исторические
проверки сохраняют своё значение только для того состояния системы, в котором
они выполнялись; секреты не являются допустимым содержимым свидетельств.
## Проверка эффективности
[Проверочные вопросы](agent-context-evals.md) показывают, можно ли получить
правильную модель системы и отличить близкие понятия. Качество оценивается по
верности ответа, найденным ограничениям и необходимости дополнительных уточнений.
Число документов, их краткость или успешная проверка Markdown сами по себе
не доказывают полезность знания. Сравнение результатов требует одинаковых
вопросов и сопоставимых условий.
+579
View File
@@ -0,0 +1,579 @@
# Audit remediation registry
## Назначение
Это единый versioned реестр текущего состояния remediation для findings из всех [технических audit snapshots](../audits/README.md). Каждый аудит — неизменяемый source of facts; этот документ хранит только текущий статус, путь продолжения и evidence последующих решений, не копируя полный текст finding.
## Правила работы
- Каждая audit-derived запись имеет один статус: `planned`, `active`, `done`, `deferred` или `accepted`.
- Каждое уникальное external claim дополнительно имеет независимый triage
disposition: `confirmed`, `already-covered`, `rejected` или `deferred`.
Повтор одного underlying claim в нескольких reports получает один Audit ID со
ссылкой на каждый source. Disposition не является remediation status:
`confirmed` ещё может быть active, а `rejected` — accepted как проверенное
решение ничего не внедрять.
- Одновременно допускается не более одного `active` remediation change. Пока он существует, продолжается только он. При наличии `planned` ровно одна запись отмечается следующей рекомендуемой задачей, но она не запускается параллельно active change.
- Новая запись `done` требует даты и ссылки на фактически выполненную работу и её проверки (например, commit/PR или существующее evidence). OpenSpec archive не является обязательным; не создавайте отдельные artifacts только ради статуса.
- `deferred` и `accepted` требуют даты, rationale и review trigger либо ссылки на явное решение владельца.
- При смене статуса обновляется только этот реестр; versioned audit snapshot не редактируется.
Текущая проверка результата определена в
[engineering governance](engineering-governance.md#проверка-результата).
Исторические записи
ниже, прежние ссылки на `completion-evidence.md` в docs и датированные audit
snapshots сохраняются без переноса или переписывания задним числом.
Датированные dispositions ниже описывают проверки и решения своего времени;
они не восстанавливают прежние SDD prerequisites. Live ссылки ведут к текущим
owners, а историческое evidence сохраняется по исходным адресам.
## Текущее состояние workflow
- Bootstrap change: [`2026-08-23-establish-audit-remediation-workflow`](../../specs/openspec/changes/archive/2026-08-23-establish-audit-remediation-workflow/) — `archived`; это process foundation и не закрывает ни одного Audit ID.
- Последний завершённый audit remediation change: [`2026-09-06-complete-frontend-audit-remediation`](../../specs/openspec/changes/archive/2026-09-06-complete-frontend-audit-remediation/) — `archived`; FE26-02–FE26-18 закрыты, FE26-01 сохраняет прежнее evidence.
- Единственный active change: отсутствует.
## Текущая задача
Frontend audit remediation завершена; [Completion Evidence][FE26-E] содержит
archive, delivery commits, verification и независимый verdict. Следующий
remediation change создаётся после выбора владельцем продолжения.
## Следующая рекомендуемая задача
`assess-auth-platform-options` для AUD24-02. Change применит Solution Assessment
к существующей реализации и зрелым auth platforms, включая Better Auth, и не
предрешает миграцию.
## Реестр findings
### Frontend audits: август 2026
Источник: [три неизменяемых snapshots](../audits/2026-08-frontend-repository-audits/README.md).
`G` — общий audit от 31 августа, `A28` — audit от 28 августа,
`S` — structural audit от 31 августа. Все 22 source findings сопоставлены
18 underlying claims ниже; одинаковые F IDs без source prefix неоднозначны.
Дата сопоставления и закрытия FE26-02–FE26-18: 06.09.2026.
[Completion Evidence][FE26-E] содержит общий archive path, delivery commits
всех четырёх областей, точные commands/results и независимый PASS.
`already-covered` обозначает прежнюю реализацию, подтверждённую текущей
проверкой, без фиктивной повторной implementation task.
Реализация прежних исправлений находится во frontend `c97f62b` и backend
`55b100d`; эти идентификаторы не доказывают исторические approvals или gates.
| Audit ID | Нормализованный claim | Sources | Disposition | Remediation status | Evidence / текущее состояние |
| --- | --- | --- | --- | --- | --- |
| FE26-01 | Общая граница финансового кэша при смене T-Bank connection. | [FE-A28] F1; [FE-S] F-01 | already-covered | done | 01.09.2026; [archive](../../specs/openspec/changes/archive/2026-09-01-harden-tbank-connection-cache-boundary/), delivery commits, gates и independent review — [существующее Completion Evidence](completion-evidence.md#2026-09-01-harden-tbank-connection-cache-boundary). |
| FE26-02 | Проверять соответствие generated types OpenAPI snapshot. | [FE-A28] F2; [FE-G] F-01 | already-covered | done | 06.09.2026; [FE26-E]. [check-openapi](../../frontend/scripts/check-openapi.mjs) включён в [verify](../../frontend/package.json); snapshot/types проверяются offline. Pre-commit состав честно описан в conventions. |
| FE26-03 | Events должен соблюдать принятую VM/view границу. | [FE-A28] F3 | confirmed | done | 06.09.2026; [FE26-E]. [Events VM](../../frontend/src/pages/account-events/ui/AccountEventsPage.vm.ts) владеет route/query/state/retry; [container](../../frontend/src/pages/account-events/ui/AccountEventsPage.tsx) использует прежние views. Остальные named pages уже разделены; см. FE26-13. |
| FE26-04 | Покрыть успешный public login journey. | [FE-G] F-02 | already-covered | done | 06.09.2026; [FE26-E]. [AppRoutes tests](../../frontend/src/app/routes/AppRoutes.test.tsx): форма, нормализованные credentials, реальный AuthProvider/MSW и protected landing. |
| FE26-05 | Безопасно отклонять невозможные календарные даты. | [FE-G] F-03 | already-covered | done | 06.09.2026; [FE26-E]. [Income VM](../../frontend/src/pages/account-income/ui/AccountIncomePage.vm.ts) проверяет finite date до ISO conversion; [regression tests](../../frontend/src/pages/account-income/ui/AccountIncomePage.test.tsx) покрывают invalid date и leap year. |
| FE26-06 | Включить type-only dependencies в public API guardrail. | [FE-G] F-04 | already-covered | done | 06.09.2026; [FE26-E]. [dependency-cruiser](../../frontend/dependency-cruiser.cjs) включает tsPreCompilationDeps; [shared API](../../frontend/src/shared/api/index.ts) экспортирует paths. |
| FE26-07 | Дать общим display/API primitives нейтральных владельцев. | [FE-G] F-05; [FE-S] F-07 | already-covered | done | 06.09.2026; [FE26-E]. [shared display](../../frontend/src/shared/lib/display.ts) владеет Money/date/logo; [apiErrorCode](../../frontend/src/shared/api/problem-details.ts) объединяет transport extraction. S F-07 расширяет тот же ownership claim API helper; повторной реализации нет. |
| FE26-08 | Синхронизировать entrypoint docs с фактическими gates. | [FE-G] F-06 | already-covered | done | 06.09.2026; [FE26-E]. [Frontend README](../../frontend/README.md), [conventions](../../frontend/.ai/conventions/frontend-ui.md) и [workflow](frontend-workflow.md) описывают существующие команды, не обещая удалённые runtime/hygiene checks. |
| FE26-09 | Убрать механическое обязательное дробление каждого component. | [FE-G] F-07 | already-covered | done | 06.09.2026; [FE26-E]. [UI pattern](../../frontend/.ai/conventions/frontend-ui.md) сохраняет semantic ownership и допускает tightly-coupled renderers; это guidance, а не line-count gate. Конкретное разделение крупных modules учтено отдельно FE26-13. |
| FE26-10 | Объединить protected query policy и endpoint query identity ownership. | [FE-G] F-08; [FE-S] F-05 | already-covered | done | 06.09.2026; [FE26-E]. [App defaults](../../frontend/src/app/ui/App.tsx), [T-Bank VM](../../frontend/src/features/tbank-connection/ui/TbankConnectionSection.vm.ts) и entity-owned options operations/portfolio; status использует общий authenticated query seam. |
| FE26-11 | Централизовать test cleanup и auth reset. | [FE-S] F-02 | already-covered | done | 06.09.2026; [FE26-E]. [Test setup](../../frontend/src/test/setup.ts): RTL cleanup, auth reset, MSW handler reset в общем afterEach. |
| FE26-12 | Убрать повтор server identity из Income view/model. | [FE-S] F-03 | confirmed | done | 06.09.2026; [FE26-E]. [Backend income service](../../backend/src/modules/investments/income/income-analytics.service.ts) передаёт instrumentKey; [Income VM](../../frontend/src/pages/account-income/ui/AccountIncomePage.vm.ts) проверяет и группирует готовую связь, не вычисляя identity. |
| FE26-13 | Выделить самостоятельные ответственности семи крупных TSX modules. | [FE-S] F-04 | already-covered | done | 06.09.2026; [FE26-E]. Income/Events/Accounts/Account state views, [route fallback](../../frontend/src/app/routes/RouteFallback.tsx), [guards](../../frontend/src/app/routes/RouteGuards.tsx), breadcrumbs и T-Bank loaded section выделены во frontend `c97f62b`. |
| FE26-14 | Устранить повтор test client policy и нестабильный cache lifetime. | [FE-S] F-06 | confirmed | done | 06.09.2026; [FE26-E]. [Test query factory](../../frontend/src/test/query.tsx) используется оставшимися auth/T-Bank harnesses; [auth test](../../frontend/src/features/auth/model/auth.test.tsx) проверяет сохранение client и seeded cache при rerender. |
| FE26-15 | Перенести компонентные стили из global CSS к владельцам. | [FE-S] F-08 | already-covered | done | 06.09.2026; [FE26-E]. [Global styles](../../frontend/src/shared/theme/styles.css) содержат базовые правила; AppShell, breadcrumbs и auth используют CSS Modules, StateView — Mantine props. |
| FE26-16 | Исправить source connectedAt contract вместо String coercion. | [FE-S] F-09 | already-covered | done | 06.09.2026; [FE26-E]. [Backend DTO](../../backend/src/modules/investments/tbank-connection/tbank-connection.dto.ts) задаёт nullable date-time string; [frontend consumer](../../frontend/src/features/tbank-connection/ui/TbankConnectionSectionLoaded.tsx) не скрывает неверный transport type через String. |
| FE26-17 | Перенести auth validation и payment invariants из JSX к model owners. | [FE-S] F-10 | already-covered | done | 06.09.2026; [FE26-E]. [Auth validation](../../frontend/src/features/auth/model/validation.ts), [ledger display](../../frontend/src/entities/broker-operation/model/display.ts) владеют соответствующими правилами. |
| FE26-18 | Удалить redundant position modes и test-only proxy exports. | [FE-S] F-11 | already-covered | done | 06.09.2026; [FE26-E]. [PositionCard](../../frontend/src/pages/account/ui/components/PositionCard.view.tsx) выводит mode из kind; [StateView](../../frontend/src/shared/ui/StateView.tsx) не экспортирует Mantine Button; page barrels сохраняют product surface. |
[FE26-E]: completion-evidence.md#2026-09-06-complete-frontend-audit-remediation
[FE-G]: ../audits/2026-08-frontend-repository-audits/frontend-repository-audit.md
[FE-A28]: ../audits/2026-08-frontend-repository-audits/frontend-repository-audit-2026-08-28.md
[FE-S]: ../audits/2026-08-frontend-repository-audits/frontend-structural-maintainability-audit.md
### Повторный multi-source review WealthLedger SDD v2 26.08.2026
Шесть исходных reports находятся в [immutable snapshot set](../audits/2026-08-26-wealth-ledger-sdd-v2-review/README.md).
Сокращения: [D2] — DeepSeek, [G2] — Gemini, [Gr2] — Grok, [K2] — Kimi,
[Q2] — Qwen, [Z2] — Z.AI. Оценки и предложенные patches не принимаются без
сопоставления с воспроизводимым project invariant.
| Audit ID | Нормализованный claim | Sources | Disposition | Remediation status | Evidence, rationale или review trigger |
| --- | --- | --- | --- | --- | --- |
| SDD26R2-01 | Сократить и визуально приоритизировать instructions, сохранив самодостаточные critical rules и единый основной язык. | [D2], [Gr2], [Z2] | confirmed | done | 2026-08-26; [archive](../../specs/openspec/changes/archive/2026-08-26-harden-openspec-sdd-agent-resilience/); specs `8462a3ba75d6dda491b9221699357c58d8f8e3cc`, `779a1e1305f8c209a4f98fae7f87718e48d61b37`; docs `d80e2e32b2ebadcd93a53105e1ac337fe2c1f4f6`; commands/results: [Completion Evidence](completion-evidence.md#2026-08-26-harden-openspec-sdd-agent-resilience). |
| SDD26R2-02 | Delta Spec template не должен предрешать `ADDED` и должен показывать все допустимые operations без активных пустых sections. | [K2] | confirmed | done | 2026-08-26; тот же [archive](../../specs/openspec/changes/archive/2026-08-26-harden-openspec-sdd-agent-resilience/), delivery commits и [Completion Evidence](completion-evidence.md#2026-08-26-harden-openspec-sdd-agent-resilience). |
| SDD26R2-03 | Planning verifier должен отличать defect артефакта от неверного invocation и failure окружения. | [Z2], [Q2] | confirmed | done | 2026-08-26; тот же [archive](../../specs/openspec/changes/archive/2026-08-26-harden-openspec-sdd-agent-resilience/), delivery commits и [Completion Evidence](completion-evidence.md#2026-08-26-harden-openspec-sdd-agent-resilience). Retry counter отклонён владельцем как лишнее состояние. |
| SDD26R2-04 | Apply должен читать точные native `contextFiles` и локальные правила affected repositories. | [K2] | confirmed | done | 2026-08-26; тот же [archive](../../specs/openspec/changes/archive/2026-08-26-harden-openspec-sdd-agent-resilience/), delivery commits и [Completion Evidence](completion-evidence.md#2026-08-26-harden-openspec-sdd-agent-resilience); отдельный context mapping не введён. |
| SDD26R2-05 | `N/A` rationale должно объяснять техническую необходимость, но его смысл нельзя надёжно валидировать простым text heuristic. | [Q2] | confirmed | done | 2026-08-26; тот же [archive](../../specs/openspec/changes/archive/2026-08-26-harden-openspec-sdd-agent-resilience/), delivery commits и [Completion Evidence](completion-evidence.md#2026-08-26-harden-openspec-sdd-agent-resilience); semantic sufficiency оставлена owner/reviewer. |
| SDD26R2-06 | Граница mechanical Apply fix должна явно исключать Requirement, Scenario, architecture и Verification changes. | [Q2], [Z2] | confirmed | done | 2026-08-26; тот же [archive](../../specs/openspec/changes/archive/2026-08-26-harden-openspec-sdd-agent-resilience/), delivery commits и [Completion Evidence](completion-evidence.md#2026-08-26-harden-openspec-sdd-agent-resilience). |
| SDD26R2-07 | Exact Requirement heading в `Covers` нужно копировать дословно и проверять детерминированно. | [Z2] | already-covered | accepted | 2026-08-26; SDD26-12 уже требует exact heading, а planning verifier отклоняет неизвестную reference и uncovered Requirement. Review trigger: удаление или ослабление exact-reference check. |
| SDD26R2-08 | Добавить `Target Repositories` в Proposal или расширить owner area произвольными repository paths. | [G2], [Gr2] | rejected | accepted | 2026-08-26; Proposal сохраняет intent без implementation topology; фиксированная Delivery Map отражает фактические четыре repos. Пересмотреть только при добавлении новой delivery area в workspace. |
| SDD26R2-09 | Хранить machine-readable approval markers внутри versioned artifacts. | [Gr2], [K2] | rejected | accepted | 2026-08-26; approval identity принадлежит разговору с Change Owner. Marker дублировал бы external state и мог бы ложно выглядеть действующим после upstream change. Review trigger: воспроизводимый случай ложного approval после потери conversation state. |
| SDD26R2-10 | Добавить отдельные verify/archive/DoD artifacts или phase. | [Gr2], [K2] | already-covered | accepted | 2026-08-26; SDD26-11 уже закрыт dispatcher, semantic verification, independent review, hard archive blockers и post-archive Completion Evidence без второго workflow artifact. Review trigger: доказанный terminal gap, который эти gates не выражают. |
| SDD26R2-11 | Ослабить последовательность Specs → Design для exploratory feasibility. | [Gr2] | rejected | accepted | 2026-08-26; production change сохраняет утверждённый behavior до architecture. Throwaway feasibility work допустима вне Apply, но не становится versioned parallel plan. Review trigger: повторяемый change, где безопасная feasibility work блокируется текущим graph. |
| SDD26R2-12 | Вынести Solution Assessment в отдельный artifact. | [Gr2] | rejected | accepted | 2026-08-26; conditional section остаётся в Design, где сравнение вариантов непосредственно обосновывает architecture; отдельный artifact усложнил бы graph без нового invariant. Review trigger: появление independently reusable assessment с отдельным approval owner. |
| SDD26R2-13 | Требовать отдельную workspace-task для semantic review каждого change. | [Gr2] | already-covered | accepted | 2026-08-26; terminal semantic verification и independent read-only review обязательны; workspace task создаётся в Tasks для orchestration, когда она нужна delivery plan. Review trigger: semantic review, пропущенный из-за отсутствия task-level representation. |
| SDD26R2-14 | Добавить Success Metrics в Proposal. | [Gr2] | rejected | accepted | 2026-08-26; observable acceptance принадлежит Requirements и Scenarios. Универсальные metrics в intent artifact создавали бы неподтверждённые цели без измеримого project need. Review trigger: утверждённая product metric, которую нельзя выразить Requirements/Scenarios. |
| SDD26R2-15 | Вести schema changelog рядом с version field. | [Gr2] | rejected | accepted | 2026-08-26; versioned Git history, OpenSpec change и archive уже фиксируют rationale и diff. Второй changelog не добавляет authority. Review trigger: наблюдаемая невозможность восстановить schema migration из canonical history. |
| SDD26R2-16 | Добавить CI/CD validation. | [D2] | deferred | deferred | 2026-08-26; текущий contract сознательно использует canonical local gates под Node 22. Review trigger: появление project CI. |
| SDD26R2-17 | Добавить dependencies между changes, sizing задач и commit convention. | [K2] | already-covered | accepted | 2026-08-26; dependencies остаются deferred в SDD26-15 до реального conflict; sizing отклонён в SDD26-16, commit convention — в SDD26-19. Review triggers наследуются из этих записей; повторный report не даёт нового evidence. |
| SDD26R2-18 | Exact schema version и criteria Solution Assessment отсутствуют или недостаточны. | [D2] | already-covered | accepted | 2026-08-26; `.cli-version`, schema version, controlled upgrade contract и explicit assessment trigger areas уже закреплены schema/governance. Review trigger: assessment, для которого текущие trigger areas не дают однозначного решения. |
[D2]: ../audits/2026-08-26-wealth-ledger-sdd-v2-review/deepseek-audit.md
[G2]: ../audits/2026-08-26-wealth-ledger-sdd-v2-review/gemini-audit.md
[Gr2]: ../audits/2026-08-26-wealth-ledger-sdd-v2-review/grok-audit.md
[K2]: ../audits/2026-08-26-wealth-ledger-sdd-v2-review/kimi-audit.md
[Q2]: ../audits/2026-08-26-wealth-ledger-sdd-v2-review/qwen-audit.md
[Z2]: ../audits/2026-08-26-wealth-ledger-sdd-v2-review/zai-audit.md
### Multi-source assessment WealthLedger SDD 26.08.2026
Raw external reports и checksums находятся в
[immutable snapshot set](../audits/2026-08-26-wealth-ledger-sdd/README.md).
Сокращения источников: [D] — DeepSeek, [G] — Gemini, [K] — Kimi, [Q] — Qwen,
[Z] — Z.AI. Ни одна внешняя рекомендация сама по себе не является project
authority.
| Audit ID | Нормализованный claim | Sources | Disposition | Remediation status | Evidence, rationale или review trigger |
| --- | --- | --- | --- | --- | --- |
| SDD26-01 | Снизить instruction bloat и устранить конкурирующих владельцев общих правил. | [D], [G], [Q], [Z] | confirmed | done | 2026-08-26; [`2026-08-26-harden-openspec-sdd-quality`](../../specs/openspec/changes/archive/2026-08-26-harden-openspec-sdd-quality/): schema v2 владеет semantics, templates — shape, config — только facts/language, scripts — hard invariants; отдельный `_rules.md` не вводится. |
| SDD26-02 | Проверять planning format машинно до approval и автоматически исправлять технические ошибки. | [D], [Z] | confirmed | done | 2026-08-26; stage-aware non-mutating verifier, controlled-invalid fixtures и format-only retry path поставлены в [`2026-08-26-harden-openspec-sdd-quality`](../../specs/openspec/changes/archive/2026-08-26-harden-openspec-sdd-quality/). |
| SDD26-03 | Каждая task должна иметь однозначную delivery area; multi-repo implementation нужно разделять. | [D], [G], [Z] | confirmed | done | 2026-08-26; schema v2 использует `[frontend\|backend\|specs\|docs\|workspace]`; planning verifier отклоняет owner из `N/A` area. |
| SDD26-04 | Добавить `Target Repositories` уже в Proposal. | [G] | rejected | accepted | 2026-08-26; Proposal остаётся owner-readable intent без implementation topology. Полный scope repositories определяется позже через обязательную Design Delivery Map. |
| SDD26-05 | Явно координировать порядок и совместимость multi-repository delivery. | [D], [G] | already-covered | accepted | 2026-08-26; Delivery Map, Contract Gate Decision, Migration Plan и workspace dispatcher уже задают owners, порядок, совместимость API и canonical gates. |
| SDD26-06 | Формализовать trigger для Solution Assessment. | [D] | already-covered | accepted | 2026-08-26; [engineering governance](engineering-governance.md#dependencies) и schema перечисляют auth, security, storage, integrations, observability, caching, concurrency и shared tooling. |
| SDD26-07 | Не допускать Design/Apply по неутверждённым artifacts. | [D], [K], [Z] | already-covered | accepted | 2026-08-26; human approval хранится в разговоре, one-artifact entrypoints останавливаются, downstream approvals каскадно инвалидируются. Фиктивный `approval.md` или status field не создаётся. |
| SDD26-08 | Разрешить partial/diff `MODIFIED` вместо полного requirement block. | [D] | rejected | accepted | 2026-08-26; native OpenSpec archive semantics требуют полного `MODIFIED`; собственный merge format создал бы второй transformation engine и риск потери scenarios. |
| SDD26-09 | Дать компактные примеры сложных delta operations. | [D], [K] | confirmed | done | 2026-08-26; schema v2 добавляет `MODIFIED`, `REMOVED` и `RENAMED` examples, сохраняя minimal ADDED-first template. |
| SDD26-10 | Генерировать в `spec.md` placeholders всех delta sections. | [K] | rejected | accepted | 2026-08-26; пустые sections повышают шум и риск неверной операции. Complex forms принадлежат instructions/examples, а template остаётся минимальным. |
| SDD26-11 | Явно определить DoD, verification и archive после Apply. | [D], [K], [Q] | already-covered | accepted | 2026-08-26; [engineering governance](engineering-governance.md#проверка-результата), workspace dispatcher, semantic verification, independent review и hard archive blockers уже образуют terminal protocol. Completion Evidence создаётся после факта, не в Tasks. |
| SDD26-12 | Связать каждую task с точными Delta Requirements и отклонять uncovered requirements. | [D], [K] | confirmed | done | 2026-08-26; schema v2 использует native identity `capability path — exact Requirement heading`; planning verifier проверяет ссылки и полное покрытие без собственного ID namespace. |
| SDD26-13 | Добавить Fast Track или сокращённую вторую schema для мелких fixes. | [G] | rejected | accepted | 2026-08-26; один lifecycle сохраняет предсказуемые approvals и archive blockers. Пропорциональность обеспечивают `skip_specs`, conditional assessment/rollback и узкие tasks. |
| SDD26-14 | Ограничивать agent context через tool-specific `.aiignore` и список repos из Proposal. | [G] | rejected | accepted | 2026-08-26; это не portable OpenSpec invariant. Context ограничивается Delivery Map, task owner и targeted reads; dependencies/build outputs уже исключаются локальными tool/repository rules. Пересмотреть при наблюдаемом context exhaustion. |
| SDD26-15 | Ввести общий `blocked_by`/`blocks` graph между changes. | [K] | deferred | deferred | Review trigger: первый реальный случай двух одновременно необходимых changes с доказанной ordering dependency, которую нельзя выразить текущим single-active-remediation rule. |
| SDD26-16 | Добавить priority и sizing в Tasks. | [K] | rejected | accepted | 2026-08-26; Tasks уже dependency-ordered и ограничены одной рабочей сессией. Оценочные points не защищают observable invariant и создают неподтверждённую точность. |
| SDD26-17 | Для труднообратимых changes нужен проверяемый rollback contract. | [K] | confirmed | done | 2026-08-26; Design template и planning verifier требуют trigger/actions/data safety/verification для risk cases либо конкретный `N/A` reason. |
| SDD26-18 | Добавить отдельный multi-repo context mapping file. | [K] | already-covered | accepted | 2026-08-26; native `openspec instructions ... --json` уже возвращает `planningHome`, `contextFiles`, `artifactPaths` и config context. Второй context map дублировал бы CLI state. |
| SDD26-19 | Зафиксировать commit-message convention в schema. | [K] | rejected | accepted | 2026-08-26; commit style не меняет artifact correctness и принадлежит repository delivery policy только при появлении отдельного проверяемого требования. |
| SDD26-20 | Сделать enterprise sections условными, чтобы снизить overhead. | [Q] | already-covered | accepted | 2026-08-26; Solution Assessment уже conditional, rollback допускает содержательный `N/A`; Delivery Map и Contract Gate Decision остаются обязательными, потому что защищают multi-repo scope. |
| SDD26-21 | Добавить явный feedback loop из Design/Apply в ранний затронутый artifact. | [Q] | confirmed | done | 2026-08-26; schema v2 и agent entrypoints маршрутизируют intent→Proposal, behavior→Specs, architecture/rollback→Design, decomposition→Tasks и инвалидируют downstream approvals. |
| SDD26-22 | Создавать `blocker.md` и `revision-request.md` при каждом blocker/revision. | [Q] | rejected | accepted | 2026-08-26; новые workflow artifacts не поддерживаются native schema и быстро устаревают. Наблюдаемое основание сообщается Change Owner в разговоре; durable artifact добавится только при реальном повторяемом trigger. |
| SDD26-23 | Требовать regression test для любой изменённой логики и happy-path test для любой новой. | [Q] | already-covered | accepted | 2026-08-26; [risk-based testing](engineering-governance.md#risk-based-testing) требует tests для существенных рисков и regressions, но осознанно отклоняет универсальные малоценные tests ради каждого logic/file change. |
| SDD26-24 | Добавлять Completion Summary в конец Tasks. | [Q] | rejected | accepted | 2026-08-26; Tasks содержат только planned work. Observed results и commits появляются после выполнения в verification/archive evidence, иначе checklist смешивает план и факты. |
| SDD26-25 | Требовать точный текстовый `STOP_PROTOCOL` после каждого artifact. | [Z] | rejected | accepted | 2026-08-26; обязательная остановка и явный approval уже enforced entrypoints. Фиксация одной строки ответа хрупка и не усиливает identity или semantic approval. |
| SDD26-26 | Сократить explanation `planningHome.root` до короткого imperative. | [Z] | rejected | accepted | 2026-08-26; пояснение предотвращает ошибочное вычисление path при store-backed и local modes OpenSpec 1.10. После удаления duplicate config rules instruction source остаётся приемлемым; пересмотреть при измеренном context regression. |
| SDD26-27 | Добавить дополнительные contract gates вроде `internal-proto`. | [G] | deferred | deferred | Review trigger: появление второго реального межсервисного committed contract, для которого `runtime-openapi` и repository gates недостаточны. До этого generic gate будет speculative. |
| SDD26-28 | Root project-owned agent projection должна восстанавливаться из versioned authority. | local triage | confirmed | done | 2026-08-26; superseded by follow-up cleanup: root `.opencode` остаётся local non-authority entrypoint без versioned projection source; hard constraints остаются в schema, docs и authority verifier. |
| SDD26-29 | Exact OpenSpec version должна быть единым compatibility contract, а не вечным literal в scripts/fixtures. | local triage | confirmed | done | 2026-08-26; `.cli-version` остаётся единственным current contract; markers/verifiers выводят expected value из него, новая stable не включает auto-upgrade. |
| SDD26-30 | Raw multi-source evidence и triage должны быть versioned раздельно. | local triage | confirmed | done | 2026-08-26; пять reports сохранены byte-for-byte в [snapshot set](../audits/2026-08-26-wealth-ledger-sdd/README.md); live registry хранит dispositions и statuses отдельно. |
[D]: ../audits/2026-08-26-wealth-ledger-sdd/deepseek-result.md
[G]: ../audits/2026-08-26-wealth-ledger-sdd/gemini-result.md
[K]: ../audits/2026-08-26-wealth-ledger-sdd/kimi-result.md
[Q]: ../audits/2026-08-26-wealth-ledger-sdd/qwen-result.md
[Z]: ../audits/2026-08-26-wealth-ledger-sdd/zai-result.md
### Аудит 13.08.2026
| Audit ID | Текущий статус | Связь и следующее действие | Terminal evidence или trigger |
| --- | --- | --- | --- |
| A-01 | done | См. WL-P2-03. | CAS rotation и regression test, 2026-08-23. |
| A-02 | accepted | Ограниченная revocation model закреплена в ADR 006; access token живёт 15 минут. | Пересмотреть, если продукт потребует немедленный отзыв access token. |
| A-03 | deferred | Production transport исключён из local MVP scope. | Пересмотреть перед hosted deployment или reverse-proxy contract. |
| A-04 | done | `docs/features/tbank-connection.md` описывает поставленные Accounts, Portfolio, Ledger и Future Events. | Current-state docs проверены 2026-08-24. |
| A-05 | done | См. WL-P1-03 и WL-P1-04. | Canonical root и hard archive lifecycle, 2026-08-23. |
| A-06 | done | См. WL-P2-10. | Architecture/development onboarding и link review, 2026-08-24. |
| A-07 | done | См. WL-P2-02. | P2002 mapping и concurrent registration regression, 2026-08-23. |
| A-08 | planned | Объединить validation payload review с `tighten-backend-request-feedback`. | Готово к выбору после active change. |
| A-09 | planned | Operations cache закрыт WL-P2-01; отдельно оценить session retention/index. | Создать scoped change после решения приоритета. |
| A-10 | deferred | Текущая operational mitigation описана в T-Bank feature doc. | Пересмотреть при наблюдаемой потере metadata или новом large-portfolio SLA. |
| A-11 | done | Исправлено и подтверждено самим snapshot 13.08.2026. | Transport tests и единый Bearer owner. |
| A-12 | done | Исправлено в snapshot; дополнительно защищено WL-P3-03. | Dependency boundary fixture passed, 2026-08-24. |
| A-13 | deferred | Frontend behavior закрыт; backend e2e зависит от WL-P2-05. | Тот же stable Prisma trigger, что у WL-P2-05. |
| A-14 | done | [`2026-08-24-harden-agent-sdd-governance`](../../specs/openspec/changes/archive/2026-08-24-harden-agent-sdd-governance/). | 2026-08-24; canonical SDD governance, local guardrails and completion evidence protocol verified. |
| A-15 | done | [`2026-08-24-harden-agent-sdd-governance`](../../specs/openspec/changes/archive/2026-08-24-harden-agent-sdd-governance/). | 2026-08-24; current-state ADR/spec contradictions fixed and strict specs passed. |
| A-16 | done | [`2026-08-24-harden-agent-sdd-governance`](../../specs/openspec/changes/archive/2026-08-24-harden-agent-sdd-governance/). | 2026-08-24; layered AGENTS and onboarding verified. |
| A-17 | deferred | Совпадает с accessibility scope WL-P3-04. | Пересмотреть при включении accessibility/second locale в MVP scope. |
| A-18 | done | [`2026-08-24-harden-agent-sdd-governance`](../../specs/openspec/changes/archive/2026-08-24-harden-agent-sdd-governance/). | 2026-08-24; main spec H1 normalized and strict validation passed. |
### Аудит 24.08.2026
| Audit ID | Цель | Статус | Связанный change | Следующее действие / evidence |
| --- | --- | --- | --- | --- |
| AUD24-01 | Ввести risk-based testing без тестов ради файла или coverage. | done | [`2026-08-24-harden-agent-sdd-governance`](../../specs/openspec/changes/archive/2026-08-24-harden-agent-sdd-governance/) | 2026-08-24; risk-based testing policy and gates recorded. |
| AUD24-02 | Оценивать mature solutions и ретроспективно проверить auth platform. | planned | `assess-auth-platform-options` | Готово к созданию change; миграция не предрешена. |
| AUD24-03 | Закрепить строгий artifact graph и роли OpenSpec. | done | [`2026-08-24-harden-agent-sdd-governance`](../../specs/openspec/changes/archive/2026-08-24-harden-agent-sdd-governance/) | 2026-08-24; one-artifact approvals, cascade reapproval and artifact roles verified. |
| AUD24-04 | Синхронизировать agent projection и hard archive blockers. | done | [`2026-08-24-harden-agent-sdd-governance`](../../specs/openspec/changes/archive/2026-08-24-harden-agent-sdd-governance/) | 2026-08-24; project entrypoints and authority verifier reject bypass cases. |
| AUD24-05 | Добавить canonical repo gates и workspace dispatcher. | done | [`2026-08-24-harden-agent-sdd-governance`](../../specs/openspec/changes/archive/2026-08-24-harden-agent-sdd-governance/) | 2026-08-24; workspace dispatcher ran all affected gates and runtime OpenAPI. |
| AUD24-06 | Ввести docs-as-code gate и authoritative process policy. | done | [`2026-08-24-harden-agent-sdd-governance`](../../specs/openspec/changes/archive/2026-08-24-harden-agent-sdd-governance/) | 2026-08-24; docs `npm run verify` passed and engineering governance is durable docs. |
| AUD24-07 | Сделать AGENTS компактными routing/enforcement layers. | done | [`2026-08-24-harden-agent-sdd-governance`](../../specs/openspec/changes/archive/2026-08-24-harden-agent-sdd-governance/) | 2026-08-24; root and repository AGENTS layers verified. |
| AUD24-08 | Охватить одним registry все audit snapshots и исправить stale status. | done | [`2026-08-24-harden-agent-sdd-governance`](../../specs/openspec/changes/archive/2026-08-24-harden-agent-sdd-governance/) | 2026-08-24; registry covers all 53 audit IDs with one snapshot source each. |
| AUD24-09 | Исправить current-state ADR/spec contradictions. | done | [`2026-08-24-harden-agent-sdd-governance`](../../specs/openspec/changes/archive/2026-08-24-harden-agent-sdd-governance/) | 2026-08-24; ADR/spec contradictions fixed without editing audit snapshots. |
### P1
| Audit ID | Цель | Репозитории | Статус | Связанный change | Следующее действие | Terminal evidence |
| --- | --- | --- | --- | --- | --- | --- |
| WL-P1-01 | Один владелец React Query cache identity. | frontend | done | [`2026-08-23-stabilize-frontend-query-and-strict-contracts`](../../specs/openspec/changes/archive/2026-08-23-stabilize-frontend-query-and-strict-contracts/) | — | 2026-08-23; frontend `1250581`, specs `b8a3f96`; `npm run verify` — passed (16 files, 48 tests). |
| WL-P1-02 | Сделать strict TypeScript исполняемым gate и устранить 16 ошибок. | frontend | done | [`2026-08-23-stabilize-frontend-query-and-strict-contracts`](../../specs/openspec/changes/archive/2026-08-23-stabilize-frontend-query-and-strict-contracts/) | — | 2026-08-23; frontend `1250581`, specs `b8a3f96`; strict typecheck and full `npm run verify` passed. |
| WL-P1-03 | Установить единственный canonical OpenSpec root. | specs | done | [`2026-08-23-harden-openspec-authority-and-lifecycle`](../../specs/openspec/changes/archive/2026-08-23-harden-openspec-authority-and-lifecycle/) | — | 2026-08-23; frontend `d870d53`, backend `fee4f8c`, specs `51344d4`, docs `0bc8d1c`; authority verifier и 15 strict specs passed. |
| WL-P1-04 | Сделать archive lifecycle доказательством завершённости. | specs, docs | done | [`2026-08-23-harden-openspec-authority-and-lifecycle`](../../specs/openspec/changes/archive/2026-08-23-harden-openspec-authority-and-lifecycle/) | — | 2026-08-23; frontend `d870d53`, backend `fee4f8c`, specs `51344d4`, docs `0bc8d1c`; hard archive gate и independent review passed. |
| WL-P1-05 | Разделить non-mutating lint check и auto-fix backend. | backend | done | [`2026-08-23-make-local-verification-agent-safe`](../../specs/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/) | — | 2026-08-23; backend `71d57d6`, specs `72444db`, docs `293df9c`; `npm run lint`, app/test typecheck и 17 suites / 51 tests — passed. |
### P2
| Audit ID | Цель | Репозитории | Статус | Связанный change | Следующее действие | Terminal evidence |
| --- | --- | --- | --- | --- | --- | --- |
| WL-P2-01 | Ограничить размер и eviction operations cache. | backend, specs, docs | done | [`2026-08-23-harden-auth-and-cache-concurrency`](../../specs/openspec/changes/archive/2026-08-23-harden-auth-and-cache-concurrency/) | — | 2026-08-23; backend `c77b3cf`, specs `6e20cdf`, docs `8568e7b`; Node 22 lint, app/test typecheck и 56 unit tests passed. |
| WL-P2-02 | Вернуть предсказуемый 409 при конкурентной регистрации. | backend, specs, docs | done | [`2026-08-23-harden-auth-and-cache-concurrency`](../../specs/openspec/changes/archive/2026-08-23-harden-auth-and-cache-concurrency/) | — | 2026-08-23; backend `c77b3cf`, specs `6e20cdf`, docs `8568e7b`; P2002 mapping and barrier regression test passed. |
| WL-P2-03 | Сделать refresh rotation атомарной. | backend, specs, docs | done | [`2026-08-23-harden-auth-and-cache-concurrency`](../../specs/openspec/changes/archive/2026-08-23-harden-auth-and-cache-concurrency/) | — | 2026-08-23; backend `c77b3cf`, specs `6e20cdf`, docs `8568e7b`; CAS one-winner regression test passed. |
| WL-P2-04 | Синхронизировать runtime, OpenAPI и auth success statuses. | backend, frontend | done | [`2026-08-24-align-api-contract-and-client-resilience`](../../specs/openspec/changes/archive/2026-08-24-align-api-contract-and-client-resilience/) | — | 2026-08-24; backend `9bd3400`, frontend `6d892da`, specs `800450d`, docs `b91280c`; metadata regression, frontend OpenAPI check и runtime/snapshot SHA совпали. |
| WL-P2-05 | Восстановить runnable backend e2e gate. | backend | done | — | — | 2026-09-13; унаследованный RUST_LOG изолирован в Prisma config, 3 последовательных и 2 параллельных полных e2e passed. [Результаты](#2026-09-13--wl-p2-05-воспроизводимый-sqlite-e2e). |
| WL-P2-06 | Добавить проходящий app/test TypeScript gate backend. | backend | done | [`2026-08-23-make-local-verification-agent-safe`](../../specs/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/) | — | 2026-08-23; backend `71d57d6`, specs `72444db`, docs `293df9c`; `npm run typecheck:app` и `npm run typecheck:test` — passed. [2026-09-13: включён strict по P1-10](#2026-09-13--wl-p2-06-strict-typescript). |
| WL-P2-07 | Сохранять cached data во время background refetch. | frontend | done | [`2026-08-24-align-api-contract-and-client-resilience`](../../specs/openspec/changes/archive/2026-08-24-align-api-contract-and-client-resilience/) | — | 2026-08-24; frontend `6d892da`, specs `800450d`, docs `b91280c`; observable Accounts/Portfolio refetch tests и `npm run verify` — passed (17 files, 55 tests). |
| WL-P2-08 | Нормализовать cold-start refresh failure. | frontend | done | [`2026-08-24-align-api-contract-and-client-resilience`](../../specs/openspec/changes/archive/2026-08-24-align-api-contract-and-client-resilience/) | — | 2026-08-24; frontend `6d892da`, specs `800450d`, docs `b91280c`; cold-start и protected-401 transport-failure tests passed. |
| WL-P2-09 | Проверять backend runtime OpenAPI против frontend snapshot. | backend, frontend | done | [`2026-08-23-make-local-verification-agent-safe`](../../specs/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/) | — | 2026-08-23; frontend `7a3048c`, backend `71d57d6`, specs `72444db`, docs `293df9c`; runtime и snapshot SHA-256 совпали, frontend `npm run openapi:check` — passed. |
| WL-P2-10 | Актуализировать onboarding и постоянную документацию. | docs, frontend, backend, specs | done | [`2026-08-24-refresh-project-documentation-and-agent-entrypoints`](../../specs/openspec/changes/archive/2026-08-24-refresh-project-documentation-and-agent-entrypoints/) | — | 2026-08-24; frontend `be80ef2`, backend `fec542a`, specs `9f68ae5`, docs `88c0cd3`; OpenSpec/authority, link-path review и independent review passed. |
| WL-P2-11 | Зафиксировать одинаковый local bootstrap frontend/backend. | frontend, backend, docs | done | [`2026-08-23-make-local-verification-agent-safe`](../../specs/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/) | — | 2026-08-23; frontend `7a3048c`, backend `71d57d6`, specs `72444db`, docs `293df9c`; Node 22 policy и `npm ci` guidance зафиксированы. |
| WL-P2-12 | Проверить применимость high dependency advisories. | frontend, backend, specs, docs | done | [`2026-08-24-address-dependency-advisories-and-repo-hygiene`](../../specs/openspec/changes/archive/2026-08-24-address-dependency-advisories-and-repo-hygiene/) | — | 2026-08-24; frontend `826936c`, backend `6052565`, specs `8897bd9`, docs `60a1c33`; React Router обновлён, Prisma tooling risk принят с review trigger. |
| WL-P2-13 | Дать одному agent workflow единый local verify interface. | frontend, backend, specs, docs | deferred | — | Вернуться после восстановления WL-P2-05 тем же Prisma trigger; затем выбрать минимальный общий interface. | 2026-08-23; полный общий verdict не может быть достоверно зелёным, пока обязательный backend e2e migration gate блокируется Prisma 7.9.1. |
### P3
| Audit ID | Цель | Репозитории | Статус | Связанный change | Следующее действие | Terminal evidence |
| --- | --- | --- | --- | --- | --- | --- |
| WL-P3-01 | Сделать unknown request fields явной ошибкой или документированной policy. | backend | planned | `tighten-backend-request-feedback` (ещё не создан) | Выбрать validation policy. | — |
| WL-P3-02 | Устранить shared i18n import cycle. | frontend | done | [`2026-08-24-tighten-frontend-quality-guardrails`](../../specs/openspec/changes/archive/2026-08-24-tighten-frontend-quality-guardrails/) | — | 2026-08-24; frontend `1fc269a`, specs `2029519`, docs `fa5db07`; Node 22 `npm run verify` — passed. |
| WL-P3-03 | Восстановить FSD public API boundary. | frontend | done | [`2026-08-24-tighten-frontend-quality-guardrails`](../../specs/openspec/changes/archive/2026-08-24-tighten-frontend-quality-guardrails/) | — | 2026-08-24; frontend `1fc269a`, specs `2029519`, docs `fa5db07`; dependency fixture gate — passed. |
| WL-P3-04 | Исправить базовые language/loading accessibility semantics. | frontend | deferred | — | Пересмотреть при включении accessibility в MVP scope. | 2026-08-24; текущий local MVP scope не включает accessibility remediation. |
| WL-P3-05 | Ввести пропорциональный bundle regression budget. | frontend | done | [`2026-08-24-tighten-frontend-quality-guardrails`](../../specs/openspec/changes/archive/2026-08-24-tighten-frontend-quality-guardrails/) | — | 2026-08-24; frontend `1fc269a`, specs `2029519`, docs `fa5db07`; initial JS payload 509,165 B ≤ 512,000 B. |
| WL-P3-06 | Добавить coverage ratchet только как regression signal. | frontend | done | [`2026-08-24-tighten-frontend-quality-guardrails`](../../specs/openspec/changes/archive/2026-08-24-tighten-frontend-quality-guardrails/) | — | 2026-08-24; frontend `1fc269a`, specs `2029519`, docs `fa5db07`; coverage 82.57/76.08/77.35/83.48% — passed. |
| WL-P3-07 | Защитить рабочий `.env` от случайного commit. | frontend, specs, docs | done | [`2026-08-24-address-dependency-advisories-and-repo-hygiene`](../../specs/openspec/changes/archive/2026-08-24-address-dependency-advisories-and-repo-hygiene/) | — | 2026-08-24; frontend `826936c`, specs `8897bd9`, docs `60a1c33`; hygiene check passed. |
| WL-P3-08 | Исключить runtime SQLite DB из Git. | backend, specs, docs | done | [`2026-08-24-address-dependency-advisories-and-repo-hygiene`](../../specs/openspec/changes/archive/2026-08-24-address-dependency-advisories-and-repo-hygiene/) | — | 2026-08-24; backend `6052565`, specs `8897bd9`, docs `60a1c33`; hygiene check passed. |
### 2026-09-13 — WL-P2-11: переход на Node 24 LTS
Продолжение bootstrap policy из WL-P2-11, части 14.backend/frontend/docs
[рабочего TODO](../../todo/wealthledger-engineering-improvements.md).
Node 24.21.0 / npm 11.19.0 выбран по [официальному каталогу релизов](https://nodejs.org/dist/index.json)
и [расписанию поддержки](https://github.com/nodejs/Release#release-schedule):
ветка Krypton LTS поддерживается до 30.04.2028.
`.nvmrc` frontend/backend/docs фиксируют 24.21.0, engines — 24.x.
Backend `@types/node` обновлён до 24.13.3; runtime dependencies не обновлялись.
Npm дополнил backend lockfile платформенными optional dependencies существующего
resolver. Для npm 11.19 явно разрешены install scripts сборочных зависимостей
с pin версий. Инструкция переустановки и отката — в
[локальном окружении](local-workspace.md#версия-node).
Specs продолжает наследовать frontend runtime; самостоятельный bootstrap CLI
из части 14.specs и CI остаются отдельными незавершёнными задачами.
Проверки относятся к frontend `53c852b`, backend `ce24a84`, docs `c74c573`,
specs `834f2d1` с текущим diff перехода Node:
- `npm ci` в frontend/backend/docs — PASS; backend native modules установлены
под Node 24, `npm run build` с генерацией Prisma client — PASS.
- Frontend `npm run verify` — PASS: 46 files, 268 tests, coverage, OpenAPI drift,
TypeScript, Biome, Knip, dependency boundaries, приложение и Storybook.
Прогон одновременно с установкой backend упирался в UI timeout; отдельный
повтор полного gate прошёл без изменения тестов или их таймаутов.
- Backend `npm run verify` — PASS: 35 suites, 257 tests, lint, оба typecheck и build.
- Backend `npm run test:e2e` — PASS: 8 suites, 98 tests; новая SQLite и штатные
migrations для каждой suite, включая runtime OpenAPI. Для HTTP-портов нужен
запуск вне sandbox. Рабочая БД не использовалась.
- Docs `npm run verify` — PASS: 54 Markdown-файла, 0 issues;
`git diff --check` во всех четырёх repos — PASS.
- OpenSpec CLI 1.10.0, существующая установка, исполненная Node 24.21.0:
`schema validate wealth-ledger-sdd --verbose` и
`validate --specs --strict --store wealth-ledger-specs` — PASS, 25 specs.
## История registry
Terminal evidence добавляется сюда только после завершения соответствующего change.
### 2026-09-13 — WL-P2-05: воспроизводимый SQLite e2e
Закрыты TODO 03 и 03.docs, повторно проверен SQLite scope 11. Backend
`8637ad2` с текущим diff, Node 22.21.0 / Prisma 7.9.1. Schema, migrations,
зависимости и HTTP-контракт не менялись.
- До исправления fresh migration: `RUST_LOG=warn` и `error` — exit 1 с пустым
`Schema engine error`; переменная отсутствует или равна `info` — exit 0.
В исходном окружении было `RUST_LOG=warn`. Это воспроизведённая зависимость
запуска от окружения, а не доказательство внутреннего дефекта Rust engine.
- Prisma config исключает унаследованный `RUST_LOG` после dotenv. Общий runner
даёт каждому suite свою DB и один URL для migration/Jest, отклоняет прямой
Jest без изоляции и очищает каталог после ошибки или сигнала.
- `npm run verify` — PASS: zero-warning lint, app/test types, 35 suites /
257 unit tests, Prisma generation и build.
- Docs `npm run verify` — PASS: Markdown, ссылки/якоря и `git diff --check`.
- `npm run test:e2e` — PASS: 8 suites / 98 tests, включая runtime OpenAPI.
- `node test/sqlite-regression.cjs` — PASS: три последовательных и два
параллельных полных e2e (каждый 98 tests, всего 40 разных DB); внешний
вымышленный файл неизменён. Недоступный schema engine возвращает exit 1;
SIGINT/SIGTERM — 130/143. Все 43 созданных DB-каталога удалены.
- `npm run test:durability -- /tmp/p111-baseline` — PASS: отдельная baseline
сборка `8ec172f48eefa2237dc0c16919fbb2e726b39a65`, additive migration,
сохранение User/Session/TbankConnection, банковской истории и защиты повторов,
restart, откат приложения и backup/restore. `RUST_LOG=info` удалён из runner;
для старого baseline унаследованная переменная также исключается.
- HTTP/durability проверены вне sandbox после воспроизведения запрета локальных
портов `EPERM`. Это ограничение среды, не failing migration. Пользовательская
DB и `.env` не использовались как цель записи.
Логи этой проверки: `/tmp/wl-sqlite-verify.log`, `/tmp/wl-sqlite-e2e-1.log`,
`/tmp/wl-sqlite-regression.log`, `/tmp/wl-sqlite-durability.log`.
Подробные regression logs сохранены в системном temp-каталоге
`wealthledger-sqlite-regression-pSyuoe`, durability —
`wealthledger-bank-durability-V0u1NA`. Эти локальные временные логи могут быть
удалены системой; воспроизводимые команды и
[матрица рисков](../../backend/README.md#реальная-sqlite-и-сохранность-данных)
остаются в backend. Исторические evidence ниже сохранены без переписывания.
### 2026-09-13 — P1-12: backend responsibilities
Статус: `done`. Backend commit `8ba0053`.
BankCards разделён на services карт, операций и категорий, сценарий сводки и
конкретный repository. Общая transaction/retry boundary, ownership, idempotency,
optimistic concurrency и ограничения возвратов сохранены. Stock/bond services
разделены по каталогу, рынку и доходам; связные operations и T-Bank client
сохранены. Business modules отделены от integrations; сложные modules используют
слои ответственности, компактные остаются плоскими.
- `npm run verify` — passed: lint, app/test typecheck, 35 suites / 257 tests, build.
- Восемь существующих HTTP/e2e suites выполнены временным runner
`/tmp/wl-modular-http.cjs`: отдельная SQLite DB на suite, migrations с
`RUST_LOG=info`, configured HTTP, 118 tests passed. OpenAPI совпадает с frontend
snapshot. Общий e2e blocker и статус задачи 03 этим не закрываются.
- Финальный перенос 47 файлов проверен сравнением 142 исходников/tests:
различий кроме относительных путей и форматирования нет.
- Formatter, docs `npm run verify`, lint backend conventions и `git diff --check`
прошли. Новые dependencies и architecture tests не добавлены.
### 2026-09-13 — P1-11: реальная SQLite
P1-11 поставлена в backend commit `7fde3ec`. Дополнено доказательство
WL-P2-03; статус общего e2e WL-P2-05 не меняется. Команды и матрица рисков —
[backend README](../../backend/README.md#реальная-sqlite-и-сохранность-данных).
- `npm run test:sqlite` — два последовательных успешных запуска после фиксации
loopback-listener: 8 auth и 21 карточный HTTP/SQLite test. Каждый suite получает
собственную временную DB; Prisma не мокируется. Барьер перед CAS гарантирует,
что оба refresh прочитали прежний hash; победившая сессия переживает replay.
- Существующие карточные tests покрывают owner links, idempotency/tombstones,
версии, конкурентные refund/archive/write, rollback source lock, атомарный
перевод, точные суммы и FK/unique constraints. Новая тестовая платформа не нужна.
- `npm run test:durability -- /tmp/p111-baseline` — PASS: migration, restart,
previous-app rollback, backup/restore и чтение сохранённой финансовой истории.
Baseline экспортирован из `8ec172f48eefa2237dc0c16919fbb2e726b39a65` и собран
отдельно. Его lockfile совпадает с текущим; при этой проверке переиспользованы
установленные node_modules. README также описывает независимый `npm ci`.
Runner проверяет наличие build и неизменённый префикс migration history.
- Node 22.21.0: `npm run verify` — PASS (35 suites, 257 unit tests, zero-warning
lint, оба typecheck, Prisma generate и build).
- Первые HTTP-запуски с автоматическим открытием/закрытием сервера Supertest
дали плавающий 404. Suites теперь держат один loopback-listener до `app.close()`;
повторные проверки прошли. Sandbox требует разрешения на локальный порт.
- Миграции выполнены с `RUST_LOG=info`. Это подтверждение выбранных DB-сценариев,
не снятие прежнего ограничения общего `test:e2e` и не выполнение задачи 03.
### 2026-09-13 — WL-P2-06: strict TypeScript
Поставка backend: commit `346a6f0` (`refactor: enforce strict backend typing`).
По продолжению P1-10 в [общем tsconfig](../../backend/tsconfig.json) включён
`strict: true` без отключения отдельных strict-проверок;
`noFallthroughCasesInSwitch` сохранён отдельно. Убраны избыточные настройки,
которые теперь включает `strict`.
После замечания владельца вариант с definite-assignment `!` заменён:
в 14 файлах 315 полей DTO объявлены через `declare`. Заполнение входных DTO
принадлежит Nest/class-transformer, ответные DTO описывают возвращаемые объекты.
`declare` не генерирует поля со значением `undefined` и не подменяет проверку
входных данных. Optional/nullable-типы, декораторы и валидация сохранены.
В [TbankClient](../../backend/src/integrations/tbank/tbank.client.ts)
`getOpenAccounts` и `getDividends` задают тип результата на `call<Response>`,
как соседние RPC; тип параметра `.then` выводится из этого результата.
В [ESLint](../../backend/eslint.config.mjs) включён
`@typescript-eslint/no-non-null-assertion: error`. Он не охватывает `field!: Type`,
поэтому `no-restricted-syntax` отдельно запрещает definite-assignment `!`
в свойствах и переменных. Устранены также 51 прежний non-null assertion и
четыре definite-assignment assertions в тестах: используются сужение типов,
локальные значения, типизированные результаты фильтрации и runtime assertions
в тестах. Транзакции, проверки доступа, cache generation и денежная точность
сохранены; отсутствующая категория в сводке приводит к явной внутренней ошибке.
Проверено на Node 22.21.0, backend `3cf883e` с указанными изменениями:
- `npm run verify` — passed: lint без warnings, strict app/test typechecks,
35 Jest suites / 257 tests, Prisma generation и production build.
- `DATABASE_URL=file:/tmp/wealthledger-declared-dto-openapi-20260913.db npm run test:openapi-contract`
— passed, 1 test; frontend snapshot не менялся.
- TypeScript Compiler API подтвердил все strict options в app/test/build/dev
configs. Каждый config отклоняет шесть примеров: неинициализированное поле,
несовместимый callback, `null` вместо строки, implicit `any`/`this`, доступ
к `unknown` в `catch` (TS2564, TS2345, TS2322, TS7006, TS2683, TS18046).
- Проверка ESLint через `lintText` подтверждает ошибки для `value!`, `field!`
и `let value!`; декларация DTO без `!` разрешена.
- Новые тесты ValidationPipe проходят до и после замены деклараций:
обязательные credentials, нормализация email, вложенные money DTO и
обязательный version с optional-полями `PartialType`. Дополнены проверки
инструментов без UID; отдельный тест проверяет имя категории и ошибку
при нарушенной ссылке в банковской сводке.
- `NODE_ENV=development NODE_OPTIONS=--experimental-vm-modules npx jest --config test/jest-e2e.json --runInBand bank-cards.e2e-spec.ts`
с новой `DATABASE_URL=file:/tmp/bank-cards-*/test.db` — passed, 21 tests,
включая реальные миграции, валидацию HTTP, права владельца, возвраты,
транзакции и перезапуск. Runner использует `RUST_LOG=info` для миграций.
Первый прогон дал 19 passed / 2 timeout в сценариях возвратов и потребовал
остановки оставшегося Jest-процесса. Повтор на новой базе прошёл 21/21;
исходный `3cf883e` в отдельной копии с теми же dependencies также прошёл
21/21. Это не доказательство стабильности общего e2e gate.
- Полный `strict` проходит; 317 diagnostics из предыдущей оценки устранены.
Общий DB/e2e gate и ограничение WL-P2-05 остаются отдельной задачей.
### 2026-09-13 — WL-P2-06: backend TypeScript
Выполнен пункт P1-10 списка инженерных улучшений от 13.09.2026
как продолжение существующего TypeScript gate. В [общем tsconfig](../../backend/tsconfig.json)
включены `noImplicitAny`, `strictBindCallApply`, `noFallthroughCasesInSwitch`;
app/test typechecks, production и dev build наследуют эти настройки.
Исходный код, зависимости, unsafe-checks и публичный API не менялись.
Проверено на Node 22.21.0, backend `3cf883e` с указанным изменением tsconfig:
- `npm run verify` до и после изменения — passed: zero-warning ESLint,
оба TypeScript gate, 33 Jest suites / 253 tests, Prisma generation и build.
- Проверка через TypeScript Compiler API с виртуальным ошибочным файлом для
каждого из четырёх configs — passed: implicit-any параметр даёт TS7006,
неверные аргументы `call`/`apply`/`bind` — TS2345/TS2322, проваливание `switch`
— TS7029. Контроль с выключенными тремя flags принимает тот же файл.
- `DATABASE_URL=file:/tmp/wealthledger-p1-10-openapi-20260913.db npm run test:openapi-contract`
— passed, 1 test; runtime OpenAPI совпал с frontend snapshot.
- `npx tsc --project tsconfig.typecheck.test.json --incremental false --strict --noImplicitAny --strictBindCallApply --noFallthroughCasesInSwitch --pretty false`
— оценка полного `strict`: 317 diagnostics, из них 315 TS2564 для полей DTO
и две TS2345 в `TbankClient.getOpenAccounts` / `getDividends` из-за callback
для `Promise<unknown>`. Полный `strict` пока не включён; это отдельный этап.
- Общий DB/e2e gate не запускался; WL-P2-05 сохраняет прежний статус.
### 2026-08-26 — SDD26-01–SDD26-03, SDD26-09, SDD26-12, SDD26-17, SDD26-21, SDD26-28–SDD26-30: done
Change синхронизирован в canonical specs и архивирован в [`2026-08-26-harden-openspec-sdd-quality`](../../specs/openspec/changes/archive/2026-08-26-harden-openspec-sdd-quality/).
- Delivery Map: frontend `N/A`, backend `N/A`, specs affected, docs affected; runtime OpenAPI contract gate `N/A`, потому что application runtime API и generated client не менялись.
- Затронутые Git-репозитории: specs `a8b2c4eed988f7e1a7b3d6b7188bbe6791f00e2a` (`feat: harden OpenSpec SDD workflow`), docs delivery `72c3a8a6683b95f7e734044fdabe568668097c33` (`docs: record SDD audit remediation`); terminal evidence фиксируется следующим docs commit.
- Pre-archive `bash specs/scripts/verify-workspace-change.sh harden-openspec-sdd-quality` под Node 22.21.0 — passed: Delivery Map verified frontend/backend `N/A`, specs/docs affected, runtime-openapi `N/A`; OpenSpec CLI 1.10.0 verified; planning coverage 9/9; schema valid; change valid; main specs 20/20; authority verified; docs `npm run verify` passed with 36 Markdown files and 0 issues.
- Controlled fixture suites: `bash specs/scripts/verify-agent-projection.test.sh` — passed at delivery time; follow-up cleanup later removed the projection layer. `bash specs/scripts/verify-openspec-authority.test.sh` — passed with expected controlled-negative failures.
- Archive: `openspec archive harden-openspec-sdd-quality -y --store wealth-ledger-specs` — updated `audit-remediation-workflow`, `engineering-change-governance`, `multi-repository-delivery` and `openspec-authority-and-lifecycle`; archived as `2026-08-26-harden-openspec-sdd-quality`.
- Post-archive verification: `openspec validate --specs --strict --store wealth-ledger-specs` — 20 passed, 0 failed; `bash specs/scripts/verify-openspec-authority.sh` — passed; `openspec schema validate wealth-ledger-sdd --verbose` — passed; `PATH=/Users/ksv741/.nvm/versions/node/v22.21.0/bin:$PATH npm run verify` in `docs` — passed.
- Semantic verification: Proposal, all 9 Delta Spec requirements, 28 scenarios, Design and 18/18 tasks matched implementation. Independent read-only review found no Critical or Important findings.
### 2026-08-24 — AUD24-01, AUD24-03–AUD24-09, A-14–A-16, A-18: done
Change синхронизирован в canonical specs и архивирован в [`2026-08-24-harden-agent-sdd-governance`](../../specs/openspec/changes/archive/2026-08-24-harden-agent-sdd-governance/).
- Delivery Map: frontend affected, backend affected, specs affected, docs affected; runtime OpenAPI contract gate required because frontend/backend contract verification pipelines changed. Workspace root `.opencode` projection and root `AGENTS.md` were updated locally; the workspace root is not a Git repository.
- Затронутые Git-репозитории: frontend `11be786943024699a4eccf91a8d1ad32a6d29e5f` (`chore: codify frontend local verification`), backend `2772d53647132e6119e411c6d18234db4301e541` (`chore: codify backend local verification`), specs `0006aea56207ad2192c678ced562feabf1a21c45` (`chore: harden SDD governance workflow`), docs delivery `1944f2fc970db2bdc292981e01b1aca549f9ac52` (`docs: record AI governance audit`); terminal evidence фиксируется следующим docs commit.
- `bash specs/scripts/verify-workspace-change.sh harden-agent-sdd-governance` под Node 22.21.0 — passed: Delivery Map verified for all four areas, OpenSpec CLI 1.10.0 verified, schema valid, change valid before archive, main specs 18/18 before sync, authority verified, frontend `npm run verify` passed (17 test files / 56 tests, coverage 82.57% statements / 76.08% branches / 77.35% functions / 83.48% lines, production build, initial JS 509,165 B), backend `npm run verify` passed (19 suites / 59 tests, build), docs `npm run verify` passed (34 Markdown files, 0 issues), runtime OpenAPI SHA-256 matched snapshot `96744da5f307b37ee3d9233b3259043425f65e49bcc60f78b46c585a78251db4`.
- После sync/archive `openspec validate --specs --strict --store wealth-ledger-specs` — 20 passed, 0 failed; `git diff --check` в frontend, backend, specs и docs — exit 0 before delivery commits.
- Semantic verification: Proposal, all 19 Delta Spec requirements, Design and 25/25 tasks matched implementation. Independent read-only re-review after fixes: Spec review 19/19, 0 Critical, 0 Important, 0 Minor semantic findings; Standards review final — no findings.
### 2026-08-24 — WL-P3-02, WL-P3-03, WL-P3-05, WL-P3-06: done
Change синхронизирован в canonical specs и архивирован в [`2026-08-24-tighten-frontend-quality-guardrails`](../../specs/openspec/changes/archive/2026-08-24-tighten-frontend-quality-guardrails/).
- Затронутые Git-репозитории: frontend `1fc269a` (`feat: enforce frontend quality guardrails`), specs `2029519` (`docs(specs): archive frontend quality guardrails`), docs `fa5db07` (`docs: activate frontend quality guardrails remediation`); terminal evidence фиксируется следующим docs commit.
- Frontend под Node 22.21.0: `npm run verify` — passed: OpenAPI and hygiene checks, strict TypeScript, Biome, 17 test files / 56 tests, coverage all committed `src` 82.57% statements / 76.08% branches / 77.35% functions / 83.48% lines, Knip, dependency gate с четырьмя fixtures, production build и bundle gate. Initial JavaScript payload — 509,165 B при budget 512,000 B; committed lazy baselines включают все dynamic route boundaries, включая `ProtectedLayout`.
- `openspec validate tighten-frontend-quality-guardrails --type change --strict --store wealth-ledger-specs` — passed до archive; после sync/archive `openspec validate --specs --strict --store wealth-ledger-specs` — 18 passed, 0 failed; `bash specs/scripts/verify-openspec-authority.sh` и `git diff --check` по затронутым repositories — passed.
- `opsx-verify` не установлен как shell executable; выполнена эквивалентная процедура из `.opencode/skills/openspec-verify-change/SKILL.md`: completeness 21/21 tasks, 3 requirements / 7 scenarios, correctness и coherence — без blocking findings. Independent read-only review нашёл три Important guardrail gaps; после fixes re-review — без Critical или Important findings.
- WL-P3-04 остаётся `planned` по явному решению owner: application accessibility semantics не входят в текущий MVP scope. Следующая единственная recommended задача — `tighten-backend-request-feedback` для WL-P3-01.
### 2026-08-24 — WL-P2-12, WL-P3-07, WL-P3-08: done
Change синхронизирован в canonical specs и архивирован в [`2026-08-24-address-dependency-advisories-and-repo-hygiene`](../../specs/openspec/changes/archive/2026-08-24-address-dependency-advisories-and-repo-hygiene/).
- Затронутые Git-репозитории: frontend `826936c` (`fix: harden frontend dependency and env hygiene`), backend `6052565` (`fix: harden backend runtime artifact hygiene`), specs `8897bd9` (`docs(specs): archive dependency hygiene remediation`), docs `60a1c33` (`docs: record dependency hygiene remediation evidence`).
- Frontend под Node 22: `npm run verify` — passed: OpenAPI check, hygiene check, strict TypeScript check, Biome, 17 test files / 55 tests, coverage, Knip, dependency check и production build. `npm audit --omit=dev` — 0 vulnerabilities; прямой `react-router` обновлён с 7.9.6 до 7.18.2 для GHSA-2w69-qvjg-hvjx.
- Backend под Node 22: `npm run hygiene:check`, `npm run lint`, `npm run typecheck:app`, `npm run typecheck:test` и `npm test -- --runInBand` — passed (19 suites, 59 tests). Runtime `dev.db` удалён из Git; intentional textual fixtures остаются versionable. `npm audit --omit=dev` всё ещё показывает три high по Prisma CLI path `prisma -> @prisma/config -> deepmerge-ts` для GHSA-ggr8-5vv4-36mx: public runtime path не найден, риск принят как tooling-only. Review trigger: совместимый stable Prisma release должен устранить `deepmerge-ts < 8.0.0`, либо появится доказуемый runtime call path.
- `npm run test:e2e` остаётся deferred по WL-P2-05: `prisma migrate deploy` на чистой temporary SQLite DB завершается пустым `Schema engine error` до Jest. Это не менялось данным change.
- `bash specs/scripts/verify-openspec-authority.sh` — passed; `openspec validate --specs --strict --store wealth-ledger-specs` — 17 passed, 0 failed; `git diff --check` по четырём репозиториям — passed. Эквивалентная `opsx-verify` процедура подтвердила 13/13 tasks, correctness и coherence. Независимый read-only review после исправлений — без Critical, Important или Minor findings.
### 2026-08-24 — WL-P2-10: done
Change синхронизирован в canonical specs и архивирован в [`2026-08-24-refresh-project-documentation-and-agent-entrypoints`](../../specs/openspec/changes/archive/2026-08-24-refresh-project-documentation-and-agent-entrypoints/).
- Затронутые Git-репозитории: frontend `be80ef2` (`docs: refresh frontend onboarding`), backend `fec542a` (`docs: refresh backend onboarding`), specs `9f68ae5` (`fix(specs): remove archived change duplicate` после archive commit `5b7bc0f`), docs `88c0cd3` (`docs: refresh project onboarding entrypoints`).
- `npm run verify` в frontend под Node 22 — passed: OpenAPI check, strict TypeScript check, Biome, 17 test files / 55 tests, coverage, Knip, dependency check и production build.
- Read-only link/path review — passed: 14 Markdown entrypoints без missing local links; `MVP.md` и `CONTEXT.md` удалены, а historical audit/archive references намеренно сохранены.
- `openspec validate --all --strict --store wealth-ledger-specs` — 16 passed, 0 failed; `bash specs/scripts/verify-openspec-authority.sh` — passed; `git diff --check` по затронутым путям — passed.
- Independent read-only standards/spec review выявил только Finder metadata и две неполные README links; metadata удалены, ссылки добавлены, re-review — passed. Эквивалентная `opsx-verify` процедура подтвердила completeness 16/16, correctness и coherence без blocking findings.
### 2026-08-24 — WL-P2-04, WL-P2-07, WL-P2-08: done
Change синхронизирован в canonical specs и архивирован в [`2026-08-24-align-api-contract-and-client-resilience`](../../specs/openspec/changes/archive/2026-08-24-align-api-contract-and-client-resilience/).
- Затронутые Git-репозитории: frontend `6d892da` (`fix: preserve cached data during refresh failures`), backend `9bd3400` (`fix: align auth success status contract`), specs `800450d` (`docs(specs): archive api contract resilience remediation`), docs `b91280c` (`docs: document auth and client refresh resilience`).
- Frontend под Node 22: `npm run verify` — passed: OpenAPI check, strict TypeScript check, Biome, 17 test files / 55 tests, coverage, Knip, dependency check и production build.
- Backend под Node 22: `npm run lint`, `npm run typecheck:app`, `npm run typecheck:test` и `npm test -- --runInBand` — passed (19 suites, 59 tests). `test:e2e` не запускался: WL-P2-05 остаётся deferred из-за Prisma migration gate.
- `bash specs/scripts/verify-runtime-openapi.sh` — passed: runtime и snapshot SHA-256 `96744da5f307b37ee3d9233b3259043425f65e49bcc60f78b46c585a78251db4` совпали. `openspec validate --specs --strict --store wealth-ledger-specs` — 16 passed, 0 failed; authority verifier и `git diff --check` — passed.
- `opsx-verify` выполнен эквивалентной процедурой из `.opencode/skills/openspec-verify-change/SKILL.md`: completeness 16/16 tasks, correctness и coherence — без blocking findings. Независимый read-only review не нашёл Critical, Important или Minor issues.
### 2026-08-23 — WL-P2-01, WL-P2-02, WL-P2-03: done
Change синхронизирован в main specs и архивирован в [`specs/openspec/changes/archive/2026-08-23-harden-auth-and-cache-concurrency`](../../specs/openspec/changes/archive/2026-08-23-harden-auth-and-cache-concurrency/).
- Затронутые Git-репозитории: backend `c77b3cf` (`fix: harden auth and cache concurrency`), specs `6e20cdf` (`docs(specs): archive auth and cache remediation`), docs `8568e7b` (`docs: record auth and cache concurrency policy`).
- Backend под Node 22: `npm run lint`, `npm run typecheck:app`, `npm run typecheck:test` и `npm test -- --runInBand` — passed (18 suites, 56 tests). `test:e2e` не запускался: WL-P2-05 остаётся deferred из-за Prisma migration gate.
- `openspec validate harden-auth-and-cache-concurrency --type change --strict --store wealth-ledger-specs` — passed до archive; после sync `openspec validate --specs --strict --store wealth-ledger-specs` — 15 passed, 0 failed; authority verifier и `git diff --check` — passed.
- `opsx-verify` не установлен как shell executable; выполнена эквивалентная процедура из `.opencode/skills/openspec-verify-change/SKILL.md`: completeness 12/12 tasks, correctness и coherence — без blocking findings. Independent read-only review не нашёл Critical/Important findings.
### 2026-08-23 — WL-P1-05, WL-P2-06, WL-P2-09, WL-P2-11: done
Change архивирован в [`specs/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe`](../../specs/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/).
- Затронутые Git-репозитории: frontend `7a3048c` (`docs: document runtime OpenAPI verification`), backend `71d57d6` (`fix: make backend local verification safe`), specs `72444db` (`docs(specs): archive local verification remediation`), docs `293df9c` (`docs: document local verification workflow`).
- Backend под Node 22: `npm run lint`, `npm run typecheck:app`, `npm run typecheck:test` и `npm test -- --runInBand` — passed (17 suites, 51 tests).
- `npm run openapi:check` в frontend — passed; `bash specs/scripts/verify-runtime-openapi.sh` — runtime и snapshot SHA-256 совпали.
- `openspec validate make-local-verification-agent-safe --type change --strict --store wealth-ledger-specs` перед archive — passed; после sync `openspec validate --specs --strict --store wealth-ledger-specs` — 15 passed, 0 failed; authority verifier, `git diff --check` и independent read-only review — passed/no findings.
### 2026-08-23 — WL-P2-05, WL-P2-13: deferred
Под Node 22 `prisma migrate deploy` 7.9.1 несколько раз завершается пустым
`Schema engine error` на новых SQLite DB до запуска Jest; при включении engine
diagnostics результат меняется. `npm view prisma version` вернул ту же
published stable версию 7.9.1. Поэтому не вводится параллельный migration runner
и не создаётся общий workspace verdict с ложным зелёным статусом. Review trigger
для обоих findings: следующая stable Prisma должна трижды подряд применить
существующие migrations к чистой SQLite DB на Node 22.
### 2026-08-23 — WL-P1-03, WL-P1-04: `harden-openspec-authority-and-lifecycle`
Статус: `done`. Change синхронизирован в main specs и архивирован в [`specs/openspec/changes/archive/2026-08-23-harden-openspec-authority-and-lifecycle`](../../specs/openspec/changes/archive/2026-08-23-harden-openspec-authority-and-lifecycle/).
- Затронутые Git-репозитории: frontend `d870d53` (`docs: define canonical OpenSpec store`); backend `fee4f8c` (`docs: define canonical OpenSpec store`); specs `51344d4` (`docs(specs): harden OpenSpec authority lifecycle`); docs `0bc8d1c` (`docs: document OpenSpec authority lifecycle`).
- `bash specs/scripts/verify-openspec-authority.test.sh` — passed: positive fixture, второй OpenSpec root, implicit/conditional store selection, parenthesized invocation, archive override и framework planning location корректно отклоняются; `references/**` исключён.
- `bash specs/scripts/verify-openspec-authority.sh` из workspace root и всех четырёх независимых repositories — `OpenSpec authority verified: wealth-ledger-specs at specs/openspec`.
- `openspec validate harden-openspec-authority-and-lifecycle --type change --strict --store wealth-ledger-specs` — passed перед archive; `openspec validate --specs --strict --store wealth-ledger-specs` — 15 passed, 0 failed после sync/archive.
- `git diff --check` в frontend, backend, specs и docs — exit 0 перед commits; независимый read-only review scope — Critical/Important findings отсутствуют.
### 2026-08-23 — WL-P1-01, WL-P1-02: `stabilize-frontend-query-and-strict-contracts`
Статус: `done`. OpenSpec change синхронизирован в main specs и архивирован в [`specs/openspec/changes/archive/2026-08-23-stabilize-frontend-query-and-strict-contracts`](../../specs/openspec/changes/archive/2026-08-23-stabilize-frontend-query-and-strict-contracts/).
- Затронутые Git-репозитории: frontend commit `1250581` (`fix: stabilize frontend query cache contracts`); specs commit `b8a3f96` (`docs(specs): archive frontend query remediation`).
- `npm run verify` в `frontend` — passed: OpenAPI check, strict TypeScript check, Biome, 16 test files / 48 tests, coverage, Knip, dependency check и production build.
- `openspec validate stabilize-frontend-query-and-strict-contracts --type change --store wealth-ledger-specs` — `Change 'stabilize-frontend-query-and-strict-contracts' is valid` перед archive.
- `openspec validate --specs --store wealth-ledger-specs` — 14 specs passed, 0 failed перед archive.
- `git diff --check` для staged OpenSpec archive/sync changes — exit 0.
- Archive выполнен по явному разрешению владельца с одной незакрытой только административной задачей 4.2: она требует archive path и поэтому не могла быть закрыта до archive. Сразу после archive задача отмечена `[x]`; WL-P1-04 теперь `active` в `harden-openspec-authority-and-lifecycle`, чтобы убрать эту циклическую зависимость из будущего процесса.
### 2026-08-23 — bootstrap verification: `establish-audit-remediation-workflow`
Этот process change не закрывает Audit ID. Перед archive выполнены следующие read-only checks:
- `cmp -s audit-20-08-2026.md docs/audits/audit-20-08-2026.md` — exit 0; versioned snapshot byte-for-byte совпадает с исходной локальной копией.
- `openspec validate establish-audit-remediation-workflow --type change --store wealth-ledger-specs` — `Change ... is valid`.
- Структурная проверка registry — 26 records, 26 `planned`, 0 `active`, 0 `done`, одна «Следующая задача»; все четыре agent pointers и оба registry links разрешаются.
- `git diff --check` в frontend, backend, specs и docs — exit 0.
- Независимый read-only review registry-to-audit mapping, OpenSpec links, instruction discoverability и scope diff — `findings: none`.
- `opsx-verify` доступен как OpenCode command, но не установлен как shell executable в этой среде; выполнена эквивалентная процедура из `.opencode/skills/openspec-verify-change/SKILL.md` с теми же dimensions completeness, correctness и coherence.
- Change синхронизирован в main specs и архивирован в [`specs/openspec/changes/archive/2026-08-23-establish-audit-remediation-workflow`](../../specs/openspec/changes/archive/2026-08-23-establish-audit-remediation-workflow/).
File diff suppressed because it is too large Load Diff
+88
View File
@@ -0,0 +1,88 @@
# Дизайн-система WealthLedger
Приложение и UI-каталог используют одну визуальную основу: цветовые роли,
типографику, интервалы, радиусы, тени и адаптивные размеры. Общая тема основана
на Mantine; её значения не дублируются отдельно в каталоге.
Поддерживается светлая схема, тёмная тема не заявлена.
## Тема и токены
Опубликованная система включает восемь палитр по десять оттенков: blue, gray,
dark, red, green, yellow, grape и orange; базовые white/black, оттенки
по умолчанию и семантические роли. Общая гарнитура применяется и к заголовкам,
стандартный радиус — средний. Настройки являются доверенной локальной частью
приложения; загрузка внешних или пользовательских токенов не предусмотрена.
| Роль | Цветовая основа | Применение |
| --- | --- | --- |
| canvas | gray.2 | Фон приложения |
| surface | white | Поверхности и формы |
| subtle | gray.0 | Приглушённая поверхность |
| text | dark.7 | Основной текст |
| muted | gray.6 | Вторичный текст |
| border | gray.3 | Граница поверхности |
| primary | blue filled | Основное действие и активная навигация |
| info | blue.6 | Информация |
| positive | green.6 | Положительный результат с текстом или знаком |
| negative | red.6 | Ошибка или отрицательный результат с текстом или знаком |
| warning | yellow.6 | Предупреждение; не мелкий текст на белом |
Цвет не заменяет подпись или знак. Палитра не гарантирует доступный контраст
произвольного сочетания текста и фона. Дополнительные grape/orange и отдельные
оформления карточек сохраняют предметный смысл соответствующего экрана;
они не образуют универсальную финансовую классификацию всей системы.
Общие шкалы основаны на принятых значениях приложения и Mantine 9.5.1.
Обновление библиотеки не заменяет их автоматически. Масштабируемые размеры,
тени и адаптивные границы требуют сохранения единиц и визуальной проверки.
Отдельные градиенты, тени и размеры экранов остаются локальными визуальными
решениями; переход всех существующих значений в общую шкалу не завершён.
Точные значения определены в [токенах приложения](../../frontend/src/shared/theme/tokens.json),
согласованное поведение — в [спецификации дизайн-системы](../../specs/openspec/specs/frontend-design-system/spec.md).
Эта страница объясняет их смысл, не вводя отдельную систему значений.
## Переносимый JSON
Опубликованные токены представлены в собственном формате WealthLedger,
версия 1. Это данные без исполняемого кода и зависимостей от runtime приложения.
Совместимость с DTCG или импортом конкретного инструмента не заявлена.
| Данные | Представление |
| --- | --- |
| Цвета | Hex, палитры оттенков 0–9 и именованные базовые цвета |
| Семантические роли | Ссылка на цвет и оттенок, описание; отсутствие оттенка палитры означает основной оттенок светлой схемы |
| Типографика | Наборы шрифтов, размеры в px, числовые веса и безразмерная высота строки |
| Интервалы и радиусы | Именованные шкалы xs–xl в px и выбранный радиус по умолчанию |
| Тени | Упорядоченные слои: смещения, размытие и растяжение в px, базовый цвет и непрозрачность |
| Адаптивные границы | Именованные размеры xs–xl в em, независимые от масштаба интерфейса |
Перенос в другую систему требует сопоставления единиц, шрифтов, слоёв теней
и семантических ссылок. Само чтение JSON не обеспечивает такой совместимости.
Несовместимое изменение формата требует новой версии; внешняя интеграция,
генератор артефактов и публикация пакета пока не реализованы.
## Общие компоненты
| Представление | Поведение и ограничения |
| --- | --- |
| Пустой результат или ошибка | Заголовок, пояснение и необязательное действие; ошибка объявляется вспомогательным технологиям, кнопка имеет доступное название |
| Загрузка | Доступная область состояния занятости с пояснением и декоративными фигурами без фокуса; структура отражает ожидаемый экран |
| Действия | Основной, вторичный и вспомогательный варианты; состояния выполнения и недоступности |
Общие представления не получают финансовые данные и не управляют
аутентификацией. Состояние выполнения действия отличается от загрузки всей
страницы. При фоновом обновлении доступные данные сохраняются с локальным
индикатором; правила восстановления описаны в
[UI resilience](../architecture/cross-cutting/ui-resilience.md).
Каркас с продуктовой навигацией не входит в заявленный общий набор.
Предметные карточки и композиции представлены в [UI-каталоге](ui-catalog.md).
## Границы доказательств
Каталог использует вымышленные данные и локальные действия без входа,
сохранённого пользовательского состояния и application API. Проверки отделяют
его от исполнения продуктовых запросов. Вид, размеры, фокус и взаимодействия
проверяются в браузере; каталог не доказывает восстановление реального API
или прохождение инвестиционного сценария.
+82
View File
@@ -0,0 +1,82 @@
# Принципы изменения и проверки системы
Ожидаемый результат определяется задачей пользователя. Сопровождение сохраняет
ответственность подсистем, безопасность, точность данных и проверяемость результата.
OpenSpec остаётся [необязательным средством согласования](openspec-authority-and-lifecycle.md);
исторические свидетельства не становятся обязательной процедурой новой задачи.
## Выбор реализации
Связное поведение принадлежит ответственной подсистеме. Существующая возможность
используется повторно, если сохраняет нужный смысл и инварианты. Новая граница
оправдана самостоятельной ответственностью, общей потребностью нескольких
потребителей либо необходимостью отделить доступ и целостность данных.
Простота оценивается по ясности всей системы и стоимости сопровождения,
а не по размеру изменения. Предполагаемая будущая возможность сама по себе
не оправдывает новую подсистему. Упрощение сохраняет валидацию, доступность,
обработку ошибок и защиту от потери данных.
## Dependencies
При выборе внешней зависимости учитываются текущая потребность, совместимость,
поддержка, безопасность и стоимость сопровождения. Использование принятой
платформы может быть проще собственной реализации; зависимость ради одной
воображаемой будущей возможности не является самостоятельной потребностью.
Новый сервис или общая инфраструктура должны решать актуальную задачу.
## Risk-based testing
Изменения безопасности и приватности, денежных расчётов, целостности данных,
миграций, конкурентного доступа, публичного контракта, критического сценария
и подтверждённой регрессии требуют автоматической проверки соответствующего
риска на наблюдаемой потребителем границе.
Статические проверки не доказывают финансовую или функциональную корректность.
Покрытие frontend служит ограничителем регрессий; для backend общего числового
порога нет. Проверки поведения библиотеки или проценты сами по себе не заменяют
проверку собственного инварианта системы.
## Проверка результата
Результат оценивается по исходной задаче: сохранены ли смысл данных и контракты,
правильно ли разграничен доступ, учтены ли ошибки и доступность интерфейса.
Проверки приложений и документации дополняются
[согласованием HTTP-контракта](runtime-openapi-verification.md), если он меняется.
Общего CI сейчас нет.
Успешная проверка применима к тому состоянию системы и тем предпосылкам,
на которых выполнена. При их изменении старое свидетельство не доказывает
новый результат. Автоматическое исправление материалов и проверка без их
изменения — разные действия.
[Влияние на документацию](../CONTRIBUTING.md#обновление-вместе-с-кодом) входит
в оценку результата: описанные понятия, сценарии, решения и ограничения
обновляются вместе с системой. Отсутствие влияния может быть обоснованным
результатом. Отчёт о поставке различает выполненное, проверенное и остающиеся
ограничения; количество формальных отметок не заменяет смысловую проверку.
## Guardrails и local gates
Автоматические проверки гарантируют только свойства, охваченные их областью.
Смысловые требования — например, правильный владелец данных, авторизация,
локализация и полезность выбранных проверок — требуют отдельной оценки.
Предпочтение между равноценными вариантами отличается от обязательного
инварианта. Новая автоматизация оправдана реальным риском или повторяющимся
дефектом; её наличие само по себе не является целью.
## UI-решения
Ожидаемые экраны, состояния, взаимодействия и поведение на разных размерах
экрана определяются пользовательским сценарием. [Каталог](ui-catalog.md)
и [дизайн-система](design-system.md) показывают существующую визуальную основу.
Неоднозначный вариант можно оценить по конкретному примеру до реализации.
Успешная сборка каталога не доказывает доступность и визуальное соответствие.
## Изменение опасного контракта
Для публичного контракта, безопасности, данных, платежей и труднообратимого
изменения существенная неопределённость разрешается до применения решения.
Для миграции важны условия отката, сохранность уже созданных данных и проверка
восстановления. Порядок разрешения конфликтов знаний описан в
[контексте системы](agent-token-efficiency.md#источники-и-конфликты).
+131
View File
@@ -0,0 +1,131 @@
# Локальное окружение и восстановление
WealthLedger поставляется как локальное приложение: frontend работает
в браузере, backend обслуживает API и обращается к SQLite и внешним источникам.
Это описание предпосылок и ограничений работы, без привязки к устройству
исходного кода. Описано локальное окружение; production-топология по нему
не подтверждается.
## Версия Node
Все локальные проверки используют Node 24 LTS; `.nvmrc` в frontend, backend
и docs фиксирует 24.21.0. Specs использует frontend `.nvmrc`.
По [официальному расписанию Node](https://github.com/nodejs/Release#release-schedule)
поддержка Node 24 завершается 30.04.2028.
В каждом npm-репозитории выполните `nvm install`, `nvm use --silent` и `npm ci`;
в backend затем `npm run build` для генерации Prisma client.
Поставляемый npm 11.19 запускает dependency install scripts только по
`allowScripts` в package.json; разрешения привязаны к версиям сборочных
зависимостей и пересматриваются при их обновлении.
После смены major обязательна переустановка native modules через `npm ci`.
Для отката верните прежние runtime/package/lockfile-конфигурации, выберите Node 22
и повторите `npm ci` и backend build. Сам переход Node не меняет схему или данные.
## Подготовка backend
Локальная серверная среда использует Node 24 и SQLite. Для работы нужны:
| Параметр | Назначение и ограничение |
| --- | --- |
| Адрес локальной базы | Одинаковая база используется при подготовке схемы и работе приложения; относительный адрес зависит от рабочей директории процесса |
| Разрешённый origin frontend | Определяет browser-источник, которому разрешён доступ к API с credentials |
| Порт API | По умолчанию 3000 |
| Секрет подписи сессий | Секрет не короче 32 символов; отличается от ключа шифрования подключения |
| Ключ шифрования подключения | 32 байта в base64; требуется для чтения сохранённого T-Bank token |
| Параметры интеграции | Адрес T-Bank, лимит запросов и при необходимости доверенный CA; [ограничения](../integrations/tbank.md#конфигурация-и-ограничения) |
T-Bank token вводится пользователем через интерфейс после входа и не является
общим параметром окружения. Существующая база и её ключ шифрования сохраняются
при обновлении. До миграции необходима резервная копия; восстановление проверяется
на отдельной копии. Потеря или замена ключа не является безвредным сбросом:
прежнее подключение нельзя расшифровать новым ключом.
После подготовки зависимостей и конфигурации примените штатные migrations:
из backend после `nvm use --silent` выполните `npx prisma migrate deploy`.
Для существующей базы сначала сохраните согласованный backup.
На Node 22.21.0 / Prisma 7.9.1 обнаружена зависимость fresh migration от
унаследованного `RUST_LOG=warn/error`. Backend Prisma config исключает эту
переменную из процесса CLI; назначать `RUST_LOG=info` больше не требуется.
Условия и доказательство — в
[сквозной проверке](verification.md#известное-ограничение-сквозной-проверки).
В development доступна [локальная Swagger UI](http://localhost:3000/api/docs)
при стандартном порте API. Она показывает контракт, но не доказывает доступность
T-Bank и не используется frontend как источник данных во время работы.
## Подготовка frontend
Стандартные адреса: API `http://localhost:3000`, frontend `http://localhost:5173`.
Адрес API в клиенте должен совпадать с работающим сервером, а разрешённый origin
backend — с origin браузера. Смена порта требует согласованного изменения
адресов. Смесь `localhost` и `127.0.0.1` меняет границу browser cookie и CORS.
Локальный HTTP предполагает development-режим backend: в других режимах
refresh-cookie получает Secure flag и требует защищённого соединения.
Поведение сессии описано в [Authentication](../domains/authentication/overview.md).
## Первый сценарий
1. Пользователь регистрируется или входит. Новый профиль получает сессию;
после перезагрузки приложение пытается её восстановить.
2. В настройках пользователь сохраняет собственный T-Bank Invest token.
Успех означает проверенный доступ к брокеру и сохранённое подключение.
3. В разделе «Счета» доступны отдельный и общий портфель. Отсутствие доступных
счетов является допустимым пустым результатом.
4. «Доходы» показывают полученные выплаты, «События» — будущие.
5. При выходе локальная сессия и её клиентский кэш очищаются.
Без T-Bank token доступны регистрация, вход, состояние отсутствующего
подключения и [ручной банковский учёт](../domains/cards/overview.md).
Банковский сценарий не подтверждает доступность инвестиционных поставщиков.
## Сохранение данных и откат
Перед обновлением сохраняется согласованный SQLite backup; ключ шифрования
T-Bank хранится отдельно. Для работающей базы используется SQLite backup API,
который учитывает WAL. Несогласованное копирование файлов базы и WAL не является
резервной копией. Восстановление сначала проверяется на отдельном файле.
Банковская миграция добавляет таблицы, сохраняя User, Session и TbankConnection.
При необходимости отката приложения сначала прекращаются новые записи и
сохраняется актуальный снимок. Затем возвращается совместимая предыдущая версия
backend при сохранении расширенной схемы. Старый backup не восстанавливается
поверх новой истории: это удалило бы введённые банковские операции.
Совместимость проверена с исходной версией backend
`8ec172f48eefa2237dc0c16919fbb2e726b39a65`: она выполняла вход с расширенной
схемой, а новая версия после возврата читала прежние карты и операции.
Отдельно проверены перезапуск, служебные данные защиты повторов и приложение
на восстановленной SQLite-копии. Это подтверждение конкретной пары версий,
а не гарантия совместимости любого будущего изменения схемы.
Проверка воспроизводится через
[процессный сценарий](../../backend/test/bank-cards-durability.cjs)
с каталогом отдельно собранного baseline: после активации backend `.nvmrc`
выполнить `node test/bank-cards-durability.cjs <baseline-directory>`.
Сценарий создаёт собственные временные базы и вымышленные данные. Migrations
и HTTP/DB-ограничения описаны в [проверке системы](verification.md).
## Если запуск или сценарий не работает
| Симптом | Проверка и безопасное восстановление |
| --- | --- |
| Порт занят | Адрес действующего процесса или выбранный свободный порт должен совпадать с настройками клиента и разрешённым origin |
| Ошибка CORS или сессия теряется после reload | Согласованность адресов, hostname и режима cookie проверяется на стороне browser и backend |
| Ошибка подготовки базы | Сохранить версию среды и текст ошибки; использовать backend Prisma config, не удалять пользовательскую базу и не обходить migrations |
| `TBANK_NOT_CONNECTED` | Подключение создаётся в настройках текущего пользователя |
| `INVALID_TBANK_TOKEN` | После проверки token и прав у брокера подключение заменяется через настройки |
| `TBANK_UNAVAILABLE`, TLS или rate limit | Проверяются сеть, лимиты и доверенный CA; проверка TLS сохраняется |
| НКР/MOEX недоступен при работающих остальных секциях | Состояние источника и повторная попытка относятся к конкретной секции; недоступность не означает отсутствия рейтинга |
| Подключение не читается после смены ключа | Восстанавливается прежний ключ из защищённой копии; если он утрачен, собственный token вводится заново через настройки |
HTTP-запросы связываются с безопасными журналами через `X-Request-ID`.
Пароли, cookies, Authorization и broker token не включаются в диагностический
отчёт. Ограничения журналирования — [ADR 008](../adr/008-pino-backend-logging.md).
## Проверка первого изменения
Успешная сборка, запуск и прохождение сценария — разные доказательства.
Границы [проверок системы](verification.md) и
[HTTP-контракта](runtime-openapi-verification.md) рассматриваются отдельно.
Завершённость результата учитывает фактические проверки и остающиеся ограничения.
@@ -0,0 +1,31 @@
# OpenSpec: роль требований и история процесса
Обязательный SDD lifecycle отменён для обычной разработки согласно
[ADR 012](../adr/012-task-driven-development-with-scoped-context.md).
Задача пользователя определяет ожидаемый результат; дополнительные planning
artifacts и архивирование не являются условием её завершения.
## Существующие материалы
Основные спецификации сохраняют согласованное проверяемое поведение, включая
текущее. OpenSpec changes содержат предложения, их дизайн и задачи, архив —
историю согласования. Store `wealth-ledger-specs` находится в `specs/openspec/`:
основные спецификации — `specs/`, изменения — `changes/` внутри Store.
[Предметный индекс](../../specs/AI_INDEX.md) ведёт к конкретным requirements/scenarios.
Существующее описание изменения относится только к своему объёму и не доказывает
готовность возможности. Docs объясняет систему и последствия правил;
при расхождении со спецификацией нужно установить причину, а не объявлять
реализацию или текст автоматически верным.
## Если OpenSpec запрошен явно
OpenSpec остаётся необязательным средством согласования требований, дизайна
и задач. Его собственная модель определяет состав и порядок артефактов;
подтверждение человеком и смысловая проверка результата не выводятся из
одного наличия файлов. Обновление требований и архивирование меняют историю,
поэтому относятся к явно выбранной работе с этой историей.
Причины прежнего процесса и его упрощения сохранены в
[ADR 010](../adr/010-native-openspec-sdd-and-local-orchestration.md) и
[ADR 011](../adr/011-native-openspec-lifecycle-with-repository-gates.md).
@@ -0,0 +1,43 @@
# Проверка runtime OpenAPI
## Назначение
Проверка сравнивает OpenAPI, сформированный настроенным backend-приложением,
с описанием API, на котором основан frontend. Backend остаётся единственным
источником исполняемого HTTP-контракта; frontend-клиент является производным.
Причины выбора описаны в [ADR 002](../adr/002-nest-generated-openapi-contract.md).
Эта проверка подтверждает согласованность публичного контракта, но не заменяет
проверки каждого приложения и наблюдаемого HTTP-поведения.
## Выбор и направление изменения
Изменением контракта считается изменение маршрута, входного или выходного поля,
статуса либо формы ошибки. Внутреннее изменение представления без изменения
API не требует нового описания контракта.
Направление согласования: публичный API backend → OpenAPI → производное
описание и типизированный клиент frontend. Две независимо редактируемые версии
контракта создавали бы риск расхождения. Перед изменением, влияющим на
совместимость, учитываются существующие потребители и сохранность данных.
Семантика запросов и ошибок описана в [HTTP API](../architecture/http-api.md).
## Границы и результат
Сравнение выполняется на границе настроенного приложения, нормализует порядок
ключей JSON-объектов и сохраняет порядок массивов. Оно не обновляет сравниваемые
описания и клиент автоматически и не обращается к T-Bank или другим внешним
источникам. Несовпадение указывает на рассогласование контракта.
Равенство схем не доказывает фактическую работу валидации, авторизации,
статусов, cookie и обработки ошибок: для этого нужны наблюдения полного
HTTP-потока. Согласованность описаний и функциональная корректность —
разные доказательства.
## Граница e2e
`npm run test:openapi-contract` подготавливает отдельную временную SQLite-базу
через общий runner и выполняет только сравнение OpenAPI. Соседний frontend
checkout нужен для snapshot. Полные HTTP-сценарии запускает `test:e2e`;
миграция перед сравнением не подменяет проверку поведения приложения.
Подробности — в [сквозной проверке](verification.md#известное-ограничение-сквозной-проверки).
+77
View File
@@ -0,0 +1,77 @@
# UI-каталог Storybook
Storybook 10.6.0 показывает реальные представления WealthLedger на общей
визуальной основе приложения без входа и подключения backend.
## Запуск и сборка
Каталог работает как отдельная локальная витрина по адресу
[localhost:6006](http://localhost:6006) либо как самостоятельная статическая
сборка. Его примеры не входят в пользовательские маршруты приложения.
Публикация отдельным сервисом не настроена.
## Компоненты и работа с примерами
Каталог включает 14 групп и 54 примера:
| Группа | Что показывает |
| --- | --- |
| Основы дизайн-системы | Палитры, семантические роли, типографику, интервалы, радиусы, тени и адаптивные размеры |
| Пустой результат и ошибки | Четыре варианта с действием и без |
| Загрузка | Доступную область занятости с нейтральными фигурами |
| Кнопки | Основное, вторичное и вспомогательное действие, выполнение и недоступность, работу с клавиатурой |
| Список счетов | Несколько счетов и валют, недоступную и пустую сводку, неоценённые бумаги, длинные названия и суммы |
| Карточка позиции | Акцию с положительным результатом, облигацию с отрицательным, прочий инструмент и отсутствие оценки |
| Ближайшие события | Успешный, загружаемый, ошибочный и пустой результат; неизвестную сумму и длинное название |
| Обновление портфеля | Сохранённые суммы при обновлении и ошибке, локальную повторную попытку |
| Загрузка страницы счёта | Контур ожидаемых заголовка, сводки и строк |
| Структура портфеля | Валюты и типы активов, только деньги, пустую и неполную оценку, неизвестные и отрицательные значения |
| Сводка банковских карт | Компактные расходы по категориям, отрицательный месяц возврата, известные нули, загрузку, ошибку, сохранённые суммы и длинные точные итоги |
| Внешний вид банковской карты | Цвета банков, маскированный номер, платёжные системы, отсутствующие реквизиты и длинные названия в архиве |
| Иконки банковских операций | Пиктограммы категорий расходов, общую иконку нестандартной категории и пополнение |
| Поля банковской карты | Независимую рублёвую карту и недоступные финансовые поля архива |
Каталог не перечисляет все элементы библиотеки или приложения. Отсутствие
представления в витрине не означает его отсутствия в продукте. Основы показывают
опубликованные шкалы и смысл ролей; особые размеры и тени конкретных экранов
не объявляются общими токенами. Границы описаны в [дизайн-системе](design-system.md).
Canvas показывает выбранный пример. Docs поясняет назначение и ограничения,
Controls меняет доступные свойства, Actions показывает локальные действия.
Наборы вымышленных данных и доступные Controls определяются конкретным
примером; подготовленные варианты перечислены в историях. Свойства можно сбросить, пример —
смонтировать заново; изменение размера просмотра не сбрасывает выбранные значения.
Витрина и приложение используют одну тему. Примеры показываются по-русски
в светлой схеме. Ошибка сохраняет доступное объявление, загрузка — состояние
занятости; декоративные фигуры не получают фокус.
## Границы и сопровождение
Все счета, позиции, события и суммы вымышлены. Каталог не читает сессии,
cookies, секреты и реальные пользовательские данные. Вместо внешних логотипов
показываются буквенные заглушки. Сама витрина может сохранять свои настройки;
телеметрия отключена.
Действия не обращаются к application API. Ссылки меняют только локальное
состояние примера, не открывают продуктовую страницу назначения и не меняют
адрес приложения. Повторная попытка демонстрирует локальное действие и не
доказывает восстановление реального запроса.
Сохранённые суммы иллюстрируют Client Cached Snapshot, но его жизненный цикл
здесь не исполняется. В приложении первичная загрузка показывает контур
ожидаемого содержимого, фоновое обновление сохраняет данные, а выполняемое
действие имеет контекстный индикатор. Подробности —
[состояния интерфейса](../architecture/cross-cutting/ui-resilience.md).
Автоматические проверки frontend охватывают каталог и его изоляцию от
продуктовых запросов. Примеры и их пояснения обновляются вместе с включёнными
представлениями. Клавиатура, реальные размеры и визуальное соответствие
проверяются в браузере; автоматической визуальной регрессии нет.
## Использование в UI задаче
Каталог помогает сравнить существующие состояния с ожидаемым пользовательским
сценарием. Он дополняет [принципы UI-решений](engineering-governance.md#ui-решения)
конкретными примерами, но успешная сборка не доказывает доступность,
визуальное соответствие и работу настоящего API.
+64
View File
@@ -0,0 +1,64 @@
# Что подтверждают проверки системы
Успешная сборка, запуск, равенство схем и прохождение пользовательского
сценария — разные доказательства. Выбор проверки зависит от наблюдаемого
риска: денежных вычислений, доступа, целостности данных или состояния интерфейса.
Общие основания описаны в [принципах сопровождения](engineering-governance.md#risk-based-testing).
## Интерфейс
Локальный gate frontend охватывает типы, статический анализ, автоматические
сценарии, зависимости и сборки приложения и UI-каталога. Он выполняется
без запущенного backend. Покрытие ограничивает регрессии, но не доказывает
правильность финансового расчёта, доступность и полный пользовательский сценарий.
[Каталог](ui-catalog.md) показывает существующие визуальные решения на
вымышленных данных. Широкий и узкий экран, клавиатурная навигация,
восстановление после ошибок и соответствие задаче проверяются в интерфейсе
отдельно от сборки. Изолированная тестовая среда не воспроизводит все свойства
браузера и живой интеграции.
## API и хранение данных
Локальный gate backend объединяет статический анализ, типы приложения и тестов,
автоматические сценарии и сборку. Он не исправляет исходные файлы;
автоматическое форматирование выполняется отдельно и не является доказательством.
Диагностика покрытия доступна без общего числового порога.
Равенство OpenAPI подтверждает согласованность публичного описания с клиентом.
Валидация, cookie, авторизация счёта, статусы и ошибки требуют полного HTTP-потока;
целостность и конкурентные изменения данных — проверки с изолированной реальной
базой. Подставной ответ базы не доказывает её транзакционные свойства.
Направление генерации и границы сравнения — в [проверке OpenAPI](runtime-openapi-verification.md).
### Известное ограничение сквозной проверки
Прежний blocker WL-P2-05 снят в конфигурации Prisma: на Node 22.21.0 /
Prisma 7.9.1 унаследованный `RUST_LOG=warn` или `error` воспроизводит пустой
`Schema engine error`, а отсутствие переменной позволяет fresh migration.
Backend Prisma config удаляет переменную в процессе CLI после загрузки `.env`;
это не меняет application logging. Версии зависимостей и SQL history сохранены.
`npm run test:e2e` применяет migrations и запускает каждую HTTP suite на отдельной
временной DB. Один URL передаётся Prisma и Jest; прямой запуск Jest без runner
отклоняется до DB-запросов. При ошибке и SIGINT/SIGTERM выполняется cleanup.
`npm run test:sqlite` выбирает auth/cards; `test:durability` отдельно проверяет
migration, restart, возврат предыдущего приложения и backup/restore.
Команды и runnable regression — в
[backend README](../../backend/README.md#реальная-sqlite-и-сохранность-данных),
фактические результаты — в
[registry](audit-remediation.md#2026-09-13--wl-p2-05-воспроизводимый-sqlite-e2e).
Backend `verify` остаётся статическим/unit/build gate; полный HTTP/DB scope
проверяется отдельным `test:e2e`. Успех этих команд не подтверждает доступность
внешнего T-Bank. HTTP tests требуют локальных портов, которые sandbox может
запрещать. Безопасное сохранение реальных данных — в
[локальном окружении](local-workspace.md).
## Где выполнять проверки
Команды и конкретные тестовые правила поддерживаются в локальных инструкциях
[frontend](../../frontend/AGENTS.md) и [backend](../../backend/AGENTS.md).
Проверки этой документации — в [CONTRIBUTING](../CONTRIBUTING.md#проверки).
Отчёт различает пройденную границу, оставшееся ограничение и проверяемое состояние;
успех одного gate не распространяется автоматически на весь workspace.
+37 -20
View File
@@ -7,11 +7,30 @@
только к backend. Счёт не хранится как долговременное локальное владение только к backend. Счёт не хранится как долговременное локальное владение
независимо от брокерского подключения. независимо от брокерского подключения.
Экран читает локальный список и сохранённые сводки. Обычный запрос запускает ```mermaid
недостающие или устаревшие зависимости в фоне. Список и сводка каждого счёта sequenceDiagram
имеют независимые timestamps и состояния. Неподтверждённый список показывает participant U as Пользователь
загрузку доступа, а не отсутствие счетов. Поток и границы описаны в participant F as Frontend
[модели свежести](../../architecture/cross-cutting/data-freshness.md). participant B as Backend
participant D as SQLite
participant T as T-Bank gRPC
U->>F: Открывает /accounts
F->>B: GET /api/v1/brokerage-accounts (Bearer)
B->>D: Читает подключение T-Bank
alt Подключение отсутствует
B-->>F: 409 TBANK_NOT_CONNECTED
F-->>U: Переход к настройкам T-Bank
else Подключение сохранено
B->>T: GetAccounts(OPEN) over TLS
T-->>B: Счета или ошибка
par Для каждого поддерживаемого счета
B->>T: GetPortfolio + GetPositions over TLS
T-->>B: Позиции и деньги либо временная ошибка
end
B-->>F: Счета со сводками либо ошибка
F-->>U: Карточки счетов или объяснение ошибки
end
```
## Счёт и его сводка ## Счёт и его сводка
@@ -50,8 +69,8 @@
сводка не подменяется нулями. сводка не подменяется нулями.
Временная недоступность сводки одного счёта остаётся локальной для него. Временная недоступность сводки одного счёта остаётся локальной для него.
Известный отказ доступа скрывает личные данные. Transient failure сохраняет Ошибка подключения, отклонение токена или невозможность получить исходный
последний список и его время; отсутствие первого списка остаётся missing. список не превращаются в частичный успешный список.
## Состояния ## Состояния
@@ -62,22 +81,20 @@
- `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` по всей области и доступна с клавиатуры. На широком экране карточки расположены в две колонки, на узком — в одну. Карточка показывает название, локализованный тип, крупный валютный итог и вторую валюту при наличии, число остальных валют, дату открытия без времени и видимое действие открытия. Отдельные цветные блоки «Акции» и «Облигации» показывают количество наименований бумаг. Недоступная или пустая сводка отображается локально; отсутствие сумм не скрывает известные количества бумаг, а недоступная сводка не подменяет их нулями. Наведение и видимый клавиатурный фокус подчёркивают переход; настройка уменьшения движения отключает анимацию.
## Свежесть и границы ## Свежесть и границы
Обычное открытие использует query owner и локальный server snapshot. Список запрашивается при открытии экрана без ручного обновления и без обновления при возврате фокуса. Frontend использует успешный кэш TanStack Query в течение 60 секунд, поэтому повторный вход в этот интервал не выполняет новый GET. Когда уже показанный список обновляется в фоне, он остаётся на экране: индикатор обновления и ошибка с повторной попыткой отображаются рядом с последним успешным ответом, включая пустой список. Это клиентский сохранённый ответ; серверный запасной список не выдаётся. Backend не хранит кэш и не передаёт метку свежести. Список не заменяет полный [портфель](../portfolio/overview.md),
«Обновить» перечитывает сохранённые membership и сводки доступных счетов; [журнал операций](../operations/overview.md) или историю закрытых счетов.
активная работа наблюдается без нового импорта. Истечение TTL само не Для совокупного результата доступен [режим «Все счета»](../portfolio/all-accounts.md).
запускает запрос: обновлением личных данных владеет часовой обход.
Membership и portfolio source имеют часовую свежесть. Время списка
не подменяет время каждой сводки. Список не заменяет полный
[портфель](../portfolio/overview.md), [журнал](../operations/overview.md)
или историю закрытых счетов. Для общего результата доступен
[режим «Все счета»](../portfolio/all-accounts.md).
Согласованное поведение описывает [спецификация счетов](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-accounts/spec.md). Согласованное поведение описывает [спецификация счетов](../../../specs/openspec/specs/brokerage-accounts/spec.md).
Её раздел об экране пока требует одноколоночного списка и не описывает
счётчики акций и облигаций. Текущая реализация и её проверки используют
две колонки на широком экране и показывают эти счётчики. Расхождение требует
отдельного согласования требований; в этой документации отражён наблюдаемый экран.
+18 -25
View File
@@ -11,25 +11,19 @@
Пользователь регистрируется или входит по 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 дней, заменяется при восстановлении
сессии и передаётся в cookie `refresh_token` с `httpOnly` и `SameSite=Lax`. сессии и передаётся в cookie `refresh_token` с `httpOnly` и `SameSite=Lax`.
Сроки определены в [спецификации сессий](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/user-authentication/spec.md#requirement-access-и-refresh-сессии) Сроки определены в [спецификации сессий](../../../specs/openspec/specs/user-authentication/spec.md#requirement-access-и-refresh-сессии)
и [ADR 006](../../adr/006-auth-session-policy.md). и [ADR 006](../../adr/006-auth-session-policy.md).
После перезагрузки SPA получает новый access token через refresh-cookie. После перезагрузки SPA получает новый access token через refresh-cookie.
@@ -61,16 +55,15 @@ sequenceDiagram
B-->>F: Access token, профиль, новая refresh-cookie B-->>F: Access token, профиль, новая refresh-cookie
F->>B: Protected request с Bearer F->>B: Protected request с Bearer
else Cookie нет или refresh отклонён else Cookie нет или refresh отклонён
B-->>F: 401 без изменения cookie B-->>F: 401 и очистка cookie
Note over F: Готовое неавторизованное состояние Note over F: Готовое неавторизованное состояние
end end
``` ```
В одной вкладке одновременные refresh используют один незавершённый запрос; В одном экземпляре frontend одновременные refresh используют один
cookie-writing requests упорядочены локально. Межвкладочная синхронизация незавершённый запрос. Между вкладками используется Web Locks, если браузер
не поддерживается: после смены сессии в другой вкладке допустимы reload/re-login. его поддерживает. Backend всё равно защищает ротацию атомарно: координация
Backend защищает ротацию атомарно. Неуспешный refresh не удаляет cookie браузеров не заменяет серверную проверку.
победившего запроса; недействительный token по-прежнему не восстанавливает сессию.
Обычный защищённый запрос с `401 UNAUTHORIZED` проходит refresh и один повтор Обычный защищённый запрос с `401 UNAUTHORIZED` проходит refresh и один повтор
через общий транспорт; auth endpoints из этого цикла исключены. Само через общий транспорт; auth endpoints из этого цикла исключены. Само
@@ -79,11 +72,11 @@ Backend защищает ротацию атомарно. Неуспешный r
## HTTP-граница ## HTTP-граница
Endpoints, статусы и формы публичных ответов определяет backend OpenAPI; Endpoints, статусы и формы публичных ответов определяет backend OpenAPI;
граница производного клиента описана в согласование производного клиента описано в
[HTTP API](../../architecture/http-api.md#источник-контракта). [contract workflow](../../development/runtime-openapi-verification.md).
Refresh-cookie ограничена auth API. Признак Secure зависит от режима Refresh-cookie ограничена auth API. Признак Secure зависит от режима
окружения: в development допускается локальный HTTP, в остальных режимах окружения: локальный HTTP предполагает development mode, см.
Secure-cookie требует защищённого соединения. [локальное окружение](../../development/local-workspace.md).
HTTP-ошибка и транспортная ошибка refresh одинаково завершают начальное HTTP-ошибка и транспортная ошибка refresh одинаково завершают начальное
восстановление готовым неавторизованным состоянием. При refresh после восстановление готовым неавторизованным состоянием. При refresh после
@@ -124,6 +117,6 @@ Backend проверяет учётные данные и tokens, хранит
и проверку атомарности с реальной изолированной базой. и проверку атомарности с реальной изолированной базой.
Согласованное поведение находится в workspace-спецификации Согласованное поведение находится в workspace-спецификации
[User authentication](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/user-authentication/spec.md). [User authentication](../../../specs/openspec/specs/user-authentication/spec.md).
Правила начального ожидания — в Правила начального ожидания — в
[состояниях интерфейса](../../architecture/cross-cutting/ui-resilience.md). [состояниях интерфейса](../../architecture/cross-cutting/ui-resilience.md).
+6 -5
View File
@@ -89,8 +89,9 @@ JavaScript Number. Отдельный ввод ограничен signed64: на
следующие страницы. Баланс при выборе прошлого периода остаётся текущим. следующие страницы. Баланс при выборе прошлого периода остаётся текущим.
Сводка учитывает все подходящие записи, включая не показанные на странице. Сводка учитывает все подходящие записи, включая не показанные на странице.
Сводка одной или всех карт отдельно показывает расходы, возвращённую сумму и Сводка одной или всех карт показывает одну итоговую сумму расходов с учётом
итог после возвратов, а также компактные строки категорий по убыванию итоговых расходов. Возврат уменьшает расходы месяца своего возвратов и компактные строки категорий по убыванию итоговых расходов. Отдельные показатели «Возвращено»
и «После возвратов» в интерфейсе скрыты; расчёт и операции возврата сохранены. Возврат уменьшает расходы месяца своего
получения: августовская покупка 1 000 ₽ и сентябрьский возврат 600 ₽ дают получения: августовская покупка 1 000 ₽ и сентябрьский возврат 600 ₽ дают
августовский итог 1 000 ₽ и сентябрьский −600 ₽ при отсутствии других трат. августовский итог 1 000 ₽ и сентябрьский −600 ₽ при отсутствии других трат.
Начальный остаток, пополнения и переводы в эти показатели не входят. Начальный остаток, пополнения и переводы в эти показатели не входят.
@@ -135,10 +136,10 @@ JavaScript Number. Отдельный ввод ограничен signed64: на
а форма сохраняет черновик и позволяет получить актуальную запись. а форма сохраняет черновик и позволяет получить актуальную запись.
При ошибке сохранения неподтверждённые суммы не выдаются за новый баланс. При ошибке сохранения неподтверждённые суммы не выдаются за новый баланс.
[Безопасное восстановление](../../architecture/containers.md#сохранение-и-восстановление-данных) [Безопасное восстановление](../../development/local-workspace.md#сохранение-данных-и-откат)
сохраняет новую финансовую историю и при откате приложения. Устройство хранения сохраняет новую финансовую историю и при откате приложения. Устройство хранения
описано в [контейнерах](../../architecture/containers.md#хранимые-данные-и-границы-доступа). описано в [контейнерах](../../architecture/containers.md#хранимые-данные-и-границы-доступа).
Согласованный контракт банковского учёта находится в Согласованный контракт этой поставки находится в
[manual-bank-cards](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/changes/add-manual-bank-cards/specs/manual-bank-cards/spec.md). [manual-bank-cards](../../../specs/openspec/changes/add-manual-bank-cards/specs/manual-bank-cards/spec.md).
Автоматическая загрузка банковских данных, кредитные карты, другие валюты, Автоматическая загрузка банковских данных, кредитные карты, другие валюты,
бюджеты и общий баланс банковских денег с инвестициями в эту версию не входят. бюджеты и общий баланс банковских денег с инвестициями в эту версию не входят.
+58 -33
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,36 +38,44 @@
При первой загрузке или новом периоде без сохранённых данных показан При первой загрузке или новом периоде без сохранённых данных показан
каркас страницы. Пустой результат объясняет отсутствие исполненных купонов и каркас страницы. Пустой результат объясняет отсутствие исполненных купонов и
дивидендов, сохраняя фильтры и налоговое пояснение. Ошибка запроса дивидендов, сохраняя фильтры, налоговое пояснение и сравнения. Ошибка запроса
или нарушение принадлежности выплат даёт локальное состояние недоступности или нарушение принадлежности выплат даёт локальное состояние с действием «Повторить».
без действия повтора; чтение восстанавливается автоматически.
При фоновом обновлении того же периода сохранённый ответ и черновик При фоновом обновлении того же периода сохранённый ответ и черновик
остаются видимыми без индикации обновления и технического предупреждения. остаются видимыми: загрузку обозначает индикатор, неуспешное обновление —
Неполнота применённого периода обозначается состоянием самого результата. предупреждение с повторной попыткой.
На узком экране поля и действия идут вертикально; все карточки выплат На узком экране поля и действия идут вертикально; все карточки выплат
остаются в общем потоке страницы без раскрытия или внутренних областей прокрутки. остаются в общем потоке страницы без раскрытия или внутренних областей прокрутки.
## Какие выплаты входят в результат ## Какие выплаты входят в результат
- Backend проверяет доступ локально и читает сохранённые операции. Фоновая загрузка должна завершить все страницы; ошибка страницы или повреждённая цепочка курсоров не подтверждают полноту. - Backend проверяет доступ к счёту, проходит все страницы исполненных операций и не возвращает частичный результат при ошибке страницы или повреждённой цепочке курсоров.
- Только точные типы купона и дивиденда источника формируют результат. Суммы остаются точными десятичными строками в исходной валюте; отрицательные корректировки сохраняют знак. Налоги, комиссии, сделки, переводы, погашения и переводы дивидендов не входят в доход. - Только точные типы купона и дивиденда источника формируют результат. Суммы остаются точными десятичными строками в исходной валюте; отрицательные корректировки сохраняют знак. Налоги, комиссии, сделки, переводы, погашения и переводы дивидендов не входят в доход.
- Итоги, временные интервалы и разбиение по инструментам не смешивают валюты и не создают общий конвертированный итог. Система не загружает другие периоды ради сравнения полученного дохода с выбранным. - Итоги, сравнения, временные интервалы и разбиение по инструментам не смешивают валюты и не создают общий конвертированный итог. На странице показано только сравнение с годом ранее, когда календарная дата и история счёта позволяют. Предыдущий период остаётся в ответе API, но не отображается.
- Временные интервалы по-прежнему приходят с backend: дни для 1–45 дней, недели для 46–180 и месяцы для более длинного периода, включая пустые интервалы. Страница дохода не строит из них график или текстовую динамику; ответ API не меняется. - Временные интервалы по-прежнему приходят с backend: дни для 1–45 дней, недели для 46–180 и месяцы для более длинного периода, включая пустые интервалы. Страница дохода не строит из них график или текстовую динамику; ответ API не меняется.
- Раздел «Выплаты» показывает карточки выплат в исходном порядке `entries`. В отдельном счёте каждая выплата — отдельная карточка. В режиме «Все счета» выплаты одного инструмента, дня и точного типа образуют группу, если их две и более ([правила группировки](../operations/overview.md#группировка-в-режиме-все-счета)). Группа стоит на месте первой выплаты; каждая строка счёта показывает название счёта и свою сумму со знаком. Визуальный итог группы — точная сумма по валютам по показанным выплатам; он скрыт, если полнота периода не `complete`, или у выплаты нет суммы, или сумма равна нулю. Это клиентский расчёт и не заменяет серверные итоги аналитики дохода, которые по-прежнему приходят из ответа API и не пересчитываются. Описание, дата, доступные идентификаторы инструмента и сумма со знаком видны в каждой выплате. Дополнительный запрос за составом не нужен. - Раздел «Выплаты» показывает единый список карточек выплат в исходном порядке `entries`: одна карточка на каждую выплату. Внешних групп по инструментам или валютам, итогов и количества выплат инструмента нет. Описание, дата, доступные идентификаторы инструмента и сумма со знаком видны в каждой карточке; валюты по-прежнему разделены в итогах и сравнениях. Дополнительный запрос за составом не нужен.
Сравнение с непосредственно предыдущим периодом сохраняется в API:
для полного месяца это предыдущий полный месяц, иначе — непосредственно
предшествующий интервал той же включительной длительности. Текущая страница
его не показывает. Это расходится с
[основным требованием сравнений](../../../specs/openspec/specs/brokerage-account-income-analytics/spec.md#requirement-period-comparisons-preserve-calendar-and-currency-meaning),
которое описывает оба сравнения. Требуется отдельно согласовать этот разрыв;
здесь сохранено различие между ответом API и фактическим экраном.
Раздел «Годом ранее» показывает цветные панели без общей белой подложки и
внутренних отступов у внешнего блока. Точные периоды показаны в заголовках двух панелей с русским форматом диапазона
и UTC для календарных дат.
Отдельного заголовка с датами и валютного бейджа нет; валюта остаётся у каждой суммы.
Панель «Разница» сохраняет серверную абсолютную разницу и дополняет итог, купоны
и дивиденды процентом: абсолютная разница / сумма годом ранее × 100.
Это округлённый показатель для отображения, а не пересчёт Money или инвестиционная
доходность. Форматирование сохраняет знак и не более одного десятичного разряда.
При нулевой или отрицательной базе либо непредставимом числовом отношении
процентный бейдж не отображается; абсолютная разница остаётся видимой.
## Полнота и принадлежность выплат ## Полнота и принадлежность выплат
Доходы читают сохранённые операции и coverage в согласованной транзакции.
Только выбранный период определяет готовность результата. Неполный период
не получает окончательных итогов.
Подтверждённый пустой период отличается от ещё не загруженного. При смене
периода прежний snapshot не показывается под новым названием.
Ручная сверка полного периода сохраняет весь прежний результат до успешной
атомарной публикации всех необходимых интервалов и счетов. История счёта
может ещё догружаться после готовности выбранного месяца.
Каждая запись `income-analytics.entries` содержит обязательный `instrumentKey`, Каждая запись `income-analytics.entries` содержит обязательный `instrumentKey`,
равный `key` соответствующей строки `instruments`. Backend вычисляет этот ключ равный `key` соответствующей строки `instruments`. Backend вычисляет этот ключ
один раз при подготовке записей дохода и использует его же для группировки. один раз при подготовке записей дохода и использует его же для группировки.
@@ -75,28 +83,45 @@
серверным правилом; клиент воспринимает ключ как непрозрачную строку. серверным правилом; клиент воспринимает ключ как непрозрачную строку.
Это поле принадлежит ответу аналитики; общий журнал операций его не требует. Это поле принадлежит ответу аналитики; общий журнал операций его не требует.
Backend обеспечивает принадлежность выплат, валюту и receipt count при Frontend проверяет принадлежность выплат по полученному ключу до подготовки
построении entries и instrument groups из одного набора данных. Frontend плоского списка выплат. Отсутствующий, пустой или
доверяет типизированному contract, не восстанавливает формулу идентификации нестроковый ключ, неоднозначные ключи инструментов, неизвестная группа, другая
и не выполняет второй consistency validator. Полностью пустой ответ допустим. валюта или несовпадение числа выплат приводят к существующему локальному
Состав и исходный серверный порядок записей и знак сумм состоянию ошибки аналитики с повторной попыткой. Частичный список выплат не показывается как
сохраняются; серверные денежные итоги frontend не пересчитывает (визуальный итог группы в режиме «Все счета» отдельный, см. выше). Стабильный ID в пределах подключения и счёта определяет одну операцию: полный; формула идентификации в клиенте не восстанавливается. Полностью пустой
повторная сверка обновляет её без двойного учёта. Одинаковые ID разных счетов ответ допустим. Состав и исходный серверный порядок записей и знак сумм
остаются разными выплатами. Изменение источника логотипа позволяет заново сохраняются, frontend не пересчитывает денежные итоги. Повторяющиеся ID
показать изображение после буквенной заглушки. операций остаются отдельными строками; повторные вхождения учитываются раздельно, а изменение источника логотипа
позволяет заново показать изображение после буквенной заглушки.
## Совместимость ответа
При обновлении с версии без `instrumentKey` сначала обновляется backend:
дополнительное поле совместимо с прежним
frontend. Затем синхронизируются OpenAPI snapshot/generated types и обновляется
клиент. При переключении клиент перезагружается, чтобы не переносить
старые результаты в памяти и незавершённые запросы. Новый frontend требует новый
backend; отсутствие ключа принадлежности не скрывается подстановкой прежней формулы.
При регрессии сначала возвращается прежний frontend вместе с соответствующим
snapshot/types и перезагружается клиент; затем при необходимости возвращается
backend. Старый frontend может временно работать с новым ответом с добавленным полем.
Календарные фильтры и отдельные карточки выплат используют тот же контракт и не требуют
дополнительного обновления backend. Схема SQLite, учётные данные и сообщённые брокером
операции не меняются: откат
не удаляет данные, производные результаты запрашиваются заново.
## Будущие события и ограничения ## Будущие события и ограничения
На странице дохода нет блоков «Динамика» и «Ближайший прогноз», блоков На странице дохода нет блоков «Динамика» и «Ближайший прогноз». Она не запрашивает
сравнения периодов и сообщений об их недоступности. Она не запрашивает
`/events` и не зависит от его доступности. Самостоятельная страница `/events` и не зависит от его доступности. Самостоятельная страница
[«Будущие события»](upcoming-events.md) по-прежнему доступна из обзора [«Будущие события»](upcoming-events.md) по-прежнему доступна из обзора
выбранного счёта и сохраняет прежнее поведение. Будущие выплаты выбранного счёта и сохраняет прежнее поведение. Будущие выплаты
не включаются в итоги полученного дохода. не включаются в итоги полученного дохода или сравнения.
Аналитика читает сохранённую историю операций и не рассчитывает налоги, инвестиционную эффективность, доходность, валютную конвертацию или общий мультивалютный итог. Аналитика только читает данные, не хранит историю операций и не рассчитывает налоги, инвестиционную эффективность, доходность, валютную конвертацию или общий мультивалютный итог.
Основное проверяемое поведение — в [спецификации дохода](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-income-analytics/spec.md), Основное проверяемое поведение — в [спецификации дохода](../../../specs/openspec/specs/brokerage-account-income-analytics/spec.md),
точная форма ответа — в [HTTP API](../../architecture/http-api.md). точная форма ответа — в [HTTP API](../../architecture/http-api.md).
Не следует использовать календарь будущих событий для восстановления Не следует использовать календарь будущих событий для восстановления
состава уже полученного дохода. состава уже полученного дохода.
+46 -56
View File
@@ -29,7 +29,7 @@
Суммы рассчитаны по количеству на `calculatedAt`. Они не учитывают будущие Суммы рассчитаны по количеству на `calculatedAt`. Они не учитывают будущие
сделки, право владения на дату реестра и персональный налоговый расчёт. сделки, право владения на дату реестра и персональный налоговый расчёт.
Есть неразрешённое ограничение интерпретации: Есть неразрешённое ограничение интерпретации:
[основная спецификация](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-upcoming-events/spec.md#requirement-forecasts-preserve-event-meaning-and-uncertainty) [основная спецификация](../../../specs/openspec/specs/brokerage-account-upcoming-events/spec.md#requirement-forecasts-preserve-event-meaning-and-uncertainty)
и текущая подсказка экрана называют прогноз суммой «до налогов», но для и текущая подсказка экрана называют прогноз суммой «до налогов», но для
дивиденда реализация только умножает `dividend_net` на количество. дивиденда реализация только умножает `dividend_net` на количество.
Налоговый смысл этого поля не подтверждён локальными материалами; на Налоговый смысл этого поля не подтверждён локальными материалами; на
@@ -39,76 +39,66 @@
## Период и список ## Период и список
Без фильтра страница применяет период по умолчанию из семи календарных Без фильтра период начинается сегодня в 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; полный экран показывает список без общего итога.
В режиме «Все счета» события одного инструмента, даты, точного типа и `dateKind` образуют Текущие controller, сервис и экран поддерживают выбор периода, тогда как
группу, если их две и более, в том числе одного счёта. Ключ дополнительно включает `dateKind`, [основная спецификация](../../../specs/openspec/specs/brokerage-account-upcoming-events/spec.md#requirement-account-page-presents-an-upcoming-events-list)
поэтому дата реестра дивиденда и иная дата того же инструмента не объединяются. Группа стоит на месте первого описывает фиксированные 30 дней и экран без фильтров. Документация отражает
события. Полный список группирует все полученные события; превью обзора сначала берёт пять реализованный выбор периода, не изменяя это несогласованное требование.
событий и группирует только их. Каждая строка счёта остаётся отдельной и показывает название
счёта и свою прогнозную сумму. Визуальный итог группы — точная сумма по валютам; он показывается
только если полнота прогноза `complete` и у каждого события группы известна прогнозная сумма.
Иначе итог скрыт, а строки остаются. Этот итог не равен итогам ответа API и не относится к полученному доходу.
## Получение и отказы ## Получение и отказы
Запрос требует сессии, подключения и локально подтверждённого membership. ```mermaid
Он читает сохранённый forecast и не запускает личную синхронизацию. Часовой sequenceDiagram
обход строит forecast из полного личного portfolio и общих календарей: T-Bank participant F as Frontend
для дивидендов, MOEX для bond events. Личные оценки сохраняются отдельно от participant B as Backend
общих календарей. participant T as T-Bank
participant M as MOEX ISS
F->>B: Счёт и выбранный период событий
B->>B: Проверяет сессию и текущее подключение
B->>T: Получает открытые счета и позиции доступного счёта
par События акций
B->>T: GetDividends за выбранный период
T-->>B: Известные дивиденды
and События облигаций
B->>M: Календарь bondization
M-->>B: Купоны, погашения и оферты
end
B->>B: Применяет период и текущее количество бумаг
B-->>F: Полный список известных событий либо ошибка
```
Последний полный forecast привязан к scope, исходному окну и membership. Если Запрос требует действующей сессии и подключения T-Bank. Получение
часовой обход не может получить полный новый forecast, прежний полный прогноз, необходимых финансовых данных брокера или календаря MOEX завершается ошибкой
его `calculatedAt` и исходное окно остаются читаемыми, в том числе после `503`, если источник недоступен: частичный список не выдаётся как полный.
рестарта; `meta.lastAttempt` отдельно сообщает об ошибке обновления. Без Доступные метаданные инструментов дополняют название и логотип; когда
полного forecast `data: null` и metadata обозначает initial loading либо исходного тикера достаточно для календаря, ошибка дополнительного поиска
временную недоступность, а не подтверждённое отсутствие выплат. В режиме `all` метаданных не скрывает события.
результат complete только при полном вкладе каждого включённого счёта. Другой
scope или изменившийся membership не получают старый forecast под новым
названием.
Успешный ответ MOEX без запрошенного блока означает отсутствие поддерживаемых
событий этого источника; это отличается от 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)
+22 -34
View File
@@ -89,11 +89,7 @@ HTML-источник, общий кэш и поведение при измен
Календарь охватывает пять прошлых лет и следующий год. Будущие купоны Календарь охватывает пять прошлых лет и следующий год. Будущие купоны
сортируются по дате выплаты, прошедшие и недатированные доступны отдельно. сортируются по дате выплаты, прошедшие и недатированные доступны отдельно.
Будущие и прошедшие события купонного графика показываются единым предметным Показаны сумма на облигацию, номер, тип, длительность периода и дата реестра,
представлением выплаты с пометкой события по графику — тем же, что у
исполненных выплат и прогноза; исполнение подтверждает только личная история.
Показаны сумма на облигацию, отдельная личная оценка, номер, тип, длительность
периода и дата реестра,
если они известны. Неопределённый будущий купон не заменяется нулём. если они известны. Неопределённый будущий купон не заменяется нулём.
Личная оценка доступна только при положительных позициях, известной сумме, Личная оценка доступна только при положительных позициях, известной сумме,
@@ -115,42 +111,34 @@ HTML-источник, общий кэш и поведение при измен
транспортного контракта задаёт [HTTP API](../../architecture/http-api.md). транспортного контракта задаёт [HTTP API](../../architecture/http-api.md).
`BondBy`, `FindInstrument` и `GetBondCoupons` используют способ `BondBy`, `FindInstrument` и `GetBondCoupons` используют способ
декодирования, сохраняющий отсутствие полей. Общие данные выпуска, quotes, декодирования, сохраняющий отсутствие полей. Рыночные RPC проходят
календари, MOEX metrics и необходимые результаты НКР сохраняются в SQLite. общие TLS, очередь, ограничение частоты, deadline и повторные попытки. Кэш успешных и совместных
Личные позиции и оценки выплат используют текущий доступ и сохранённый незавершённых запросов ограничен LRU и сроком 30 секунд по пользователю,
portfolio; они не попадают в общие источники. поколению подключения и параметрам. Ошибки не кэшируются; ошибки авторизации
не подавляются при дополнительной загрузке данных поиска.
Исходный ответ `BondBy` совместно используется поиском, карточкой, котировкой,
графиком, купонами и сводкой показателей в пределах того же пользователя, поколения и TTL.
Одновременная загрузка секций вызывает один `BondBy` на UID. Календарь
и сводка совместно используют один запрос купонов; котировка переиспользуется
при расчёте купонной доходности. MOEX и НКР загружаются только для открытого
выпуска, независимо от поиска. Для 30 новых
результатов поиск выполняет 32 RPC до повторных попыток: один `FindInstrument`,
30 `BondBy` и один пакетный запрос цен. Общий ограничитель backend не заменяет
квоты Т-Банка; при их исчерпании сохраняется `TBANK_UNAVAILABLE`.
BondBy переиспользуется зависимостями выпуска. Discovery принадлежит query, Разделы загружаются независимо, показывают каркас загрузки и локальную повторную попытку.
свежесть — конкретному source/range. Missing/stale зависимости ставятся
в фоновую очередь через прежний limiter; обычный HTTP-ответ не ждёт брокера.
Покрытые завершённые свечи не перечитываются после restart. Открытый хвост
остаётся изменяемым; ручной refresh охватывает показанный период.
Последняя полная личная оценка сохраняется при ошибке новых обязательных
зависимостей, вместе с исходным временем. Неполнота не означает отсутствие
выплат. Наблюдение статуса не запускает повторный импорт. Политика источников
и границы — в [модели свежести](../../architecture/cross-cutting/data-freshness.md).
Разделы загружаются независимо, показывают каркас загрузки и локальное
спокойное состояние недоступности без действия повтора.
Ошибка фонового обновления сохраняет последние данные. Смена подключения Ошибка фонового обновления сохраняет последние данные. Смена подключения
сбрасывает поддерево клиентских запросов облигаций вместе с остальными финансовыми запросами; сбрасывает поддерево клиентских запросов облигаций вместе с остальными финансовыми запросами;
выход очищает кэш сессии. Общие график, позиция и личные события выход очищает кэш сессии. Общие график, позиция и личные события
имеют общее представление на страницах акций и облигаций. имеют общее представление на страницах акций и облигаций.
Основные спецификации [портфеля](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md) Основные спецификации [портфеля](../../../specs/openspec/specs/portfolio-view/spec.md)
и [журнала](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-ledger/spec.md) и [журнала](../../../specs/openspec/specs/brokerage-account-ledger/spec.md)
описывают исходные финансовые понятия. Отдельная основная спецификация описывают исходные финансовые понятия. Отдельная основная спецификация
страницы облигации в текущем OpenSpec Store не найдена; условия вычислений страницы облигации в текущем OpenSpec Store не найдена; условия вычислений
подтверждены [каталогом](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/bonds/application/bond-catalog.service.ts), подтверждены [каталогом](../../../backend/src/modules/investments/instruments/bonds/application/bond-catalog.service.ts),
[рыночными данными](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/bonds/application/bond-market.service.ts), [рыночными данными](../../../backend/src/modules/investments/instruments/bonds/application/bond-market.service.ts),
[выплатами](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/bonds/application/bond-income.service.ts) [выплатами](../../../backend/src/modules/investments/instruments/bonds/application/bond-income.service.ts)
и [проверками](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/bonds/application/bond.spec.ts). и [проверками](../../../backend/src/modules/investments/instruments/bonds/application/bond.spec.ts).
Границы источников описаны в [разделе интеграций](../../integrations/README.md), Границы источников описаны в [разделе интеграций](../../integrations/README.md),
сходное разделение рыночных и личных данных — в [акциях](stocks.md). сходное разделение рыночных и личных данных — в [акциях](stocks.md).
## Online reference
Страница облигации, поиск и переход по тикеру обращаются к онлайн-справочнику
брокера; сохранённые quotes, свечи, события, ratings и метки портфеля
сохраняют собственные границы свежести и ошибок.
+36 -63
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, которое может быть старше времени получения ответа. Статус торгов не
выводится из свежести цены. Отсутствующее значение не заменяется нулём. выводится из свежести цены. Отсутствующее значение не заменяется нулём.
@@ -80,8 +73,7 @@ Frontend не получает брокерский токен и не обра
Личная история использует существующий Личная история использует существующий
`GET /api/v1/brokerage-accounts/all/operations?instrumentUid=...`. `GET /api/v1/brokerage-accounts/all/operations?instrumentUid=...`.
Фильтр UID валидируется и применяется к локальной истории. При ручной Фильтр UID валидируется и передаётся upstream как `instrumentId`; ответ также
сверке бумаги он также передаётся upstream как `instrumentId`; ответ
проверяется по UID. Без `from` такой запрос начинается с самой ранней известной проверяется по UID. Без `from` такой запрос начинается с самой ранней известной
даты открытия доступных счетов; если даты неполны, нижняя граница — Unix epoch. даты открытия доступных счетов; если даты неполны, нижняя граница — Unix epoch.
Обычный журнал без фильтра инструмента сохраняет начало текущего года. Обычный журнал без фильтра инструмента сохраняет начало текущего года.
@@ -126,11 +118,7 @@ UID включён в ключ кэша и подпись общего курс
## Дивиденды и финансовые показатели ## Дивиденды и финансовые показатели
События компании отделены от личных зачислений. Будущие и прошедшие события События компании отделены от личных зачислений. Известные будущие выплаты
дивидендного графика показываются единым предметным представлением выплаты
с пометкой «событие по графику» — тем же, что у исполненных выплат и прогноза;
сумма на акцию, отдельная личная оценка и дата реестра сохраняют свои подписи
и не выдаются за личное зачисление. Известные будущие выплаты
определяются по `paymentDate`, при её отсутствии — по `recordDate`; прошедшие определяются по `paymentDate`, при её отсутствии — по `recordDate`; прошедшие
и недатированные события помещены в отдельную историю компании. Отменённые и недатированные события помещены в отдельную историю компании. Отменённые
события исключаются. Личная оценка умножает сумму на акцию на текущее положительное события исключаются. Личная оценка умножает сумму на акцию на текущее положительное
@@ -154,27 +142,18 @@ TTM означает последние 12 месяцев, MRQ — послед
## Получение данных и ограничения ## Получение данных и ограничения
Общие metadata, discovery, company, quotes, свечи и календари сохраняются Запросы T-Bank проходят общую серверную интеграцию с TLS, лимитом частоты,
в SQLite. Скринер получает каталог online и показывает время его получения; очередью, предельным временем ожидания и повторными попытками. Проверка доступа
временная недоступность каталога не подтверждает отсутствие подходящих акций. и расшифровка token выполняются только backend. Успешные ответы и совместные незавершённые запросы кэшируются
Отсутствующее enrichment не скрывает найденную бумагу. Сохранённые общие в ограниченном кэше LRU на 30 секунд по пользователю, поколению подключения и параметрам.
источники переиспользуются между пользователями, но личные позиции и выплаты Для поиска `FindInstrument` дополняется сведениями `ShareBy` через тот же
всегда проверяют доступ. кэш карточки инструмента и одним пакетным `GetLastPrices` для списка. Ошибка
дополнительной загрузки или исчезнувшая карточка не скрывает исходную запись
Обычное чтение не ждёт RPC; missing/stale зависимости ставятся в фоновую каталога; недоступные логотипы и цены имеют заглушки. Ошибки авторизации
очередь через существующий T-Bank limiter. Часовая свежесть применяется не подавляются.
к quotes и открытому хвосту свечей; metadata/company/календари, будучи Ошибка целого запроса не кэшируется; отсутствие дополнительного источника
сохранёнными, не перечитываются обычным чтением. обозначается в ответе. Поддерево клиентских запросов акций сбрасывается вместе с финансовыми
Закрытые полные свечные интервалы, включая пустые, сохраняются после restart. данными при изменении подключения; выход очищает кэш сессии.
Новый день не делает прежнюю незавершённую свечу полной.
Отдельного действия «Обновить» на странице нет: чтение текущего инструмента,
выбранного графика и personal-history scope выполняется автоматически.
Частичные строки видны с обозначением неполноты; неполные личные оценки
не становятся нулями. Статус и `sync=observe` не создают новый импорт.
Смена подключения и известный access loss очищают личное представление;
поздние ответы не возвращают прежние данные. Детали —
[модель свежести](../../architecture/cross-cutting/data-freshness.md).
Полные даты в интерфейсе имеют формат ДД.ММ.ГГГГ; ISO-формат транспорта и Полные даты в интерфейсе имеют формат ДД.ММ.ГГГГ; ISO-формат транспорта и
временные зоны расчётов не меняются. Личная история использует общий с портфелем формат строки операции, временные зоны расчётов не меняются. Личная история использует общий с портфелем формат строки операции,
@@ -190,17 +169,11 @@ TTM означает последние 12 месяцев, MRQ — послед
История открытых счетов не обещает полную историю владения, включая закрытые История открытых счетов не обещает полную историю владения, включая закрытые
счета, переводы и универсальные корпоративные события. счета, переводы и универсальные корпоративные события.
Основные спецификации [портфеля](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md) Основные спецификации [портфеля](../../../specs/openspec/specs/portfolio-view/spec.md)
и [журнала](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-ledger/spec.md) и [журнала](../../../specs/openspec/specs/brokerage-account-ledger/spec.md)
задают исходные финансовые понятия. Отдельная основная спецификация страницы задают исходные финансовые понятия. Отдельная основная спецификация страницы
акции в текущем OpenSpec Store не найдена; описанные механизмы проверяются акции в текущем OpenSpec Store не найдена; описанные механизмы проверяются
по [каталогу](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/stocks/application/stock-catalog.service.ts), по [каталогу](../../../backend/src/modules/investments/instruments/stocks/application/stock-catalog.service.ts),
[рыночным данным](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/instruments/stocks/application/stock-market.service.ts), [рыночным данным](../../../backend/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). [выплатам](../../../backend/src/modules/investments/instruments/stocks/application/stock-income.service.ts) и [проверкам](../../../backend/src/modules/investments/instruments/stocks/application/stock.spec.ts).
Сопоставление с облигациями — на [странице облигаций](bonds.md). Сопоставление с облигациями — на [странице облигаций](bonds.md).
## Online reference
Страница акции, поиск и переход по тикеру обращаются к онлайн-справочнику
брокера; сохранённые позиции, quotes, свечи и события остаются независимыми
локальными ресурсами и не подменяются ошибкой справочника.
+43 -51
View File
@@ -13,27 +13,9 @@
Исполненная операция содержит тип события, его категорию, описание, Исполненная операция содержит тип события, его категорию, описание,
момент совершения, доступные сведения об инструменте и денежное движение. момент совершения, доступные сведения об инструменте и денежное движение.
Запросы отменённых или ожидающих исполнения поручений не образуют этот журнал. Запросы отменённых или ожидающих исполнения поручений не образуют этот журнал.
Суммы остаются точными десятичными строками в исходной валюте. Frontend Суммы остаются точными десятичными строками в исходной валюте; frontend
форматирует их и считает визуальный итог группы по показанным записям только форматирует их. Положительное и отрицательное движение сохраняют
(см. [группировку](#группировка-в-режиме-все-счета)). Положительное и отрицательное знак. Отсутствие `payment` означает «Без движения денег», а не нулевую сумму.
движение сохраняют знак. Отсутствие `payment` означает «Без движения денег», а не нулевую сумму.
## Группировка в режиме «Все счета»
Только в режиме `all` записи журнала и превью обзора, которые относятся к одному
дню экрана, одному инструменту и точному типу операции, объединяются в группу.
Ключом служат день, `instrumentUid` (иначе `figi`) и тип; группа образуется при
двух и более записях, в том числе одного счёта. Группа стоит на месте первой записи.
Каждая запись остаётся отдельной строкой с названием счёта простым текстом и своей суммой.
Одиночный счёт не группируется.
Группировка выполняется после лимита: превью обзора сначала берёт пять записей,
затем группирует только их. Полный журнал группирует страницу, уже загруженную
курсором; группы не переносятся между страницами. Курсор и порядок записей не меняются.
Итог группы — точная сумма по каждой валюте, без конвертации. Он показывается только
если полнота журнала `complete`, у каждой записи группы есть `payment`, и ни одна сумма
не равна нулю. Иначе итог скрыт, а строки счетов остаются.
Категории объединяют разные события для фильтрации: сделка (`trade`), Категории объединяют разные события для фильтрации: сделка (`trade`),
доход (`income`), налог (`tax`), комиссия (`fee`), перевод (`transfer`) доход (`income`), налог (`tax`), комиссия (`fee`), перевод (`transfer`)
@@ -64,9 +46,8 @@ ID операций разных счетов не означают дублик
На [страницах акций](../instruments/stocks.md) и На [страницах акций](../instruments/stocks.md) и
[облигаций](../instruments/bonds.md) этот же журнал становится личной историей [облигаций](../instruments/bonds.md) этот же журнал становится личной историей
конкретной бумаги. Фильтр `instrumentUid` применяется к локальным операциям. Ручная сверка конкретной бумаги. Фильтр `instrumentUid` передаётся брокеру и проверяется
личной истории может ограничить источниковый запрос этой бумагой, не присваивая в ответе. Без явного начала периода он охватывает историю с открытия счёта;
такому subset полноту всех операций счёта. Без явного начала периода он охватывает историю с открытия счёта;
в общем режиме — с самой ранней известной даты открытия доступных счетов. в общем режиме — с самой ранней известной даты открытия доступных счетов.
Если даты открытия неполны, нижняя граница — Unix epoch. Это позволяет Если даты открытия неполны, нижняя граница — Unix epoch. Это позволяет
видеть прошлые операции уже проданной бумаги, но не обещает историю видеть прошлые операции уже проданной бумаги, но не обещает историю
@@ -74,38 +55,50 @@ ID операций разных счетов не означают дублик
## Получение и полнота ## Получение и полнота
`GET /api/v1/brokerage-accounts/:accountId/operations` читает сохранённые ```mermaid
операции и coverage после локальной проверки доступа. Период, категории, sequenceDiagram
UID, cursor и limit применяются к этой выборке. Транспорт описан в participant F as Frontend
[HTTP API](../../architecture/http-api.md). participant B as Backend
participant T as T-Bank
F->>B: Запрашивает применённый период и страницу операций
B->>B: Проверяет сессию и текущее подключение
B->>T: Получает открытые счета
T-->>B: Подтверждает доступность счёта
alt Подходящая страница есть в кэше
B-->>F: Сохранённая страница
else Нужна загрузка
B->>T: GetOperationsByCursor, только исполненные операции
T-->>B: Страница операций
B->>B: Нормализует записи и применяет фильтры
B-->>F: Записи и продолжение либо ошибка
end
```
Стабильность broker operation ID в пределах счёта и подключения принята как `GET /api/v1/brokerage-accounts/:accountId/operations` требует Bearer token.
допущение проекта. Повторный ID обновляет ту же запись без двойного учёта. Период, категории, необязательный UID, курсор и размер страницы задают запрос;
Страницы источника сначала сохраняются в staging. Полный успешный обход ответ содержит записи, время наблюдения и сведения о продолжении.
разрешает upsert, удаление исчезнувших записей в перечитанном predicate Точные поля и допустимые значения задаёт [HTTP API](../../architecture/http-api.md).
и публикацию coverage одной транзакцией. При повторной сверке полного
показанного периода транзакция охватывает все его окна и счета.
Полнота относится к UTC-интервалу `[from,to)`, включая подтверждённый пустой. Backend хранит не более 100 страниц в памяти процесса. Свежая страница
Категории, размер страницы и `hasNext=false` её не определяют. При первичном живёт до 30 секунд; при переполнении вытесняется давно не использовавшаяся.
импорте подтверждённые строки видны как partial; итоги ждут полного периода. Ключ разделяет пользователя, поколение подключения, счёт, нормализованный
Догрузка остальной истории обозначается отдельно. Без известной нижней период и категории, UID инструмента, курсор и размер страницы.
границы нельзя заявить полную историю счёта. Отсутствующий конец периода приводится к текущему 30-секундному интервалу.
Текущее подключение и доступ к счёту проверяются до выдачи страницы из кэша.
Cursor связан с пользователем, подключением, membership для `all`, фильтрами После успешного изменения подключения страницы предыдущего поколения,
и ревизией набора операций. Изменение строк даёт `INVALID_OPERATIONS_CURSOR`; в том числе поздно завершившиеся запросы, не выдаются журналу или аналитике
клиент возвращается на первую страницу, сохраняя применённые фильтры. дохода. Они удаляются по обычным ограничениям времени и объёма.
Coverage/status без изменения rowset cursor не ломают. Перезапуск сохраняет Локальной синхронизации и долговременного хранения операций нет.
опубликованные интервалы; незавершённая работа возобновляется только запросом. Продолжения [общего журнала](../portfolio/all-accounts.md#общая-пагинация-операций)
имеют собственные ограничения и восстановление после истечения.
## Представление и ошибки ## Представление и ошибки
Полный журнал и превью используют одинаковую компактную строку: логотип Полный журнал и превью используют одинаковую компактную строку: логотип
или буквенная заглушка, описание, дата, инструмент и денежное движение. или буквенная заглушка, описание, дата, инструмент и денежное движение.
Исполненные купоны и дивиденды показываются единым предметным представлением Для купона и дивиденда денежная метка называет точное событие; для прочих
выплаты — тем же, что на странице дохода, в прогнозе и графиках инструментов; операций — категорию. Она показывается один раз, без повтора рядом с датой.
денежная метка называет точное исполненное событие. Для прочих
операций метка называет категорию. Она показывается один раз, без повтора рядом с датой.
Знак и цвет обозначают направление денег; отсутствие движения нейтрально. Знак и цвет обозначают направление денег; отсутствие движения нейтрально.
Если описание отсутствует, показывается локализованный тип операции. Если описание отсутствует, показывается локализованный тип операции.
@@ -117,9 +110,8 @@ Coverage/status без изменения rowset cursor не ломают. Пе
не заменяется каркасом при применении действий или фоновом обновлении. не заменяется каркасом при применении действий или фоновом обновлении.
Ошибки `TBANK_NOT_CONNECTED`, `INVALID_TBANK_TOKEN`, недоступный счёт (`404`) Ошибки `TBANK_NOT_CONNECTED`, `INVALID_TBANK_TOKEN`, недоступный счёт (`404`)
и `TBANK_UNAVAILABLE` сохраняют [общую семантику API](../../architecture/http-api.md). и `TBANK_UNAVAILABLE` сохраняют [общую семантику API](../../architecture/http-api.md).
Только полный пустой ответ означает отсутствие подходящих операций. Пустой успешный ответ означает отсутствие подходящих операций, а не сбой.
Partial empty сообщает о неполноте; source failure сохраняет прежний результат.
Согласованное поведение описано в [спецификации журнала](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-ledger/spec.md). Согласованное поведение описано в [спецификации журнала](../../../specs/openspec/specs/brokerage-account-ledger/spec.md).
Фильтр конкретной бумаги и общий режим расширяют исходное описание отдельного Фильтр конкретной бумаги и общий режим расширяют исходное описание отдельного
счёта; их механизм раскрыт на страницах инструментов и общего портфеля. счёта; их механизм раскрыт на страницах инструментов и общего портфеля.
+50 -37
View File
@@ -51,56 +51,69 @@ Backend публикует статические маршруты под `/api/
используют прежнюю карточку. Доля относится к позициям той же валюты используют прежнюю карточку. Доля относится к позициям той же валюты
во всём показанном портфеле, без денежных остатков. во всём показанном портфеле, без денежных остатков.
- Журнал объединяет операции по убыванию времени, при равном времени использует - Журнал объединяет операции по убыванию времени, при равном времени использует
порядок идентификаторов счетов и операций. ID порядок идентификаторов счетов, сохраняя порядок внутри одного счёта. ID
операций разных счетов не считаются дублями. Переводы не схлопываются. операций разных счетов не считаются дублями. Переводы не схлопываются.
- Доходы собираются из всех страниц операций каждого счёта за выбранный период. - Доходы собираются из всех страниц операций каждого счёта за общие периоды.
Итоги, временные интервалы и принадлежность выплат рассчитывает Итоги, сравнения, временные интервалы и принадлежность выплат рассчитывает
сервис дохода. Данные сравнения периодов не загружаются и не возвращаются; сервис дохода.
состав счетов влияет только на фактические выплаты выбранного периода. Для сравнения с годом ранее используется текущий состав счетов; новый счёт
добавляет только реально полученные выплаты. При известных датах открытия всех счетов
доступность сравнения определяется самым ранним открытием в этом составе.
Закрытые счета в историческое сравнение не добавляются.
- Будущие события сортируются по дате и сохраняют отдельную строку каждого счёта. - Будущие события сортируются по дате и сохраняют отдельную строку каждого счёта.
Прогноз не включается в реализованные доходы. Прогноз не включается в реализованные доходы.
## Полнота, свежесть и доступ ## Полнота, свежесть и доступ
Общий запрос читает membership и разрешённые snapshots из SQLite в одной Для каждого общего запроса backend определяет состав доступных счетов
согласованной границе. Время источников может различаться: это не обещание и единое поколение подключения. Получение данных использует эту же границу подключения;
одновременного наблюдения счетов у брокера. Неполный счёт делает aggregate проверки сессии, доступа и существующий лимитер внешних запросов сохраняются.
неполным; отсутствующие суммы не подменяются нулями. `lastSuccessfulAt` Финансовые данные не сохраняются в БД; снимки и продолжения хранятся
показывает самое раннее время обязательных источников. в памяти процесса.
Секции портфеля, операций, дохода и событий готовы независимо. Ошибка Ошибка источника не превращается в частичный успешный ответ раздела.
обновления сохраняет прежний полный результат того же scope и периода. Портфель может использовать разрешённый существующей политикой запасной снимок
Известный отказ доступа и смена подключения очищают personal view. отдельного счёта. Тогда общий `isStale` равен `true`, а `lastSuccessfulAt`
показывает самое раннее время среди включённых снимков. Для пустого состава
возвращается корректный пустой результат. Общий запрос не является атомарным
наблюдением всех счетов в один момент времени.
Ошибка блока операций, доходов или будущих событий не скрывает основной
портфель. При фоновом обновлении страницы уже полученный результат сохраняется
с индикатором или предупреждением и повторной попыткой. Смена сессии или
подключения сбрасывает общий режим вместе с остальными финансовыми запросами.
## Общая пагинация операций ## Общая пагинация операций
Страница строится из локальных строк с порядком occurredAt, accountId и ID Сервис объединяет потоки страниц каждого счёта и сохраняет непрочитанные строки
операции. Cursor проверяет пользователя, подключение, membershipRevision, между запросами. Фильтрованные пустые страницы не обрывают поток. Повреждённая
фильтры, limit и ревизии операций. Изменение rowset возвращает цепочка курсоров и нарушенный порядок дат возвращают ошибку без частичной страницы.
`400 INVALID_OPERATIONS_CURSOR`; клиент сбрасывает стек страниц, сохраняя
применённые фильтры. Coverage/status без изменения строк не ломают cursor.
Продолжение не зависит от RAM-кэша или перезапуска.
Визуальная группировка строк одного инструмента в общих списках выполняется только Курсор общего журнала — случайный непрозрачный идентификатор продолжения,
в пределах уже полученного списка: в журнале — на загруженной странице, без переноса группы привязанного к пользователю, поколению подключения, составу счетов, фильтрам
на следующую; в полном списке будущих событий и выплатах дохода — по всем полученным строкам и размеру страницы. Верхняя граница периода фиксируется первым запросом.
периода. Группировка не меняет порядок, cursor или ответ API. Протокол курсора прежний. Состояние ограничено 100 продолжениями на процесс со сроком жизни пять минут;
Правила группировки — в [журнале](../operations/overview.md#группировка-в-режиме-все-счета). повторный запрос того же продолжения возвращает тот же успешный результат.
Повторная сверка полного показанного периода публикует все счета и интервалы Неизвестный, вытесненный, истёкший либо несовместимый курсор возвращает
одной транзакцией; успешное первое окно не смешивается с прежними остальными. `400 INVALID_OPERATIONS_CURSOR`. Действие «Повторить» сбрасывает пагинацию,
Подробнее — [журнал](../operations/overview.md#получение-и-полнота). обновляет первую страницу и сохраняет фильтры. Перезапуск backend также
инвалидирует продолжения. Раздел рассчитан на один экземпляр backend, как
существующий финансовый кэш.
## Совместимость и границы требований ## Совместимость и границы требований
Общий режим использует те же persistent sources и envelope, что и отдельный Общий режим добавляет маршруты, сохраняя маршруты отдельных счетов.
счёт. Новый envelope несовместим с прежним bare response: backend и frontend При совместном обновлении сначала нужен backend, затем frontend с
поставляются согласованной парой по [HTTP contract](../../architecture/http-api.md). соответствующим сгенерированным контрактом; прежний клиент продолжает
Additive migration сохраняет прежние таблицы; rollback приложения не удаляет работать с отдельными счетами. При откате сначала возвращается клиент.
новые данные и не заменяет рабочую базу старым backup. Возврат требует заранее Этот механизм не требует изменения схемы SQLite и миграции финансовых данных.
проверенной совместимой пары; произвольный прежний клиент не считается совместимым.
Процедура backup/restore описана в [runbook](../../architecture/deployment.md).
Основные спецификации областей сохраняют исходные понятия отдельного счёта. Основные спецификации [портфеля](../../../specs/openspec/specs/portfolio-view/spec.md),
Утверждённый change `persist-tbank-data` распространяет полноту, локальный [операций](../../../specs/openspec/specs/brokerage-account-ledger/spec.md),
доступ, атомарную сверку и состояния синхронизации на режим `all`. [дохода](../../../specs/openspec/specs/brokerage-account-income-analytics/spec.md)
и [событий](../../../specs/openspec/specs/brokerage-account-upcoming-events/spec.md)
описывают отдельный счёт. Общий режим подтверждён действующими маршрутизаторами,
сервисами и [проверками агрегации](../../../backend/src/modules/investments/all-accounts.spec.ts),
но не выделен в самостоятельную основную спецификацию. Не следует считать
описание общего режима изменением требований к отдельному счёту.
-81
View File
@@ -1,81 +0,0 @@
# Концентрация портфеля
Вкладка «Концентрация» на странице счёта `/accounts/:accountId` показывает,
какие акции, облигации, секторы и эмитенты занимают наибольшую часть текущей
оценки Portfolio. Она отвечает на вопрос «где сконцентрированы деньги»,
который не решают карточки позиций и [структура
активов](overview.md#как-читать-экран). Точный смысл полей и долей — в
[справочнике данных](data-reference.md).
## Вкладка и разрезы
Вкладка есть только у отдельного счёта: в режиме [«Все счета»](all-accounts.md)
её нет. Появление вкладки не меняет вкладку, открытую по умолчанию («Позиции»),
и содержание остальных вкладок. Пока Portfolio загружается, при его ошибке или
штатном пустом составе вкладки нет; при Portfolio хотя бы с одной позицией или
денежным остатком вкладка показывается.
Вкладка содержит четыре разреза и показывает один из них одновременно:
| Разрез | Что ранжирует | Типы позиций |
| --- | --- | --- |
| «Акции» | Пять крупнейших позиций акций | `STOCK` |
| «Облигации» | Пять крупнейших позиций облигаций | `BOND` |
| «Секторы» | Пять крупнейших секторов по данным T-Bank | `STOCK`, `BOND`, `FUND` |
| «Эмитенты» | Пять крупнейших эмитентов (брендов актива) | `STOCK`, `BOND` |
Фонды участвуют только в разрезе «Секторы»: их эмитент — управляющая компания,
а не компания внутри фонда. Позиции типов `CURRENCY`, `FUTURE`, `OPTION`,
`STRUCTURED_NOTE`, `DIGITAL_FINANCIAL_ASSET` и `OTHER` во вкладку не входят.
Позиции без сектора образуют явную группу «Сектор не указан», без бренда —
«Эмитент не указан»; обе группы показываются с видимой долей. Государственные
облигации определяются значением сектора T-Bank `government` с подписью
«Государственный сектор»; других собственных классификаций WealthLedger
не вводит. Название элемента в разрезе эмитентов — название бренда; если у
одного бренда разные названия, показывается название позиции с наибольшей
стоимостью.
## Топ-5 и «Прочее»
В каждом разрезе показаны пять крупнейших элементов по текущей денежной оценке,
каждый с суммой и долей. Рядом показан суммарный показатель «Топ-5 · N %».
Если в разрезе семь и более элементов, все остальные объединены в одну строку
«Прочее» с числом элементов, суммой и долей. При шести и меньшем числе
элементов каждый показан своей строкой. По нажатию на «Прочее» раскрывается
полный перечень остальных элементов, каждый со своей суммой и долей; топ-5
и диаграмма при этом не меняются.
Доли считаются от суммы стоимостей позиций разреза в выбранной валюте и
в сумме дают ровно 100 %; округление — до десятой доли процента, очень малые
доли показываются как «< 0,1 %». Отрицательные и нулевые стоимости, а также
позиции без оценки в разрез не входят. Конвертации валют нет: каждая позиция
входит только в валюту своей оценки.
## Валюта
У вкладки свой список валют — валюты оценок позиций акций, облигаций и фондов,
отсортированные по ISO-коду, — и свой выбор, независимый от «Структуры
портфеля». По умолчанию выбрана первая валюта списка. При одной валюте
переключатель не показывается. Общего итога по нескольким валютам нет.
Выбранные разрез и валюта сохраняются, пока пользователь остаётся на странице
счёта, включая переключение её вкладок, и сбрасываются при новом заходе и при
смене счёта.
## Диаграмма и неполные данные
Рядом с перечнем показана кольцевая диаграмма, согласованная с перечнем;
информация передаётся не только цветом, есть текстовый эквивалент. Если ответ
отмечен как неполный (`completeness: "incomplete"` — часть позиций не имеет
оценки), над разрезом появляется предупреждение «Данные неполные», а под
списком — сноска «Не учтено позиций: N» с числом позиций, не вошедших в разрез.
Пустой разрез (нет позиций с положительной оценкой в выбранной валюте)
показывает текстовое состояние вместо диаграммы.
## Откуда берутся сектор и эмитент
Сектор и эмитент — часть данных Portfolio: их получает backend в часовом
обходе и сохраняет в снимке вместе с остальными полями позиций
([поток загрузки](flows/portfolio-loading.md#обновление-сектора-и-эмитента),
[интеграция T-Bank](../../integrations/tbank.md)). Вкладка считается по уже
загруженному снимку и не обращается к T-Bank.
+5 -9
View File
@@ -32,9 +32,7 @@
| Cash Balance | Суммы из `GetPositions.money`, объединённые по валюте | Заблокированные деньги не прибавляются; торгуемый инструмент `CURRENCY` остаётся позицией | | Cash Balance | Суммы из `GetPositions.money`, объединённые по валюте | Заблокированные деньги не прибавляются; торгуемый инструмент `CURRENCY` остаётся позицией |
| `totalsByCurrency` | Стоимости позиций + доступные деньги той же валюты | Не включает невычислимую стоимость; не обещает полную оценку портфеля | | `totalsByCurrency` | Стоимости позиций + доступные деньги той же валюты | Не включает невычислимую стоимость; не обещает полную оценку портфеля |
| `lastSuccessfulAt` | Время формирования успешного снимка, UTC | Не является временем каждой котировки; запасной снимок сохраняет исходное время | | `lastSuccessfulAt` | Время формирования успешного снимка, UTC | Не является временем каждой котировки; запасной снимок сохраняет исходное время |
| `isStale` | Истёк часовой срок свежести сохранённого portfolio source | Не описывает состояние клиентского кэша или ошибку фонового запроса | | `isStale` | Backend вернул предыдущий полный снимок после временной ошибки | Не описывает состояние клиентского кэша или ошибку фонового запроса |
| `sector` | Сектор инструмента в том виде, как его определяет T-Bank, с обрезанными пробелами | Только для позиций `STOCK`, `BOND` и `FUND`; значение из одних пробел считается отсутствующим. Во всех остальных случаях — `null` |
| `issuer` | Бренд актива инструмента по данным T-Bank: `{ id, name }` | Только для позиций `STOCK`, `BOND` и `FUND`, когда у актива известны uid и название бренда. Во всех остальных случаях — `null`. Не влияет на количество, стоимость и свежесть снимка; используется вкладкой [«Концентрация»](concentration.md) |
Ненулевые денежные остатки одной валюты суммируются. Позиции одинаковых Ненулевые денежные остатки одной валюты суммируются. Позиции одинаковых
инструментов в ответе отдельного счёта не объединяются; визуальная группировка инструментов в ответе отдельного счёта не объединяются; визуальная группировка
@@ -58,10 +56,8 @@ UID определяет конкретный инструмент брокер
ISIN, тикер и `logoName` могут быть `null`. `logoName` — имя файла бренда, ISIN, тикер и `logoName` могут быть `null`. `logoName` — имя файла бренда,
а не URL: frontend строит ссылку на публичный CDN. Ошибка изображения а не URL: frontend строит ссылку на публичный CDN. Ошибка изображения
или метаданных не меняет количество, суммы и свежесть снимка и не скрывает или метаданных не меняет количество, суммы и свежесть снимка и не скрывает
финансовые данные. Metadata обновляется независимо; отсутствие имени не задерживает выдачу финансовые данные. Имя ожидается перед завершением нового снимка;
финансового снимка и оставляет доступный идентификатор. ошибка поиска оставляет доступный идентификатор.
`logoName` не равен эмитенту из вкладки «Концентрация»: он берётся из metadata
инструмента, а эмитент — из [классификации инструмента](flows/portfolio-loading.md#обновление-сектора-и-эмитента).
## Структура и доли ## Структура и доли
@@ -94,8 +90,8 @@ frontend показывает до сотых. Положительное зна
поэтому ограничение долей действует на все валюты. В общем портфеле проверка поэтому ограничение долей действует на все валюты. В общем портфеле проверка
отрицательных денег выполняется по остаткам счетов до взаимозачёта. отрицательных денег выполняется по остаткам счетов до взаимозачёта.
Нормативные основы: [нормализованный состав](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-нормализованный-состав-portfolio) Нормативные основы: [нормализованный состав](../../../specs/openspec/specs/portfolio-view/spec.md#requirement-нормализованный-состав-portfolio)
и [точные значения и валютные агрегаты](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-точные-значения-и-валютные-агрегаты). и [точные значения и валютные агрегаты](../../../specs/openspec/specs/portfolio-view/spec.md#requirement-точные-значения-и-валютные-агрегаты).
Структура и общий режим подтверждены реализацией и её проверками; Структура и общий режим подтверждены реализацией и её проверками;
отдельных требований к ним в основной спецификации портфеля пока нет. отдельных требований к ним в основной спецификации портфеля пока нет.
Поля транспорта следует сверять с [HTTP API](../../architecture/http-api.md), Поля транспорта следует сверять с [HTTP API](../../architecture/http-api.md),
+143 -51
View File
@@ -1,66 +1,158 @@
# Загрузка портфеля # Загрузка портфеля
Экран `/accounts/:accountId` читает последний полный snapshot из SQLite после Поток начинается при открытии `/accounts/:accountId` пользователем
проверки сессии, подключения и локального membership. Если список счетов ещё с действующей сессией. Его результат — полный нормализованный снимок
неизвестен, сервер сообщает о загрузке доступа без выдачи личных данных доступного счёта либо объяснимая ошибка. Браузер обращается к backend;
неизвестного ID. Известный недоступный счёт возвращает `404`. доступ к брокерскому токену и внешние запросы остаются на сервере.
## Основной обмен ## Основной обмен
Обычный GET и retry возвращают read-only локальный `{ data, meta }` envelope: Сначала frontend проверяет свой успешный кэш: в течение 30 секунд повторное
они не ждут и не вызывают T-Bank и не предлагают ручную синхронизацию. Часовой открытие может показать ответ без нового HTTP-запроса. Следующая схема
обход текущего подключения обновляет Portfolio независимо от экрана. Для нового описывает запрос, который действительно дошёл до backend.
полного snapshot ему нужны оба финансовых ответа `GetPortfolio` и
`GetPositions`; суммы и позиции публикуются вместе. Metadata инструмента и
классификация (сектор и эмитент) дополняют представление независимо,
с доступным UID/FIGI вместо имени до их загрузки.
## Обновление сектора и эмитента ```mermaid
sequenceDiagram
participant F as Frontend
participant B as Backend API
participant D as SQLite
participant T as T-Bank
F->>B: Запрос портфеля счёта с Bearer token
B->>B: Проверяет сессию
B->>D: Читает текущее подключение пользователя
D-->>B: Подключение либо отсутствие
alt Подключения нет
B-->>F: 409 TBANK_NOT_CONNECTED
else Подключение доступно
B->>B: Определяет поколение подключения
alt Снимок того же поколения не старше 30 секунд
B-->>F: Полный снимок, isStale=false
else Нужен новый снимок
B->>T: Получает открытые счета
T-->>B: Состав доступных счетов
alt Счёт недоступен
B-->>F: 404
else Счёт доступен
par Финансовые позиции
B->>T: GetPortfolio
T-->>B: Позиции и цены
and Доступные деньги
B->>T: GetPositions
T-->>B: Денежные остатки
end
B->>T: Метаданные отсутствующих инструментов по UID, затем FIGI
T-->>B: Метаданные, отсутствие или ошибка
B->>B: Нормализует состав и точно вычисляет суммы
B-->>F: Полный новый снимок, isStale=false
end
end
end
```
Часовой обход также обновляет сектор и эмитента позиций типов `share`, `bond` Свежий серверный снимок выдаётся после разрешения текущего сохранённого
и `etf`. Значения хранятся как последнее известное ([ADR подключения, но без нового `GetAccounts`. После истечения свежего кэша
022](../../../adr/022-stored-instrument-classification.md)): сектор — как его проверка доступности счёта предшествует финансовым RPC. SQLite хранит
определяет T-Bank, эмитент — бренд актива (`uid` и `name`). Каждое поле подключение, а не снимок или право владения счётом вне этого подключения.
обновляется при первом обходе, начавшемся не ранее чем через 24 часа после его
последнего успешного получения; пока значение не получено, попытка повторяется
в каждом обходе. Сектор и эмитент обновляются независимо.
Неуспешное получение не мешает публикации snapshot и не меняет Для нового снимка требуются оба финансовых ответа. Backend не выдаёт
`lastSuccessfulAt` и `isStale`; сохранённое значение остаётся доступным. позиции без денег или деньги без позиций как полный портфель.
Исключение — отказ token: он обрабатывается так же, как отказ token при Метаданные дополняют имя и идентификаторы, но их временная недоступность
обходе Portfolio. Инструмент ищется по UID, затем по FIGI; ответ «не найдено» не срывает финансовый результат. Поиск выполняется партиями по пять позиций,
сохраняет прежнее значение и отсчёт 24 часов начинается заново. Позиции без чтобы крупный портфель не переполнял очередь одновременными запросами.
полученных значений участвуют в разрезах вкладки Порядок позиций сохраняется. Backend ищет сведения по UID, затем по FIGI,
[«Концентрация»](../concentration.md) как «Сектор не указан» и если UID не дал пригодного названия или тикера.
«Эмитент не указан». Ожидание метаданных ограничено 30 секундами на снимок счёта. После этого
используется доступный кэш либо идентификатор, новые поиски для снимка
не запускаются. Уже начатые запросы могут закончиться позже и обновить общий
справочный кэш, но не меняют выданный финансовый снимок.
Общий серверный клиент обеспечивает TLS, очередь,
ограничение частоты, deadline RPC и разрешённые повторные попытки —
[граница интеграции](../../../integrations/tbank.md).
Открытый экран выполняет локальное перечитывание не чаще одного раза в ## Время и изменение подключения
30 минут; retry также повторяет только HTTP GET. `data: null` означает, что
полного snapshot ещё нет, а не нулевой портфель. Пустой подтверждённый portfolio
остаётся available complete result. Секции могут быть готовы независимо.
Подробнее — [свежесть](../../../architecture/cross-cutting/data-freshness.md).
## Границы доступа | Слой | Что сохраняется | Условие повторного использования |
| --- | --- | --- |
| Frontend | Последний успешный HTTP-ответ для выбранного счёта и сессии | Свежим считается 30 секунд; при обычном обновлении уже показанный ответ остаётся видимым |
| Backend | Полный снимок пары пользователь–счёт в памяти процесса | До 30 секунд, только в том же поколении текущего подключения |
| Backend при временной ошибке | Тот же полный снимок | Не старше пяти минут с момента успешного сохранения, только в том же поколении |
| Метаданные инструмента | Успешный поиск или подтверждённое отсутствие | Свежими считаются 24 часа; общий кэш на 500 ключей UID/FIGI, ошибки не сохраняются |
Публикация проверяет connection revision и attempt в той же транзакции. Замена Это параметры текущей реализации, а не самостоятельная политика этого
или удаление подключения не допускает позднюю запись прежней работы. Frontend документа: [снимки](../../../../backend/src/modules/investments/portfolio/application/portfolio.service.ts),
очищает недоступный personal view и отклоняет запоздалые ответы. В режиме [метаданные](../../../../backend/src/integrations/tbank/instrument-metadata.service.ts) и
[«Все счета»](../all-accounts.md#полнота-свежесть-и-доступ) состав accounts и [клиентский запрос](../../../../frontend/src/entities/portfolio/api/usePortfolio.ts).
snapshots читается согласованно, но их source times могут различаться.
Пять минут — предельный возраст успешного снимка, а не дополнительные
пять минут после окончания свежих 30 секунд. Запасной ответ сохраняет
исходное `lastSuccessfulAt` и получает `isStale: true`.
После истечения 24 часов backend обновляет справочные данные. Если обновление
временно недоступно, ранее успешные сведения остаются запасным вариантом,
пока запись есть в ограниченном кэше процесса. Ошибка не продлевает срок
свежести: следующий поиск снова пытается обновить запись. Это относится только
к названию, тикеру, ISIN и логотипу, а не к ценам или денежным значениям.
Перезапуск backend и вытеснение записи из кэша удаляют этот запасной вариант.
Неполный ответ без названия и тикера также не затирает известные сведения
и не обновляет срок их свежести. Подтверждённое отсутствие инструмента
заменяет прежнюю запись отрицательным результатом поиска.
Время снимка фиксируется при получении финансовых ответов, до ожидания
названий. Сроки серверного кэша отсчитываются от этого времени, поэтому
долгая загрузка справочных данных не делает старые суммы новыми.
Успешные справочные данные общие для всех пользователей; выполняющиеся
запросы объединяются только внутри одного брокерского токена, чтобы ошибка
его авторизации не передавалась другому подключению.
Успешное создание, замена или удаление подключения меняет границу
повторного использования финансовых данных. Старое или поздно завершившееся
получение снимка не делает его доступным новым запросам другого поколения.
Явная очистка памяти при изменении подключения дополняет эту проверку.
Frontend сбрасывает всё поддерево финансовых запросов при изменении
подключения и общий кэш сессии при выходе; старый портфель не служит
заглушкой загрузки нового подключения.
В общем режиме сначала определяется состав доступных счетов одного
подключения, затем получаются их снимки. Это
[не атомарное наблюдение нескольких счетов](../all-accounts.md#полнота-свежесть-и-доступ).
## Ошибки и результат для пользователя ## Ошибки и результат для пользователя
Нет подключения — `409 TBANK_NOT_CONNECTED`, известный отказ токена — | Причина | Ответ backend | Действие на экране |
`400 INVALID_TBANK_TOKEN`, недоступный счёт — `404`. Они не маскируются | --- | --- | --- |
запасным snapshot. Временная ошибка часового обхода оставляет прежний полный | Нет подключения | `409 TBANK_NOT_CONNECTED` | Открыть настройки T-Bank |
portfolio и его исходное `lastSuccessfulAt`; `meta.lastAttempt` сообщает о | Брокер отклонил токен | `400 INVALID_TBANK_TOKEN` | Открыть настройки; токен автоматически не удаляется |
failed или partial попытке отдельно. `Portfolio.isStale` сообщает возраст | Счёт недоступен через текущее подключение | `404` | Вернуться к списку счетов |
snapshot, а Client Cached Snapshot и ошибка браузерного HTTP-перечитывания | Временная ошибка, пригодный предыдущий снимок есть | `200`, `isStale: true` | Показать снимок и его исходное время с предупреждением |
имеют независимый смысл. При отсутствии полного snapshot `data: null` и | Временная ошибка, пригодного снимка нет | `503 TBANK_UNAVAILABLE` | Повторить загрузку |
metadata обозначает initial loading либо временную недоступность, без нулевых | Неизвестная ошибка | Безопасная ошибка API | Повторить загрузку |
итогов. `503` означает ошибку обслуживания самого HTTP-запроса, а не
завершившуюся фоновую работу.
Денежные правила находятся в [справочнике](../data-reference.md), проверка `INVALID_TBANK_TOKEN` и `404` не заменяются серверным запасным снимком.
расхождения — в [диагностике](../how-to/investigate-discrepancy.md), RPC/лимиты Временная ошибка определения доступных счетов также может использовать
— в [интеграции T-Bank](../../../integrations/tbank.md). пригодный снимок того же поколения. При временном сбое метаданных сначала
используются ранее успешные сведения, а при их отсутствии — идентификатор
инструмента; это не устанавливает `isStale`. Отказ авторизации при поиске
метаданных не маскируется запасным названием.
Для запроса самого портфеля на клиенте нет polling, автоматического повтора или
обновления при возврате фокуса. При обновлении того же уже показанного
результата клиент оставляет его на экране, добавляя индикатор загрузки
или предупреждение с доступной с клавиатуры повторной попыткой.
Это клиентский сохранённый ответ, независимо от серверного `isStale`.
Frontend отображает название и остальные сведения из ответа портфеля.
Повторные попытки поиска для акций, облигаций и других типов инструментов
принадлежат [общему клиенту T-Bank на backend](../../../integrations/tbank.md#конфигурация-и-ограничения).
Первое обращение к крупному портфелю может потребовать много метаданных.
Переполнение общей очереди RPC может произойти при конкурирующих запросах;
поиск метаданных в этом случае повторяется с ограниченными паузами. После
успешного поиска метаданные используются повторно до истечения
кэша. Значение `TBANK_RATE_LIMIT_PER_SECOND` следует соотносить с квотой
источника; его увеличение не является гарантированным исправлением
ограничения самого T-Bank.
Проверяемые правила: [свежесть и запасной снимок](../../../../specs/openspec/specs/portfolio-view/spec.md#requirement-свежесть-и-fallback-полного-portfolio-snapshot),
[клиентские состояния](../../../../specs/openspec/specs/portfolio-view/spec.md#requirement-состояния-и-ошибки-portfolio-на-клиенте).
Для расследования конкретного значения используйте
[порядок диагностики](../how-to/investigate-discrepancy.md).
@@ -12,13 +12,12 @@
величины. Убедитесь, что сравниваемые значения относятся к одному составу величины. Убедитесь, что сравниваемые значения относятся к одному составу
счетов и инструменту. Название или тикер сами по себе не доказывают счетов и инструменту. Название или тикер сами по себе не доказывают
совпадение выпуска; используйте доступные UID, FIGI и ISIN. совпадение выпуска; используйте доступные UID, FIGI и ISIN.
2. **Сопоставьте время.** Проверьте `lastSuccessfulAt`, `isStale` и показанный 2. **Сопоставьте время.** Проверьте `lastSuccessfulAt`, `isStale` и наличие
исход последней часовой попытки. Серверный снимок не обещает время предупреждения о клиентском обновлении. Серверный снимок не обещает время
каждой котировки, а общий портфель может соединять разные моменты. каждой котировки, а общий портфель может соединять разные моменты.
Действия «Повторить» на экране нет: временный сбой чтения восстанавливается Если запрос завершился ошибкой, доступное действие «Повторить» должно
автоматически, поэтому расхождение проверяйте по показанному состоянию обновить результат или оставить объяснимую ошибку. Перезагрузка backend
источника, а не по результату ручного обновления. Перезагрузка backend удаляет кэш и не восстанавливает историю стоимости.
сохраняет последний полный снимок в SQLite, но не создаёт историю стоимости.
3. **Проверьте состав суммы.** Для одной валюты итог складывается из 3. **Проверьте состав суммы.** Для одной валюты итог складывается из
вычислимых стоимостей позиций и доступных денег. Не добавляйте деньги вычислимых стоимостей позиций и доступных денег. Не добавляйте деньги
второй раз, не складывайте валюты и не включайте заблокированные средства. второй раз, не складывайте валюты и не включайте заблокированные средства.
@@ -38,16 +37,16 @@
теряет цифры, валюту или показатель, исследуйте frontend. теряет цифры, валюту или показатель, исследуйте frontend.
Если исходные безопасные данные и нормализованный ответ расходятся, Если исходные безопасные данные и нормализованный ответ расходятся,
исследуйте backend. Если не совпадают ожидаемое и описанное поведение, исследуйте backend. Если не совпадают ожидаемое и описанное поведение,
сначала сопоставьте [спецификацию портфеля](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md) сначала сопоставьте [спецификацию портфеля](../../../../specs/openspec/specs/portfolio-view/spec.md)
и ограничения [потока загрузки](../flows/portfolio-loading.md). и ограничения [потока загрузки](../flows/portfolio-loading.md).
Исправление документации само по себе не разрешает конфликт требований Исправление документации само по себе не разрешает конфликт требований
и реализации. и реализации.
Для адресной проверки исходников владельцами служат Для адресной проверки исходников владельцами служат
[сервис снимков](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/portfolio/application/portfolio.service.ts), [сервис снимков](../../../../backend/src/modules/investments/portfolio/application/portfolio.service.ts),
[валютные итоги](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/portfolio/infrastructure/tbank-portfolio.mapper.ts), [валютные итоги](../../../../backend/src/modules/investments/portfolio/infrastructure/tbank-portfolio.mapper.ts),
[структура](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/portfolio/domain/portfolio-structure.ts) и [структура](../../../../backend/src/modules/investments/portfolio/domain/portfolio-structure.ts) и
[подготовка экрана](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/src/pages/account/ui/AccountPage.vm.ts). [подготовка экрана](../../../../frontend/src/pages/account/ui/AccountPage.vm.ts).
Это точки продолжения расследования; смысл вычислений изложен выше. Это точки продолжения расследования; смысл вычислений изложен выше.
Разбор завершён, когда различие объяснено составом, временем или определением Разбор завершён, когда различие объяснено составом, временем или определением
+16 -25
View File
@@ -17,11 +17,11 @@
[справочнике данных](data-reference.md). [справочнике данных](data-reference.md).
Доступность счёта определяется [текущим подключением и списком счетов](../accounts/overview.md). Доступность счёта определяется [текущим подключением и списком счетов](../accounts/overview.md).
Backend получает данные через серверную SQLite не хранит сам портфель: backend получает данные через серверную
[интеграцию T-Bank](../../integrations/tbank.md) и постоянно сохраняет последний [интеграцию T-Bank](../../integrations/tbank.md) и временно сохраняет успешный
полный снимок в SQLite. Frontend получает нормализованный результат через HTTP API. снимок в памяти. Frontend получает нормализованный результат через HTTP API.
[Поток загрузки](flows/portfolio-loading.md) объясняет границы доступа, [Поток загрузки](flows/portfolio-loading.md) объясняет границы доступа,
свежесть снимка и восстановление после временной ошибки. свежий кэш и восстановление после временной ошибки.
Обзор счёта также показывает пять последних [операций](../operations/overview.md), Обзор счёта также показывает пять последних [операций](../operations/overview.md),
[полученный доход](../income/overview.md) текущего месяца и до пяти [полученный доход](../income/overview.md) текущего месяца и до пяти
@@ -64,11 +64,7 @@ Backend получает данные через серверную
без логотипа и с подписью результата. Отсутствующие показатели скрываются, без логотипа и с подписью результата. Отсутствующие показатели скрываются,
а не подменяются нулём или заглушкой. а не подменяются нулём или заглушкой.
Рядом с вкладкой «Позиции» на странице отдельного счёта есть вкладка Существует расхождение: [согласованная спецификация представления позиций](../../../specs/openspec/specs/portfolio-view/spec.md#requirement-представление-карточек-позиций-на-клиенте)
[«Концентрация»](concentration.md): она показывает, где сконцентрированы
деньги — крупнейшие бумаги, секторы и эмитенты. В режиме «Все счета» её нет.
Существует расхождение: [согласованная спецификация представления позиций](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-представление-карточек-позиций-на-клиенте)
требует доступности всех иных типов в отдельной секции, а текущие frontend требует доступности всех иных типов в отдельной секции, а текущие frontend
и его проверка исключают `CURRENCY` только из списка карточек. Такие позиции и его проверка исключают `CURRENCY` только из списка карточек. Такие позиции
остаются в ответе, валютных итогах, структуре и знаменателе долей остальных остаются в ответе, валютных итогах, структуре и знаменателе долей остальных
@@ -83,26 +79,21 @@ Backend получает данные через серверную
не содержат данных и не получают фокус. При обычном фоновом обновлении не содержат данных и не получают фокус. При обычном фоновом обновлении
сохраняются предыдущие суммы и время успешного получения в UTC. сохраняются предыдущие суммы и время успешного получения в UTC.
Серверный `isStale` обозначает возраст снимка старше часа: обновлением Серверный `isStale` обозначает разрешённый запасной снимок backend.
владеет часовой обход, а не запрос экрана. Он показывается рядом с суммами Клиентское предупреждение означает неудачную попытку обновить уже показанный
вместе с временем последнего успешного результата и исходом последней часовой ответ. Эти состояния независимы и могут появиться одновременно.
попытки. Неудачная попытка клиента обновить уже показанный ответ о себе не Успешный повторный запрос обновляет суммы и время, снимает клиентскую ошибку;
сообщает: эти состояния независимы, но пользователю видимо только состояние серверное предупреждение определяется новым `isStale`.
источника. Успешное автоматическое перечитывание обновляет суммы и время;
обозначение устаревания определяется новым `isStale`.
Правила времени и ошибок приведены в [потоке загрузки](flows/portfolio-loading.md). Правила времени и ошибок приведены в [потоке загрузки](flows/portfolio-loading.md).
Отдельного чтения истории стоимости нет, но каждый полный снимок В текущем решении нет долговременной истории снимков, графиков стоимости
сохраняет точку оценки по счёту и валюте для будущих поддерживаемых портфеля, валютной конвертации, учёта виртуальных позиций или заблокированных
представлений; публичного графика в текущем решении нет. Нет также средств. Кэш теряется при перезапуске и рассчитан на локальную работу одного
валютной конвертации, учёта виртуальных позиций или заблокированных экземпляра backend. Отдельной команды принудительного обновления на экране
средств. Последний полный снимок сохраняется после перезапуска; нет; повторная попытка доступна после ошибки.
обновлением личных данных владеет часовой обход, рассчитанный на один
экземпляр backend. Действие «Обновить» выполняет только локальное
перечитывание открытого экрана и не запускает работу с T-Bank.
Для проверки конкретной суммы используйте Для проверки конкретной суммы используйте
[руководство по расхождениям](how-to/investigate-discrepancy.md). [руководство по расхождениям](how-to/investigate-discrepancy.md).
Проверяемое поведение отдельного счёта описано в Проверяемое поведение отдельного счёта описано в
[спецификации портфеля](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md); [спецификации портфеля](../../../specs/openspec/specs/portfolio-view/spec.md);
точную форму HTTP-ответа задаёт [OpenAPI backend](../../architecture/http-api.md). точную форму HTTP-ответа задаёт [OpenAPI backend](../../architecture/http-api.md).
+2 -82
View File
@@ -4,14 +4,6 @@
Связи между ними объясняет [обзор продукта](overview.md), подробные правила — Связи между ними объясняет [обзор продукта](overview.md), подробные правила —
[предметные области](README.md#предметные-области). [предметные области](README.md#предметные-области).
## Релиз развёртывания WealthLedger
Версия комплекта установки WealthLedger, закрепляющая проверенную совместимую
пару релизов frontend и backend. Понятие принято в
[ADR 016](adr/016-versioned-deployment-bundle.md).
_Не использовать_: релиз frontend или backend как синоним релиза всей установки.
## User ## User
Локальный владелец данных WealthLedger, идентифицируемый `id` и Локальный владелец данных WealthLedger, идентифицируемый `id` и
@@ -119,46 +111,6 @@ Instrument. Она остаётся в `positions` и не объединяет
_Не использовать_: денежный остаток, cash balance. _Не использовать_: денежный остаток, cash balance.
## Концентрация
Вкладка страницы одного Brokerage Account, показывающая, какие акции,
облигации, секторы и эмитенты занимают наибольшую часть текущей оценки
Portfolio, без валютной конвертации. Отличается от [структуры
портфеля](domains/portfolio/data-reference.md#структура-и-доли): в её разрезах
деньги не входят в знаменатель долей.
_Не использовать_: структура портфеля, диверсификация.
## Разрез
Одна из четырёх группировок вкладки «Концентрация»: «Акции», «Облигации»,
«Секторы», «Эмитенты». Элемент разреза — единица ранжирования; сектор
агрегирует позиции `STOCK`, `BOND` и `FUND`, эмитенты — `STOCK` и `BOND`.
_Не использовать_: фильтр, категория структуры.
## Сектор
Значение сектора инструмента в том виде, как его определяет T-Bank, без
перекодирования и собственной классификации WealthLedger. Заполняется для
позиций `STOCK`, `BOND` и `FUND`; государственные облигации определяются
значением `government` с подписью «Государственный сектор». Позиция без
сектора попадает в явную группу «Сектор не указан».
_Не использовать_: отрасль, собственная классификация приложения.
## Эмитент
В разрезе «Эмитенты» вкладки «Концентрация» — бренд актива по данным T-Bank,
объединяющий акции и облигации одной компании; фонды в разрез не входят.
Название эмитента — название бренда. **Оговорка:** в этом разрезе слово
означает бренд актива T-Bank, а не юридического эмитента ценной бумаги;
[рейтинг выпуска](integrations/nkr.md) не подставляется вместо рейтинга
эмитента и наоборот. Позиция без бренда попадает в явную группу
«Эмитент не указан».
_Не использовать_: юридический эмитент, эмитент рейтинга.
## Instrument ## Instrument
Ценная бумага или иной торгуемый актив T-Bank, на который ссылается Portfolio Ценная бумага или иной торгуемый актив T-Bank, на который ссылается Portfolio
@@ -168,14 +120,6 @@ Instrument обогащают отображение Position или Event, но
_Не использовать_: бумага, тикер, актив позиции. _Не использовать_: бумага, тикер, актив позиции.
## Скринер акций
Подбор выпусков акций по заданным условиям из каталога T-Bank с указанием
времени получения списка. Скринер отличается от текстового поиска конкретного
инструмента, личного портфеля и подтверждения исполнимости сделки.
_Не использовать_: гарантия покупки, личная проверка доступа к сделке.
## Instrument Logo ## Instrument Logo
Имя файла бренда T-Bank для отображения Instrument в интерфейсе; Имя файла бренда T-Bank для отображения Instrument в интерфейсе;
@@ -200,15 +144,6 @@ _Не использовать_: валютная позиция, заблоки
_Не использовать_: история портфеля, snapshot для графика. _Не использовать_: история портфеля, snapshot для графика.
## Точка истории стоимости портфеля
Полная оценка одного Brokerage Account в одной валюте на момент успешного
обновления Portfolio; последовательность таких точек описывает изменение
оценки во времени, включая неторговые дни. Не является Portfolio Snapshot
или инвестиционной доходностью; отсутствие цены позиции не приравнивается к нулю.
_Не использовать_: снимок состава портфеля, доходность.
## Client Cached Snapshot ## Client Cached Snapshot
Технический термин: последний успешно полученный frontend-ответ React Query, который остаётся Технический термин: последний успешно полученный frontend-ответ React Query, который остаётся
@@ -234,19 +169,6 @@ _Не использовать_: история Portfolio, баланс счёт
_Не использовать_: проводка, рассчитанная операция. _Не использовать_: проводка, рассчитанная операция.
## Полнота истории счёта
Подтверждённость того, что за указанный период загружены все доступные
исполненные операции счёта на момент успешной сверки с брокером. Полный период
может не содержать операций; полнота не гарантирует отсутствия последующих
исправлений брокера и отличается от свежести данных.
Понятие принято в [ADR 017](adr/017-local-tbank-reads-and-background-sync.md).
Текущее хранение coverage и независимая свежесть объяснены в
[модели данных](architecture/cross-cutting/data-freshness.md).
_Не использовать_: свежесть истории, отсутствие операций как признак полноты.
## Operation Category ## Operation Category
Нормализованная категория Ledger Entry для фильтрации и отображения: Нормализованная категория Ledger Entry для фильтрации и отображения:
@@ -256,15 +178,13 @@ _Не использовать_: upstream operation type как category.
## Ledger Page Cache ## Ledger Page Cache
Исторический технический термин: RAM-кэш страницы Account Ledger. Технический термин: кэш страницы Account Ledger, отделённый от кэша отдельных операций.
Текущее чтение использует сохранённые операции и coverage в SQLite;
страница не является единицей полноты.
_Не использовать_: operations cache, кэш отдельных операций. _Не использовать_: operations cache, кэш отдельных операций.
## Ledger Page Cache Key ## Ledger Page Cache Key
Исторический ключ RAM-кэша, разделявший страницы по пользователю, подключению, Ключ, разделяющий страницы журнала по пользователю, текущему подключению,
счёту и параметрам запроса, включая необязательный UID инструмента. счёту и параметрам запроса, включая необязательный UID инструмента.
Полный состав и правила нормализации описаны в Полный состав и правила нормализации описаны в
[журнале операций](domains/operations/overview.md#получение-и-полнота). [журнале операций](domains/operations/overview.md#получение-и-полнота).
+2 -2
View File
@@ -48,6 +48,6 @@ HTTP-запрос ограничен таймаутом. Сеть, неуспе
Наличие успешного HTTP-ответа не гарантирует котировку без задержки. Наличие успешного HTTP-ответа не гарантирует котировку без задержки.
Условия обработки источника в прогнозе закреплены в Условия обработки источника в прогнозе закреплены в
[спецификации будущих событий](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-upcoming-events/spec.md#requirement-upcoming-events-use-complete-source-results). [спецификации будущих событий](../../specs/openspec/specs/brokerage-account-upcoming-events/spec.md#requirement-upcoming-events-use-complete-source-results).
Точный механизм транспорта, разбора и сопоставления реализован в Точный механизм транспорта, разбора и сопоставления реализован в
[клиенте MOEX](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/integrations/moex/moex.client.ts). [клиенте MOEX](../../backend/src/integrations/moex/moex.client.ts).
+1 -1
View File
@@ -36,7 +36,7 @@ ISIN, даты и ссылки на выпуск. Изменение табли
После ошибки новая загрузка ограничена минутным интервалом; внутри одного После ошибки новая загрузка ограничена минутным интервалом; внутри одного
HTTP-запроса повторных попыток нет. Обновление запускается следующим обращением HTTP-запроса повторных попыток нет. Обновление запускается следующим обращением
после истечения кэша, без фонового задания. Точный механизм и сроки принадлежат после истечения кэша, без фонового задания. Точный механизм и сроки принадлежат
[клиенту НКР](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/integrations/ncr/ncr-ratings.client.ts). [клиенту НКР](../../backend/src/integrations/ncr/ncr-ratings.client.ts).
При отказе реестра показатели облигации отмечают недоступность рейтинга, При отказе реестра показатели облигации отмечают недоступность рейтинга,
сохраняя остальные доступные показатели. Готовый ответ метрик имеет сохраняя остальные доступные показатели. Готовый ответ метрик имеет
+20 -42
View File
@@ -14,27 +14,12 @@ WealthLedger обращается к Invest API на сервере по gRPC ov
| Портфель и денежные остатки | Показать текущий состав инвестиций и итоги по исходным валютам | | Портфель и денежные остатки | Показать текущий состав инвестиций и итоги по исходным валютам |
| Исполненные операции | Построить журнал и выделить полученные купоны и дивиденды | | Исполненные операции | Построить журнал и выделить полученные купоны и дивиденды |
| Инструменты и рынок | Имена, идентификаторы, цены, свечи, параметры акций и облигаций | | Инструменты и рынок | Имена, идентификаторы, цены, свечи, параметры акций и облигаций |
| Классификация инструментов | Сектор акций, облигаций и фондов и бренд актива для разрезов [концентрации](../domains/portfolio/concentration.md) |
| Купоны и дивиденды | Построить будущие события по текущим позициям и календарь инструмента | | Купоны и дивиденды | Построить будущие события по текущим позициям и календарь инструмента |
Сектор получается из описаний инструмента (отдельные описания для акций,
облигаций и фондов, поиск по UID, затем по FIGI); эмитент — из описания актива
по `asset_uid` инструмента: его uid и название бренда. Эмитент не выводится из
названия, тикера или ISIN. Классификация хранится как последнее известное
значение с обновлением не чаще раза в сутки ([ADR
022](../adr/022-stored-instrument-classification.md)); все вызовы проходят
через общий лимитер запросов T-Bank.
T-Bank определяет исходные данные и права token. Backend проверяет локальную T-Bank определяет исходные данные и права token. Backend проверяет локальную
сессию, выбирает подключение её пользователя и устанавливает доступ к счёту. сессию, выбирает подключение её пользователя и устанавливает доступ к счёту.
Наличие идентификатора счёта в URL этого доступа не подтверждает. Наличие идентификатора счёта в URL этого доступа не подтверждает.
Скринер акций получает полный online-каталог акций T-Bank для каждого нового
поиска. В него входят только инструменты, для которых каталог подтверждает и
доступность покупки, и торговли через API. Эти признаки описывают полученный
снимок каталога: они не подтверждают личные права пользователя, состояние торгов
или исполнение будущей заявки. Скринер не использует личный портфель как каталог.
[MOEX](moex.md) и [НКР](nkr.md) дополняют публичные сведения об облигациях; они не заменяют [MOEX](moex.md) и [НКР](nkr.md) дополняют публичные сведения об облигациях; они не заменяют
брокера как источник личных позиций и исполненных выплат. Границы источников брокера как источник личных позиций и исполненных выплат. Границы источников
показаны в [контексте](../architecture/context.md), смысл дополнений — в показаны в [контексте](../architecture/context.md), смысл дополнений — в
@@ -50,10 +35,8 @@ Backend передаёт имя файла `logoName` из метаданных
Логотипы используются в портфеле, операциях, будущих событиях, поиске и Логотипы используются в портфеле, операциях, будущих событиях, поиске и
страницах инструментов. Отсутствующее имя или ошибка изображения оставляют страницах инструментов. Отсутствующее имя или ошибка изображения оставляют
буквенную заглушку и не меняют финансовые значения. Логотип — это metadata буквенную заглушку и не меняют финансовые значения. Правило построения URL
инструмента; он не равен эмитенту из [концентрации](../domains/portfolio/concentration.md), принадлежит [форматированию frontend](../../frontend/src/shared/lib/display.ts).
который берётся из бренда актива. Правило построения URL
принадлежит [форматированию frontend](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/src/shared/lib/display.ts).
## Проверка и сохранение подключения ## Проверка и сохранение подключения
@@ -79,7 +62,7 @@ sequenceDiagram
B->>T: GetAccounts OPEN по gRPC over TLS B->>T: GetAccounts OPEN по gRPC over TLS
alt Проверка успешна, в том числе при пустом списке счетов alt Проверка успешна, в том числе при пустом списке счетов
T-->>B: Доступные открытые счета T-->>B: Доступные открытые счета
B->>D: Атомарно сохранить token, новую revision и membership B->>D: Создать или заменить шифротекст AES-256-GCM
B-->>F: Подключено и время сохранения B-->>F: Подключено и время сохранения
F->>F: Обновить статус и сбросить финансовые выборки F->>F: Обновить статус и сбросить финансовые выборки
F-->>U: Показать подтверждённое подключение F-->>U: Показать подтверждённое подключение
@@ -108,18 +91,12 @@ sequenceDiagram
`INVALID_TBANK_TOKEN` означает отказ внешней аутентификации или прав доступа. `INVALID_TBANK_TOKEN` означает отказ внешней аутентификации или прав доступа.
`TBANK_UNAVAILABLE` сообщает о временной или иной upstream-ошибке, включая `TBANK_UNAVAILABLE` сообщает о временной или иной upstream-ошибке, включая
исчерпание очереди. Диагностические причины жизненного цикла часовой попытки исчерпание очереди. Ответы и ошибки не содержат token. Удаление подключения
(`INTERRUPTED` — прерывание до нормального завершения, `DEADLINE` — истечение идемпотентно: удаляется локальный секрет и прекращается его использование
ограничения ожидания шага) не относятся к ошибкам T-Bank: они остаются для последующих запросов. Оно не описывает отзыв token на стороне T-Bank.
внутренней диагностикой и не раскрываются в hourly-read. Истечение ограничения
ожидания не отменяет уже начатые обращения к T-Bank; их поздние результаты
остаются в строках шагов. Ответы и ошибки не содержат token. Удаление подключения
идемпотентно: удаляются локальный секрет, личные datasets и work; поздняя
публикация прежней revision запрещена. Общие неперсональные источники могут
сохраняться. Оно не описывает отзыв token на стороне T-Bank.
Согласованное поведение сохранено в workspace-спецификации Согласованное поведение сохранено в workspace-спецификации
[T-Bank connection](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/tbank-connection/spec.md). [T-Bank connection](../../specs/openspec/specs/tbank-connection/spec.md).
Точные endpoints и формы ответов определяет backend OpenAPI; Точные endpoints и формы ответов определяет backend OpenAPI;
[HTTP API](../architecture/http-api.md) описывает общую границу. [HTTP API](../architecture/http-api.md) описывает общую границу.
@@ -129,7 +106,7 @@ sequenceDiagram
использования финансовых данных. Это относится и к сохранению того же самого использования финансовых данных. Это относится и к сохранению того же самого
token: предыдущее подключение не становится источником данных нового. token: предыдущее подключение не становится источником данных нового.
Неуспешная операция сохраняет прежнее подключение и его доступные данные. Неуспешная операция сохраняет прежнее подключение и его доступные данные.
Механизм внутренней revision, поздние ответы и сброс клиентских выборок объясняет Механизм generation, поздние ответы и сброс клиентских выборок объясняет
[свежесть данных](../architecture/cross-cutting/data-freshness.md#смена-подключения). [свежесть данных](../architecture/cross-cutting/data-freshness.md#смена-подключения).
## Конфигурация и ограничения ## Конфигурация и ограничения
@@ -149,29 +126,30 @@ Backend хранит token в шифротексте AES-256-GCM с новым n
| `TBANK_CA_CERT_PATH` | Необязательный путь к доверенному корневому CA в PEM для сетей с TLS-инспекцией; проверка TLS сохраняется | | `TBANK_CA_CERT_PATH` | Необязательный путь к доверенному корневому CA в PEM для сетей с TLS-инспекцией; проверка TLS сохраняется |
Параметры окружения определяет backend Параметры окружения определяет backend
[validateEnv](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/config/env.validation.ts) (справочный источник). [validateEnv](../../backend/src/config/env.validation.ts) (workspace-ссылка).
Общий FIFO-лимитер ограничивает поток RPC и не допускает более 50 Общий FIFO-лимитер ограничивает поток RPC и не допускает более 50
ожидающих. Для `UNAVAILABLE`, `DEADLINE_EXCEEDED` и `RESOURCE_EXHAUSTED` ожидающих. Для `UNAVAILABLE`, `DEADLINE_EXCEEDED` и `RESOURCE_EXHAUSTED`
каждый RPC допускает один повтор через секунду. Deadline каждой попытки — 15 секунд, обычный RPC допускает один повтор через секунду. Deadline каждой попытки — 15 секунд,
а ожидание места в лимитере не является временем этой сетевой попытки. а ожидание места в лимитере не является временем этой сетевой попытки.
Метаданные используют ту же retry policy. Переполненная очередь завершается Для поиска метаданных `GetInstrumentBy` разрешены две повторные попытки
ошибкой сразу, без повторов admission. Постоянные ошибки и отказ авторизации с паузами 2 и 4 секунды после неудачных попыток. Они применяются к тем же
временным кодам и к переполнению локальной очереди; перед каждой попыткой
проверяется её вместимость. Постоянные ошибки и отказ авторизации
не повторяются. Подтверждённый `NOT_FOUND` при поиске метаданных означает не повторяются. Подтверждённый `NOT_FOUND` при поиске метаданных означает
отсутствие инструмента, а не временный сбой. отсутствие инструмента, а не временный сбой. Остальные RPC при полной
очереди завершаются сразу.
Параметры попыток принадлежат Параметры попыток принадлежат
[T-Bank client](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/integrations/tbank/tbank.client.ts) (справочный источник); [T-Bank client](../../backend/src/integrations/tbank/tbank.client.ts) (workspace-ссылка);
решение об ограниченных повторах — [ADR 005](../adr/005-tbank-upstream-resilience.md). решение об ограниченных повторах — [ADR 005](../adr/005-tbank-upstream-resilience.md).
Большое число ранее неизвестных инструментов требует отдельных запросов Большое число ранее неизвестных инструментов требует отдельных запросов
metadata. При перегруженной очереди необязательные имена могут быть заменены metadata. При перегруженной очереди необязательные имена могут быть заменены
идентификаторами; это не доказывает потерю самой позиции. Изменение локального идентификаторами; это не доказывает потерю самой позиции. Изменение локального
лимита требует учёта разрешённой квоты T-Bank: оно не увеличивает квоту лимита требует учёта разрешённой квоты T-Bank: оно не увеличивает квоту
поставщика. поставщика. Диагностика соединения описана в
[локальном окружении](../development/local-workspace.md).
Ключ шифрования не ротируется автоматически. Новый ключ не расшифрует Ключ шифрования не ротируется автоматически. Лимитер и кэши принадлежат
прежнее подключение: нужен исходный ключ либо повторный ввод собственного
token через настройки. Правила [резервного копирования](../architecture/containers.md#сохранение-и-восстановление-данных)
учитывают отдельное хранение ключа. Лимитер и кэши принадлежат
одному экземпляру; изменения подключения не координируются между одному экземпляру; изменения подключения не координируются между
распределёнными экземплярами backend. Интеграция использует сохранённый распределёнными экземплярами backend. Интеграция использует сохранённый
protobuf-контракт T-Bank: обновление внешнего протокола и проверка protobuf-контракт T-Bank: обновление внешнего протокола и проверка
+1 -2
View File
@@ -20,12 +20,11 @@ WealthLedger — локальное приложение для частного
| Найти свой инвестиционный счёт | [Список счетов](domains/accounts/overview.md), их типы и доступные сводки | | Найти свой инвестиционный счёт | [Список счетов](domains/accounts/overview.md), их типы и доступные сводки |
| Вести банковские карты | [Ручной учёт](domains/cards/overview.md): независимые балансы, расходы, пополнения, переводы, возвраты и месячные сводки | | Вести банковские карты | [Ручной учёт](domains/cards/overview.md): независимые балансы, расходы, пополнения, переводы, возвраты и месячные сводки |
| Понять текущий состав инвестиций | [Портфель](domains/portfolio/overview.md): позиции, денежные остатки и итоги по валютам | | Понять текущий состав инвестиций | [Портфель](domains/portfolio/overview.md): позиции, денежные остатки и итоги по валютам |
| Увидеть, где сконцентрированы деньги | [Концентрация](domains/portfolio/concentration.md): крупнейшие бумаги, секторы и эмитенты счёта |
| Разобрать уже совершённые движения | [Журнал операций](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/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): рынок, личная позиция, история и события |
## Как связаны представления ## Как связаны представления
+303 -3190
View File
File diff suppressed because it is too large Load Diff
+6 -28
View File
@@ -1,38 +1,16 @@
{ {
"name": "wealth-ledger-docs", "name": "wealth-ledger-docs",
"version": "0.1.7", "version": "1.0.0",
"private": true, "private": true,
"engines": { "engines": {
"node": "24.22.1" "node": "24.x"
}, },
"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:prepare": "release-it --ci",
"commitlint": "commitlint"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "21.2.4", "markdownlint-cli2": "0.23.2",
"@commitlint/config-conventional": "21.2.4", "markdownlint-rule-relative-links": "5.1.2"
"@release-it/conventional-changelog": "12.0.2",
"conventional-changelog-conventionalcommits": "10.4.1",
"markdownlint-cli": "0.49.1",
"markdownlint-rule-relative-links": "5.1.4",
"release-it": "21.1.1"
},
"overrides": {
"fast-uri": "3.1.9",
"katex": "0.19.1",
"markdownlint-cli@0.49.1": {
"js-yaml": "5.4.2",
"minimatch": "11.0.2",
"smol-toml": "1.9.1"
},
"release-it": {
"basic-ftp": "6.2.3",
"undici": "7.30.1"
},
"markdown-it": "15.0.3"
} }
} }
+1 -29
View File
@@ -1,31 +1,3 @@
{ {
"$schema": "https://docs.renovatebot.com/renovate-schema.json", "$schema": "https://docs.renovatebot.com/renovate-schema.json"
"extends": [
"config:recommended",
":semanticCommitTypeAll(chore)"
],
"timezone": "Europe/Moscow",
"labels": [
"dependencies"
],
"dependencyDashboard": true,
"rangeStrategy": "pin",
"packageRules": [
{
"matchUpdateTypes": [
"minor",
"patch"
],
"groupName": "non-major dependencies"
},
{
"matchUpdateTypes": [
"major"
],
"dependencyDashboardApproval": true
}
],
"baseBranchPatterns": [
"develop"
]
} }
-10
View File
@@ -1,10 +0,0 @@
const assert = require("node:assert/strict");
const { execFileSync } = require("node:child_process");
const { version } = require("../package.json");
const git = (...args) => execFileSync("git", args, { encoding: "utf8" }).trim();
assert.equal(git("status", "--porcelain"), "", "Commit all changes before preparing a release");
const tags = git("tag", "--merged", "HEAD", "--list", "v[0-9]*", "--sort=-version:refname")
.split("\n").filter(tag => /^v\d+\.\d+\.\d+$/.test(tag));
assert.equal(version, tags[0]?.slice(1) ?? "0.0.0",
"Version is already prepared or release tags are missing. Fetch tags; finish the existing release before preparing another.");