spec: finalize wealth-ledger SDD migration
This commit was merged in pull request #26.
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Получение Portfolio выбранного Brokerage Account
|
||||
Система SHALL предоставлять авторизованному User `GET /api/v1/brokerage-accounts/:accountId/portfolio`. Она MUST возвращать нормализованный полный Portfolio Snapshot только для открытого поддерживаемого Brokerage Account, доступного через текущий T-Bank connection User. Ответ MUST иметь envelope `{ data, meta }` по контракту `tbank-data-sync`; сохранённый полный Snapshot в `data` MUST содержать `lastSuccessfulAt` в RFC 3339 UTC без миллисекунд, boolean `isStale`, `totalsByCurrency`, `positions` и `cashBalances`.
|
||||
|
||||
Portfolio Snapshot является результатом наблюдения Portfolio на конкретный момент. Система MUST сохранять последний полный Snapshot постоянно, без накопления исторических снимков стоимости. Это MUST NOT подтверждать доступность Brokerage Account вне текущего T-Bank connection. Проверка локальной сессии, текущего подключения и сохранённого состава доступных счетов MUST выполняться без синхронного запроса списка счетов T-Bank. При отсутствии сохранённого Snapshot ответ MUST иметь `200`, `data: null`, `meta.availability: missing` и состояние запрошенной загрузки; это MUST NOT обозначать пустой Portfolio.
|
||||
|
||||
#### Scenario: Получение текущего Portfolio
|
||||
- **WHEN** User запрашивает Portfolio доступного Brokerage Account, а полный Snapshot текущего подключения успешно сохранён и ещё свеж
|
||||
- **THEN** система отвечает `200` с полным нормализованным Portfolio Snapshot в `data` без ожидания T-Bank с `isStale: false` и временем успешного получения финансового снимка
|
||||
|
||||
#### Scenario: Недоступный Brokerage Account
|
||||
- **WHEN** для текущего подключения подтверждено, что `accountId` не принадлежит User, закрыт, неподдерживаем или больше не доступен
|
||||
- **THEN** система отвечает `404` без Portfolio Snapshot
|
||||
|
||||
#### Scenario: Сохранённый Portfolio читается после перезапуска
|
||||
- **WHEN** backend перезапущен, текущий connection не изменился и полный Snapshot уже сохранён
|
||||
- **THEN** запрос возвращает его из постоянного хранилища без ожидания T-Bank и отдельно сообщает свежесть и состояние фонового обновления
|
||||
|
||||
### Requirement: Свежесть и fallback полного Portfolio Snapshot
|
||||
Система MUST постоянно сохранять последний полный успешный Portfolio Snapshot отдельно для User, Brokerage Account и текущего T-Bank connection. Обычный запрос MUST возвращать сохранённый Snapshot без ожидания T-Bank; при возрасте не менее одной минуты он MUST запросить фоновое обновление. Временная ошибка MUST NOT удалять Snapshot или ограничивать его выдачу прежним пределом пяти минут. `lastSuccessfulAt` MUST сохранять время успешного получения финансового снимка и совпадать с соответствующим временем в `meta`; `isStale` MUST отражать `meta.freshness: stale`, а не состояние клиентского кэша. Ручное действие «Обновить» MUST обходить проверку давности текущего Portfolio и его показанных зависимостей. Неуспешное ручное обновление MUST сохранять исходное `lastSuccessfulAt` и MUST NOT само по себе устанавливать `isStale: true`, пока минутный срок свежести сохранённого финансового снимка не истёк; ошибка MUST отображаться отдельно через `meta.sync`.
|
||||
|
||||
Metadata инструмента MUST NOT влиять на успешность или свежесть Portfolio Snapshot. Сохранённые metadata MUST использоваться без ожидания их обновления, а отсутствующие сведения MUST допускать существующий fallback идентификатора. Новые доступные metadata MUST дополнять представление, не меняя времени финансового снимка. Общие данные инструментов MUST следовать суточной свежести `tbank-data-sync`, без сохранения персональных позиций в общих данных.
|
||||
|
||||
После успешной замены или удаления T-Bank connection прежние Snapshot и поздние результаты его запросов MUST NOT обслуживать новое подключение. Известный отказ token текущего подключения MUST блокировать личное чтение с существующим `INVALID_TBANK_TOKEN`; он MUST NOT маскироваться сохранённым Snapshot.
|
||||
|
||||
#### Scenario: Свежий Snapshot
|
||||
- **WHEN** User повторно запрашивает тот же Portfolio ранее чем через одну минуту после успешного получения
|
||||
- **THEN** система возвращает свежий Snapshot без нового обращения к T-Bank
|
||||
|
||||
#### Scenario: Медленный lookup инструмента
|
||||
- **WHEN** metadata инструмента ещё отсутствует и его lookup не завершился
|
||||
- **THEN** система возвращает сохранённый финансовый Snapshot без ожидания lookup с доступным fallback, а после успешного lookup последующее чтение использует полученное name
|
||||
|
||||
#### Scenario: Fallback после временной upstream-ошибки
|
||||
- **WHEN** обновление устаревшего Snapshot неуспешно из-за временной недоступности T-Bank, а последний полный успешный Snapshot получен более пяти минут назад
|
||||
- **THEN** система отвечает `200` этим Snapshot с `isStale: true`, исходным `lastSuccessfulAt` и ошибкой обновления в `meta.sync`, без повторной синхронной попытки
|
||||
|
||||
#### Scenario: Ручное обновление ещё свежего Snapshot неуспешно
|
||||
- **WHEN** User принудительно обновляет Snapshot, полученный менее минуты назад, и попытка завершается временной ошибкой до истечения его TTL
|
||||
- **THEN** наблюдение возвращает прежний Snapshot с `isStale: false`, исходным `lastSuccessfulAt` и отдельной ошибкой синхронизации; ошибка не продлевает TTL и не делает данные устаревшими раньше срока
|
||||
|
||||
#### Scenario: Недействительный token не маскируется fallback-данными
|
||||
- **WHEN** фоновое обновление подтвердило отклонение token текущего подключения как неаутентифицированного или без прав, после чего запрошен Portfolio
|
||||
- **THEN** система не возвращает fallback Snapshot и отвечает `400` Problem Details с code `INVALID_TBANK_TOKEN`
|
||||
|
||||
### Requirement: Состояния и ошибки Portfolio на клиенте
|
||||
|
||||
Клиент SHALL загружать Portfolio при открытии защищённого маршрута
|
||||
`/accounts/:accountId`. Он MUST использовать успешный ответ в течение
|
||||
30 секунд, без automatic retry неуспешного запроса, периодической синхронизации или refetch при возврате фокуса. Наблюдение за состоянием уже запрошенной работы MUST следовать `tbank-data-sync` и MUST NOT запускать новую работу.
|
||||
При загрузке без ранее полученного успешного ответа либо при `data: null`
|
||||
с состоянием загрузки для этого же Brokerage Account клиент MUST показывать структурное skeleton-состояние ожидаемого
|
||||
содержимого, включая сводку, без вымышленных сумм. Область загрузки MUST
|
||||
иметь доступное обозначение занятости; skeleton MUST NOT быть интерактивным
|
||||
или получать клавиатурный фокус.
|
||||
|
||||
Истечение срока свежести MUST NOT само по себе скрывать уже доступный
|
||||
Client Cached Snapshot. Во время фонового обновления и после его временной ошибки
|
||||
клиент MUST сохранять ранее полученные данные того же Portfolio, включая
|
||||
валютные итоги и исходное `lastSuccessfulAt`. При обновлении клиент MUST
|
||||
показывать компактную неблокирующую индикацию без замены данных skeleton.
|
||||
При ошибке обновления он MUST показывать локализованное неблокирующее
|
||||
предупреждение о последних полученных данных и предоставлять действие
|
||||
повторной попытки. Сохранённые данные MUST NOT считаться новым успешным
|
||||
снимком или подтверждением текущей доступности Brokerage Account.
|
||||
|
||||
Клиент MUST показать штатное empty-состояние только при `meta.availability: complete`, непустом `data` и пустых `positions`
|
||||
и `cashBalances`, без нулевых валютных карточек. Для показываемого состава
|
||||
Portfolio клиент MUST сохранять время успешного получения снимка
|
||||
`lastSuccessfulAt`, форматируемое в UTC. При `isStale: true` клиент MUST
|
||||
показывать Snapshot, его исходное время и нейтральное предупреждение
|
||||
об устаревших данных. Ошибка синхронизации MUST иметь отдельное безопасное объяснение. На экране MUST быть доступно действие «Обновить», обновляющее текущий экран и его зависимости независимо от давности. Server-side stale и ошибка фонового
|
||||
обновления Client Cached Snapshot MUST оставаться раздельными состояниями:
|
||||
ошибка клиента MUST NOT менять или подменять `isStale`, а оба предупреждения
|
||||
и действие повтора клиентского обновления MUST оставаться доступными при
|
||||
одновременном наличии этих состояний.
|
||||
|
||||
При ответе `404` для этого же Portfolio клиент MUST показать состояние недоступного счёта
|
||||
с действием возврата к списку счетов без действия повтора. При `TBANK_NOT_CONNECTED` или `INVALID_TBANK_TOKEN` клиент MUST скрыть личные данные даже при наличии Snapshot. При отсутствии Snapshot и `TBANK_UNAVAILABLE`
|
||||
или иной ошибке клиент MUST показать безопасное error-состояние с доступными
|
||||
для ошибки действиями; действие повтора разрешено только для временной
|
||||
и неизвестной ошибки.
|
||||
|
||||
#### Scenario: Показ устаревшего Portfolio
|
||||
|
||||
- **WHEN** клиент получает успешный ответ с `isStale: true`
|
||||
- **THEN** клиент показывает позиции, остатки, валютные итоги, время
|
||||
последнего успешного снимка, нейтральное предупреждение об устаревании
|
||||
и доступное действие «Обновить»
|
||||
|
||||
#### Scenario: Недоступный счёт на клиенте
|
||||
|
||||
- **WHEN** клиент получает `404` для текущего Portfolio
|
||||
- **THEN** клиент не показывает Portfolio и предлагает вернуться к списку
|
||||
Brokerage Account без действия повтора
|
||||
|
||||
#### Scenario: Первичная загрузка Portfolio
|
||||
|
||||
- **WHEN** клиент ожидает Portfolio этого Brokerage Account без ранее
|
||||
полученного успешного ответа либо получает `data: null` с активной загрузкой
|
||||
- **THEN** он показывает структурный skeleton, включая место сводки,
|
||||
с доступным обозначением загрузки, без вымышленных или нулевых сумм
|
||||
и без фокусируемых placeholders
|
||||
|
||||
#### Scenario: Обновление после истечения срока свежести
|
||||
|
||||
- **WHEN** срок свежести показанного Portfolio истёк и началось фоновое
|
||||
обновление данных того же Brokerage Account
|
||||
- **THEN** клиент сохраняет прежние валютные итоги и время их успешного
|
||||
получения, показывает неблокирующую индикацию обновления и не заменяет
|
||||
доступный состав и сводку skeleton-состоянием
|
||||
|
||||
#### Scenario: Ошибка обновления после успешного ответа
|
||||
|
||||
- **WHEN** клиент уже показывает Client Cached Snapshot Portfolio,
|
||||
а обновление этих же данных завершается временной ошибкой
|
||||
- **THEN** клиент сохраняет данные, суммы и время прежнего снимка,
|
||||
показывает неблокирующее предупреждение и действие повтора;
|
||||
ошибка не делает сохранённый снимок свежим и не изменяет `isStale`
|
||||
|
||||
#### Scenario: Ошибка клиентского обновления при server-side stale
|
||||
|
||||
- **WHEN** клиент показывает Portfolio с `isStale: true`, а фоновое
|
||||
обновление этого же Portfolio завершается временной ошибкой
|
||||
- **THEN** прежние суммы и время остаются видимыми одновременно
|
||||
с нейтральным server-side предупреждением, отдельным предупреждением
|
||||
об ошибке клиентского обновления и его действием повтора
|
||||
|
||||
#### Scenario: Повторное обновление успешно
|
||||
|
||||
- **WHEN** после ошибки фонового обновления User запускает повторную
|
||||
попытку и получает успешный Portfolio Snapshot
|
||||
- **THEN** до ответа прежние суммы и время остаются видимыми, затем
|
||||
клиент показывает значения и `lastSuccessfulAt` успешного ответа,
|
||||
убирает предупреждение об ошибке клиентского обновления и показывает
|
||||
server-side предупреждение только в соответствии с новым `isStale`
|
||||
|
||||
#### Scenario: Штатный пустой Portfolio на клиенте
|
||||
|
||||
- **WHEN** успешно полученный ответ имеет `meta.availability: complete` и Portfolio в `data` содержит пустые `positions`
|
||||
и `cashBalances`
|
||||
- **THEN** клиент показывает существующее empty-состояние без валютных
|
||||
карточек с вымышленными нулевыми итогами
|
||||
Reference in New Issue
Block a user