Files
specs/openspec/changes/archive/2026-08-25-align-domain-model-and-money-contract/design.md
T

11 KiB
Raw Blame History

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 amount and currency break. 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 cover null projected amount and existing «без суммы» rendering.
  • Currency instrument vs cash balance confusion persists in fixtures. Mitigation: add or preserve focused portfolio fixture/test proving CURRENCY Portfolio Position remains separate from Cash Balance.

Migration Plan

  1. Update backend Upcoming Events DTO and mapping to expose nullable Money for projected event amount while preserving event selection, sorting and calculation timing.
  2. Update backend tests for coupon/dividend/maturity Money, offer null, and unchanged account access/error behavior where touched.
  3. Regenerate or sync OpenAPI/frontend contract artifacts through the established contract workflow and verify runtime OpenAPI.
  4. Update frontend generated consumers, fixtures, event page and account overview event block to render nullable Money directly.
  5. Update frontend tests for known event amount and no-amount states.
  6. Update docs glossary and affected feature/system documentation to as-built terminology after implementation verification.
  7. 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.