11 KiB
Context
Поведенческий contract зафиксирован в delta specs. Текущее состояние уже близко
к целевой модели, но терминология и один public API fragment расходятся:
Portfolio и Ledger используют Money как { currency, value }, а Future Cash
Event возвращает прогнозную сумму двумя nullable полями. Docs и specs местами
говорят о счёте как о принадлежащем User, хотя runtime проверяет доступность
через текущий T-Bank connection и не хранит Brokerage Account как durable
aggregate.
Backend уже имеет общий Money DTO для Portfolio и Account Ledger, нормализацию
денежных значений, проверку account access через T-Bank connection и generated
OpenAPI как источник frontend types. Frontend уже умеет форматировать Money
через portfolio display helper, но страницы будущих событий и overview читают
amount/currency напрямую. Docs содержит canonical domain glossary, feature
docs и system overview, которые должны описывать только as-built состояние
после реализации.
Ограничение SDD: этот change меняет публичный контракт финансовой read-модели, поэтому реализация должна синхронизировать backend runtime OpenAPI, generated frontend types, frontend consumers, tests, specs и docs в одном delivery.
Goals / Non-Goals
Goals:
- Сделать Future Cash Event прогнозную сумму единым nullable Money value во всём runtime contract, generated contract и frontend rendering.
- Закрепить единый доменный язык для доступности Brokerage Account через текущий T-Bank connection без durable ownership claim.
- Явно различить Portfolio Snapshot, Portfolio Position, Currency Instrument Position и Cash Balance в specs, docs и проверяемом поведении.
- Расширить Instrument language на Future Cash Events без изменения смысла существующих инструментальных identifiers.
- Обновить glossary и feature docs только после реализации, чтобы они описали фактическое поведение, а не план.
Non-Goals:
- Не переименовывать публичные routes, пользовательские страницы или legacy controller/service names только ради терминологии.
- Не вводить storage для Brokerage Account, Portfolio, Portfolio Snapshot, Ledger Entry или Future Cash Event.
- Не менять supported account type policy, T-Bank token lifecycle, upstream data sources, cache TTL, stale fallback или сортировку событий.
- Не выполнять широкую модульную перестройку NestJS/React сверх мест, необходимых для contract и terminology alignment.
Decisions
D1. Сделать Future Cash Event amount единственным nullable Money field
Backend Upcoming Event DTO должен перейти с пары amount/currency на одно
nullable Money-поле для прогнозной суммы. Coupon, dividend и maturity получают
Money в исходной валюте, когда сумма известна; offer и неизвестные денежные
условия получают null. Расчёт количества, даты, event type, identifiers,
sorting и calculatedAt сохраняются.
Rationale: это убирает частный контракт, который противоречит Money в Portfolio и Ledger, и делает неизвестность явной: нет суммы целиком, а не отдельно нет amount или currency. Alternative оставить два поля и описать их как legacy отклонена, потому что она закрепляет второй денежный язык и заставляет frontend каждый раз реконструировать Money вручную.
D2. Переиспользовать существующую Money normalization surface
Design не вводит новую денежную библиотеку или второй DTO. Upcoming Events
использует тот же shape и правила десятичных строк, что Portfolio и Account
Ledger: ISO currency, decimal string, no exponential notation, missing value as
null. Tests должны покрыть переходные edge cases: рассчитанная сумма,
нулевая/отсутствующая сумма offer, отрицательная или дробная normalization
там, где она проходит через общий money formatting path.
Alternative отдельный ProjectedAmount shape отклонена: доменно это тот же
Money, только nullable по uncertainty события.
D3. Оставить route/UI names стабильными, уточнив domain language
Публичные routes и пользовательские entrypoints остаются brokerage-accounts,
Portfolio page и «Будущие события». Термины Portfolio Snapshot, Currency
Instrument Position и Cash Balance закрепляются в requirements, glossary,
feature docs и test naming там, где они объясняют поведение. Runtime class/file
names можно менять только если это локально улучшает читаемость без широкой
цепочки churn.
Rationale: пользователю и API consumers не нужна массовая переадресация из-за уточнения модели; ценность change в согласованном смысле и денежном contract.
D4. Считать account access текущим наблюдаемым правом через T-Bank connection
Implementation не должна добавлять ownership state. Все account-scoped flows продолжают проверять, что account открыт, поддержан и доступен через текущий T-Bank connection User на момент запроса. Specs/docs/client copy используют этот язык; code tests защищают existing observable behavior, если затронутая реализация меняется.
Alternative хранить список счетов как owned accounts отклонена: это расширяет domain и storage без требования продукта и противоречит текущей брокерской интеграции.
D5. Frontend consumes generated Money contract directly
После backend contract change generated OpenAPI types синхронизируются штатным
workflow. Frontend pages, account overview block, event entity tests и MSW
fixtures переходят на nullable Money-поле и используют существующий
formatMoney без ручной сборки { currency, value }. UI copy «без суммы»
сохраняется для null, а known Money отображается в исходной валюте.
Alternative временно поддерживать оба shapes во frontend отклонена для этого локального продукта: это усложняет presentation path без совместимого backend режима и оставляет двусмысленность в tests.
Delivery Map
| Area | Status | Architectural outcome or N/A reason |
|---|---|---|
| frontend | affected |
Generated API types, event fixtures, account overview and full events page consume nullable Money for projected event amount and preserve existing event UI behavior. |
| backend | affected |
Upcoming Events runtime DTO/service/tests expose projected event amount as nullable Money and reuse existing money normalization semantics; account access behavior remains current-connection based. |
| specs | affected |
Proposal, Delta Specs and this Design define the canonical domain and contract alignment; archive later syncs modified requirements into main specs. |
| docs | affected |
Domain glossary, system/feature docs and any affected README/index references describe as-built canonical terminology after implementation without duplicating SDD artifacts. |
Contract Gate Decision
| Gate | Status | Reason |
|---|---|---|
| runtime-openapi | required |
Backend response schema and frontend generated consumer types both change for Upcoming Events, so runtime OpenAPI must be compared and synchronized before completion. |
Risks / Trade-offs
- Breaking consumer contract. Existing consumers using
amountandcurrencybreak. Mitigation: update generated types, fixtures and all in-repo frontend consumers in the same change; no compatibility layer because product is local and the old shape contradicts canonical Money. - Terminology-only churn hiding behavior changes. Many docs/spec words change without runtime changes. Mitigation: keep implementation edits scoped to observable contract and targeted regressions; docs update only as-built terms after verification.
- Money uncertainty can be mistaken for zero. Events without known monetary
terms must not show
0. Mitigation: DTO and UI tests covernullprojected amount and existing «без суммы» rendering. - Currency instrument vs cash balance confusion persists in fixtures.
Mitigation: add or preserve focused portfolio fixture/test proving
CURRENCYPortfolio Position remains separate from Cash Balance.
Migration Plan
- Update backend Upcoming Events DTO and mapping to expose nullable Money for projected event amount while preserving event selection, sorting and calculation timing.
- Update backend tests for coupon/dividend/maturity Money, offer
null, and unchanged account access/error behavior where touched. - Regenerate or sync OpenAPI/frontend contract artifacts through the established contract workflow and verify runtime OpenAPI.
- Update frontend generated consumers, fixtures, event page and account overview event block to render nullable Money directly.
- Update frontend tests for known event amount and no-amount states.
- Update docs glossary and affected feature/system documentation to as-built terminology after implementation verification.
- Run affected local gates plus the workspace change dispatcher before archive.
Rollback is a normal code/docs/spec revert: no database migration, persisted state migration, cache invalidation or dependency rollback is introduced.