18 KiB
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 не выявляют регрессий. |