From 3a99af1a6e49836ae6ea2378dd2e4037092d08fc Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sat, 12 Sep 2026 13:09:37 +0300 Subject: [PATCH] docs: make project documentation self-contained --- .markdownlint-cli2.jsonc | 11 +- AGENTS.md | 25 ++- AI_INDEX.md | 87 +++++----- README.md | 59 +++++++ adr/008-pino-backend-logging.md | 7 + ...ve-openspec-sdd-and-local-orchestration.md | 14 +- ...penspec-lifecycle-with-repository-gates.md | 40 +++-- ...-driven-development-with-scoped-context.md | 14 +- architecture/domain-glossary.md | 12 +- architecture/http-api.md | 53 +++--- architecture/system-overview.md | 135 ++++++++++++--- development/README.md | 31 ++-- development/agent-context-evals.md | 64 +++---- development/agent-token-efficiency.md | 76 ++++----- development/backend-workflow.md | 47 +++--- development/design-system.md | 157 ++++++------------ development/documentation.md | 88 ++++++++++ development/engineering-governance.md | 133 +++++++-------- development/frontend-workflow.md | 67 +++----- development/local-workspace.md | 84 ++++++++++ .../openspec-authority-and-lifecycle.md | 44 ++--- development/runtime-openapi-verification.md | 65 +++----- development/ui-catalog.md | 148 ++++++----------- features/README.md | 65 ++++++-- features/all-accounts.md | 6 +- features/authentication.md | 67 +++++++- features/bond-detail.md | 7 +- .../brokerage-account-income-analytics.md | 4 +- features/brokerage-account-ledger.md | 2 +- features/brokerage-account-upcoming-events.md | 8 +- features/portfolio-view.md | 2 +- features/stock-detail.md | 27 +-- features/tbank-connection.md | 11 +- package-lock.json | 33 +++- package.json | 3 +- research/README.md | 13 ++ 36 files changed, 1006 insertions(+), 703 deletions(-) create mode 100644 README.md create mode 100644 development/documentation.md create mode 100644 development/local-workspace.md create mode 100644 research/README.md diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc index 2485208..e11c49d 100644 --- a/.markdownlint-cli2.jsonc +++ b/.markdownlint-cli2.jsonc @@ -1,5 +1,14 @@ { "config": { "MD013": false - } + }, + "customRules": ["markdownlint-rule-relative-links"], + "overrides": [ + { + // Immutable audit/evidence records keep original source paths, outside the standalone book. + "filter": ["audits/**", "development/completion-evidence.md", "development/audit-remediation.md"], + "config": { "relative-links": false }, + "combine": "merge" + } + ] } diff --git a/AGENTS.md b/AGENTS.md index dcd1978..8349034 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,17 +1,14 @@ -# Docs +# Документация WealthLedger -Здесь живут as-built факты, проектные решения и общие engineering conventions. -Работай напрямую по задаче пользователя; обязательного SDD lifecycle нет. +Этот репозиторий описывает устройство системы, пользовательские возможности, +ограничения и причины принятых решений. [Главная страница](README.md) и +[тематический индекс](AI_INDEX.md) дают вход в эти знания без доступа к исходникам. -Для поиска проектного источника начни с [AI_INDEX](AI_INDEX.md). Если задача -уже указывает точный документ (например, правка опечатки), читай его напрямую. -Architecture/features описывают текущее поведение, -ADR сохраняют rationale; audits и прежнее Completion Evidence — история. -Изменение утверждённого архитектурного решения фиксируется successor ADR, -но не требует создания OpenSpec artifacts. +[Правила ведения документации](development/documentation.md) определяют +ответственность за актуальность, сохранение смысла и проверку самостоятельности. +Архитектурные решения сохраняются в ADR; аудиты и датированные свидетельства +остаются историческими материалами и не заменяют описание текущего поведения. -Перед Node-командами: `nvm use --silent`. После `npm ci` запускай -`npm run verify` (Markdown lint + `git diff --check`, без auto-fix). -Проверь links/anchors затронутых документов отдельно. Новые документы должны -быть доступны через соответствующий index. Runtime facts сверяй с code/config; -не копируй framework docs, secrets или один контракт в несколько owners. +Локальные проверки документации охватывают Markdown, относительные ссылки +и якоря; смысловая согласованность проверяется отдельно. Синтаксическая +проверка не гарантирует актуальность архитектурного или функционального факта. diff --git a/AI_INDEX.md b/AI_INDEX.md index 98eaab7..a747899 100644 --- a/AI_INDEX.md +++ b/AI_INDEX.md @@ -1,78 +1,83 @@ # Project knowledge index -Открывай раздел по конкретному вопросу, затем только нужный source. -Это карта фактов, не обязательное чтение всего docs. Текущий wire contract -генерируется backend; устаревший ADR/пример кода не переопределяет его. +Карта проектных знаний по вопросам читателя. Все необходимые объяснения +находятся внутри документации. Для первого знакомства откройте +[главную страницу](README.md), для конкретного сценария — +[карту возможностей](features/README.md). ## HTTP API -| Trigger | Source | +| Вопрос | Документ | | --- | --- | -| Endpoint, DTO, validation, status, exception/error shape | [HTTP semantics](architecture/http-api.md) | -| Изменение публичного wire contract, snapshot/generated types | [Contract workflow](development/runtime-openapi-verification.md) | -| Почему Nest — источник OpenAPI | [ADR 002](adr/002-nest-generated-openapi-contract.md) | +| Как API проверяет запрос, подтверждает доступ и сообщает об ошибках? | [HTTP semantics](architecture/http-api.md) | +| Как сохраняется согласованность публичного контракта frontend и backend? | [Contract workflow](development/runtime-openapi-verification.md) | +| Почему backend является источником OpenAPI? | [ADR 002](adr/002-nest-generated-openapi-contract.md) | ## Security -| Trigger | Source | +| Вопрос | Документ | | --- | --- | -| Login/session/logout, cookie/access storage | [Authentication](features/authentication.md) | -| Concurrent/superseded refresh | [ADR 009](adr/009-concurrent-refresh-rotation-policy.md), [термины](architecture/domain-glossary.md#concurrent-refresh-attempt) | -| T-Bank token encryption, connection generation/cache | [Connection](features/tbank-connection.md), [ADR 004](adr/004-encrypted-per-user-tbank-token.md) | +| Как работают вход, восстановление сессии, cookie и logout? | [Authentication](features/authentication.md) | +| Что происходит при конкурентном refresh или повторном предъявлении заменённого token? | [ADR 009](adr/009-concurrent-refresh-rotation-policy.md), [термины](architecture/domain-glossary.md#concurrent-refresh-attempt) | +| Как защищён T-Bank token и что меняется при замене подключения? | [Connection](features/tbank-connection.md), [ADR 004](adr/004-encrypted-per-user-tbank-token.md) | ## Data -Persistence source — [Prisma schema](../backend/prisma/schema.prisma), -[migrations](../backend/prisma/migrations/). [ADR 001](adr/001-prisma-sqlite-local-mvp.md) -описывает исторический выбор SQLite, не текущий список таблиц. -Transaction/ownership/migration decisions — [backend data guide](../backend/.ai/conventions/backend-data.md). +Локальная база хранит пользователей, сессии и зашифрованные подключения. +Брокерские счета и финансовые снимки не сохраняются как долговременная история. +Принадлежность данных и границы хранения описаны в +[обзоре системы](architecture/system-overview.md#хранимые-данные). +Причина выбора локальной базы — [ADR 001](adr/001-prisma-sqlite-local-mvp.md). +Точность и валюты — [Money](architecture/domain-glossary.md#money). +Сохранение данных при обновлении — [локальное окружение](development/local-workspace.md#подготовка-backend). ## Integrations -T-Bank/proto/TLS/retry/limits — [connection configuration](features/tbank-connection.md#конфигурация-и-ограничения) +T-Bank, TLS, повторные попытки и лимиты — +[ограничения подключения](features/tbank-connection.md#конфигурация-и-ограничения) и [server-side boundary](adr/003-tbank-server-side-grpc-boundary.md). -Cache generation — [connection cache](features/tbank-connection.md#граница-финансового-кэша). -Logging — [ADR 008](adr/008-pino-backend-logging.md) и -[реальный logger boundary](../backend/.ai/conventions/backend-architecture.md#logging). +MOEX/НКР и происхождение показателей — [облигации](features/bond-detail.md#содержание) +и [внешние системы](architecture/system-overview.md#контекст-системы). +Смена подключения и финансовый кэш — [connection cache](features/tbank-connection.md#граница-финансового-кэша). +Логирование выполняется на инфраструктурной границе backend: события связываются +с HTTP-запросом, а секреты и полные запросы не попадают в журнал. +Принятые ограничения описаны в [ADR 008](adr/008-pino-backend-logging.md). ## Domain -Начинай с точного термина в [глоссарии](architecture/domain-glossary.md): +Определения и различия понятий — в [глоссарии](architecture/domain-glossary.md): [Money](architecture/domain-glossary.md#money), [Portfolio Position](architecture/domain-glossary.md#portfolio-position), [Client Cached Snapshot](architecture/domain-glossary.md#client-cached-snapshot), [Ledger](architecture/domain-glossary.md#account-ledger), [Realized Income](architecture/domain-glossary.md#realized-income-analytics). -Cross-area topology — [system overview](architecture/system-overview.md). +Связи понятий и подсистем — [system overview](architecture/system-overview.md#предметная-модель). ## Features -| Предмет задачи | Current behavior | +| Предмет вопроса | Текущее поведение | | --- | --- | -| Счета и их сводки | [Brokerage accounts](features/brokerage-accounts.md) | +| Счета и сводки | [Brokerage accounts](features/brokerage-accounts.md) | | Общий портфель и разделы по всем счетам | [All accounts](features/all-accounts.md) | -| Страница акции, поиск, график и история одной бумаги | [Stock detail](features/stock-detail.md) | -| Страница облигации, поиск, купоны и переходы из портфеля | [Bond detail](features/bond-detail.md) | -| Источники кредитных рейтингов облигаций | [Исследование источников](research/bond-credit-ratings.md) | -| Позиции/суммы портфеля, backend cache | [Portfolio](features/portfolio-view.md) | -| Операции/фильтры/cursor | [Account ledger](features/brokerage-account-ledger.md) | +| Поиск, график и история акции | [Stock detail](features/stock-detail.md) | +| Поиск, купоны и показатели облигации | [Bond detail](features/bond-detail.md) | +| Покрытие источников кредитных рейтингов | [Исследование источников](research/bond-credit-ratings.md) | +| Позиции, суммы портфеля и свежесть данных | [Portfolio](features/portfolio-view.md) | +| Операции, фильтры и пагинация | [Account ledger](features/brokerage-account-ledger.md) | | Будущие выплаты | [Upcoming events](features/brokerage-account-upcoming-events.md) | -| Реализованные доходы/периоды | [Income analytics](features/brokerage-account-income-analytics.md) | -| UI recovery/error boundaries | [Frontend resilience](features/frontend-ui-resilience.md) | - -Behavioral requirements при необходимости — через [specs index](../specs/AI_INDEX.md). -Это не запускает SDD lifecycle. +| Реализованные доходы и периоды | [Income analytics](features/brokerage-account-income-analytics.md) | +| Восстановление после ошибок интерфейса | [Frontend resilience](features/frontend-ui-resilience.md) | ## UI -Theme/tokens/reuse — [design system](development/design-system.md). -Существующие components/stories и ограничения — [UI catalog](development/ui-catalog.md). -Query freshness/focus policy — [ADR 007](adr/007-react-query-fetch-policy.md). +Визуальные роли и общие состояния — [дизайн-система](development/design-system.md). +Примеры экранов и ограничения их проверки — [UI-каталог](development/ui-catalog.md). +Обновление данных при возвращении в окно — [ADR 007](adr/007-react-query-fetch-policy.md). ## Engineering -Scope/abstraction/dependency/testing — [общие правила](development/engineering-governance.md). -Routing/recovery — [context discipline](development/agent-token-efficiency.md). -Commands — [frontend](development/frontend-workflow.md) / [backend](development/backend-workflow.md). -Известный Audit ID — найденная запись [registry](development/audit-remediation.md); -читай её evidence только для этого вопроса. Historical audits не являются live rules. +Принципы изменения и проверки системы — [engineering governance](development/engineering-governance.md). +Ведение знаний и разрешение расхождений — [документация](development/documentation.md). +Локальное окружение и диагностика — [local workspace](development/local-workspace.md). +Служебные материалы сопровождения и исторические результаты отделены от +описания системы в [разделе разработки](development/README.md). diff --git a/README.md b/README.md new file mode 100644 index 0000000..c611e92 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# WealthLedger: документация проекта + +WealthLedger помогает частному инвестору понимать состав портфеля, исполненные +операции, полученные доходы и будущие выплаты по счетам T-Bank. Приложение +работает локально; данные брокера получает backend. Здесь объясняются понятия, +границы системы, ключевые сценарии и причины решений. + +## Первое знакомство + +1. Прочитайте [обзор системы](architecture/system-overview.md): пользователи, + внешние системы, исполняемые части и движение данных. +2. Откройте [карту возможностей](features/README.md): выберите предметную + область и найдите её поведение, ограничения и ответственность компонентов. +3. Сверяйте незнакомые понятия с [глоссарием](architecture/domain-glossary.md). + Особенно важны различия между User, Session, подключением и брокерским + счётом, между полученным доходом и прогнозом выплаты. +4. Изучите [локальное окружение](development/local-workspace.md) и один + сценарий: вход → подключение T-Bank → счёт → портфель → доходы или события. + +После этого вы должны уметь объяснить, откуда приходит финансовое значение, +какой компонент им владеет и где проверить его ограничения. + +## Найти ответ по задаче + +| Вопрос | Начать здесь | +| --- | --- | +| Как части проекта работают вместе? | [Обзор и схемы](architecture/system-overview.md) | +| Как устроена конкретная возможность и кто за неё отвечает? | [Карта возможностей](features/README.md) | +| Что означает термин или финансовый показатель? | [Глоссарий](architecture/domain-glossary.md) | +| Почему выбраны эти архитектурные ограничения? | [Решения ADR](adr/README.md) | +| Какие условия нужны для работы и как понимать локальные ошибки? | [Окружение и восстановление](development/local-workspace.md) | +| Где точные API-типы, статусы и правила изменения контракта? | [HTTP API](architecture/http-api.md) и [генерация контракта](development/runtime-openapi-verification.md) | +| Как проверяются согласованность и качество системы? | [Сопровождение](development/README.md) | +| Как обновлять знания вместе с кодом? | [Ведение документации](development/documentation.md) | + +[Тематический индекс](AI_INDEX.md) связывает вопросы с соответствующими +объяснениями. Читать весь репозиторий для ответа на один вопрос не требуется. + +## Какому источнику доверять + +Документация объясняет текущее устройство и поведение системы. Точный публичный +HTTP-контракт публикует backend в OpenAPI; ADR сохраняют причины принятых решений. +Требования и предложения изменений могут отдельно согласовываться в OpenSpec, +но для понимания описанных сценариев доступ к нему не нужен. +[Карта владельцев фактов](development/documentation.md#владельцы-фактов) +помогает различать назначение этих знаний и разрешать расхождения. + +Текущие объяснения находятся в `architecture` и `features`; руководства — в +`development`. [Исследования](research/README.md) содержат проверенные наблюдения +и варианты, [аудиты](audits/README.md) — исторические свидетельства. +Наличие рекомендации в исследовании или требования в старом change ещё не +означает, что возможность реализована. Статус каждого ADR указан в +[журнале решений](adr/README.md). + +Основная документация читается самостоятельно, без исходников других +репозиториев. [Исторические свидетельства](development/README.md#исторические-свидетельства) +сохраняют исходные технические адреса своего времени и не являются +обязательным чтением для понимания системы. Документы используют Markdown, +схемы — Mermaid. diff --git a/adr/008-pino-backend-logging.md b/adr/008-pino-backend-logging.md index 6f9aee5..caa6274 100644 --- a/adr/008-pino-backend-logging.md +++ b/adr/008-pino-backend-logging.md @@ -13,3 +13,10 @@ Backend нуждается в структурированных JSON-логах ## Consequences Backend получает новую production dependency и использует Pino API для application-событий. Collector, retention и alerting остаются за пределами приложения. + +Логирование сосредоточено на инфраструктурной границе backend и связывает +события с контекстом HTTP-запроса. В журнал передаются стабильное имя события +и разрешённые скалярные поля. Неизвестные поля отбрасываются; полные запросы, +тела ответов и секреты не являются допустимыми данными журнала. Фильтрация +полей не защищает секрет, помещённый в свободный текст сообщения или stack: +содержимое таких сообщений требует отдельной проверки. diff --git a/adr/010-native-openspec-sdd-and-local-orchestration.md b/adr/010-native-openspec-sdd-and-local-orchestration.md index 089c86f..1f79adf 100644 --- a/adr/010-native-openspec-sdd-and-local-orchestration.md +++ b/adr/010-native-openspec-sdd-and-local-orchestration.md @@ -14,15 +14,15 @@ SDD lifecycle. Native OpenSpec остаётся optional инструменто [ADR 011](011-native-openspec-lifecycle-with-repository-gates.md) заменяет части этого решения о schema v3, custom verifiers и dispatcher. Выбор native OpenSpec, независимых repository gates и исходное rationale сохраняются. Ниже оставлено -историческое описание прежней реализации; действующую процедуру определяют -authorities, указанные в ADR 011. Version contract этим уточнением не меняется. +историческое описание прежней реализации; текущие принципы работы описаны +в ADR 012. Version contract этим уточнением не меняется. ## Исторический контекст и решение -WealthLedger использует project-local schema `wealth-ledger-sdd` version 3 с +На момент решения WealthLedger использовал проектную schema `wealth-ledger-sdd` version 3 с последовательностью `Proposal → Delta Specs → Design → Tasks → Apply`, один exact -OpenSpec CLI contract из `.cli-version` и тонкие non-mutating verifiers. Это -сохраняет один lifecycle authority в `specs/openspec`, выражает approval +OpenSpec CLI contract и проверки без изменения проверяемых материалов. Это +сохраняло единый источник правил жизненного цикла в хранилище спецификаций, выражало approval boundaries нативными extension points и не вводит второй workflow engine или monorepo control plane для четырёх независимых Git-репозиториев. @@ -39,8 +39,8 @@ monorepo control plane для четырёх независимых Git-репо OpenSpec CLI закреплён exact version contract; новая stable release не включает auto-upgrade, а смена contract является отдельным SDD change со staged -compatibility fixtures. Root `.opencode` остаётся локальным non-authority -entrypoint, а hard constraints проверяются через authority verifier. Planning +проверками совместимости. Локальная точка входа оставалась навигацией, +а обязательные ограничения проверялись отдельно. Planning verifier до approval проверяет только детерминированную структуру: capability set, Delivery Map, rollback, task area и Requirement coverage. Он различает artifact-validation, неверный invocation и failure окружения, чтобы agent не diff --git a/adr/011-native-openspec-lifecycle-with-repository-gates.md b/adr/011-native-openspec-lifecycle-with-repository-gates.md index 50a2b4a..8f5068a 100644 --- a/adr/011-native-openspec-lifecycle-with-repository-gates.md +++ b/adr/011-native-openspec-lifecycle-with-repository-gates.md @@ -13,40 +13,46 @@ status: superseded [ADR 010](010-native-openspec-sdd-and-local-orchestration.md) сохраняет rationale выбора native OpenSpec и независимых repository gates. Его описание schema v3, custom authority/planning verifiers и dispatcher относится к прежней реализации. -Текущая project-local [schema wealth-ledger-sdd](../../specs/openspec/schemas/wealth-ledger-sdd/schema.yaml) -имеет version 5 и граф `Proposal → Delta Specs → Design → Tasks → Apply`. -Native instructions и общие authorities уже определяют действующую процедуру; -optional entrypoints направляют к ней. +На момент решения проектная schema `wealth-ledger-sdd` +имела version 5 и граф `Proposal → Delta Specs → Design → Tasks → Apply`. +Порядок работы определялся средствами OpenSpec и общими правилами проекта; +дополнительные точки входа служили навигацией по единой процедуре. -Отслеживаемый [`.cli-version`](../../specs/openspec/.cli-version) существует и -содержит `1.10.0`. Версия schema и CLI contract — разные сведения. Наличие этого -файла не доказывает запуск прежних verifiers; это решение не меняет CLI pin, +Версия OpenSpec CLI была зафиксирована как `1.10.0`. +Версия schema и CLI contract — разные сведения. Фиксация версии +не доказывает запуск прежних проверок; это решение не меняет CLI pin, version contract, schema или требования к tooling. ## Решение Частично заменить ADR 010 в части прежней schema version и orchestration. -Canonical SDD store остаётся `wealth-ledger-specs` в `specs/openspec`. -Действующая процедура принадлежит существующим owners: +Единым хранилищем согласования оставался `wealth-ledger-specs`. +Процедура разделяла следующие ответственности: -- [Engineering governance](../development/engineering-governance.md) — роли, отдельные approvals, Delivery Map, обязательность проверок и completion safeguards. -- [OpenSpec lifecycle](../development/openspec-authority-and-lifecycle.md) — native planning/apply, strict structural validation, semantic sync и completion procedure. -- [Context discipline](../development/agent-token-efficiency.md) — выбор и повторная сверка inputs, fingerprints, recovery и evidence reuse. +- Согласование ролей, подтверждений, объёма поставки и обязательных проверок. +- Планирование и применение изменений, структурную проверку и смысловое согласование требований. +- Выбор и повторную сверку исходных знаний, восстановление контекста и повторное использование свидетельств. Native OpenSpec получает structure и contextFiles текущего artifact или Apply. -Каждый affected repository владеет своим gate через local AGENTS. Выполнение +Каждое изменяемое приложение владело своей проверкой качества. Выполнение процедуры не зависит от custom verifier, dispatcher, skill или session adapter; optional entrypoints остаются navigation, без второго workflow engine. ## Последствия -Агент и человек используют один актуальный lifecycle. Отдельный порядок команд -в ADR не поддерживается: точные команды находятся у lifecycle и repository -owners. Исходные альтернативы и rationale остаются в ADR 010; historical -archives, audits и evidence сохраняют описание своего времени. +Люди и автоматизированные инструменты следовали единой процедуре, а каждый +репозиторий отвечал за собственные проверки. 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). +Эти материалы отражают последующее упрощение процесса, а не восстанавливают +обязательность исторической процедуры. diff --git a/adr/012-task-driven-development-with-scoped-context.md b/adr/012-task-driven-development-with-scoped-context.md index 38c6947..fcf7fe8 100644 --- a/adr/012-task-driven-development-with-scoped-context.md +++ b/adr/012-task-driven-development-with-scoped-context.md @@ -11,10 +11,10 @@ ## Decision -Задача пользователя определяет результат. Root/local AGENTS задают короткий -рабочий процесс; CONVENTIONS выбирает темы; SOURCES и docs/specs indexes указывают -точных владельцев фактов. Frontend/backend guides содержат решения, безопасные -пути, exceptions и реальные примеры. Общие rules и contract workflow не дублируются. +Задача пользователя определяет результат. Рабочий контекст выбирается по +предмету изменения: локальные правила разработки отделены от проектных фактов, +а тематические источники объясняют соответствующую область. Общие принципы +и согласование контракта не дублируются в независимых описаниях. [Engineering governance](../development/engineering-governance.md) и [context discipline](../development/agent-token-efficiency.md) описывают текущую @@ -28,6 +28,6 @@ операции. Правильность routing проверяется на задачах и counterexamples; качество агента оценивается по наблюдаемым outcomes, а не по числу файлов или checkboxes. -Имена файлов и примерные context budgets из исследований адаптируются к -WealthLedger: FSD без widgets, Nest как OpenAPI source, существующие локальные -gates. Новые services, CI и checkers ради context architecture не добавляются. +Рекомендации исследований адаптируются к принятой архитектуре и существующим +проверкам WealthLedger. Backend остаётся источником OpenAPI. Новые сервисы, +общая инфраструктура и проверки не вводятся только ради организации контекста. diff --git a/architecture/domain-glossary.md b/architecture/domain-glossary.md index 9ca7544..6f32752 100644 --- a/architecture/domain-glossary.md +++ b/architecture/domain-glossary.md @@ -25,7 +25,7 @@ _Не использовать_: T-Bank сессия, browser session. неприменимое денежное значение передаётся как `null`, а не как ноль и не как раздельные `amount`/`currency`. -_Не использовать_: number amount, MoneyDto как доменный термин. +_Не использовать_: сумма без валюты, транспортное представление как доменный термин. ## T-Bank connection @@ -138,9 +138,8 @@ _Не использовать_: история Portfolio, баланс счёт Нормализованное представление одной операции T-Bank в Account Ledger; хранит исходные суммы и доступные детали операции, не создавая синтетических записей -для комиссии сделки. API/code сохраняют legacy names `BrokerageOperationDto`, -`BrokerageOperationsPageDto` и `/operations`; в доменной документации primary -term — Ledger Entry. +для комиссии сделки. В публичном API история доступна через маршруты +`/operations`; в доменной документации запись называется Ledger Entry. _Не использовать_: проводка, рассчитанная операция. @@ -188,9 +187,8 @@ _Не использовать_: активный refresh token, признак не является исполненной Ledger Entry. Прогнозная сумма события передаётся как nullable Money: известные coupon, dividend и maturity содержат Money в исходной валюте, offer или неизвестные денежные условия передаются как `null`. -API/code сохраняют legacy names `UpcomingEventDto`, `UpcomingEventsDto`, -`/events` и frontend `BrokerageEvent`; в доменной документации primary term — -Future Cash Event. +В публичном API прогноз доступен через маршруты `/events`; в доменной +документации событие называется Future Cash Event. _Не использовать_: будущая операция, гарантированная выплата. diff --git a/architecture/http-api.md b/architecture/http-api.md index bd013c4..abd992a 100644 --- a/architecture/http-api.md +++ b/architecture/http-api.md @@ -2,25 +2,24 @@ ## Источник контракта -Nest controllers/DTO/Swagger metadata генерируют runtime OpenAPI. Настройка -приложения — [configureApp](../../backend/src/main.ts); frontend хранит derived -snapshot и generated TS. Изменения проходят +Backend генерирует OpenAPI из собственного публичного API. Frontend использует +производное описание контракта и типизированный клиент. Изменения проходят [contract workflow](../development/runtime-openapi-verification.md). API имеет prefix `/api/v1`; Swagger публикуется для локальной разработки по environment policy, а не используется frontend в runtime. ## Validation и identity -Global ValidationPipe использует `transform: true`, `whitelist: true`. -Unknown properties удаляются, а их rejection не обещается без -`forbidNonWhitelisted`. Runtime DTO validators и преобразования задают допустимый -вход; бизнес-ограничения и доступ к account проверяются service/access boundary. -Guard подтверждает identity; accountId из URL сам по себе не доказывает доступ. +Backend преобразует и проверяет входные данные на общей HTTP-границе. +Неизвестные свойства запроса удаляются; сам факт их передачи не обещает отказа. +Проверка формата не заменяет бизнес-ограничения и проверку доступа к счёту. +Аутентификация подтверждает личность пользователя; accountId из URL сам по себе +не доказывает доступ к брокерским данным. ## Error contract -Общий [HttpExceptionFilter](../../backend/src/common/filters/http-exception.filter.ts) -возвращает `application/problem+json` со `status` и публичным `code`. +Общая граница обработки HTTP-ошибок возвращает `application/problem+json` +со `status` и публичным `code`. Неожиданный exception возвращает `500` / `INTERNAL_SERVER_ERROR`, без исходного message/stack. Типичный ответ: @@ -30,21 +29,22 @@ message/stack. Типичный ответ: Для input validation контракт требует `400`, `VALIDATION_ERROR` и `errors` как массив `{ pointer, detail }`, где pointer адресует поле запроса. Это требование -не считается проверенным одним Swagger snapshot: direct controller call не -запускает ValidationPipe. Текущий filter преобразует `ValidationError.constraints`, -а default Nest pipe формирует массив строк; корректность field details в -configured HTTP pipeline нужно проверять отдельно и не копировать этот seam -как доказанно полный пример. +не считается проверенным одним описанием OpenAPI: нужна проверка реального +HTTP-ответа после всех стадий обработки запроса. -Документированный DTO — [ProblemDetailsDto](../../backend/src/common/decorators/api-safe-error-response.decorator.ts). -Filter не добавляет автоматически все поля из RFC (`type`, `title`, `instance`). -Не вводи дополнительные обязательные поля без согласованного изменения wire contract. -Caller выбирает поведение по public code, не по внутреннему тексту exception. +**Требует ручной проверки:** согласованность деталей ошибок валидации полей +в полном HTTP-потоке пока не подтверждена. Известно расхождение формата ошибок +между стадиями валидации и формирования ответа; наличие описания в OpenAPI +не доказывает передачу ожидаемых `pointer` и `detail`. + +Ответ не включает автоматически все поля RFC (`type`, `title`, `instance`). +Добавление обязательного поля является изменением публичного контракта. +Клиент выбирает поведение по публичному коду, а не по внутреннему тексту ошибки. ## Domain errors и auth -Специальный code задаётся ответственным use case; generic mapping и безопасный -envelope принадлежат filter. Auth endpoints используют свои status и cookie +Специальный code определяется сценарием; общее преобразование ошибок и безопасная +форма ответа принадлежат HTTP-границе. Auth endpoints используют свои status и cookie operations: [Authentication](../features/authentication.md). T-Bank token failure и временная недоступность имеют разные значения: [Connection API](../features/tbank-connection.md#api). Raw upstream/Prisma errors @@ -52,8 +52,9 @@ T-Bank token failure и временная недоступность имеют ## Граница доказательства -OpenAPI integration проверяет равенство schemas, а не результат каждого request. -Filter unit test проверяет безопасный unexpected error. Для изменения validation, -guard, status/cookie или domain error нужны representative configured HTTP cases; -известный e2e environment blocker не превращает отсутствующую проверку в success. -Выбор уровня и команд — [backend testing](../../backend/.ai/conventions/backend-testing.md). +Сравнение OpenAPI подтверждает согласованность схем, а не результат каждого +запроса. Безопасная обработка неожиданной ошибки проверяется отдельно. +Валидация, доступ, статусы и cookie требуют проверки полного HTTP-потока; +атомарность хранения — проверки с изолированной реальной базой. +Известное ограничение сквозных проверок описано в +[проверке backend](../development/backend-workflow.md#локальная-проверка). diff --git a/architecture/system-overview.md b/architecture/system-overview.md index 37a5373..79f970f 100644 --- a/architecture/system-overview.md +++ b/architecture/system-overview.md @@ -1,7 +1,9 @@ # Обзор системы -WealthLedger — локальное приложение для просмотра инвестиционных данных через -безопасное server-side подключение к T-Bank Invest. +WealthLedger — локальное приложение для частного инвестора: оно связывает +состав портфеля, операции, полученный доход и будущие выплаты по счетам T-Bank. +Обзор объясняет систему сверху вниз. Детали сценариев и ответственность подсистем находятся +в [карте возможностей](../features/README.md). ## Поставленные возможности @@ -18,13 +20,84 @@ WealthLedger — локальное приложение для просмотр loading, error, empty и cached-snapshot состояния; initial и частичные loading-состояния показывают structural skeleton, а уже доступный контекст остаётся видимым при фоновом обновлении или действии пользователя. +- [Все счета](../features/all-accounts.md): общий портфель, журнал, доходы + и будущие выплаты с сохранением принадлежности к счетам. +- [Акции](../features/stock-detail.md) и [облигации](../features/bond-detail.md): + поиск, рыночные данные, личная позиция и события конкретного инструмента. Термины этих возможностей определяет [глоссарий домена](domain-glossary.md), а причины ключевых технических решений — [ADR](../adr/README.md). +## Контекст системы + +```mermaid +flowchart LR + U[Частный инвестор] -->|Смотрит инвестиции| W[WealthLedger] + W -->|Счета, позиции, операции и рынок| T[T-Bank Invest API] + W -->|Публичные данные облигаций| M[MOEX ISS] + W -->|Рейтинги выпусков| N[Реестр НКР] + W -->|Изображения логотипов| C[T-Bank Brands CDN] +``` + +T-Bank — источник личных брокерских данных и рынка. MOEX дополняет будущие +события и показатели облигаций, НКР — рейтинг конкретного выпуска. Публичный +рейтинг или календарь выпуска не подтверждает личное зачисление инвестору. +Отсутствие записи и недоступность источника имеют разный смысл; детали — +[Bond detail](../features/bond-detail.md). + +## Исполняемые части + +На уровне C4 containers здесь два приложения и локальная база. Каталоги +`docs` и `specs` не являются runtime-компонентами. + +```mermaid +flowchart LR + subgraph Browser[Браузер пользователя] + F[React SPA] + end + subgraph Local[Локальное окружение] + B[NestJS API] + D[(SQLite)] + B -->|Prisma: identity, sessions, connection| D + end + F -->|HTTP JSON, Bearer и auth cookie| B + B -->|gRPC over TLS, token пользователя| T[T-Bank Invest] + B -->|HTTPS, публичные данные| M[MOEX ISS] + B -->|HTTPS, публичный HTML| N[НКР] + F -->|HTTPS, изображения| C[Brands CDN] +``` + +- **Frontend** владеет представлением, навигацией и клиентским query cache. + Access token существует в памяти; refresh-cookie обслуживает browser. +- **Backend** владеет identity, проверкой доступа, credentials брокера, + нормализацией, денежными расчётами и upstream-кэшем. +- **SQLite** хранит локального User, sessions и зашифрованное подключение. +- **Интеграции backend** обращаются к T-Bank, MOEX и НКР, сохраняя различия + личных данных брокера и публичных данных выпуска. Браузер получает + изображения логотипов непосредственно из публичного CDN. + +Frontend связывает пользовательские экраны навигацией. Backend объединяет +аутентификацию, подключение брокера и инвестиционные сценарии через публичный API. +Согласование адресов и origin — [локальный запуск](../development/local-workspace.md). + +## Хранимые данные + +Локальная база содержит идентичность пользователя и hash пароля, состояние +его сессий со сроком действия, признаком отзыва и hash текущего refresh token, +а также одно зашифрованное подключение T-Bank с временем подключения. +Сессии и подключение принадлежат конкретному пользователю; plaintext пароля +или broker token не является сохраняемым представлением credentials. + +Изменения связанных локальных данных требуют сохранения их целостности. +Ротация сессии атомарно заменяет текущий refresh token: конкурентные запросы +не могут оба выиграть. Сетевой вызов брокера и изменение локальной базы не +образуют одну общую транзакцию. Финансовые наблюдения хранятся в ограниченном +кэше, а не как долговременная история брокерских данных. + ## Локальные границы -- Frontend — React-клиент; он не вызывает T-Bank напрямую. +- Финансовые API-запросы frontend проходят backend; browser не получает + T-Bank credential или gRPC client. Загрузка публичных логотипов — отдельный поток. - Backend — NestJS API `/api/v1`, владеющий auth, SQLite-данными и T-Bank gRPC boundary. - OpenAPI backend — единственный контракт frontend; committed client @@ -37,24 +110,32 @@ WealthLedger — локальное приложение для просмотр долговременные агрегаты. Summary списка сохраняет исходные валюты без FX- агрегации и может быть локально недоступна для отдельного счета. +## Предметная модель + +Локальный [User](domain-glossary.md#user) владеет +[Session](domain-glossary.md#session) и одним +[T-Bank connection](domain-glossary.md#t-bank-connection). Через подключение +backend устанавливает доступ к [Brokerage Accounts](domain-glossary.md#brokerage-account). +Идентификатор счёта из URL сам по себе не подтверждает этот доступ. + +[Portfolio](domain-glossary.md#portfolio) отвечает на вопрос «что есть сейчас», +[Account Ledger](domain-glossary.md#account-ledger) — «что исполнено», +[Realized Income](domain-glossary.md#realized-income-analytics) — «какие выплаты +уже получены», будущие события — «что ожидается». Эти представления нельзя +подменять друг другом. [Money](domain-glossary.md#money) сохраняет исходную +валюту и точность; неизвестное значение отличается от нуля. + +Режим [«Все счета»](../features/all-accounts.md) агрегирует доступный состав, +сохраняя происхождение строк; он не создаёт новый брокерский счёт. Обновление +подключения меняет границу финансовых кэшей; перезапуск backend не восстанавливает +прежние in-memory snapshots и cursor continuations из SQLite. + ## Поток auth/session -```mermaid -sequenceDiagram - participant U as User - participant F as Frontend - participant B as Backend - participant D as SQLite - U->>F: Регистрация или вход - F->>B: POST /api/v1/auth/* - B->>D: Создаёт User и Session - B-->>F: Access token + refresh cookie - F->>B: Защищённый запрос с Bearer token - B-->>F: Данные или Problem Details - F->>B: POST /api/v1/auth/refresh при истечении access token - B->>D: Ротирует refresh token - B-->>F: Новый access token + refresh cookie -``` +Вход создаёт локальную сессию; последующие запросы используют access token. +После перезагрузки SPA восстанавливает сессию через refresh-cookie. Схема, +конкурентная ротация, logout и ограничения собраны у одного owner: +[Authentication](../features/authentication.md). ## Поток инвестиционных данных @@ -67,12 +148,22 @@ sequenceDiagram participant T as T-Bank gRPC U->>F: Открывает счета или Portfolio F->>B: GET /api/v1/brokerage-accounts/* (Bearer) - B->>D: Читает T-Bank connection + B->>D: Читает подключение текущего User + B->>T: Проверяет доступ к счёту B->>T: Запрос через server-side gRPC boundary T-->>B: Upstream-данные B-->>F: Нормализованные данные или Problem Details F-->>U: Экран с данными или локальным состоянием ошибки ``` -Подробный статус локальных gates и deferred Prisma e2e находится в -[audit remediation registry](../development/audit-remediation.md). +Схема показывает запрос, которому нужны upstream-данные. Повторные чтения могут +обслуживаться кэшем по правилам конкретной feature. Проверку connection/account +выполняет backend: запрос связывается с пользователем, его текущим подключением +и доступными через него счетами, включая выдачу данных из кэша. +Внешняя ошибка не становится нулевым балансом; допустимость stale snapshot и +локальное восстановление описывает соответствующий сценарий. + +Причины server-side границы, шифрования и retry policy — +[ADR 003](../adr/003-tbank-server-side-grpc-boundary.md), +[ADR 004](../adr/004-encrypted-per-user-tbank-token.md) и +[ADR 005](../adr/005-tbank-upstream-resilience.md). diff --git a/development/README.md b/development/README.md index feb8db8..f128d59 100644 --- a/development/README.md +++ b/development/README.md @@ -1,13 +1,22 @@ -# Разработка +# Сопровождение системы -Работа начинается с задачи пользователя; обязательного OpenSpec/SDD lifecycle нет. -Проектные факты выбирайте через [AI_INDEX](../AI_INDEX.md). +Здесь описаны локальное окружение, согласованность компонентов, проверка +качества и поддержание проектных знаний. Материалы самостоятельны и не +требуют открытия внутренних файлов приложений. -- [Engineering governance](engineering-governance.md) — выбор решения/dependency, risk-based tests, local gates, UI и опасные изменения. -- [Context discipline](agent-token-efficiency.md) — semantic/path routing, sources, recovery и проверка эффективности. -- [Routing eval cases](agent-context-evals.md) — реальные decision cases и counterexamples для conventions. -- [Frontend workflow](frontend-workflow.md) / [backend workflow](backend-workflow.md) — запуск и существующие checks. -- [HTTP contract workflow](runtime-openapi-verification.md) — Nest OpenAPI → snapshot → generated types и контрактная проверка. -- [Дизайн-система](design-system.md) / [UI-каталог](ui-catalog.md) — theme/tokens, реальные components/stories и ограничения. -- [Remediation registry](audit-remediation.md) — найденная запись Audit ID и historical evidence. -- [OpenSpec](openspec-authority-and-lifecycle.md) — optional legacy workflow, только по явному запросу. +- [Локальное окружение](local-workspace.md) — предпосылки работы, первый пользовательский сценарий и диагностика. +- [Frontend](frontend-workflow.md) / [backend](backend-workflow.md) — ответственность приложений и границы проверки. +- [HTTP contract](runtime-openapi-verification.md) — направление генерации OpenAPI и проверка совместимости. +- [Дизайн-система](design-system.md) / [UI-каталог](ui-catalog.md) — визуальные роли, реальные состояния и ограничения примеров. +- [Принципы изменений](engineering-governance.md) — связность системы, риск и доказательства результата. +- [Ведение документации](documentation.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) +сохраняют статусы, исходные адреса и результаты прежних проверок. +[Аудиты](../audits/README.md) — неизменяемые снимки своего времени. Эти материалы +не входят в самостоятельное описание текущей системы; их внутренние ссылки +могут требовать архивного окружения и не являются условием чтения документации. diff --git a/development/agent-context-evals.md b/development/agent-context-evals.md index a03409d..8be85d9 100644 --- a/development/agent-context-evals.md +++ b/development/agent-context-evals.md @@ -1,50 +1,36 @@ -# Проверка agent context +# Проверка полноты проектных знаний -Это небольшой corpus решений для настройки routing. Выполняйте cases по текущим -AGENTS/router/conventions: сначала определите решение и необходимые источники, -затем сравните с ожиданием. Это не unit tests, повторяющие Markdown, и не новый -verification framework. Для проверки изменения правила нужны activating case, -допустимое исключение и unrelated counterexample. +Эти вопросы помогают оценить, объясняет ли документация систему без доступа +к исходникам. Для каждого вопроса важны правильный ответ, существенное +ограничение и отсутствие ошибочного вывода из похожего сценария. ## Cases -| Задача | Необходимый контекст / ожидаемое решение | Что не должно активироваться без новой границы | +| Вопрос | Объяснение | Существенная граница | | --- | --- | --- | -| Изменить props и layout existing finance view | Frontend UI; props остаются presentation, derived collection values — у VM | API, Prisma, architecture, tests без risk/test change | -| Исправить только продуктовую строку/Zod message | UI section «Строки и валидация»; locale/validationMessage, mandatory review | Остальные UI sections, HTTP/structure/testing | -| Добавить page-local фильтр без сети | Frontend architecture + UI; оставить у page, если нет independent capability | Новый shared abstraction/feature ради click; API | -| Добавить business summary component | Architecture + UI; business meaning и existing owner определяют placement | shared только из-за reuse; widgets layer | -| Изменить query refetch/error recovery | Frontend data + testing; cached snapshot/current generation и exact feature source | Backend data при неизменном server behavior | -| Добавить новый HTTP response field | Backend HTTP + общий contract workflow + применимые tests; Nest → snapshot → generated TS, affected client data rules | Второй editable OpenAPI в specs; ручной generated TS | -| Добавить account endpoint с новым RPC | Backend HTTP, architecture при новом use case, security/access, integrations, testing; TbankAccessService/TbankClient | Прямой gRPC из controller; доверие accountId без ownership | -| Исправить только Prisma query без HTTP/auth boundary | Backend data, testing при data-risk; existing PrismaService | Весь HTTP/security guide автоматически | -| Атомарно обновить связанные записи | Data + testing; transaction/constraint у инварианта, isolated DB proof | Generic repository; mock как доказательство реальной atomicity | -| Изменить конкурентный refresh | Security + data + testing; ADR 009; один winner, superseded не отзывает его session | Replay-history/grace window без задачи; новый coordinator | -| Изменить DTO validation error | HTTP + testing + exact error source; configured HTTP case проверяет pipe/filter pair | Success по одному direct controller call/Swagger snapshot | -| Изменить gRPC retry/deadline | Integrations + testing; bounded existing client path, controlled upstream failure | Вложенный retry loop; реальная сеть/secret | -| Добавить безопасное scalar log field | Architecture/logging; проверить allowlist и event | Весь auth guide без sensitive boundary; считать message автоматически redacted | -| Изменить только тест | Testing соответствующего repo; source guides только по проверяемой границе | Все production conventions из-за расширения `.ts` | -| Добавить dependency/adapter для одной задачи | Общий выбор/dependencies; existing owner/platform/library прежде нового кода | Новый interface только для hypothetical reuse | -| Исправить опечатку в общей документации | Docs AGENTS и затронутый source | Любые frontend/backend conventions | -| После UI начала появились mutation/schema/new files | Routing повторяется по staged/unstaged/new, новые границы читаются до решения | Продолжать по первоначальному UI-only scope | -| Отсутствует обязательный security source / источники конфликтуют | Остановить зависимое решение, восстановить факт/уточнить задачу | Догадка из соседнего code или жёсткий лимит на число документов | +| Кто подтверждает доступ к брокерскому счёту? | [Обзор](../architecture/system-overview.md#предметная-модель) | Сессия подтверждает пользователя, но идентификатор счёта не доказывает доступ | +| Как меняются финансовые данные при замене token? | [Подключение](../features/tbank-connection.md#граница-финансового-кэша) | Данные прежнего поколения не становятся сохранённым снимком нового подключения | +| Что происходит при конкурентном refresh? | [Authentication](../features/authentication.md), [ADR 009](../adr/009-concurrent-refresh-rotation-policy.md) | Один победитель; заменённый token не отзывает его сессию сам по себе | +| Как восстанавливается интерфейс после ошибки обновления? | [UI resilience](../features/frontend-ui-resilience.md) | Фоновая ошибка отличается от первой загрузки и смены пользователя | +| Чем будущая выплата отличается от дохода? | [События](../features/brokerage-account-upcoming-events.md), [доходы](../features/brokerage-account-income-analytics.md) | Прогноз не подтверждает личное зачисление | +| Можно ли складывать разные валюты или заменить неизвестное нулём? | [Money](../architecture/domain-glossary.md#money) | Точность, валюта и отсутствие значения сохраняются отдельно | +| Откуда рейтинг и доходность облигации? | [Bond detail](../features/bond-detail.md) | Рейтинг эмитента не заменяет рейтинг выпуска; рыночная доходность не равна личному результату | +| Доказывает ли OpenAPI правильность ошибки запроса? | [HTTP API](../architecture/http-api.md#error-contract) | Равенство схем не доказывает полный HTTP-поток | +| Что гарантирует проверка хранения данных? | [Backend](backend-workflow.md#локальная-проверка) | Проверка с подставными ответами не доказывает атомарность реальной базы | +| Как ограничиваются обращения к внешнему сервису? | [T-Bank](../features/tbank-connection.md#конфигурация-и-ограничения) | Повторы, очередь и лимит ограничены; TLS остаётся включённым | +| Защищает ли фильтрация логов любое сообщение? | [ADR 008](../adr/008-pino-backend-logging.md) | Секрет в свободном тексте требует отдельной проверки | +| Есть ли восстановление пароля по письму? | [Ограничения auth](../features/authentication.md#limitations) | Пример будущего сценария не является поставленной возможностью | ## Что измерять -Для startup/working-set измерения перечислите ровно прочитанные файлы/sections, -инструмент подсчёта и unit (символы или tokenizer). Required source нельзя -исключать только ради хорошего числа. Число доступных файлов на диске не является -стоимостью конкретной задачи. - -Для реальной first-pass оценки выполните одинаковые задачи с одинаковой моделью, -version/tools и comparable prompts: прежний context, только новые indexes, -затем полный routing. Считайте принятие без substantive correction, iterations, -ошибки placement/contract/security, лишние abstractions и пропущенные sources. -Routing review и зелёные application tests сами по себе такого улучшения не доказывают. +Полезное объяснение позволяет найти ответ, назвать ограничение и связать его +с другими сценариями. Оцениваются неверные выводы о доступе, деньгах, полноте +данных и источниках, а также число уточнений до правильного понимания. +Время поиска и объём чтения имеют смысл только вместе с точностью ответа. ## Изменение правил -Если нужный source не прочитан — исправьте trigger. Если source прочитан, но -не разрешает выбор — уточните decision/safe path/example. Если дефект -детерминирован — используйте подходящий existing test/linter. Не добавляйте -новый always-on paragraph для каждого отдельного случая. +Если знание есть, но его трудно найти, уточняется навигация. Если документ +найден, но не объясняет различие понятий или ограничение, уточняется объяснение. +При конфликте источников сохраняется отметка о ручном решении; догадка не +подменяет установленный факт. diff --git a/development/agent-token-efficiency.md b/development/agent-token-efficiency.md index 3803df5..ad37581 100644 --- a/development/agent-token-efficiency.md +++ b/development/agent-token-efficiency.md @@ -1,62 +1,42 @@ -# Context discipline +# Контекст знаний о системе -Цель — получить правила, которые меняют текущее решение, и не читать остальные -заранее. Это процедура прямой работы по задаче, без обязательных SDD artifacts, -ролей, approval fingerprints или подсчёта документов как delivery gate. +Цель навигации — дать читателю знания, необходимые для конкретного вопроса, +без предварительного чтения всей документации. Самостоятельное объяснение +связывает понятия, сценарии и ограничения, а не воспроизводит устройство кода. ## Routing context -1. Определи требуемое поведение, тип решения и affected repos до supporting reads. -2. Прочитай root/local AGENTS, затем CONVENTIONS и SOURCES affected code repo. -3. Объедини сработавшие semantic, path и boundary triggers. Путь — сигнал ответственности, а не повод загружать всё по расширению `.tsx`/`.ts`. -4. Прочитай выбранные conventions или явно указанные sections до зависимого решения. При domain/API/security вопросе открой точный source через [docs index](../AI_INDEX.md). -5. Проследи ближайший существующий owner и подходящие код/тесты. Его поведение не заменяет canonical fact. -6. Повтори routing при новой потребности и по финальным staged, unstaged и новым файлам. Содержимое работы важнее первоначального названия задачи. - -Первый проход работает без diff. Auth + refresh, например, активирует API, -security и testing вместе. Отображение query state без изменения запросов не -добавляет transport rules. Docs-only опечатка не требует frontend/backend conventions. -Обычно хватает нескольких тем; это наблюдение, не лимит. Обязательный source -нельзя пропускать ради числа файлов или token budget. - -Новая граница внутри задачи требует дочитать правила до работы. Расширение -самой задачи сначала сообщи пользователю; уже данное разрешение не запрашивай -повторно. Чужой concurrent diff учитывай при разграничении ownership. +[Тематический индекс](../AI_INDEX.md) направляет к нужному предмету: +аутентификация связана с безопасностью и сессиями, денежный показатель — +с его происхождением и валютой, ошибка интерфейса — с восстановлением данных. +Вопрос о внешнем виде не требует автоматически изучать хранение данных. +При обнаружении новой существенной границы расширяется и нужный контекст. ## Источники и конфликты -Прямое указание пользователя определяет задачу. Текущий executable HTTP contract -генерируется backend; docs описывает проектные факты/решения; conventions задают -выбор реализации; code показывает существующие аналоги. Generic framework advice -используется после проектных источников. Research содержит рекомендации, которые -нужно сверить с actual code/config, а не автоматически превращать в новый контракт. +Публичный HTTP-контракт описывается OpenAPI backend; документация объясняет +систему, ADR сохраняют причины решений, исследования предлагают варианты. +Существующее поведение не отменяет согласованное требование, а рекомендация +из исследования не становится принятым решением автоматически. -Если задача намеренно меняет контракт, обновляй его source и зависимые материалы -согласованно. Missing input, противоречие security/data правил или неоднозначный -контракт блокируют только зависимое решение до установления фактов/уточнения. -История чата, случайный аналог и предполагаемый путь не заменяют источник. +При расхождении возможны устаревшее объяснение, дефект реализации или +незавершённое изменение. Неустановленный смысл, особенно в безопасности, +данных и публичном контракте, явно отмечается для ручного решения. +Карта ответственности за знания — [ведение документации](documentation.md). ## Recovery и handoff -Для продолжения достаточно текущей задачи, сделанных изменений, remaining work, -точных checks/results и blockers. Не создавай параллельную копию требований, -Design, state registry или обязательный handoff-файл. - -В новом контексте восстанови необходимые sources; в том же переиспользуй уже -прочитанные неизменившиеся inputs. Supporting reads добавляй по возникшему вопросу. -Git status/diff показывают staged и unstaged; новые файлы учитывай отдельно. -Relevant ignored/workspace-owned inputs (например `.env`, root AGENTS) не -теряются только потому, что Git их не показывает. Секреты в evidence не печатай. +Продолжение работы опирается на ожидаемый результат, выполненные изменения, +оставшиеся вопросы, результаты проверок и ограничения. Дублирование требований +в нескольких независимых пересказах создаёт риск расхождения. Исторические +проверки сохраняют своё значение только для того состояния системы, в котором +они выполнялись; секреты не являются допустимым содержимым свидетельств. ## Проверка эффективности -Используй [набор routing cases](agent-context-evals.md) для настройки новых правил. -Проверяй activating case, safe exception и unrelated counterexample по реальным -инструкциям. Source links и gates дополняют эту проверку, но не доказывают first-pass quality. - -Сравнивай startup context и реально выбранные sections, фиксируя способ измерения. -Не объявляй меньшее число файлов или зелёный application test улучшением качества -агента. Для first-pass acceptance нужны одинаковые реальные задачи/модель/tools, -наблюдённые ошибки ownership/contract/security и число исправлений. -Добавляй convention по повторяющейся decision error; уточняй trigger при missed -source; deterministic regression отдавай существующему test/linter, если он подходит. +[Проверочные вопросы](agent-context-evals.md) показывают, можно ли получить +правильную модель системы и отличить близкие понятия. Качество оценивается по +верности ответа, найденным ограничениям и необходимости дополнительных уточнений. +Число документов, их краткость или успешная проверка Markdown сами по себе +не доказывают полезность знания. Сравнение результатов требует одинаковых +вопросов и сопоставимых условий. diff --git a/development/backend-workflow.md b/development/backend-workflow.md index d1723ac..577228a 100644 --- a/development/backend-workflow.md +++ b/development/backend-workflow.md @@ -1,34 +1,31 @@ -# Рабочий процесс backend +# Backend: ответственность и проверка ## Предварительные условия -Backend требует Node 22. Создайте `backend/.env` на основе -`backend/.env.example` и замените placeholder-значения `JWT_SECRET` и -`TOKEN_ENCRYPTION_KEY`; token T-Bank в этот файл не записывается. +Backend обслуживает публичный HTTP API, локальные сессии, хранение данных +и интеграции с внешними источниками. Локальная поставка использует Node 22 +и SQLite. Окружению нужны адрес frontend, адрес базы, секрет подписи сессий +и отдельный ключ шифрования подключения. T-Bank token вводится пользователем +через настройки и не является общим секретом окружения. -```bash -cd backend -cp .env.example .env -npm ci -``` +Назначение параметров, согласование адресов и сохранение данных описаны в +[локальном окружении](local-workspace.md). ## Локальная проверка -Canonical gate не изменяет versioned-файлы: +Проверка backend объединяет статический анализ, проверку типов приложения +и тестов, автоматические сценарии и сборку. Она не изменяет исходные материалы; +автоматическое исправление форматирования не считается доказательством проверки. +Диагностика покрытия доступна отдельно, общего числового порога для backend нет. -```bash -npm run verify -``` +[Сравнение OpenAPI](runtime-openapi-verification.md) проверяет соответствие +публичного контракта ожиданиям frontend. Валидация запросов, cookie, доступ +и ошибки требуют полного HTTP-потока; целостность и конкурентные изменения +данных — проверки с изолированной реальной базой. -Он последовательно запускает lint, application/test TypeScript, Jest и build. `npm run lint:fix` — явная -записывающая команда, а `npm run test:cov` — отдельная диагностика без общего -числового threshold. Runtime OpenAPI сверяется с frontend snapshot из workspace: - -```bash -cd backend -npm run test:openapi-contract -``` - -`npm run test:e2e` временно не является passing gate: Prisma schema engine -останавливает migration до Jest. Точная диагностика и trigger пересмотра -зафиксированы в [audit remediation registry](audit-remediation.md). +**Известное ограничение:** движок Prisma 7.9.1 при применении миграций к новой +SQLite-базе возвращает `Schema engine error` до запуска сквозных тестов. +Это воспроизведено и при проверке локального окружения 12 сентября 2026 года. +Успех остальных проверок не подтверждает прохождение этой границы. Условие +пересмотра — стабильная версия с тремя последовательными успешными применениями +миграций на новых базах под Node 22. diff --git a/development/design-system.md b/development/design-system.md index 8bd855f..d9dcaeb 100644 --- a/development/design-system.md +++ b/development/design-system.md @@ -1,135 +1,84 @@ # Дизайн-система WealthLedger -Общий фундамент находится в -[`frontend/src/shared/theme`](../../frontend/src/shared/theme/index.ts). -Его public API экспортирует `theme`, `cssVariablesResolver`, `colorRoles`, `designTokens` и -подключает базовые CSS. App владеет MantineProvider; Storybook подключает свой -изолированный provider к тому же API. Тема и значения не копируются в preview. -Оба runtime используют светлую схему; dark theme не заявлена как поддерживаемая. +Приложение и UI-каталог используют одну визуальную основу: цветовые роли, +типографику, интервалы, радиусы, тени и адаптивные размеры. Общая тема основана +на Mantine; её значения не дублируются отдельно в каталоге. +Поддерживается светлая схема, тёмная тема не заявлена. ## Тема и токены -[`tokens.json`](../../frontend/src/shared/theme/tokens.json) — единственный -редактируемый источник опубликованных токенов WealthLedger. Он содержит восемь -палитр по десять оттенков: blue, gray, dark, red, green, yellow, grape и orange; -базовые white/black, оттенки по умолчанию, семантические роли с описаниями, -типографику, интервалы, радиусы, тени и breakpoints. Существующие значения -приняты из темы приложения и Mantine 9.5.1; теперь ими владеет JSON. Default -radius остаётся md, font family заголовков совпадает с общей. +Опубликованная система включает восемь палитр по десять оттенков: blue, gray, +dark, red, green, yellow, grape и orange; базовые white/black, оттенки +по умолчанию и семантические роли. Общая гарнитура применяется и к заголовкам, +стандартный радиус — средний. Настройки являются доверенной локальной частью +приложения; загрузка внешних или пользовательских токенов не предусмотрена. -[`theme.ts`](../../frontend/src/shared/theme/theme.ts) напрямую адаптирует JSON -в Mantine. Файл — доверенная конфигурация репозитория; приложение не загружает -внешние или пользовательские токены и не проверяет JSON runtime-схемой. -Размеры px преобразуются native `rem`, сохраняя `--mantine-scale`, тени собираются -из слоёв с `rgba`, а breakpoints остаются em без масштабирования. Неопубликованные -палитры и настройки поведения компонентов остаются defaults Mantine. -Небольшой [`theme.test.ts`](../../frontend/src/shared/theme/theme.test.ts) -проверяет преобразования адаптера; корректность изменений локального JSON -проверяется при review, сборке и проверке внешнего вида. - -Используйте props Mantine (`gap="md"`, `p="xl"`, `radius="md"`, `shadow="xs"`) -или `--mantine-spacing-*`, `--mantine-radius-*` и другие native CSS variables. -[Foundations](http://localhost:6006/?path=/story/design-system-foundations--colors) -читает эффективную тему и описания из JSON, показывает фактические значения -и образцы. Обновление Mantine не заменяет принятые шкалы автоматически и требует -повторной проверки адаптера и внешнего вида приложения. - -`colorRoles` связывает смысл и native Mantine reference; resolver создаёт -`--wl-color-`, ссылающийся на Mantine variable, без повторного hex-значения. - -| Role | Native reference | Применение | +| Роль | Цветовая основа | Применение | | --- | --- | --- | | canvas | gray.2 | Фон приложения | -| surface | white | Paper и формы в светлой схеме | +| surface | white | Поверхности и формы | | subtle | gray.0 | Приглушённая поверхность | -| text | dark.7 | Основной текст страницы | -| muted | gray.6 | Вторичный текст (dimmed светлой схемы) | +| text | dark.7 | Основной текст | +| muted | gray.6 | Вторичный текст | | border | gray.3 | Граница поверхности | | primary | blue filled | Основное действие и активная навигация | | info | blue.6 | Информация | -| positive | green.6 | Положительный результат с текстом/знаком | -| negative | red.6 | Ошибка или отрицательный результат с текстом/знаком | -| warning | yellow.6 | Предупреждение, не мелкий текст на белом | +| positive | green.6 | Положительный результат с текстом или знаком | +| negative | red.6 | Ошибка или отрицательный результат с текстом или знаком | +| warning | yellow.6 | Предупреждение; не мелкий текст на белом | -Базовые CSS используют canvas/text aliases. Остальные роли доступны для -постепенного использования; существующие локальные Mantine references не -переписаны массово. Цвет не заменяет подпись статуса. Палитра не гарантирует -доступный контраст произвольных сочетаний: для текста и фона проверяйте контекст. -Grape и orange используются Product; предметные значения задают их FSD-владельцы. +Цвет не заменяет подпись или знак. Палитра не гарантирует доступный контраст +произвольного сочетания текста и фона. Дополнительные grape/orange и отдельные +оформления карточек сохраняют предметный смысл соответствующего экрана; +они не образуют универсальную финансовую классификацию всей системы. -Особые card gradients/shadows, размеры страницы и финансовые цвета CSS остаются -у своих FSD-владельцев. Это сохранение существующего UI, а не завершённый редизайн -или миграция всех literals в tokens. Новую общую шкалу стоит вводить только при -реальном повторном использовании, если native Mantine scale не подходит. +Общие шкалы основаны на принятых значениях приложения и Mantine 9.5.1. +Обновление библиотеки не заменяет их автоматически. Масштабируемые размеры, +тени и адаптивные границы требуют сохранения единиц и визуальной проверки. +Отдельные градиенты, тени и размеры экранов остаются локальными визуальными +решениями; переход всех существующих значений в общую шкалу не завершён. ## Переносимый JSON -Файл использует собственный формат WealthLedger `formatVersion: 1`, без runtime -imports, CSS variables или исполняемого кода. Это контракт данных, а не заявление -о совместимости с DTCG или importer конкретной системы. +Опубликованные токены представлены в собственном формате WealthLedger, +версия 1. Это данные без исполняемого кода и зависимостей от runtime приложения. +Совместимость с DTCG или импортом конкретного инструмента не заявлена. | Данные | Представление | | --- | --- | -| Цвета | Hex; palette — массив оттенков 0–9; base — именованные white/black. | -| Семантическая роль | `color`, `shade`, `description`; ссылка на palette или base. Для palette `shade: null` означает `primaryShade.light`, для base оттенок не нужен. | -| Типографика | CSS font stacks; размеры и headings в px, веса числовые, line heights без единиц. | -| Интервалы и радиусы | Именованные `values` xs–xl с `unit: "px"`; `radius.default` выбирает имя шкалы. | -| Тени | Упорядоченные слои: offsetX/offsetY/blur/spread в px, ссылка `color` на base и opacity 0–1. | -| Breakpoints | Именованные `values` xs–xl с `unit: "em"`, не зависят от масштаба UI. | +| Цвета | Hex, палитры оттенков 0–9 и именованные базовые цвета | +| Семантические роли | Ссылка на цвет и оттенок, описание; отсутствие оттенка палитры означает основной оттенок светлой схемы | +| Типографика | Наборы шрифтов, размеры в px, числовые веса и безразмерная высота строки | +| Интервалы и радиусы | Именованные шкалы xs–xl в px и выбранный радиус по умолчанию | +| Тени | Упорядоченные слои: смещения, размытие и растяжение в px, базовый цвет и непрозрачность | +| Адаптивные границы | Именованные размеры xs–xl в em, независимые от масштаба интерфейса | -Не используйте для палитр специальные имена Mantine `dimmed` и `bright`; -`white` и `black` принадлежат base colors. Задавайте вес шрифта в диапазоне -CSS 1–1000. Это правила редактирования формата, а не runtime-проверки. - -Внешний потребитель читает raw JSON напрямую. Например, из workspace root, -без Node, React или Mantine: - -```python -import json -from pathlib import Path - -tokens = json.loads(Path("frontend/src/shared/theme/tokens.json").read_text()) -assert tokens["formatVersion"] == 1 -colors = tokens["color"] -role = colors["roles"]["primary"] -shade = role["shade"] -if shade is None: - shade = colors["primaryShade"]["light"] -primary_hex = colors["palettes"][role["color"]][shade] -``` - -`shared/theme/index.ts` предназначен для frontend: он подключает CSS и Mantine -runtime. При интеграции с другой системой сопоставьте единицы, font stacks, -слои теней и semantic references с её форматом; парсер JSON сам этого не делает. -Несовместимое изменение требует новой версии контракта. Внешняя интеграция, +Перенос в другую систему требует сопоставления единиц, шрифтов, слоёв теней +и семантических ссылок. Само чтение JSON не обеспечивает такой совместимости. +Несовместимое изменение формата требует новой версии; внешняя интеграция, генератор артефактов и публикация пакета пока не реализованы. ## Общие компоненты -Public API — [`shared/ui`](../../frontend/src/shared/ui/index.ts). Минимальный -каталог включает две существующие реализации, реально используемые приложением: - -| Компонент | Контракт и правила | +| Представление | Поведение и ограничения | | --- | --- | -| StateView | Empty/error с title, text, необязательным action; error получает role alert. Кнопке нужен доступный текст; не использовать для initial loading. | -| LoadingSkeleton | Общая status/busy-область с доступной label; caller задаёт только декоративные shapes без фокуса. Структура skeleton принадлежит section/page. | +| Пустой результат или ошибка | Заголовок, пояснение и необязательное действие; ошибка объявляется вспомогательным технологиям, кнопка имеет доступное название | +| Загрузка | Доступная область состояния занятости с пояснением и декоративными фигурами без фокуса; структура отражает ожидаемый экран | +| Действия | Основной, вторичный и вспомогательный варианты; состояния выполнения и недоступности | -Они не импортируют API, auth, счета или позиции. Не создаются wrappers над всеми -примитивами: для действий используется Mantine Button с общей темой. В каталоге -показаны filled для основного действия, default для вторичного/retry и subtle -для вспомогательного, а также loading и disabled. Provider не меняет defaults -Button или Paper ради каталога. Loading действия не заменяет loading страницы; -при refetch сохраняются данные и показывается локальное состояние. +Общие представления не получают финансовые данные и не управляют +аутентификацией. Состояние выполнения действия отличается от загрузки всей +страницы. При фоновом обновлении доступные данные сохраняются с локальным +индикатором; правила восстановления описаны в +[UI resilience](../features/frontend-ui-resilience.md). -Route-aware AppShell не входит в заявленный общий набор. Бизнес-компоненты -и композиции остаются у entities/pages и показываются в Product; витрина не -определяет FSD ownership. Подробности запуска, взаимодействий и coverage — -[UI-каталог Storybook](ui-catalog.md). +Каркас с продуктовой навигацией не входит в заявленный общий набор. +Предметные карточки и композиции представлены в [UI-каталоге](ui-catalog.md). ## Границы доказательств -Каталог использует synthetic fixtures, локальные Actions и MemoryRouter, без -application API/auth/storage runtime. Dependency-cruiser проверяет FSD и -транзитивную runtime-изоляцию stories. Browser проверяет виды, размеры, focus и -действия. Эти примеры не доказывают API recovery и прохождение инвестиционного -сценария. Продуктовый пилот остаётся отдельным change и здесь не проводился. +Каталог использует вымышленные данные и локальные действия без входа, +сохранённого пользовательского состояния и application API. Проверки отделяют +его от исполнения продуктовых запросов. Вид, размеры, фокус и взаимодействия +проверяются в браузере; каталог не доказывает восстановление реального API +или прохождение инвестиционного сценария. diff --git a/development/documentation.md b/development/documentation.md new file mode 100644 index 0000000..2eec613 --- /dev/null +++ b/development/documentation.md @@ -0,0 +1,88 @@ +# Ведение документации + +Документация объясняет систему независимо от размещения исходного кода. +За актуальность затронутого объяснения отвечает автор изменения предметной +области; проверка сопоставляет изложение с фактическим поведением и принятыми +решениями. Ответственность подсистем описана в [карте возможностей](../features/README.md). + +## Владельцы фактов + +| Тип знания | Где объясняется | Граница достоверности | +| --- | --- | --- | +| Устройство и взаимодействие системы | [Обзор](../architecture/system-overview.md) | Описывает существующие подсистемы, данные и потоки | +| Наблюдаемое поведение | [Возможности](../features/README.md) | Сценарий, инвариант, ошибка и ограничение объясняются здесь полностью | +| Значение терминов | [Глоссарий](../architecture/domain-glossary.md) | Одно определение, ссылки из связанных сценариев | +| HTTP-контракт | [HTTP API](../architecture/http-api.md), публичный OpenAPI backend | Объяснение семантики не создаёт второй независимо редактируемый wire contract | +| Причины значимого решения | [ADR](../adr/README.md) | Контекст, решение и последствия относятся к указанному статусу решения | +| Окружение и восстановление | [Локальное окружение](local-workspace.md) | Предпосылки и ограничения описаны без знания устройства репозиториев | +| Принципы сопровождения | [Engineering governance](engineering-governance.md) | Объясняют согласованность и доказательства результата | +| Гипотезы и внешние наблюдения | [Исследования](../research/README.md) | Рекомендация не означает реализованную возможность | +| Результаты прежних проверок | [Исторические свидетельства](README.md#исторические-свидетельства) | Действительны для состояния и условий своего времени | + +Поведенческие требования и предложения изменений могут отдельно согласовываться +в OpenSpec. Их роль описана [здесь](openspec-authority-and-lifecycle.md); +понимание текущего сценария не требует доступа к хранилищу спецификаций. + +При конфликте возможны устаревший текст, нарушение договорённости реализацией +или незавершённое изменение. Текущее поведение не отменяет согласованное +требование автоматически. Неустановленный смысл явно отмечается для ручного +решения; особенно это важно для безопасности, данных и публичного контракта. + +## Куда поместить знание + +Объяснение принадлежит существующему документу о соответствующей области. +Понятие определяется в глоссарии; сценарий связывает его с поведением и +ограничениями; ADR сохраняет причину выбора; исследование — ещё не принятый +вариант и его источники. Новый документ нужен для самостоятельного вопроса, +которому нет подходящего места. Навигация делает его доступным из индекса. + +Один факт имеет одно полное объяснение, на которое ссылаются остальные страницы. +Ссылка внутри документации уточняет контекст, а не скрывает необходимое условие +понимания за внутренним файлом другого репозитория. + +## Содержание страницы + +Связное объяснение содержит назначение, ответственность компонентов, +предпосылки и инварианты, основной поток, ошибки, восстановление и ограничения. +Диаграмма нужна, когда помогает понять взаимодействие. Это ориентир, а не +обязательный набор пустых разделов. + +Вместо имён классов, внутренних вспомогательных функций и настроек разработки +излагается их архитектурный или функциональный смысл. Публичные API, единицы +показателей, внешние системы и принятые технологические решения остаются +частью объяснения, если нужны читателю. Детали разработки и инструкции +инструментам не входят в описание системы. + +Статус понятен из вступления или индекса: текущее поведение, объяснение решения, +исследование или история. Дата наблюдения не гарантирует актуальность спустя +время. Для замены принятого решения сохраняются его прежний контекст и ссылка +на последующее решение. + +## Обновление вместе с кодом + +| Что изменилось в системе | Какое знание затронуто | +| --- | --- | +| Сценарий, инвариант, ошибка или восстановление | Объяснение возможности и её ограничений | +| Ответственность, внешний источник или поток данных | Обзор, схема и причина значимого решения | +| Значение понятия | Глоссарий и связанные сценарии | +| Окружение, диагностика или граница проверки | Материалы сопровождения | +| Новая возможность | Карта возможностей и тематический индекс | +| Только внутреннее устройство без изменения описанных знаний | Изменение документации может не требоваться | + +Изменения нескольких компонентов относятся к общей задаче; объяснение +поставленного результата не должно обещать ещё отсутствующую часть. +Смысловая согласованность важнее совпадения названий задач и формальных отметок. + +## Проверка + +Проверка охватывает Markdown, внутренние ссылки и якоря, доступность страниц +через навигацию и чтение основной документации без соседних репозиториев. +Внешние URL и HTML-ссылки не проверяются правилом относительных ссылок. +Исторические аудиты, журнал устранения замечаний и свидетельства завершения +сохраняют исходные адреса отдельно от основной книги. + +Смысловая проверка подтверждает, что читатель понимает ответственность, +инвариант и ограничение; будущая возможность не выдана за существующую; +API и архитектура не изменены редакторским пересказом. Непонятный смысл +внешней ссылки отмечается для ручной проверки, а не восполняется догадкой. +Успешная синтаксическая проверка не доказывает актуальность знания. diff --git a/development/engineering-governance.md b/development/engineering-governance.md index bbc5911..e480ac9 100644 --- a/development/engineering-governance.md +++ b/development/engineering-governance.md @@ -1,103 +1,82 @@ -# Engineering governance +# Принципы изменения и проверки системы -Это общий owner решений о scope, dependencies, testing и проверке результата. -Работа начинается с задачи пользователя и выполняется напрямую. OpenSpec/SDD -не обязателен; прежняя [процедура](openspec-authority-and-lifecycle.md) доступна -только по явному запросу. Исторические artifacts не переписываются. +Ожидаемый результат определяется задачей пользователя. Сопровождение сохраняет +ответственность подсистем, безопасность, точность данных и проверяемость результата. +OpenSpec остаётся [необязательным средством согласования](openspec-authority-and-lifecycle.md); +исторические свидетельства не становятся обязательной процедурой новой задачи. ## Выбор реализации -Читай этот раздел при новой abstraction/dependency, refactor, multi-module -изменении или неоднозначном ownership. +Связное поведение принадлежит ответственной подсистеме. Существующая возможность +используется повторно, если сохраняет нужный смысл и инварианты. Новая граница +оправдана самостоятельной ответственностью, общей потребностью нескольких +потребителей либо необходимостью отделить доступ и целостность данных. -1. Требуемое поведение уже существует — используй его. -2. У поведения есть подходящий owner/helper — расширь его, сохранив связность. -3. Задачу решает платформа, стандартная библиотека или принятая dependency — используй её. -4. Иначе добавь небольшую реализацию там, где принадлежит инвариант. -5. Если текущая структура мешает правильному ownership, сделай необходимый связный refactor с проверкой сохранения поведения. - -Оптимизируй ясность всей системы. Число файлов/строк само по себе не определяет -качество: coherent refactor лучше дублированного guard в нескольких callers. - -**Безопасный путь:** при неопределённом будущем reuse оставь код у ближайшего -подходящего владельца. Выделение оправдано текущей общей ответственностью, -несколькими реальными consumers или необходимой trust/transaction boundary. -Один consumer не запрещает модуль с самостоятельным инвариантом, но не оправдывает -interface/factory/registry только для воображаемой расширяемости. - -Примеры: фильтр одного экрана остаётся page-local; чистое денежное преобразование -использует существующий decimal helper; запрос Prisma не требует generic repository. -Security validation, доступность, error handling и защита данных сохраняются -даже в маленькой реализации. +Простота оценивается по ясности всей системы и стоимости сопровождения, +а не по размеру изменения. Предполагаемая будущая возможность сама по себе +не оправдывает новую подсистему. Упрощение сохраняет валидацию, доступность, +обработку ошибок и защиту от потери данных. ## Dependencies -Перед новой dependency проверь installed stack и существующий helper. Если -подходящего решения нет, оцени поддерживаемую библиотеку, совместимость, -maintenance/security cost и точную недостающую возможность. Собственный код -выбирай по совокупной стоимости, а не ради отсутствия dependency любой ценой. - -Версии и scripts принадлежат package.json/lockfile. Frontend сохраняет exact -versions; backend использует свою текущую policy. Transitive package не считается -прямо принятой dependency. Не меняй весь lockfile или инструментарий ради -устаревшей рекомендации в Markdown. Новые сервисы, CI и browser mocking требуют -задачи, в которой они действительно нужны. +При выборе внешней зависимости учитываются текущая потребность, совместимость, +поддержка, безопасность и стоимость сопровождения. Использование принятой +платформы может быть проще собственной реализации; зависимость ради одной +воображаемой будущей возможности не является самостоятельной потребностью. +Новый сервис или общая инфраструктура должны решать актуальную задачу. ## Risk-based testing -Автоматический тест обязателен для изменённого security/privacy поведения, -денежных вычислений, data integrity/migration, concurrency, публичного контракта, -критического journey и подтверждённой регрессии. +Изменения безопасности и приватности, денежных расчётов, целостности данных, +миграций, конкурентного доступа, публичного контракта, критического сценария +и подтверждённой регрессии требуют автоматической проверки соответствующего +риска на наблюдаемой потребителем границе. -Выбирай самую дешёвую надёжную границу, наблюдаемую потребителем. Тест нужен -для собственного branching, преобразования или инварианта; не для каждого -DTO, декларативной конфигурации, thin adapter или поведения библиотеки. -Меняющийся тест тоже активирует testing convention, даже без production diff. -Frontend coverage — anti-regression constraint; backend общего threshold не имеет. -Не пиши тесты только ради процента или повторения Markdown. +Статические проверки не доказывают финансовую или функциональную корректность. +Покрытие frontend служит ограничителем регрессий; для backend общего числового +порога нет. Проверки поведения библиотеки или проценты сами по себе не заменяют +проверку собственного инварианта системы. ## Проверка результата -В итерации используй targeted check из применимого convention. Перед завершением -запусти `npm run verify` каждого изменённого code/docs repo под его `.nvmrc` -после lockfile install. Для HTTP contract — дополнительно -[contract checks](runtime-openapi-verification.md). Общего CI сейчас нет. +Результат оценивается по исходной задаче: сохранены ли смысл данных и контракты, +правильно ли разграничен доступ, учтены ли ошибки и доступность интерфейса. +Проверки приложений и документации дополняются +[согласованием HTTP-контракта](runtime-openapi-verification.md), если он меняется. +Общего CI сейчас нет. -Проверь actual staged/unstaged/new scope и результат против задачи: ownership, -сохранение данных/контрактов, ошибки, доступность, отсутствие лишних abstractions. -Неизменившийся успешный gate можно использовать повторно, если известны команда, -его полный relevant input scope и неизменные prerequisites; при сомнении выполни -его снова. Auto-fix не заменяет non-mutating gate. +Успешная проверка применима к тому состоянию системы и тем предпосылкам, +на которых выполнена. При их изменении старое свидетельство не доказывает +новый результат. Автоматическое исправление материалов и проверка без их +изменения — разные действия. -В ответе достаточно: что поставлено, как проверено, какие ограничения остались. -Не создавай role records, fingerprints всех repositories, Completion Evidence -или delivery commits только ради процесса. Существующие рабочие изменения -других задач сохраняются. Нужный review не заменяется количеством checkboxes. +[Влияние на документацию](documentation.md#обновление-вместе-с-кодом) входит +в оценку результата: описанные понятия, сценарии, решения и ограничения +обновляются вместе с системой. Отсутствие влияния может быть обоснованным +результатом. Отчёт о поставке различает выполненное, проверенное и остающиеся +ограничения; количество формальных отметок не заменяет смысловую проверку. ## Guardrails и local gates -- **Автоматический invariant:** существующий deterministic checker блокирует нарушение в фактическом scope с документированными исключениями. -- **Обязательное semantic правило/процедура:** исполнитель и reviewer проверяют решение; отсутствие checker не переводит i18n, validation или authorization в пожелание. -- **Guidance:** необязательное предпочтение, например эстетический выбор между эквивалентными вариантами. - -Не обещай enforcement, отсутствующий в config/tests. Новая автоматическая проверка -оправдана реальным риском или повторяющимся дефектом, а не возможностью написать -ещё один checker. Механические правила принадлежат tools; выбор правильного owner -и полезного теста остаётся semantic judgement. +Автоматические проверки гарантируют только свойства, охваченные их областью. +Смысловые требования — например, правильный владелец данных, авторизация, +локализация и полезность выбранных проверок — требуют отдельной оценки. +Предпочтение между равноценными вариантами отличается от обязательного +инварианта. Новая автоматизация оправдана реальным риском или повторяющимся +дефектом; её наличие само по себе не является целью. ## UI-решения -Для изменяемого UI определи ожидаемые экраны, states, interactions и responsive -поведение из задачи. Используй существующий [каталог](ui-catalog.md) и -[дизайн-систему](design-system.md). Сложный или неоднозначный вариант покажи -пользователю в конкретной форме до дорогой реализации; простой согласованный -вариант делай сразу. Визуальные материалы нужны по задаче, не как обязательный -SDD-пакет. Сборка Storybook не доказывает доступность или визуальное соответствие. +Ожидаемые экраны, состояния, взаимодействия и поведение на разных размерах +экрана определяются пользовательским сценарием. [Каталог](ui-catalog.md) +и [дизайн-система](design-system.md) показывают существующую визуальную основу. +Неоднозначный вариант можно оценить по конкретному примеру до реализации. +Успешная сборка каталога не доказывает доступность и визуальное соответствие. ## Изменение опасного контракта -При public/security/data/billing или труднообратимом выборе заранее уточни -существенную неопределённость. Для миграции определи trigger отката, действия, -сохранность уже созданных данных и проверку восстановления. Сначала подготовь -конкретное reviewable решение; обычные обратимые edits не требуют ритуального approval. -Источники и конфликтующие версии разрешай через [context discipline](agent-token-efficiency.md). +Для публичного контракта, безопасности, данных, платежей и труднообратимого +изменения существенная неопределённость разрешается до применения решения. +Для миграции важны условия отката, сохранность уже созданных данных и проверка +восстановления. Порядок разрешения конфликтов знаний описан в +[контексте системы](agent-token-efficiency.md#источники-и-конфликты). diff --git a/development/frontend-workflow.md b/development/frontend-workflow.md index a5ff87d..1c6f48b 100644 --- a/development/frontend-workflow.md +++ b/development/frontend-workflow.md @@ -1,51 +1,34 @@ -# Frontend Workflow +# Frontend: ответственность и проверка -Frontend требует Node 22. В `frontend/` чистая установка и полный локальный quality gate выполняются так: +Frontend предоставляет пользовательские экраны, навигацию и представление +финансовых данных. Он получает данные только через публичный API backend; +credentials T-Bank остаются на сервере. Состав приложения и потоки показаны +в [обзоре системы](../architecture/system-overview.md). -```bash -nvm use --silent -npm ci -npm run verify -``` +## Совместимость с backend -`verify` проверяет OpenAPI drift, TypeScript, Biome, один coverage-backed запуск Vitest, Knip, -FSD dependency rules, runtime graph stories и отдельные сборки приложения и -Storybook. Он работает без backend. Coverage -остаётся anti-regression constraint; правила выбора тестов зафиксированы в -[engineering governance](engineering-governance.md). +Типизированный клиент frontend производится из опубликованного backend +OpenAPI. Согласованность клиента с сохранённым описанием API и согласованность +этого описания с работающим backend проверяются отдельно. +Изменение публичного контракта требует обновления производного клиента; +подробности — [согласованность OpenAPI](runtime-openapi-verification.md). -Изолированные примеры реальных компонентов и композиций запускаются в Storybook -через `npm run dev:catalog`. `npm run build:catalog` создаёт `storybook-static/`, -а `npm run preview:catalog` обслуживает эту статическую сборку. Структура каталога, -работа с Docs, Controls и Actions, доступность и ограничения описаны в -[UI-каталоге Storybook](ui-catalog.md). +## Проверка интерфейса -Для UI задачи используйте каталог как reference существующего решения. -Ожидаемые экраны, states и interactions определите по [задаче](engineering-governance.md#ui-решения). -Storybook показывает реализацию; визуальное соответствие и доступность -проверяются отдельно от сборки. SDD-пакет для этого не обязателен. +Локальная проверка frontend охватывает корректность типов, статический анализ, +автоматические сценарии, зависимости и сборку приложения и UI-каталога. +Она может выполняться без запущенного backend. Покрытие тестами ограничивает +регрессии; само по себе число покрытых строк не доказывает правильность +финансового расчёта, доступность или прохождение пользовательского сценария. -Backend остаётся источником OpenAPI. После изменения опубликованного -backend-контракта разработчик обновляет в `frontend/` committed snapshot -`src/shared/api/spec/openapi.json` копированием backend OpenAPI document, затем -запускает: - -```bash -npm run openapi:generate -``` - -Команда использует только committed snapshot и регенерирует -`src/shared/api/generated/schema.d.ts` offline. Проверку соответствия backend -runtime contract и frontend snapshot выполняют отдельной backend-командой -`npm run test:openapi-contract`. +[Каталог](ui-catalog.md) показывает существующие визуальные решения. +Соответствие задаче, широкий и узкий экран, клавиатурная навигация и восстановление +после ошибок проверяются в интерфейсе отдельно от успешной сборки. ## Состояние тестового клиента -`frontend/src/test/query.tsx` предоставляет `createTestQueryClient` с отключёнными -test retries и query-only `createQueryWrapper`. Независимый case получает свой -client до React wrapper render; rerender сохраняет тот же экземпляр и seeded -cache. Auth и T-Bank tests используют эту же фабрику. Mantine, Router и -AuthProvider остаются явно выбранными локальными providers. - -Общий teardown последовательно выполняет RTL cleanup, сброс auth client и -сброс MSW handlers. Production query defaults этой инфраструктурой не меняются. +Независимые проверки получают изолированное состояние клиента. Обновление +представления внутри одного сценария сохраняет его кэш; следующий сценарий +не наследует сессию и ответы предыдущего. Повторные попытки в тестовой среде +могут быть отключены для предсказуемого результата. Это не меняет +[политику обновления данных приложения](../adr/007-react-query-fetch-policy.md). diff --git a/development/local-workspace.md b/development/local-workspace.md new file mode 100644 index 0000000..db847d1 --- /dev/null +++ b/development/local-workspace.md @@ -0,0 +1,84 @@ +# Локальное окружение и восстановление + +WealthLedger поставляется как локальное приложение: frontend работает +в браузере, backend обслуживает API и обращается к SQLite и внешним источникам. +Это описание предпосылок и ограничений работы, без привязки к устройству +исходного кода. Production-развёртывание в текущую поставку не входит. + +## Подготовка backend + +Локальная серверная среда использует Node 22 и SQLite. Для работы нужны: + +| Параметр | Назначение и ограничение | +| --- | --- | +| Адрес локальной базы | Одинаковая база используется при подготовке схемы и работе приложения; относительный адрес зависит от рабочей директории процесса | +| Разрешённый origin frontend | Определяет browser-источник, которому разрешён доступ к API с credentials | +| Порт API | По умолчанию 3000 | +| Секрет подписи сессий | Секрет не короче 32 символов; отличается от ключа шифрования подключения | +| Ключ шифрования подключения | 32 байта в base64; требуется для чтения сохранённого T-Bank token | +| Параметры интеграции | Адрес T-Bank, лимит запросов и при необходимости доверенный CA; [ограничения](../features/tbank-connection.md#конфигурация-и-ограничения) | + +T-Bank token вводится пользователем через интерфейс после входа и не является +общим параметром окружения. Существующая база и её ключ шифрования сохраняются +при обновлении. До миграции необходима резервная копия; восстановление проверяется +на отдельной копии. Потеря или замена ключа не является безвредным сбросом: +прежнее подключение нельзя расшифровать новым ключом. + +**Известное ограничение:** проверка новой временной SQLite-базы 12 сентября +2026 года под Node 22 воспроизвела `Schema engine error` до создания схемы. +Полный первый запуск на чистой базе пока не подтверждён; пользовательская база +в этой проверке не использовалась. Условия пересмотра — +[проверка backend](backend-workflow.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](../features/authentication.md). + +## Первый сценарий + +1. Пользователь регистрируется или входит. Новый профиль получает сессию; + после перезагрузки приложение пытается её восстановить. +2. В настройках пользователь сохраняет собственный T-Bank Invest token. + Успех означает проверенный доступ к брокеру и сохранённое подключение. +3. В разделе «Счета» доступны отдельный и общий портфель. Отсутствие доступных + счетов является допустимым пустым результатом. +4. «Доходы» показывают полученные выплаты, «События» — будущие. +5. При выходе локальная сессия и её клиентский кэш очищаются. + +Без T-Bank token доступны регистрация, вход и состояние отсутствующего +подключения. Это не подтверждает работу финансовых сценариев. + +## Если запуск или сценарий не работает + +| Симптом | Проверка и безопасное восстановление | +| --- | --- | +| Порт занят | Адрес действующего процесса или выбранный свободный порт должен совпадать с настройками клиента и разрешённым origin | +| Ошибка CORS или сессия теряется после reload | Согласованность адресов, hostname и режима cookie проверяется на стороне browser и backend | +| Ошибка подготовки базы | Установленный blocker не устраняется удалением пользовательской базы или обходом миграций; сохраняются версия среды и текст ошибки | +| `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). + +## Проверка первого изменения + +Успешная сборка, запуск и прохождение сценария — разные доказательства. +Границы проверки описаны для [frontend](frontend-workflow.md), +[backend](backend-workflow.md) и [HTTP-контракта](runtime-openapi-verification.md). +Завершённость результата учитывает фактические проверки и остающиеся ограничения. diff --git a/development/openspec-authority-and-lifecycle.md b/development/openspec-authority-and-lifecycle.md index 195fc16..9d51ab0 100644 --- a/development/openspec-authority-and-lifecycle.md +++ b/development/openspec-authority-and-lifecycle.md @@ -1,35 +1,25 @@ -# OpenSpec: optional legacy workflow +# OpenSpec: роль требований и история процесса -Обязательный SDD lifecycle отменён для обычной разработки. Текущие правила — -[engineering governance](engineering-governance.md), -[context discipline](agent-token-efficiency.md) и root/local AGENTS. -Задача пользователя не требует Proposal/Delta/Design/Tasks, отдельных agent roles, -Completion Evidence или archive как условия завершения. +Обязательный SDD lifecycle отменён для обычной разработки согласно +[ADR 012](../adr/012-task-driven-development-with-scoped-context.md). +Задача пользователя определяет ожидаемый результат; дополнительные planning +artifacts и архивирование не являются условием её завершения. ## Существующие материалы -`specs/openspec` сохраняет behavioral specs, active changes, schemas и archives. -Существующий planning artifact описывает только свой прежний scope и не доказывает -готовность нового запроса. Датированные evidence/archives не переписываются. -Нужную behavioral spec выбирай через [specs index](../../specs/AI_INDEX.md). +OpenSpec сохраняет поведенческие требования, предложенные изменения и историю +согласования. Существующее описание изменения относится только к своему объёму +и не доказывает готовность другой возможности. Текущее поведение системы +объясняется в [карте возможностей](../features/README.md). ## Если OpenSpec запрошен явно -Canonical store прежнего workflow — `wealth-ledger-specs`, физически -`specs/openspec`. Перед Node-командами выбери frontend `.nvmrc`, если у specs -нет своей. Для чтения/изменения change всегда задавай store явно: +OpenSpec остаётся необязательным средством согласования требований, дизайна +и задач. Его собственная модель определяет состав и порядок артефактов; +подтверждение человеком и смысловая проверка результата не выводятся из +одного наличия файлов. Обновление требований и архивирование меняют историю, +поэтому относятся к явно выбранной работе с этой историей. -```bash -openspec status --change --json --store wealth-ledger-specs -openspec instructions --change --json --store wealth-ledger-specs -``` - -Native status/instructions определяют фактические artifacts и schema. Получай -разрешение на именно запрошенное изменение, не выводи approval из наличия файлов. -Sync/archive меняют existing specs/history и выполняются только в рамках такой -явной задачи, после проверки ожидаемого semantic diff и сохранности данных. -Structural validation не доказывает соответствие реализации задаче. - -Прежнее rationale — [ADR 010](../adr/010-native-openspec-sdd-and-local-orchestration.md) -и [011](../adr/011-native-openspec-lifecycle-with-repository-gates.md); -текущий выбор прямой работы — [ADR 012](../adr/012-task-driven-development-with-scoped-context.md). +Причины прежнего процесса и его упрощения сохранены в +[ADR 010](../adr/010-native-openspec-sdd-and-local-orchestration.md) и +[ADR 011](../adr/011-native-openspec-lifecycle-with-repository-gates.md). diff --git a/development/runtime-openapi-verification.md b/development/runtime-openapi-verification.md index d11293b..bcbf0d7 100644 --- a/development/runtime-openapi-verification.md +++ b/development/runtime-openapi-verification.md @@ -2,55 +2,40 @@ ## Назначение -`npm run test:openapi-contract` в `backend/` строит configured Nest application, -создаёт runtime OpenAPI document и сверяет его с committed snapshot -`frontend/src/shared/api/spec/openapi.json` после детерминированной canonicalization. +Проверка сравнивает OpenAPI, сформированный настроенным backend-приложением, +с описанием API, на котором основан frontend. Backend остаётся единственным +источником исполняемого HTTP-контракта; frontend-клиент является производным. +Причины выбора описаны в [ADR 002](../adr/002-nest-generated-openapi-contract.md). -Это отдельный контрактный gate, а не общий verifier всех репозиториев. Он обязателен перед завершением задачи, меняющей -backend API или frontend transport contract и не заменяет -локальные lint/typecheck команды приложений. +Эта проверка подтверждает согласованность публичного контракта, но не заменяет +проверки каждого приложения и наблюдаемого HTTP-поведения. ## Выбор и направление изменения -1. Определите, меняется wire contract (route, request/response field, status, error shape) или только внутренний код/UI model. Во втором случае snapshot не меняется. -2. Прочитайте [HTTP semantics](../architecture/http-api.md) и нужный feature/domain source. Backend Nest controllers/DTO/Swagger metadata — единственный editable источник OpenAPI; specs repo не является вторым OpenAPI source. -3. Измените этот source и проверьте representative HTTP behavior. При изменении контракта скопируйте generated runtime OpenAPI в committed frontend snapshot, затем в frontend выполните `npm run openapi:generate`. -4. Выполните frontend `npm run openapi:check` и backend `npm run test:openapi-contract`, а также verify affected repos. Эти команды проверяют разное: schema parity не доказывает validation/auth/error behavior. +Изменением контракта считается изменение маршрута, входного или выходного поля, +статуса либо формы ошибки. Внутреннее изменение представления без изменения +API не требует нового описания контракта. -Generated frontend TS и backend Prisma client вручную не редактируются. -Frontend domain/UI type допустим, когда он отличается от transport DTO по смыслу. -Перед compatibility-sensitive изменением оцените существующих consumers и -rollback/data constraints; одного обновления snapshot недостаточно. - -## Запуск - -Из корня workspace: - -```bash -cd backend -nvm use --silent -npm run test:openapi-contract -``` - -Перед первым запуском в backend выполняют `npm ci` под Node 22. +Направление согласования: публичный API backend → OpenAPI → производное +описание и типизированный клиент frontend. Две независимо редактируемые версии +контракта создавали бы риск расхождения. Перед изменением, влияющим на +совместимость, учитываются существующие потребители и сохранность данных. +Семантика запросов и ошибок описана в [HTTP API](../architecture/http-api.md). ## Границы и результат -Команда: +Сравнение выполняется на границе настроенного приложения, нормализует порядок +ключей JSON-объектов и сохраняет порядок массивов. Оно не обновляет сравниваемые +описания и клиент автоматически и не обращается к T-Bank или другим внешним +источникам. Несовпадение указывает на рассогласование контракта. -- использует configured Nest application на integration boundary; -- рекурсивно сортирует только ключи JSON objects и сохраняет порядок arrays; -- не записывает snapshot или generated frontend types. - -Она не записывает committed snapshot или generated frontend types и не -обращается к T-Bank либо другому внешнему сервису. Если контракт действительно -изменён, snapshot обновляют явным копированием backend OpenAPI document в -`frontend/src/shared/api/spec/openapi.json`, затем в `frontend` запускают -`npm run openapi:generate` и снова выполняют эту проверку. +Равенство схем не доказывает фактическую работу валидации, авторизации, +статусов, cookie и обработки ошибок: для этого нужны наблюдения полного +HTTP-потока. Согласованность описаний и функциональная корректность — +разные доказательства. ## Ограничение e2e -Проверка Swagger не запускает backend e2e tests. WL-P2-05 (runnable e2e) и -WL-P2-13 (единый workspace verdict) отложены из-за нестабильного Prisma 7.9.1 -migration gate; дата, rationale и trigger пересмотра находятся в -[audit remediation registry](audit-remediation.md). +Сравнение OpenAPI не запускает сквозные проверки с миграциями базы. +Известная ошибка движка миграций и условие пересмотра описаны в +[проверке backend](backend-workflow.md#локальная-проверка). diff --git a/development/ui-catalog.md b/development/ui-catalog.md index ff57821..68be384 100644 --- a/development/ui-catalog.md +++ b/development/ui-catalog.md @@ -1,120 +1,72 @@ # UI-каталог Storybook -Storybook 10.6.0 показывает реальные компоненты и композиции WealthLedger -на общей визуальной основе приложения без входа и подключения backend. +Storybook 10.6.0 показывает реальные представления WealthLedger на общей +визуальной основе приложения без входа и подключения backend. ## Запуск и сборка -Из `frontend/` выберите Node 22 и установите зависимости из lockfile: - -```bash -nvm use --silent -npm ci -npm run dev:catalog -``` - -Storybook запускается на [localhost:6006](http://localhost:6006); браузер -автоматически не открывается. Отдельная статическая сборка и её preview: - -```bash -npm run build:catalog -npm run preview:catalog -``` - -Результат находится в `frontend/storybook-static/`, вход — `index.html`. -Команда preview выводит адрес статической витрины в терминале. Обычный -`npm run build` сохраняет приложение в `frontend/dist/`; stories не входят -в его entrypoint и продуктовые маршруты. Публикация отдельным сервисом не настроена. +Каталог работает как отдельная локальная витрина по адресу +[localhost:6006](http://localhost:6006) либо как самостоятельная статическая +сборка. Его примеры не входят в пользовательские маршруты приложения. +Публикация отдельным сервисом не настроена. ## Компоненты и работа с примерами -В дереве доступны два блока, 9 групп / 30 историй: +Два блока каталога включают 9 групп и 30 примеров: -| Блок / группа | Что показывает | +| Группа | Что показывает | | --- | --- | -| `Design System/Foundations` | Восемь палитр и семантические роли, типографику, интервалы, радиусы, тени и breakpoints из общей темы, адаптированной из tokens.json. | -| `Design System/Components/StateView` | Четыре empty/error варианта с действием и без. | -| `Design System/Components/LoadingSkeleton` | Общую доступную busy-область с нейтральными structural shapes. | -| `Design System/Components/Button` | Прямой Mantine Button: primary, default, subtle, loading, disabled; локальное действие и keyboard interaction. | -| `Product/BrokerageAccountList` | Сводки счетов в нескольких валютах, недоступную/пустую сводку и длинное название. | -| `Product/PositionCard` | Акцию с положительным результатом, облигацию с отрицательным, прочий инструмент и позицию без оценки. | -| `Product/RecentEvents` | Полученные события, loading/error/empty и неизвестную ожидаемую сумму. | -| `Product/PortfolioRefreshState` с `PortfolioTotals` | Сохранённые суммы при обновлении и ошибке, локальный retry. | -| `Product/AccountPageLoading` | Реальный skeleton заголовка, сводки и строк страницы счёта. | +| Основы дизайн-системы | Палитры, семантические роли, типографику, интервалы, радиусы, тени и адаптивные размеры | +| Пустой результат и ошибки | Четыре варианта с действием и без | +| Загрузка | Доступную область занятости с нейтральными фигурами | +| Кнопки | Основное, вторичное и вспомогательное действие, выполнение и недоступность, работу с клавиатурой | +| Список счетов | Несколько валют, недоступную и пустую сводку, длинное название | +| Карточка позиции | Акцию с положительным результатом, облигацию с отрицательным, прочий инструмент и отсутствие оценки | +| Последние события | Успешный, загружаемый, ошибочный и пустой результат; неизвестную сумму | +| Обновление портфеля | Сохранённые суммы при обновлении и ошибке, локальную повторную попытку | +| Загрузка страницы счёта | Контур ожидаемых заголовка, сводки и строк | -Быстрые входы: [Design System — цвета](http://localhost:6006/?path=/story/design-system-foundations--colors) -и [Product — карточка позиции](http://localhost:6006/?path=/story/product-positioncard--stock-gain). -Все исходные 21 истории сохранены; 9 новых добавляют foundations и общие примеры. -Бизнес-компоненты остаются в entities/pages, а общие StateView и LoadingSkeleton -— в shared/ui. Button показан напрямую: новое имя-wrapper не вводится. +Каталог не перечисляет все элементы библиотеки или приложения. Отсутствие +представления в витрине не означает его отсутствия в продукте. Основы показывают +опубликованные шкалы и смысл ролей; особые размеры и тени конкретных экранов +не объявляются общими токенами. Границы описаны в [дизайн-системе](design-system.md). -Каталог не перечисляет весь Mantine или все компоненты приложения. AppShell -с product navigation не объявлен компонентом общего набора. Отсутствие компонента -в каталоге не запрещает его использование. Foundations показывает опубликованные -шкалы из `shared/theme/tokens.json`, описания ролей и эффективные Mantine значения. -Локальные размеры и особые тени не объявляются глобальными токенами; -неопубликованные defaults остаются у Mantine. [Дизайн-система](design-system.md) -описывает JSON-формат, внешнее чтение, ownership и правила расширения. +Canvas показывает выбранный пример. Docs поясняет назначение и ограничения, +Controls меняет доступные свойства, Actions показывает локальные действия. +Вымышленные исходные данные не редактируются через Controls: другой набор +данных выбирается отдельным примером. Свойства можно сбросить, пример — +смонтировать заново; изменение размера просмотра не сбрасывает выбранные значения. -Выберите историю в дереве, чтобы открыть её состояние в Canvas. Docs описывает -назначение, ограничения и свойства группы, а также её примеры. Controls меняют -разрешённые свойства; синтетические объекты и callbacks недоступны для редактирования. -Например, `kind` у `PositionCard` меняет вариант представления фиксированной позиции, -а разные данные инструмента выбираются отдельной историей. Actions регистрирует -локальные callbacks кнопок и retry. - -Для возврата изменённых свойств используйте штатный reset args в Controls; -для повторного монтирования примера — remount в панели Canvas. Размер preview -можно менять без сброса args. Изолированный iframe позволяет отдельно проверить -компонент на узком экране; навигацией самой витрины управляет Storybook. - -Preview и приложение используют один public API `shared/theme`: theme, -CSSVariablesResolver и базовые styles вместе с Mantine CSS. Preview использует -русскую i18n и фиксированную светлую схему без чтения сохранённой темы. -Stories находятся рядом с существующими presentation components и импортируют -их реализации. Токены и описания их семантических ролей принадлежат -`shared/theme/tokens.json`; `theme.ts` напрямую адаптирует этот локальный файл -в Mantine, preview не хранит копию токенов. Error StateView сохраняет alert semantics, -skeleton — busy-область с декоративными фигурами без фокуса. Проверяйте доступность -действий и ссылок с клавиатуры вместе с визуальным состоянием. +Витрина и приложение используют одну тему. Примеры показываются по-русски +в светлой схеме. Ошибка сохраняет доступное объявление, загрузка — состояние +занятости; декоративные фигуры не получают фокус. ## Границы и сопровождение -Все счета, позиции, события и суммы вымышлены. Stories не читают application -storage, cookies, секреты или реальные пользовательские данные. Внешние логотипы -не загружаются: примеры используют буквенный fallback существующего компонента. -Storybook manager может сохранять собственные настройки витрины; telemetry отключена. +Все счета, позиции, события и суммы вымышлены. Каталог не читает сессии, +cookies, секреты и реальные пользовательские данные. Вместо внешних логотипов +показываются буквенные заглушки. Сама витрина может сохранять свои настройки; +телеметрия отключена. -Stories не подключают AuthProvider, QueryClient, API clients и исполняемые VM. -Действия не обращаются к application API. Ссылки меняют только локальный маршрут -MemoryRouter: страница назначения не отображается, URL приложения не меняется. -Retry демонстрирует локальный callback и не доказывает восстановление API или -полный пользовательский сценарий. Loading/error относятся к выбранному примеру. +Действия не обращаются к application API. Ссылки меняют только локальное +состояние примера, не открывают продуктовую страницу назначения и не меняют +адрес приложения. Повторная попытка демонстрирует локальное действие и не +доказывает восстановление реального запроса. -Композиция сохранённых сумм показывает представление Client Cached Snapshot; -сам query lifecycle не исполняется. Для запросных экранов действует -[UI Pattern](../../frontend/.ai/conventions/frontend-ui.md#состояния-и-recovery): сохранение данных при refetch, -structural skeleton для initial loading и контекстный индикатор mutation. -Skeleton конкретной страницы остаётся ответственностью страницы. +Сохранённые суммы иллюстрируют Client Cached Snapshot, но его жизненный цикл +здесь не исполняется. В приложении первичная загрузка показывает контур +ожидаемого содержимого, фоновое обновление сохраняет данные, а выполняемое +действие имеет контекстный индикатор. Подробности — +[состояния интерфейса](../features/frontend-ui-resilience.md). -При изменении включённого компонента проверяйте вместе с ним его пример, -описание и ограничения. `npm run verify` включает Storybook в strict TypeScript, -Biome, Vitest, Knip и dependency-cruiser, затем собирает приложение и статическую -витрину. Основная проверка FSD сохраняется; отдельный `deps:storybook` проверяет -runtime graph stories и preview, запрещая достижимость application data. -Type-only ссылки на типы VM и API стираются и не запускают запросы. -Declarative stories не требуют искусственного unit coverage, а продуктовые -coverage floors сохраняются. Клавиатура, реальные размеры и визуальное -соответствие проверяются в браузере; автоматической visual regression нет. +Автоматические проверки frontend охватывают каталог и его изоляцию от +продуктовых запросов. Примеры и их пояснения обновляются вместе с включёнными +представлениями. Клавиатура, реальные размеры и визуальное соответствие +проверяются в браузере; автоматической визуальной регрессии нет. ## Использование в UI задаче -Каталог помогает выбрать existing component, состояние и ограничения. Ожидаемое -поведение определите из задачи: экраны, states, interactions и узкий viewport. -Для сложного варианта покажите конкретный макет/пример до дорогой реализации; -простой согласованный вариант реализуйте сразу по [UI policy](engineering-governance.md#ui-решения). - -Story — reference текущей реализации. Сборка не доказывает visual match, -keyboard accessibility или работу настоящего API. Проверяйте соответствие задаче -и применимые states в браузере. Использование установленного каталога не требует -повторного approval установки или OpenSpec Design artifact. +Каталог помогает сравнить существующие состояния с ожидаемым пользовательским +сценарием. Он дополняет [принципы UI-решений](engineering-governance.md#ui-решения) +конкретными примерами, но успешная сборка не доказывает доступность, +визуальное соответствие и работу настоящего API. diff --git a/features/README.md b/features/README.md index 0ff8c8f..f95616e 100644 --- a/features/README.md +++ b/features/README.md @@ -1,16 +1,55 @@ # Возможности -Здесь документируется фактическое поведение завершенных пользовательских фич: пользовательский поток, API-сценарий и ограничения. Не реализованные возможности сюда не добавляются. +Карта текущих возможностей по предметным областям. Каждый сценарий описывает +поведение, взаимодействие компонентов и ограничения. Границы системы — +в [обзоре](../architecture/system-overview.md), значения терминов — +в [глоссарии](../architecture/domain-glossary.md). -- [Аутентификация](authentication.md) — регистрация, сессии и refresh. -- [Подключение T-Bank](tbank-connection.md) — безопасное управление - подключением. -- [Брокерские счета](brokerage-accounts.md) — список поддерживаемых счетов. -- [Портфель](portfolio-view.md) — состав инвестиций и freshness states. -- [Account Ledger](brokerage-account-ledger.md) — исполненные операции счёта. -- [Future Cash Events](brokerage-account-upcoming-events.md) — прогнозируемые - события по текущим позициям. -- [Пассивный доход брокерского счёта](brokerage-account-income-analytics.md) — - исполненные купоны и дивиденды. -- [Frontend UI resilience](frontend-ui-resilience.md) — устойчивые состояния - интерфейса. +## Доступ и подключение + +Аутентификация связывает локального пользователя с сессиями. Подключение +T-Bank обеспечивает доступ к брокеру; наличие сессии не заменяет проверку +подключения и доступа к конкретному счёту. + +| Сценарий | Что объясняет | Ответственность компонентов | +| --- | --- | --- | +| [Аутентификация](authentication.md) | Вход, восстановление и завершение сессии | Backend проверяет credentials и управляет сессиями; frontend хранит текущее состояние входа и выбирает доступные экраны | +| [Подключение T-Bank](tbank-connection.md) | Защита token, замена подключения и сброс кэшей | Backend проверяет и шифрует token; frontend показывает подтверждённый статус и обновляет финансовые данные | + +## Инвестиционные данные + +Сценарии используют текущий состав доступных счетов. Денежные расчёты принадлежат +backend; frontend выбирает представление и формат. Исполненные операции, +полученные выплаты и будущие события имеют разные источники и смысл. + +| Сценарий | Что объясняет | Ответственность компонентов | +| --- | --- | --- | +| [Брокерские счета](brokerage-accounts.md) | Доступный состав и отдельные сводки | Backend устанавливает доступ и собирает сводки; frontend показывает каждый счёт и состояние его данных | +| [Портфель](portfolio-view.md) | Позиции, остатки, точные суммы и свежесть | Подсистема портфеля нормализует снимки и рассчитывает итоги; frontend сохраняет различие актуальных и устаревших данных | +| [Все счета](all-accounts.md) | Агрегация, происхождение строк и общая пагинация | Backend объединяет доступные счета по правилам каждого раздела; frontend сохраняет выбранный раздел и фильтры при переключении | +| [Журнал операций](brokerage-account-ledger.md) | Исполненные операции, фильтры и пагинация | Backend получает и нормализует операции брокера; frontend управляет применёнными фильтрами и навигацией | +| [Пассивный доход](brokerage-account-income-analytics.md) | Полученные купоны/дивиденды, периоды и сравнения | Backend вычисляет доход по исполненным выплатам; frontend показывает суммы, периоды и состав расчёта | +| [Будущие выплаты](brokerage-account-upcoming-events.md) | Прогноз по текущим позициям и его ограничения | Backend сопоставляет календарь выплат и позиции; frontend отделяет прогноз от фактически полученного дохода | + +## Инструменты + +Рыночные данные выпуска отличаются от личной позиции и факта зачисления. +Страницы акций и облигаций используют общий подход к показу графика, позиции +и личных событий, сохраняя различия денежных и процентных шкал. + +| Сценарий | Что объясняет | Ответственность компонентов | +| --- | --- | --- | +| [Акции](stock-detail.md) | Поиск, идентификация выпуска, котировки, дивиденды и личная история | Backend разрешает идентификатор и объединяет источники; frontend независимо показывает секции выбранной акции | +| [Облигации](bond-detail.md) | Цена в процентах, купоны, доходности и рейтинг выпуска | Backend сопоставляет данные T-Bank, MOEX и НКР; frontend сохраняет происхождение, время и единицы каждого показателя | + +## Состояния интерфейса + +[Frontend UI resilience](frontend-ui-resilience.md) объясняет первичную загрузку, +ошибки, пустые результаты и сохранённые клиентом данные. Frontend управляет +доступом к экранам, доступностью запросов и восстановлением отдельных разделов. +Ошибки фонового обновления не должны скрывать уже доступные данные текущего +пользователя; смена сессии или подключения очищает данные прежней границы. + +Новая возможность описывается после реализации вместе с ответственными +подсистемами и ограничениями. Порядок обновления знаний — +[ведение документации](../development/documentation.md). diff --git a/features/all-accounts.md b/features/all-accounts.md index d03eb8c..7e9d42d 100644 --- a/features/all-accounts.md +++ b/features/all-accounts.md @@ -33,7 +33,7 @@ Backend публикует статические endpoints под `/api/v1/brok OpenAPI, snapshot и frontend types генерируются из него. - Портфель и доступные денежные остатки суммируются сервером точно, по исходной - валюте, через существующую decimal-арифметику. FX-конвертации нет; правила + валюте, с точной десятичной арифметикой. FX-конвертации нет; правила неизвестных значений и состава сумм наследуются от [портфеля](portfolio-view.md). - Одинаковые бумаги разных счетов показаны одной карточкой с общим названием и идентификатором. Внутри всегда видны строки счетов: ссылка на портфель, @@ -60,8 +60,8 @@ OpenAPI, snapshot и frontend types генерируются из него. ## Полнота, свежесть и доступ -Каждый общий запрос получает состав счетов и единую generation через -`TbankAccessService`. Получение данных использует эту же connection boundary; +Для каждого общего запроса backend определяет состав доступных счетов +и единое поколение подключения. Получение данных использует эту же connection boundary; проверки сессии, доступа и существующий лимитер внешних запросов сохраняются. Финансовые данные не сохраняются в БД; новые зависимости не добавляются. diff --git a/features/authentication.md b/features/authentication.md index db6e2ca..c83b9ce 100644 --- a/features/authentication.md +++ b/features/authentication.md @@ -1,21 +1,82 @@ # Authentication +Текущее объяснение локальной identity и сессий WealthLedger. Auth связывает +[User](../architecture/domain-glossary.md#user) с browser session; доступ к +брокеру принадлежит отдельному [T-Bank connection](tbank-connection.md). +Credentials и ротация принадлежат backend, auth state и route decisions — frontend. + ## User flow -Пользователь регистрируется или входит по email и паролю. Регистрация сразу создаёт первую сессию. Email сохраняется в нормализованном виде; пароль должен содержать от 12 до 72 UTF-8 байтов. +Пользователь регистрируется или входит по email и паролю. Регистрация сразу создаёт первую сессию. Внешние пробелы email удаляются, буквы приводятся к нижнему регистру. Backend требует пароль не короче 12 символов и не длиннее 72 UTF-8 байтов. + +**Требует ручного решения:** сохранённое требование регистрации и проверка +в форме frontend считают нижнюю границу в UTF-8 байтах, а backend — в символах. +Не-ASCII пароль может пройти форму и получить отказ API. Это описание +наблюдаемого рассогласования, а не принятое изменение password policy; +исправление требует отдельного согласованного security решения и HTTP-проверки. Access token живёт 15 минут только в памяти браузера. Refresh token живёт 7 дней в cookie `refresh_token` с `httpOnly` и `SameSite=Lax`; при восстановлении сессии он заменяется. После перезагрузки приложение восстанавливает access token через refresh-cookie до показа защищённых маршрутов. Пока результат восстановления неизвестен, frontend показывает нейтральный structural skeleton каркаса без public или protected route content; после завершения bootstrap сохраняются прежние decisions и redirects. Все маршруты, кроме `/login` и `/register`, требуют сессию. После входа пользователь возвращается на исходный внутренний маршрут, если он был. Logout доступен в настройках, очищает локальную сессию независимо от состояния сети и отзывает серверную сессию при доступном refresh-cookie. +## Восстановление сессии + +```mermaid +sequenceDiagram + participant F as React SPA + participant B as NestJS Auth + participant D as SQLite Session + Note over F: Reload: access token в памяти отсутствует + F->>B: Refresh с httpOnly cookie + B->>D: Проверить Session и текущий hash token + alt Действительная сессия, ротация выиграна + B->>D: Атомарно заменить hash и срок + B-->>F: Access token, профиль, новая refresh-cookie + F->>B: Protected request с Bearer + else Cookie нет, token истёк или уже заменён + B-->>F: 401 и очистка cookie + Note over F: Готовое неавторизованное состояние + end +``` + +В одном экземпляре frontend одновременные refresh используют один незавершённый запрос; +между вкладками используется Web Locks, когда browser его поддерживает. +Backend всё равно защищает ротацию атомарно: browser coordination не заменяет +server-side invariant. Обычный protected запрос с `401 UNAUTHORIZED` проходит +refresh и один повтор через общий transport; auth endpoints из этого цикла +исключены. Сам по себе истёкший access token не запускает фоновый таймер refresh. + ## API -`POST /api/v1/auth/register`, `POST /api/v1/auth/login` и `POST /api/v1/auth/refresh` возвращают профиль и access token с HTTP `200`. `POST /api/v1/auth/logout` отвечает `204`. `GET /api/v1/auth/me` требует Bearer access token. +Endpoints, статусы и формы публичных ответов определяет backend через OpenAPI; +проверка snapshot описана в [contract workflow](../development/runtime-openapi-verification.md). +Причины хранения и сроков tokens — [ADR 006](../adr/006-auth-session-policy.md). +Refresh-cookie ограничена auth API. Признак Secure зависит от режима окружения: +локальный HTTP предполагает development mode, см. +[локальное окружение](../development/local-workspace.md). -Ошибки API имеют `application/problem+json` с полями `status` и `code`. Ошибки валидации содержат `errors` с JSON Pointer полей. HTTP- и транспортная ошибка refresh одинаково завершают cold start готовым неавторизованным состоянием; при refresh после protected `401` frontend очищает in-memory session и React Query cache, сохраняя исходный `401` для существующего login flow. +Форма ошибок и граница её HTTP-проверки описаны у владельца +[HTTP API](../architecture/http-api.md#error-contract). +HTTP- и транспортная ошибка refresh одинаково завершают cold start готовым неавторизованным состоянием; при refresh после protected `401` frontend очищает in-memory session и React Query cache, сохраняя исходный `401` для существующего login flow. ## Limitations MVP не включает подтверждение email, восстановление или смену пароля, смену email, удаление аккаунта, список устройств, logout всех устройств или rate limit попыток входа. Access token может оставаться действительным до 15 минут после logout. Если несколько клиентов одновременно предъявляют один действительный refresh token, сервер завершает одну ротацию успешно, а остальные запросы получают `401`; сессия победившего запроса остаётся действительной. Уже заменённый refresh token также даёт `401`, но сам по себе не отзывает сессию: MVP не хранит историю token или grace-window, чтобы отличить конкурентный запрос от replay. + +Это ограничение и его rationale приняты в +[ADR 009](../adr/009-concurrent-refresh-rotation-policy.md). Refresh token имеет +server-side срок, но cookie не задаёт собственные `maxAge`/`expires`: сохранение +cookie после закрытия браузера не является обещанием семидневного входа. + +## Ответственность и проверка + +Backend проверяет credentials и tokens, хранит состояние Session и атомарно +выполняет ротацию. Frontend управляет состоянием входа, восстановлением после +reload и доступностью экранов; общий HTTP-транспорт передаёт Bearer token +и выполняет описанный выше refresh/retry. + +Проверки охватывают конкурентную ротацию, состояние входа в браузере и поведение +транспорта. Изолированные проверки этих частей не заменяют полный HTTP-поток +и реальную базу при проверке нового изменения безопасности. diff --git a/features/bond-detail.md b/features/bond-detail.md index 5820cc9..637cf30 100644 --- a/features/bond-detail.md +++ b/features/bond-detail.md @@ -59,7 +59,7 @@ FX-конвертация не выполняется, первоначальн отсутствия рейтингов других агентств. Покрытие и альтернативы исследованы в [обзоре источников](../research/bond-credit-ratings.md). -`NcrRatingsClient` разбирает HTML через Cheerio: штатного открытого API для +Интеграция НКР читает публичную HTML-таблицу: штатного открытого API для этой таблицы не подтверждено. Общий публичный реестр кэшируется на шесть часов, незавершённые загрузки объединяются; повторные запросы после ошибки ограничены одной минутой. Изменение структуры таблицы, неоднозначные данные и ошибки @@ -98,8 +98,7 @@ FX-конвертация не выполняется, первоначальн ## Владельцы и восстановление -`BondController` и `BondService` принадлежат существующему -`TbankConnectionModule`. Все `/api/v1/bonds` endpoints требуют Bearer session +Страницу облигации обслуживает инвестиционная подсистема backend. Все `/api/v1/bonds` endpoints требуют Bearer session и актуального подключения Т-Банка: поиск, разрешение тикера, карточка по UID, `quote`, `candles`, `position`, `coupons`, `metrics`. Wire contract принадлежит runtime OpenAPI, frontend snapshot и types генерируются из него. @@ -124,4 +123,4 @@ Instruments client, сохраняющий отсутствие полей. Ры Ошибка фонового обновления сохраняет последние данные. Смена подключения сбрасывает subtree `/bonds` вместе с остальными финансовыми запросами; logout использует общий session reset. Общие график, позиция и личные события -переиспользуются страницами акций и облигаций через `instrument-details`. +имеют общее представление на страницах акций и облигаций. diff --git a/features/brokerage-account-income-analytics.md b/features/brokerage-account-income-analytics.md index 1885c93..8dccde6 100644 --- a/features/brokerage-account-income-analytics.md +++ b/features/brokerage-account-income-analytics.md @@ -43,7 +43,7 @@ skeleton. Пустой результат объясняет отсутстви - Только точные upstream types coupon и dividend формируют результат. Суммы остаются exact decimal strings в исходной валюте; отрицательные корректировки сохраняют знак. Налоги, комиссии, сделки, переводы, погашения и dividend transfer не входят в доход. - Итоги, сравнения, buckets и Instrument breakdown не смешивают валюты и не создают общий FX total. На странице показано только сравнение с годом ранее, когда календарная дата и история счёта позволяют. Предыдущий период остаётся в API response, но не отображается. - Buckets по-прежнему приходят с backend: дни для 1–45 дней, недели для 46–180 и месяцы для более длинного периода, включая пустые buckets. Страница дохода не строит из них график или текстовую динамику; API response не меняется. -- Раздел «Выплаты» показывает единый список LedgerEntryRow-карточек в исходном порядке `entries`: одна карточка на каждую выплату. Внешних групп по инструментам или валютам, итогов и количества выплат инструмента нет. Описание, дата, доступная identity и signed Money видны в каждой карточке; валюты по-прежнему разделены в summary/comparisons. Дополнительный запрос за составом не нужен. +- Раздел «Выплаты» показывает единый список карточек выплат в исходном порядке `entries`: одна карточка на каждую выплату. Внешних групп по инструментам или валютам, итогов и количества выплат инструмента нет. Описание, дата, доступная identity и signed Money видны в каждой карточке; валюты по-прежнему разделены в summary/comparisons. Дополнительный запрос за составом не нужен. Раздел «Годом ранее» показывает цветные панели без общей белой подложки и внутренних отступов у внешнего блока. Точные периоды показаны в заголовках двух панелей через @@ -65,7 +65,7 @@ skeleton. Пустой результат объясняет отсутстви серверным правилом; клиент воспринимает ключ как непрозрачную строку. Общий Ledger endpoint не приобретает income-specific поле. -Income VM проверяет принадлежность entries по полученному ключу до подготовки +Frontend проверяет принадлежность выплат по полученному ключу до подготовки плоского списка выплат. Отсутствующий, пустой или нестроковый ключ, неоднозначные instrument keys, неизвестная группа, другая валюта или несовпадение receipt count приводят к существующему локальному diff --git a/features/brokerage-account-ledger.md b/features/brokerage-account-ledger.md index aa1d808..a8524f0 100644 --- a/features/brokerage-account-ledger.md +++ b/features/brokerage-account-ledger.md @@ -38,7 +38,7 @@ filters и данные сохраняют существующую семант - Запрашиваются только исполненные операции T-Bank. - Полный журнал фильтруется по периоду (два DateInput «С»/«По» с форматом DD.MM.YYYY и min/max-ограничением, русская локаль календаря) и категориям (MultiSelect с мультивыбором из trade, income, tax, fee, transfer, other). Фильтры имеют черновик в контролах и применённое состояние: изменение черновика не вызывает новый запрос к серверу; «Применить» коммитит черновик и сбрасывает cursor-пагинацию на первую страницу; «Сбросить» очищает черновик и немедленно применяет пустой фильтр (период по умолчанию и все категории). Cursor-пагинация («Назад»/«Далее») работает по последнему применённому фильтру, не по черновику. - В полном журнале и пяти последних записях обзора одна и та же компактная строка показывает логотип инструмента либо буквенный fallback, описание операции, дату, инструмент и денежное движение. Денежная метка строки показывает подпись конкретного типа события: для income-записи — «Купон» или «Дивиденд» (по upstream-типу купона или дивиденда), для остальных — подпись категории («Сделка», «Налог», «Комиссия», «Перевод», «Прочее»). Подпись типа выводится один раз и не повторяется во вторичной метадате (дата · инструмент); направление передаётся знаком «+» / «−` и семантическим цветом, а не текстом. Записи без движения денег остаются нейтральными («Без движения денег»). -- Полный журнал не показывает цену, количество, НКД и комиссию записи. Страница акции переиспользует тот же `LedgerEntryRow` с дополнительными количеством, ценой и ссылкой на счёт. Даты имеют формат ДД.ММ.ГГГГ, время операций явно обозначено UTC. При отсутствии описания используется локализованный тип операции. +- Полный журнал не показывает цену, количество, НКД и комиссию записи. Страница акции использует тот же формат строки с дополнительными количеством, ценой и ссылкой на счёт. Даты имеют формат ДД.ММ.ГГГГ, время операций явно обозначено UTC. При отсутствии описания используется локализованный тип операции. - Backend хранит в памяти процесса до 100 страниц Account Ledger: свежая страница живёт до 30 секунд, а при переполнении вытесняется давно не использовавшаяся страница (LRU). Ключ включает непрозрачную generation текущего T-Bank connection, пользователя, счёт, нормализованные период и categories, cursor и limit; отсутствующий `to` приводится к текущему 30-секундному bucket. Проверка connection и доступа к счёту выполняется до выдачи кэшированной страницы. После успешного создания, замены или удаления connection сохранённая либо поздно завершившаяся страница предыдущей generation недоступна Operations, Income Summary и Income Analytics. Старые страницы удаляются обычными TTL/LRU limits; операции не сохраняются и не синхронизируются локально. - Журнал не рассчитывает остаток после операции, суммарные доходы или налоги, историческую стоимость Portfolio, конвертацию валют и рыночные изменения цены. - `409 TBANK_NOT_CONNECTED`, `400 INVALID_TBANK_TOKEN`, `404` недоступного счёта и `503 TBANK_UNAVAILABLE` используют существующую семантику ошибок T-Bank. diff --git a/features/brokerage-account-upcoming-events.md b/features/brokerage-account-upcoming-events.md index 626c94a..41c939e 100644 --- a/features/brokerage-account-upcoming-events.md +++ b/features/brokerage-account-upcoming-events.md @@ -32,10 +32,10 @@ sequenceDiagram заголовка и event rows вместо отдельного spinner. Placeholder не представляет данные и не является интерактивным; при пустом результате фильтр остаётся доступным для изменения периода. -Page VM владеет account route input, authenticated entity query, выбором -loading/error/loaded, пустого результата и retry. Container вызывает один VM и передаёт -данные существующим state views. Query без account ID остаётся отключённым; -выделение VM не меняет порядок выбора состояний или поведение фонового refetch. +Frontend определяет выбранный счёт по адресу страницы, запрашивает данные +в рамках текущей сессии и показывает загрузку, ошибку, пустой или успешный +результат. Без идентификатора счёта запрос не выполняется. Повторная попытка +и фоновое обновление сохраняют правила отображения уже доступных данных. - По умолчанию период включает текущую календарную дату и следующие 30 дней. Полный список позволяет выбрать другой период полями «С» и «По», как в операциях: «Применить» сохраняет даты в URL и запрашивает события, «Сбросить» возвращает период по умолчанию. Обзор счёта сохраняет прогноз на 30 дней. - Суммы до налогов рассчитываются по текущему количеству бумаг на `calculatedAt`; они не учитывают будущие сделки или владение на дату реестра. diff --git a/features/portfolio-view.md b/features/portfolio-view.md index 7674bdc..85735a8 100644 --- a/features/portfolio-view.md +++ b/features/portfolio-view.md @@ -33,7 +33,7 @@ sequenceDiagram `GET /api/v1/brokerage-accounts/:accountId/portfolio` требует Bearer access token. Успешный ответ содержит Portfolio Snapshot: `lastSuccessfulAt` в RFC 3339 UTC, `isStale`, `totalsByCurrency`, `positions` и `cashBalances`. -Все количества и суммы передаются decimal strings без экспоненциальной записи. Деньги передаются как Money `{ currency, value }` с ISO-кодом валюты. Backend выполняет точные операции через `BigInt`; frontend форматирует строки для русской локали и не выполняет финансовую арифметику кроме display-only оценки доли позиции. +Все количества и суммы передаются decimal strings без экспоненциальной записи. Деньги передаются как Money `{ currency, value }` с ISO-кодом валюты. Backend выполняет точные денежные операции; frontend форматирует строки для русской локали и не выполняет финансовую арифметику кроме display-only оценки доли позиции. `Portfolio Position` -- ненулевой обычный актив из T-Bank portfolio. Position с type `CURRENCY` означает торгуемый валютный Instrument и не является Cash Balance. `currentValue` равен количеству, умноженному на текущую цену, когда оба значения и валюта доступны. `absoluteReturn` доступен только при одинаковой известной валюте текущей и средней цены. `instrumentUid` передаётся из upstream для точной идентификации и перехода на [страницу акции](stock-detail.md). `isin` и `ticker` берутся из уже разрешённой metadata Instrument и передаются nullable-строками. Невычислимые значения передаются `null`, а не нулём. Позиции остаются в upstream-порядке; виртуальные позиции не входят в ответ. diff --git a/features/stock-detail.md b/features/stock-detail.md index 967642b..fc094a3 100644 --- a/features/stock-detail.md +++ b/features/stock-detail.md @@ -38,7 +38,7 @@ SBER не заменяется на SBERP. Однобуквенные тикер Денежные показатели используют символ валюты; описание компании видно сразу под логотипом и названием, перед ценой. Далее идут график, моя позиция, предстоящие дивиденды и личная история событий. -Логотип использует существующий CDN helper и буквенный fallback. +Логотип загружается из публичного CDN; при отсутствии изображения показывается буквенная заглушка. Позиция отсутствующей или полностью проданной бумаги не создаёт пустого блока; рыночные данные и личная история остаются доступны. @@ -51,8 +51,7 @@ UTC, которое может быть старше времени получе ## API и владельцы Все методы требуют Bearer session и текущее подключение Т-Банка. -Новые endpoints принадлежат `StockController` / `StockService` существующего -`TbankConnectionModule`: +Публичный API страницы акции обслуживает инвестиционная подсистема backend: | Метод | Содержимое | | --- | --- | @@ -76,7 +75,7 @@ UID включён в ключ кэша и подпись общего курс восстанавливается переходом на первую страницу с повторным запросом. Сервис портфеля остаётся владельцем снимков и оценки позиций. Новая сводка -фильтрует `STOCK` по точному UID и использует decimal/BigInt, без FX. +фильтрует `STOCK` по точному UID и использует точную десятичную арифметику, без конвертации валют. Средняя цена равна сумме количеств, умноженных на средние цены, делённой на общее количество; она округляется сервером до девяти знаков, проценты — до шести. Неполные или разновалютные денежные итоги недоступны. Для нулевого количества @@ -134,9 +133,9 @@ TTM означает последние 12 месяцев, MRQ — послед ## Границы и проверка -Новые RPC проходят существующий `TbankClient`: TLS, rate limit, очередь, -deadline и retry. Доступ/расшифровка остаются у `TbankAccessService` и -`TbankTokenService`. Успешные ответы и совместные незавершённые запросы кэшируются +Запросы T-Bank проходят общую серверную интеграцию с TLS, лимитом частоты, +очередью, предельным временем ожидания и повторными попытками. Проверка доступа +и расшифровка token выполняются только backend. Успешные ответы и совместные незавершённые запросы кэшируются в bounded LRU на 30 секунд по пользователю, поколению подключения и параметрам. Для поиска `FindInstrument` дополняется сведениями `ShareBy` через тот же кэш карточки инструмента и одним пакетным `GetLastPrices` для списка. Ошибка @@ -148,15 +147,19 @@ deadline и retry. Доступ/расшифровка остаются у `Tban данными при изменении подключения; logout использует существующий session reset. Контракт генерируется из runtime Nest OpenAPI, затем обновляются frontend snapshot -и generated types. Unit/HTTP/MSW tests проверяют точность, разделение инструментов, +и generated types. Проверки отдельных подсистем и HTTP-взаимодействия проверяют точность, разделение инструментов, фильтрацию и курсоры, валидацию, отсутствие позиции и локальные ошибки. Браузерная проверка использует синтетические ответы и широкий/узкий viewport. Полные даты в интерфейсе имеют формат ДД.ММ.ГГГГ; ISO-формат транспорта и -временные зоны расчётов не меняются. Личная история использует общий с портфелем -`LedgerEntryRow`, дополненный количеством, ценой и ссылкой на счёт. История +временные зоны расчётов не меняются. Личная история использует общий с портфелем формат строки операции, +дополненный количеством, ценой и ссылкой на счёт. История дивидендов компании раскрывается в оформленном блоке с отдельными карточками выплат, датой реестра, доступной датой выплаты и суммой на акцию. -Реальные ограничения источника описаны в -[результатах исследования](../../design/stock-detail-api-live-findings.md). +Проверка реальных ответов источника выявила ограничения: отсутствие финансового +поля нельзя считать нулём, время получения не подтверждает дату отчётности, +а налог нельзя однозначно связать с отдельным дивидендом по совпадению даты. +Алгоритм корректировки исторических цен после корпоративных событий не +подтверждён, поэтому точное наложение старых личных сделок на график не обещается. +Выборка проверенных бумаг не доказывает полноту данных всего рынка. История открытых счетов не обещает полную историю владения, включая закрытые счета, переводы и универсальные корпоративные события. diff --git a/features/tbank-connection.md b/features/tbank-connection.md index 584c1db..a1283c7 100644 --- a/features/tbank-connection.md +++ b/features/tbank-connection.md @@ -36,7 +36,7 @@ API, logs или errors. Поэтому сохранённый или поздн После успешного `PUT` или `DELETE` frontend сначала коммитит подтверждённый статус connection, затем сбрасывает все generated GET query в -`/api/v1/brokerage-accounts` и `/api/v1/stocks` subtrees. Активные выборки запрашиваются заново, а +`/api/v1/brokerage-accounts`, `/api/v1/stocks` и `/api/v1/bonds` subtrees. Активные выборки запрашиваются заново, а неактивные загружаются при следующем использовании; данные предыдущей границы не остаются Client Cached Snapshot. Неуспешная mutation не меняет status, backend generation или financial cache. Обычная ошибка фонового refetch @@ -58,9 +58,10 @@ FIFO-ограничитель на экземпляр запускает не б ## Ограничения Подключение используется поставленными сценариями Brokerage Accounts, -Portfolio, Account Ledger, Future Cash Events и [страницы акции](stock-detail.md); frontend не обращается к +Portfolio, Account Ledger, Future Cash Events, [страницы акции](stock-detail.md) +и [облигации](bond-detail.md); frontend не обращается к T-Bank напрямую. Token encryption key не ротируется, rate limit остаётся per-instance, а обновление connection не сериализуется между распределёнными -экземплярами backend. Вендорные protobuf-контракты взяты из -`references/moex-vibe`; при необходимости обновляйте их вручную по официальной -документации T-Bank Invest API. +экземплярами backend. Интеграция использует protobuf-контракт T-Bank Invest API. Автоматическое +обновление внешнего протокола не предусмотрено; совместимость новой версии +проверяется при отдельном обновлении интеграции. diff --git a/package-lock.json b/package-lock.json index a99fb96..1cb5736 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,8 @@ "name": "wealth-ledger-docs", "version": "1.0.0", "devDependencies": { - "markdownlint-cli2": "0.23.2" + "markdownlint-cli2": "0.23.2", + "markdownlint-rule-relative-links": "5.1.2" }, "engines": { "node": "22.x" @@ -601,6 +602,20 @@ "markdownlint-cli2": ">=0.0.4" } }, + "node_modules/markdownlint-rule-relative-links": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/markdownlint-rule-relative-links/-/markdownlint-rule-relative-links-5.1.2.tgz", + "integrity": "sha512-mskM3EOV7SdAPEfhaLzdtaZ/25S/i6ef0sX6+O2LqqD7JvHgaY1OLVgMb3ZfybD90DFLRhzry3LieF+W/uS3gA==", + "dev": true, + "license": "MIT", + "dependencies": { + "markdown-it": "14.3.0", + "mime": "4.1.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, "node_modules/mdurl": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.1.0.tgz", @@ -1168,6 +1183,22 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", + "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa" + ], + "license": "MIT", + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/package.json b/package.json index 0f540ed..0c642b0 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "verify": "npm run lint && git diff --check" }, "devDependencies": { - "markdownlint-cli2": "0.23.2" + "markdownlint-cli2": "0.23.2", + "markdownlint-rule-relative-links": "5.1.2" } } diff --git a/research/README.md b/research/README.md new file mode 100644 index 0000000..1acf692 --- /dev/null +++ b/research/README.md @@ -0,0 +1,13 @@ +# Исследования + +Здесь хранятся наблюдения о внешних источниках и варианты решений. Дата и +ограничения относятся к проведённой проверке; рекомендация не становится +контрактом или реализованной возможностью автоматически. + +- [Источники кредитных рейтингов облигаций](bond-credit-ratings.md) — покрытие, + ограничения и выбор первого источника. Текущая интеграция НКР описана в + [странице облигации](../features/bond-detail.md). + +Принятые причины решений ищите в [ADR](../adr/README.md), текущее поведение — +в [карте возможностей](../features/README.md). Новое исследование добавляется +в этот индекс после проверки его источников и явного обозначения выводов.