Files

18 KiB
Raw Permalink Blame History

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