Files

210 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 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 не выявляют регрессий. |