210 lines
18 KiB
Markdown
210 lines
18 KiB
Markdown
## Context
|
||
|
||
Proposal утверждён Change Owner в текущей conversation. Change использует
|
||
`skip_specs: true`: пользовательское поведение уже определено Requirement
|
||
`brokerage-account-income-analytics — Instrument breakdown is traceable to exact Ledger Entries`.
|
||
Уточнение transport representation ниже реализует это требование, не меняя
|
||
правила расчёта дохода или выбранные пользователем возможности.
|
||
|
||
IncomeAnalyticsService возвращает отдельные `instruments` и `entries`.
|
||
Backend группирует выплаты по приоритету UID, FIGI, ticker/type, operation ID
|
||
с добавлением валюты. Frontend повторяет эту формулу для поиска состава строки.
|
||
Формулы пока совпадают; отсутствие явной связи допускает их дальнейшее
|
||
расхождение без изменения самих денежных итогов.
|
||
|
||
Frontend уже имеет page VM/view pattern, общую фабрику test QueryClient и
|
||
централизованный teardown. Остатки локальны: Events выбирает состояние и читает
|
||
данные в container; auth tests используют самостоятельные clients, один из
|
||
которых создаётся при render wrapper; T-Bank mutation tests повторяют client
|
||
configuration. Большинство остальных замечаний устранено ранее.
|
||
|
||
## Goals / Non-Goals
|
||
|
||
**Goals:**
|
||
|
||
- Один владелец связи каждой выплаты с aggregate Instrument/currency row.
|
||
- Frontend получает состав breakdown без восстановления server identity.
|
||
- Events следует существующему VM/view pattern; тестовый cache lifetime
|
||
определяется одним test case и сохраняется при rerender.
|
||
- Audit status и документация опираются на фактически подтверждённую поставку.
|
||
|
||
**Non-Goals:**
|
||
|
||
- Изменение identity priority, денежных алгоритмов, source currencies,
|
||
pagination, временных границ или прав доступа.
|
||
- Изменение runtime auth/query/cache policy, общий cleanup приложения,
|
||
новый framework тестирования или универсальный provider harness.
|
||
- Изменение governance, исторических audit claims либо восстановление
|
||
отсутствующих approvals прежних commits по косвенным признакам.
|
||
|
||
## Decisions
|
||
|
||
### 1. Backend передаёт opaque membership key каждой income entry
|
||
|
||
Income-specific transport type `IncomeAnalyticsEntryDto` расширяет существующий
|
||
`BrokerageOperationDto` обязательным строковым `instrumentKey`.
|
||
`IncomeAnalyticsDto.entries` использует этот subtype. Ledger endpoint и его
|
||
общий DTO не приобретают income-specific обязанностей.
|
||
|
||
`instrumentKey` совпадает с `IncomeInstrumentDto.key` соответствующей строки.
|
||
После отбора подходящих выплат backend один раз вычисляет ключ каждой entry
|
||
по существующей формуле. Aggregation по инструментам использует уже
|
||
вычисленный `entry.instrumentKey`, а не повторный вызов формулы. Остальные
|
||
агрегаты продолжают работать с теми же выплатами и exact decimal helpers.
|
||
|
||
Сохраняются состав и порядок top-level entries, порядок выплат внутри группы,
|
||
сортировка instruments, отрицательные значения и receipt counts. Не вводятся
|
||
дедупликация записей, новый идентификатор операции или зависимость от
|
||
уникальности ID, которой текущая коллекция дополнительно не проверяет.
|
||
Все entries выбранной аналитики имеют membership, даже если upstream не дал
|
||
UID, FIGI, ticker или instrument type; прежний fallback остаётся у backend.
|
||
|
||
Frontend воспринимает значение как opaque string и не разбирает его формат.
|
||
Изменение backend identity representation впоследствии не требует второй
|
||
реализации правила в клиенте.
|
||
|
||
Alternatives:
|
||
|
||
- Только дополнить тесты одинаковой формулы: фиксирует текущие примеры, но
|
||
оставляет двух владельцев правила; не устраняет исходную причину drift.
|
||
- Добавить `entryIds` к instruments: компактно, но требует второй связи через
|
||
ID и дополнительных предположений при одинаковых ID; прямой ключ entry
|
||
выражает ровно нужное отношение без такого lookup.
|
||
- Вложить полные entries в каждую строку: устраняет сопоставление, но при
|
||
сохранении существующего top-level массива дублирует наиболее объёмную часть
|
||
ответа; удаление массива осложняет совместимость без необходимости.
|
||
- Shared frontend/backend package с формулой: добавляет межрепозиторную
|
||
зависимость и по-прежнему раскрывает клиенту server identity algorithm.
|
||
|
||
### 2. Income VM собирает и проверяет только переданную связь
|
||
|
||
Существующий response-to-display mapper остаётся у Income VM. Он строит
|
||
`Map<string, entries[]>` одним проходом, добавляя entries к bucket без
|
||
копирования ранее накопленного массива. Для каждой instrument row берётся
|
||
bucket её ключа; trend derivation остаётся там же, UI geometry — во view.
|
||
Клиентская `incomeEntryKey` удаляется.
|
||
|
||
На этой границе проверяется целостность нового отношения: membership key
|
||
присутствует и является непустой строкой, instrument keys однозначны, каждой
|
||
entry соответствует существующая строка той же валюты, количество записей
|
||
каждой строки соответствует `receiptCount`. Для непустой строки отсутствие
|
||
entries не подменяется успешным пустым drill-down. Денежные суммы на frontend
|
||
повторно не рассчитываются.
|
||
|
||
При нарушении связи mapper возвращает явный неуспешный результат, а VM выбирает
|
||
существующее локальное error/retry состояние аналитики. Это сохраняет уже
|
||
принятое поведение для неполного результата: не показывать его как полный и
|
||
не превращать ожидаемую ошибку данных в route crash. Нет fallback на прежнюю
|
||
формулу, пропуска неизвестных entries или исправления полученных данных.
|
||
Полностью пустой ответ с пустыми instruments и entries остаётся допустимым.
|
||
|
||
Проверка ограничена новым membership seam; общий runtime DTO validator,
|
||
новые schema libraries и общий error protocol не вводятся. Backend service
|
||
tests фиксируют соответствие membership, counts и точных итогов для UID/FIGI/
|
||
ticker/type/operation fallback, валют и отрицательных корректировок. Frontend
|
||
behavior tests используют opaque keys, не выводимые из identity fields, и
|
||
отдельно проверяют recoverable отказ при повреждённой связи.
|
||
|
||
### 3. Events получает обычный page VM
|
||
|
||
Page VM владеет account route input, существующим authenticated entity hook,
|
||
retry callback и discriminated state loading/error/empty/loaded. Container
|
||
вызывает один VM и выбирает уже существующие state views. Данные и callbacks
|
||
передаются props; новые layout wrappers и state-view files не нужны.
|
||
|
||
Извлечение сохраняет текущий порядок выбора состояний, поведение при
|
||
отсутствующем account ID, UI событий и действия восстановления. Изменение
|
||
фонового refetch поведения не смешивается с переносом обязанностей.
|
||
Наблюдаемая граница проверки — страница и существующий entity seam.
|
||
|
||
### 4. Остатки test clients используют существующую фабрику
|
||
|
||
Auth и T-Bank test harnesses используют `createTestQueryClient`; при необходимости
|
||
query-only wrapper остаётся `createQueryWrapper`. Client создаётся до render
|
||
React wrapper, один раз на независимый test case, и доступен для cache assertions.
|
||
Существующий локальный выбор AuthProvider, Mantine, Router и initial entries
|
||
сохраняется; глобальный client или render-with-everything helper не появляются.
|
||
|
||
Создание client внутри обычной функции `renderPage` или `renderRoutes`, которая
|
||
вызывается один раз на case, само по себе не является дефектом. Перенос нужен
|
||
там, где создание выполняется в React render wrapper или повторяет default
|
||
policy. Shared teardown сохраняет порядок cleanup, auth reset, MSW reset.
|
||
Общая инфраструктура не заменяется, используются уже установленные зависимости
|
||
и имеющиеся helpers; обязательный trigger Solution Assessment отсутствует.
|
||
|
||
### 5. Audit evidence остаётся в существующих durable owners
|
||
|
||
Три исходных audit reports сохраняются byte-for-byte как versioned snapshots
|
||
в docs с однозначным index. Исходные source files и прежние snapshots не
|
||
редактируются. Единственный live remediation registry получает нормализованное
|
||
сопоставление повторяющихся claims, текущий change и evidence: одинаковые
|
||
номера F из разных отчётов не считаются одинаковыми Audit IDs автоматически.
|
||
|
||
Существующие исправления отмечаются как подтверждённое состояние с реальными
|
||
ссылками, без фиктивных implementation tasks. Незакрытые остатки относятся к
|
||
текущему change; все существенные claims получают disposition, чтобы состав
|
||
закрытия можно было восстановить из registry. T-Bank cache boundary ссылается
|
||
на уже существующую завершённую поставку.
|
||
|
||
As-built docs обновляются до фактического результата реализации. Completion
|
||
Evidence принадлежит существующему двухфазному завершению после archive и
|
||
delivery commits. Новая запись различает прежние implementation commits и
|
||
собственные наблюдения текущей проверки, не объявляя прежние commits прошедшими
|
||
несуществовавший SDD lifecycle. Исторические statements о результатах остаются
|
||
историческими; `done` требует полного действующего evidence contract.
|
||
|
||
## Delivery Map
|
||
|
||
| Area | Status | Architectural outcome or N/A reason |
|
||
| --- | --- | --- |
|
||
| frontend | `affected` | Generated income membership contract, display mapping без identity formula, Events VM и стабильные test clients. |
|
||
| backend | `affected` | Income entry subtype с membership key, вычисляемым одним owner и используемым instrument aggregation. |
|
||
| specs | `affected` | Canonical Proposal/Design/Tasks и archive текущего change; main Requirements сохраняются, Delta Specs пропущены. |
|
||
| docs | `affected` | As-built income/frontend описание, неизменяемые audit sources, их нормализованные статусы и фактическое terminal evidence. |
|
||
|
||
### Contract Gate Decision
|
||
|
||
| Gate | Status | Reason |
|
||
| --- | --- | --- |
|
||
| runtime-openapi | `required` | Меняются backend income response representation и frontend snapshot/generated consumer; нужна проверка runtime document против committed snapshot. |
|
||
|
||
## Risks / Trade-offs
|
||
|
||
- Membership key повторяется у каждой entry и слегка увеличивает response.
|
||
Это меньше дублирования целых records и исключает formula ownership в UI.
|
||
- Новый frontend требует обновлённый backend. Совместимость обеспечивается
|
||
порядком поставки, без бессрочного fallback на старую формулу.
|
||
- Повреждённое отношение прекращает показ загруженной аналитики вместо тихой
|
||
потери строк. Проверки этого случая принадлежат public behavior seam.
|
||
- Существующие backend caches хранят Ledger pages, а analytics response
|
||
формируется заново; schema migration и новая cache generation не нужны.
|
||
- Не все предыдущие audit corrections имеют историческое terminal evidence.
|
||
Текущий change подтверждает наблюдаемое состояние, а не создаёт историю
|
||
approval задним числом. Непрошедший affected-area gate блокирует завершение.
|
||
|
||
## Migration Plan
|
||
|
||
Сначала backend начинает возвращать additive `instrumentKey`, сохраняя все
|
||
прежние поля и порядок. Старый frontend продолжает работать с таким ответом.
|
||
Backend OpenAPI остаётся source authority; frontend snapshot и generated types
|
||
обновляются существующим workflow, после чего переключается frontend consumer.
|
||
|
||
При локальном переключении клиент перезагружается: прежние in-memory analytics
|
||
results и незавершённые запросы не переносятся в новый consumer. Новый frontend
|
||
не поставляется раньше backend. Events/test changes независимы от runtime
|
||
contract и не требуют данных для миграции.
|
||
|
||
Проверки соответствия существующим требованиям и canonical affected-area gates
|
||
предшествуют независимому read-only review. Затем применяется действующий
|
||
archive/delivery/evidence порядок; Design не объявляет будущие результаты.
|
||
|
||
### Rollback Contract
|
||
|
||
| Field | Decision |
|
||
| --- | --- |
|
||
| Status | `required` |
|
||
| Trigger | Обнаружено неверное сопоставление выплат, нарушение совместимости income response или регрессия отображения после переключения. |
|
||
| Actions | Сначала вернуть предыдущий frontend consumer и соответствующие snapshot/types, перезагрузить клиент. Затем при необходимости вернуть backend representation; дополнительное поле совместимо со старым клиентом и может временно оставаться. Events/test changes откатываются независимо при регрессии их поведения. |
|
||
| Data safety | Изменяются только производные read-only response и клиентские представления; SQLite schema, credentials, Ledger storage и upstream operations не меняются. In-memory данные перечитываются; записи и денежные данные не удаляются. |
|
||
| Verification | В возвращённой паре consumer/provider income drill-down соответствует исходным Instrument/currency/period и signed totals; runtime OpenAPI совпадает со snapshot, generated types соответствуют ему, affected-area gates и semantic review не выявляют регрессий. |
|