spec: finalize wealth-ledger SDD migration
This commit was merged in pull request #26.
This commit is contained in:
@@ -15,7 +15,11 @@ Session-oriented SDD workflow SHALL направлять каждую agent-се
|
||||
phase record или ожидание решения MUST NOT само по себе принуждать к новой
|
||||
сессии той же роли. Specification MUST поддерживать bounded progression
|
||||
`Proposal + Delta Specs` и `Design + Tasks`, сохраняя отдельную explicit
|
||||
approval boundary перед каждым downstream artifact.
|
||||
approval boundary перед каждым downstream artifact. Для `wealth-ledger-sdd-v2`
|
||||
Specification SHALL также поддерживать System Analysis после Delta Specs,
|
||||
параллельные Design и QA Analysis после System Analysis, Docs Impact после
|
||||
обоих и Tasks после Docs Impact. Для active `wealth-ledger-sdd` сохраняются
|
||||
прежние пары и approvals без автоматической миграции.
|
||||
|
||||
#### Scenario: Начинается новый change
|
||||
|
||||
@@ -40,14 +44,14 @@ approval boundary перед каждым downstream artifact.
|
||||
|
||||
- **WHEN** Change Owner явно одобряет неизменившиеся Delta Specs
|
||||
- **THEN** workflow завершает bounded `Proposal + Delta Specs` progression и
|
||||
направляет следующий Specification outcome к Design
|
||||
направляет следующий Specification outcome к Design для legacy либо к System Analysis для v2
|
||||
|
||||
#### Scenario: Design явно одобрен
|
||||
|
||||
- **WHEN** Change Owner явно одобряет неизменившийся Design в bounded
|
||||
`Design + Tasks` progression
|
||||
- **THEN** Specification MAY продолжить в той же роли и создать Tasks без
|
||||
перехода к Implementation до отдельного approval Tasks
|
||||
- **THEN** для legacy Specification MAY продолжить в той же роли и создать Tasks;
|
||||
для v2 Tasks ожидает также QA Analysis и Docs Impact; Implementation ждёт отдельного approval Tasks
|
||||
|
||||
#### Scenario: Завершилась реализация
|
||||
|
||||
|
||||
@@ -127,3 +127,18 @@
|
||||
#### Scenario: Пагинация по применённому фильтру
|
||||
- **WHEN** User переходит к следующей или предыдущей странице журнала при изменённом, но не применённом черновике
|
||||
- **THEN** пагинация выполняется по последнему применённому фильтру, а черновик в контролах сохраняется
|
||||
|
||||
### Requirement: Отсутствующий исторический интервал загружается только впервые
|
||||
Система MUST позволять авторизованному User при первом открытии ещё не загруженного периода операций доступного Brokerage Account начать получение только недостающего интервала без ожидания T-Bank в локальном ответе. Это является исключением первой загрузки и MUST NOT запускать обновление других личных данных. Полностью загруженный прошлый интервал MUST NOT повторно сверяться из-за чтения страницы или часового обхода. Изменяемый хвост операций MUST обновляться часовым обходом. Незавершённый интервал и ошибка загрузки MUST NOT обозначаться полными, а последний подтверждённый результат MUST оставаться доступным.
|
||||
|
||||
#### Scenario: Первый просмотр отсутствующего прошлого периода
|
||||
- **WHEN** User открывает доступный исторический период без подтверждённой полноты
|
||||
- **THEN** система возвращает локальный результат с явным состоянием неполноты и начинает загрузку недостающих данных
|
||||
|
||||
#### Scenario: Повторное открытие полного периода
|
||||
- **WHEN** User повторно открывает уже полностью загруженный прошлый период
|
||||
- **THEN** система читает сохранённые операции без повторной сверки с T-Bank
|
||||
|
||||
#### Scenario: Ошибка загрузки
|
||||
- **WHEN** загрузка части отсутствующего периода завершается ошибкой
|
||||
- **THEN** ранее подтверждённые операции сохраняются, а неполная часть не помечается полной
|
||||
|
||||
@@ -7,41 +7,53 @@
|
||||
## Requirements
|
||||
|
||||
### Requirement: User can retrieve upcoming events for an account
|
||||
Система MUST предоставлять аутентифицированному User read-only список известных будущих событий открытого поддерживаемого Brokerage Account, доступного через текущий T-Bank connection User, от момента запроса до 30 календарных дней после него включительно. Ответ MUST содержать только события coupon, dividend, maturity и offer, отсортированные по дате по возрастанию, и момент расчёта прогноза.
|
||||
Система MUST предоставлять аутентифицированному User read-only `GET /api/v1/brokerage-account-upcoming-events` для выбранного открытого поддерживаемого Brokerage Account либо существующего режима «Все счета» (`all`) в границах текущего T-Bank connection User. Параметры `from` и `to` MUST сохранять существующий контракт: необязательные строгие календарные даты `YYYY-MM-DD` с включительными границами в UTC. Без `from` начало MUST соответствовать текущей календарной дате UTC; без `to` конец MUST соответствовать дате `from` плюс 30 календарных дней. При `from` позже `to` система MUST возвращать существующую ошибку `INVALID_EVENTS_PERIOD`, не подменять период, не запускать загрузку другого периода и не возвращать его прогноз. Обычный GET MUST NOT запускать личную синхронизацию.
|
||||
|
||||
Список будущих событий является прогнозом по текущему наблюдаемому состоянию счёта и MUST NOT означать, что WealthLedger хранит долговременное право владения Brokerage Account независимо от T-Bank connection.
|
||||
Ответ MUST иметь `{ data, meta }`, где `meta` соответствует наблюдаемому состоянию resource по `tbank-data-sync`. Доступный `data` MUST содержать только события coupon, dividend, maturity и offer, отсортированные по дате по возрастанию, `calculatedAt`, а в режиме `all` -- принадлежность каждого события счёту. Прогноз MUST быть рассчитан по количеству бумаг полного Portfolio Snapshot на `calculatedAt`; сохранённый прогноз MUST сохранять свой исходный `calculatedAt` и рассчитанное от него включительное окно, даже когда запрошенное `from`/`to` отличается. При чтении сохранённого прогноза система MUST возвращать только события, чьи даты попадают во включительное пересечение исходного окна прогноза и запрошенного UTC-окна; пустое пересечение либо отсутствие событий в пересечении MUST возвращать успешный пустой список с теми же `calculatedAt`, `meta` и исходным окном. В режиме `all` доступ и полнота обязательных источников MUST проверяться для каждого входящего счёта; отсутствие вклада счёта MUST NOT подменяться полным результатом. Список будущих событий является прогнозом по наблюдаемому состоянию счёта и количеству бумаг на `calculatedAt` и MUST NOT означать долговременное право владения Brokerage Account независимо от T-Bank connection.
|
||||
|
||||
#### Scenario: Получение событий доступного счёта
|
||||
- **WHEN** аутентифицированный User запрашивает будущие события открытого поддерживаемого Brokerage Account, доступного через текущий T-Bank connection
|
||||
- **THEN** система возвращает известные события на следующие 30 календарных дней и момент, на который был выполнен расчёт
|
||||
- **WHEN** аутентифицированный User запрашивает события доступного счёта с последним полным прогнозом
|
||||
- **THEN** система возвращает из сохранённого прогноза события в запрошенном включительном UTC-окне, его исходные `calculatedAt` и рассчитанное окно без обращения к T-Bank
|
||||
|
||||
#### Scenario: Запрошенное окно не пересекается с сохранённым прогнозом
|
||||
- **WHEN** запрошенный включительный UTC-окно не пересекается с исходным окном последнего полного прогноза
|
||||
- **THEN** система возвращает успешный пустой `data` с исходными `calculatedAt`, окном и `meta`
|
||||
|
||||
#### Scenario: Отсутствие событий в периоде
|
||||
- **WHEN** для счёта нет известных событий в ближайшие 30 календарных дней
|
||||
- **THEN** система возвращает успешный пустой список и момент расчёта
|
||||
- **WHEN** пересечение запрошенного и исходного окна прогноза не содержит событий
|
||||
- **THEN** система возвращает успешный пустой список, момент расчёта, исходное окно и `meta`
|
||||
|
||||
#### Scenario: Некорректный период
|
||||
- **WHEN** User передаёт `from` позже `to`
|
||||
- **THEN** система возвращает существующую ошибку `INVALID_EVENTS_PERIOD` без прогноза и без запуска личной синхронизации
|
||||
|
||||
#### Scenario: Прогноз всех счетов
|
||||
- **WHEN** User запрашивает режим `all` и для каждого входящего счёта доступен полный прогноз
|
||||
- **THEN** система возвращает события всех счетов в запрошенном окне с принадлежностью каждого события счёту
|
||||
|
||||
#### Scenario: Отклонение недоступного счёта
|
||||
- **WHEN** аутентифицированный User запрашивает события счёта, который не является открытым поддерживаемым счётом, доступным через его T-Bank connection
|
||||
- **WHEN** User запрашивает события счёта, недоступного через его текущий T-Bank connection
|
||||
- **THEN** система возвращает существующий ответ о недоступности счёта и не раскрывает события
|
||||
|
||||
### Requirement: Forecasts preserve event meaning and uncertainty
|
||||
Система MUST передавать для каждого события его календарную дату, тип, доступные идентификаторы и название Instrument, а также прогнозную сумму до налогов как Money, если она известна. Прогнозная сумма MUST рассчитываться по текущему количеству бумаг на момент запроса. Если денежные условия события неизвестны или неприменимы, прогнозная сумма MUST быть `null`, а не нулём и не парой разрозненных полей. Дивиденд MUST обозначать переданную дату как дату реестра, а offer MUST передаваться без вымышленной суммы.
|
||||
Система MUST передавать для каждого события его календарную дату, тип, доступные идентификаторы и название Instrument, а также прогнозную сумму до налогов как Money, если она известна. Прогнозная сумма MUST рассчитываться по количеству бумаг полного Portfolio Snapshot на `calculatedAt`, использованного для сохранённого прогноза, а не по количеству бумаг на момент HTTP-запроса. Сохранённый прогноз MUST сохранять свой `calculatedAt` и эту исходную зависимость, в том числе после неуспешного обновления, и MUST NOT приписываться более новому Portfolio Snapshot. Если денежные условия события неизвестны или неприменимы, прогнозная сумма MUST быть `null`, а не нулём и не парой разрозненных полей. Дивиденд MUST обозначать переданную дату как дату реестра, а offer MUST передаваться без вымышленной суммы.
|
||||
|
||||
Instrument в Future Cash Event MUST иметь тот же доменный смысл, что и в Portfolio: это финансовый инструмент, к которому относится событие, даже если событие не является Portfolio Position.
|
||||
|
||||
#### Scenario: Прогноз купона по текущей позиции
|
||||
- **WHEN** поддерживаемая облигация в текущей Portfolio Position имеет известный купон в пределах периода
|
||||
- **THEN** система возвращает событие coupon с прогнозной суммой до налогов как Money в исходной валюте, рассчитанной по текущему количеству облигаций
|
||||
- **WHEN** поддерживаемая облигация в Portfolio Snapshot на `calculatedAt` имеет известный купон в пределах исходного окна прогноза
|
||||
- **THEN** система возвращает сохранённое событие coupon с прогнозной суммой до налогов как Money в исходной валюте, рассчитанной по количеству облигаций этого Snapshot
|
||||
|
||||
#### Scenario: Прогноз дивиденда
|
||||
- **WHEN** поддерживаемая акция в текущей Portfolio Position имеет объявленный дивиденд с датой фиксации реестра в пределах периода
|
||||
- **THEN** система возвращает событие dividend с датой, обозначенной как дата реестра (`record_date`), и прогнозной суммой до налогов как Money, равной `dividend_net` на одну бумагу, умноженной на текущее количество акций
|
||||
- **WHEN** поддерживаемая акция в Portfolio Snapshot на `calculatedAt` имеет объявленный дивиденд с датой фиксации реестра в пределах исходного окна прогноза
|
||||
- **THEN** система возвращает сохранённое событие dividend с датой, обозначенной как дата реестра (`record_date`), и прогнозной суммой до налогов как Money, равной `dividend_net` на одну бумагу, умноженной на количество акций этого Snapshot
|
||||
|
||||
#### Scenario: Отменённый дивиденд не попадает в прогноз
|
||||
- **WHEN** запись дивиденда T-Bank имеет тип выплаты `Cancelled`
|
||||
- **THEN** система не добавляет такое событие в результат
|
||||
- **THEN** система не добавляет такое событие в сохранённый прогноз
|
||||
|
||||
#### Scenario: Отображение оферты без суммы
|
||||
- **WHEN** для поддерживаемой облигации известна оферта в пределах периода, но её денежные условия недоступны
|
||||
- **WHEN** для поддерживаемой облигации известна оферта в пределах исходного окна прогноза, но её денежные условия недоступны
|
||||
- **THEN** система возвращает событие offer с прогнозной суммой `null`
|
||||
|
||||
#### Scenario: Неподдерживаемое частичное погашение
|
||||
@@ -49,42 +61,66 @@ Instrument в Future Cash Event MUST иметь тот же доменный с
|
||||
- **THEN** система НЕ создаёт синтетическое событие частичного погашения
|
||||
|
||||
### Requirement: Upcoming events use complete source results
|
||||
Система MUST завершать запрос ошибкой, если необходимые для прогноза данные брокера или MOEX недоступны, и MUST NOT возвращать частичный список как полный результат. Система MUST пропускать инструменты, для которых источник не предоставляет поддерживаемые события, не создавая строки «нет данных».
|
||||
Система MUST публиковать прогноз только после полного получения необходимых данных брокера и MOEX для полного Portfolio Snapshot, по количеству бумаг которого он рассчитан. Неуспешное обновление MUST NOT заменять последний полный прогноз, его `calculatedAt` или окно событий частичным списком. При неуспешном обновлении и существующем последнем полном прогнозе система MUST возвращать отфильтрованную для запрошенного окна часть этого прогноза с его исходными `calculatedAt`, окном и `meta`, отдельно обозначающим неуспех последней попытки, даже если результат фильтрации пуст. При отсутствии последнего полного прогноза система MUST возвращать `data: null` и в `meta` обозначать выполнение первой загрузки либо её неуспех без частичного списка. Система MUST пропускать инструменты, для которых источник не предоставляет поддерживаемые события, не создавая строки «нет данных».
|
||||
|
||||
#### Scenario: Недоступность источника
|
||||
- **WHEN** необходимый источник данных брокера или MOEX временно недоступен
|
||||
- **THEN** система возвращает ошибку запроса будущих событий без частичного списка
|
||||
- **WHEN** необходимый источник данных брокера или MOEX временно недоступен во время часового обновления
|
||||
- **THEN** система не публикует частичный список как полный прогноз
|
||||
|
||||
#### Scenario: Ошибка обновления при сохранённом прогнозе
|
||||
- **WHEN** часовой обход не завершает обновление необходимых источников, а последний полный прогноз существует
|
||||
- **THEN** система возвращает его события только в запрошенном окне, исходные `calculatedAt` и окно, а `meta` отдельно обозначает ошибку обновления
|
||||
|
||||
#### Scenario: Ошибка обновления при сохранённом прогнозе вне запрошенного окна
|
||||
- **WHEN** часовой обход не завершает обновление, последний полный прогноз существует, но его окно не пересекается с запрошенным
|
||||
- **THEN** система возвращает успешный пустой `data` с исходными `calculatedAt`, окном и `meta`, обозначающим ошибку обновления
|
||||
|
||||
#### Scenario: Инструмент без поддерживаемых данных
|
||||
- **WHEN** Portfolio содержит инструмент, для которого источник не предоставляет поддерживаемые будущие события
|
||||
- **THEN** система не добавляет для него синтетическую строку в результат
|
||||
|
||||
#### Scenario: MOEX отвечает без запрошенного блока
|
||||
- **WHEN** MOEX отвечает успешно, но не содержит запрошенного блока данных (например, дивидендов)
|
||||
- **THEN** система трактует ответ как отсутствие данных по инструменту и не завершает запрос ошибкой
|
||||
- **WHEN** MOEX отвечает успешно, но не содержит запрошенного блока данных
|
||||
- **THEN** система трактует ответ как отсутствие данных по инструменту и не завершает обновление ошибкой
|
||||
|
||||
### Requirement: Account page presents an upcoming events list
|
||||
Страница Brokerage Account MUST предоставлять действие перехода на отдельную страницу «Будущие события». Эта страница MUST показывать все полученные события компактным списком без фильтров, пагинации и суммарного итога, с датой, типом, инструментом и прогнозной суммой в исходной валюте, если она известна. Страница MUST объяснять, что прогноз основан на текущей позиции, указан до налогов и может быть неполным из-за доступности данных источника.
|
||||
Страница Brokerage Account MUST предоставлять read-only действие перехода на отдельную страницу «Будущие события». Эта страница MUST показывать события сохранённого полного прогноза компактным списком без фильтров, пагинации, суммарного итога или действия ручной синхронизации, с датой, типом, инструментом и прогнозной суммой в исходной валюте, если она известна. Страница MUST показывать `calculatedAt`, исходное окно прогноза и наблюдаемые freshness, полноту, время последнего успешного прогноза, последнюю часовую попытку и доступность из `meta` понятным текстом или иконкой с доступным именем, а не только цветом. Она MUST объяснять, что прогноз рассчитан по количеству бумаг на `calculatedAt`, а показанные события дополнительно ограничены запрошенным включительным UTC-окном; прогноз указан до налогов и может быть неполным из-за доступности данных источника. Открытие страницы и повтор HTTP-чтения MUST NOT запускать личную синхронизацию.
|
||||
|
||||
#### Scenario: Просмотр ближайших событий
|
||||
- **WHEN** User открывает «Будущие события» из обзора Brokerage Account
|
||||
- **THEN** он видит отсортированный список событий ближайших 30 дней, где offer обозначен без суммы
|
||||
- **THEN** он видит отсортированный read-only список событий сохранённого 30-календарного окна и `calculatedAt`, где offer обозначен без суммы
|
||||
|
||||
#### Scenario: Пустое состояние страницы
|
||||
- **WHEN** запрос будущих событий успешно возвращает пустой список
|
||||
- **THEN** страница сообщает, что на ближайшие 30 дней событий не найдено, и объясняет ограничения покрытия источника
|
||||
- **THEN** страница сообщает, что в сохранённом окне прогноза событий не найдено, показывает `calculatedAt` и объясняет ограничения покрытия источника
|
||||
|
||||
#### Scenario: Первая загрузка либо ошибка без сохранённого прогноза
|
||||
- **WHEN** запрос будущих событий возвращает `data: null`, а `meta` обозначает выполнение первой загрузки либо её неуспех
|
||||
- **THEN** страница показывает доступное состояние загрузки либо ошибки без частичного списка и предоставляет при ошибке действие повторить только HTTP-чтение
|
||||
|
||||
#### Scenario: Ошибка страницы
|
||||
- **WHEN** запрос будущих событий завершается ошибкой
|
||||
- **THEN** страница не показывает частичный или устаревший список и предоставляет действие повторить запрос
|
||||
- **THEN** страница не показывает частичный список и предоставляет действие повторить только HTTP-чтение
|
||||
|
||||
#### Scenario: Ошибка обновления при сохранённом прогнозе на странице
|
||||
- **WHEN** запрос будущих событий возвращает последний полный прогноз вместе с ошибкой его обновления
|
||||
- **THEN** страница показывает этот прогноз с его исходными `calculatedAt` и окном, отдельно сообщает об ошибке обновления и предоставляет действие повторить только HTTP-чтение
|
||||
|
||||
### Requirement: Account overview includes upcoming events
|
||||
Обзор Brokerage Account MUST показывать не более пяти ближайших Future Cash Event и действие перехода на полную страницу «Будущие события». Неуспешный или пустой запрос этого блока MUST оставаться локальным состоянием блока и не заменять успешно отображённый Portfolio.
|
||||
Обзор Brokerage Account MUST показывать не более пяти ближайших Future Cash Event сохранённого полного прогноза в применённом запрошенном окне, его `calculatedAt`, исходное окно, наблюдаемую freshness, полноту, последнюю часовую попытку и доступность из `meta`, а также действие перехода на read-only полную страницу «Будущие события». Статус MUST быть понятен текстом или иконкой с доступным именем, а не только цветом. Неуспешное обновление при сохранённом прогнозе MUST оставаться локальным состоянием блока, сохранять показанные события и их исходное окно и MUST NOT заменять успешно отображённый Portfolio. Если полного прогноза нет, неуспешное обновление MUST оставаться локальным состоянием блока без частичного списка. Обзор MUST NOT предоставлять действие ручной синхронизации; его повтор MUST выполнять только HTTP-чтение.
|
||||
|
||||
#### Scenario: Отображение ближайших событий
|
||||
- **WHEN** обзор счёта успешно загружен и календарь содержит будущие события
|
||||
- **THEN** обзор показывает первые пять событий в порядке даты и действие перехода к полной странице
|
||||
- **WHEN** обзор счёта успешно загружен и сохранённый прогноз содержит будущие события
|
||||
- **THEN** обзор показывает первые пять событий в порядке даты, `calculatedAt` и действие перехода к полной странице
|
||||
|
||||
#### Scenario: Ближайшие события недоступны без сохранённого прогноза
|
||||
- **WHEN** обзор счёта успешно загружен, а события возвращают `data: null` и `meta` обозначает выполнение первой загрузки либо её неуспех
|
||||
- **THEN** Portfolio остаётся доступным, а блок будущих событий отображает собственное доступное состояние загрузки либо ошибки без частичного списка
|
||||
|
||||
#### Scenario: Ближайшие события недоступны
|
||||
- **WHEN** обзор счёта успешно загружен, но запрос будущих событий завершается ошибкой
|
||||
- **THEN** Portfolio остаётся доступным, а блок будущих событий отображает собственную ошибку
|
||||
|
||||
#### Scenario: Неуспешное обновление не скрывает сохранённый прогноз
|
||||
- **WHEN** обзор счёта получает сохранённый полный прогноз вместе с ошибкой его обновления
|
||||
- **THEN** Portfolio и события сохранённого окна остаются доступными, а блок отдельно обозначает ошибку и предлагает повторить только HTTP-чтение
|
||||
|
||||
@@ -7,57 +7,65 @@
|
||||
## Requirements
|
||||
|
||||
### Requirement: Список активных брокерских счетов
|
||||
Система SHALL предоставлять авторизованному User `GET /api/v1/brokerage-accounts`, возвращающий массив открытых поддерживаемых инвестиционных счетов T-Bank, доступных через текущий T-Bank connection User, в порядке, полученном от T-Bank. Каждый элемент MUST содержать стабильный `id`, непустое `name`, нормализованный `type` (`BROKERAGE`, `INDIVIDUAL_INVESTMENT`, `INVEST_BOX` или `MONEY_MARKET_FUND`) и nullable `openedOn` в формате `YYYY-MM-DD`. Система MUST включать только счета T-Bank типов «брокерский счёт», «ИИС», «Инвесткопилка» и «фонд денежного рынка»; банковские и неизвестные типы MUST NOT возвращаться. Система MUST удалить внешние пробелы из имени и при пустом результате использовать непустое название соответствующего типа.
|
||||
Система SHALL предоставлять авторизованному User `GET /api/v1/brokerage-accounts`, возвращающий `{ data, meta }`, где `data` является сохранённым подтверждённым массивом открытых поддерживаемых инвестиционных счетов T-Bank текущего подключения User в ранее подтверждённом upstream-порядке, а `meta` соответствует наблюдаемому состоянию resource по `tbank-data-sync`. Обычный GET MUST NOT обращаться к T-Bank или запускать личную синхронизацию. Каждый элемент MUST содержать стабильный `id`, непустое `name`, нормализованный `type` (`BROKERAGE`, `INDIVIDUAL_INVESTMENT`, `INVEST_BOX` или `MONEY_MARKET_FUND`) и nullable `openedOn` в формате `YYYY-MM-DD`. Система MUST включать только счета T-Bank типов «брокерский счёт», «ИИС», «Инвесткопилка» и «фонд денежного рынка»; банковские и неизвестные типы MUST NOT возвращаться. Система MUST удалить внешние пробелы из имени и при пустом результате использовать непустое название соответствующего типа.
|
||||
|
||||
Каждый элемент MUST также содержать nullable `portfolioSummary`. Доступная сводка MUST содержать `totalsByCurrency`: точные Money-итоги Portfolio по исходным ISO-валютам, включающие стоимость позиций и Cash Balance в той же валюте. Она MUST NOT содержать FX-конвертированный или межвалютный итог и MUST NOT выдавать Cash Balance за самостоятельный второй итог. Если сводка Portfolio конкретного доступного счёта временно недоступна, `portfolioSummary` MUST быть `null`, не исключая этот счёт из списка и не делая список неуспешным. Пустой доступный Portfolio MUST передаваться доступной сводкой с пустым `totalsByCurrency`, а не `null`.
|
||||
Каждый элемент MUST также содержать nullable `portfolioSummary`. Доступная сводка MUST содержать `totalsByCurrency`: точные Money-итоги последнего Portfolio по исходным ISO-валютам, включающие стоимость позиций и Cash Balance в той же валюте. Она MUST NOT содержать FX-конвертированный или межвалютный итог и MUST NOT выдавать Cash Balance за самостоятельный второй итог. Если последний Portfolio конкретного доступного счёта отсутствует либо временно недоступен, `portfolioSummary` MUST быть `null`, не исключая этот счёт из списка и не делая список неуспешным. Пустой доступный Portfolio MUST передаваться доступной сводкой с пустым `totalsByCurrency`, а не `null`.
|
||||
|
||||
Список Brokerage Account и его сводки являются результатом наблюдения текущего брокерского подключения и MUST NOT создавать ожидание, что WealthLedger хранит долговременное право владения счётом или Portfolio независимо от T-Bank connection.
|
||||
|
||||
#### Scenario: Поддерживаемые счета возвращаются в upstream-порядке
|
||||
- **WHEN** User с действующим T-Bank connection запрашивает список, а T-Bank возвращает открытые брокерский счёт, ИИС и неподдержанный дебетовый счёт
|
||||
- **THEN** система отвечает `200` массивом брокерского счёта и ИИС в исходном порядке T-Bank без дебетового счёта
|
||||
- **WHEN** часовой обход подтвердил открытые брокерский счёт, ИИС и неподдержанный дебетовый счёт
|
||||
- **THEN** обычный GET отвечает `200` этими счетами в `data` в исходном порядке и наблюдаемым состоянием в `meta` без обращения к T-Bank
|
||||
|
||||
#### Scenario: Пустой список поддерживаемых счетов
|
||||
- **WHEN** User с действующим T-Bank connection не имеет открытых поддерживаемых инвестиционных счетов
|
||||
- **THEN** система отвечает `200` пустым массивом
|
||||
- **WHEN** последний успешный обход подтвердил отсутствие открытых поддерживаемых инвестиционных счетов
|
||||
- **THEN** система отвечает `200` с `data: []` и `meta`, обозначающим доступный полный результат
|
||||
|
||||
#### Scenario: Пустое название получает fallback
|
||||
- **WHEN** T-Bank возвращает поддерживаемый открытый счёт с пустым либо состоящим из пробелов именем
|
||||
- **WHEN** последний подтверждённый счёт имеет пустое либо состоящее из пробелов имя
|
||||
- **THEN** система отвечает этим счётом с непустым названием его нормализованного типа
|
||||
|
||||
#### Scenario: Дата открытия отсутствует
|
||||
- **WHEN** T-Bank не возвращает дату открытия поддерживаемого открытого счёта
|
||||
- **WHEN** последний подтверждённый счёт не имеет даты открытия
|
||||
- **THEN** система отвечает этим счётом с `openedOn: null`
|
||||
|
||||
#### Scenario: Мультивалютная сводка счёта
|
||||
- **WHEN** доступный Portfolio счёта содержит итоги в RUB и USD, включая Cash Balance в USD
|
||||
- **WHEN** последний Portfolio счёта содержит итоги в RUB и USD, включая Cash Balance в USD
|
||||
- **THEN** ответ содержит одну доступную `portfolioSummary` с отдельными Money-итогами RUB и USD без FX-конвертации, общего итога или отдельного итога Cash Balance
|
||||
|
||||
#### Scenario: Сводка одного счёта временно недоступна
|
||||
- **WHEN** список доступных счетов получен, но Portfolio одного из них временно недоступен
|
||||
- **THEN** система отвечает `200` всеми счетами в исходном порядке, передаёт для этого счёта `portfolioSummary: null` и не заменяет успешные сводки остальных счетов ошибкой списка
|
||||
- **WHEN** подтверждённый список содержит счёт без последнего доступного Portfolio
|
||||
- **THEN** система отвечает `200` всеми счетами в исходном порядке и передаёт для этого счёта `portfolioSummary: null`
|
||||
|
||||
#### Scenario: Доступный Portfolio пуст
|
||||
- **WHEN** доступный счёт не имеет ненулевых позиций и доступных денежных остатков
|
||||
- **WHEN** последний доступный Portfolio счёта не имеет ненулевых позиций и доступных денежных остатков
|
||||
- **THEN** система передаёт доступную `portfolioSummary` с пустым `totalsByCurrency`
|
||||
|
||||
### Requirement: Состояния недоступности списка счетов
|
||||
Система MUST NOT возвращать частичный или сохранённый ранее список счетов. При отсутствии T-Bank connection система MUST отвечать `409` Problem Details с code `TBANK_NOT_CONNECTED`. Если T-Bank отклоняет сохранённый token как неаутентифицированный или без прав, система MUST отвечать `400` Problem Details с code `INVALID_TBANK_TOKEN` и сохранять connection. При иной неуспешной попытке получить список из T-Bank система MUST отвечать `503` Problem Details с code `TBANK_UNAVAILABLE`.
|
||||
Система MUST читать подтверждённый список текущего подключения локально. При отсутствии T-Bank connection система MUST отвечать `409` Problem Details с code `TBANK_NOT_CONNECTED`. Если T-Bank отклонил сохранённый token как неаутентифицированный или без прав во время часового обхода, система MUST отвечать `400` Problem Details с code `INVALID_TBANK_TOKEN` и сохранять connection. Временная ошибка обхода MUST сохранять последний подтверждённый список доступным; до первого подтверждённого списка система MUST обозначать продолжающуюся первую загрузку и MUST NOT возвращать частичный список как полный.
|
||||
|
||||
#### Scenario: Подключение отсутствует
|
||||
- **WHEN** User без сохранённого T-Bank connection запрашивает список
|
||||
- **THEN** система не обращается к T-Bank и отвечает `409` Problem Details с code `TBANK_NOT_CONNECTED`
|
||||
|
||||
#### Scenario: Сохранённый token больше не действителен
|
||||
- **WHEN** T-Bank отклоняет сохранённый token при запросе списка как неаутентифицированный или без прав
|
||||
- **THEN** система отвечает `400` Problem Details с code `INVALID_TBANK_TOKEN` и не удаляет connection
|
||||
- **WHEN** T-Bank отклоняет сохранённый token при часовом обходе списка как неаутентифицированный или без прав
|
||||
- **THEN** последующее чтение отвечает `400` Problem Details с code `INVALID_TBANK_TOKEN` и не раскрывает личные данные
|
||||
|
||||
#### Scenario: Ошибка обхода после успешного списка
|
||||
- **WHEN** часовой обход списка завершается временной ошибкой после ранее успешной проверки
|
||||
- **THEN** обычный GET возвращает последний подтверждённый список без ожидания T-Bank
|
||||
|
||||
#### Scenario: T-Bank временно недоступен
|
||||
- **WHEN** запрос списка не завершается из-за временной либо иной upstream-ошибки
|
||||
- **THEN** система отвечает `503` Problem Details с code `TBANK_UNAVAILABLE` без счетов
|
||||
- **WHEN** User запрашивает список до первого подтверждённого списка, а часовой обход завершился временной либо иной upstream-ошибкой
|
||||
- **THEN** система сообщает о неуспешной первой загрузке без счетов
|
||||
|
||||
#### Scenario: Первый обход ещё не завершён
|
||||
- **WHEN** User открывает список после подключения до получения первого подтверждённого списка
|
||||
- **THEN** система сообщает о продолжающейся загрузке без частичного либо неподтверждённого списка
|
||||
|
||||
### Requirement: Экран списка брокерских счетов
|
||||
Клиент SHALL загружать список при открытии защищённого маршрута `/accounts` без ручного обновления и без автоматического refetch при возврате фокуса. Клиент MUST использовать успешный frontend-кэш списка в течение 60 секунд. После истечения этого срока клиент MUST запрашивать актуальный список; при уже имеющемся успешном списке он MUST сохранять последний успешный Client Cached Snapshot видимым во время фонового обновления и после его ошибки, ясно отделяя это состояние от свежего результата. При успешном непустом ответе клиент MUST отобразить адаптивный одноколоночный список широких интерактивных карточек счетов с локализованными типом и, если доступна, датой открытия без времени; каждая карточка MUST ясно показывать тип, имя, финансовую сводку, вторичные метаданные и заметное действие открытия счёта, вести на `/accounts/:accountId` по всей области и сохранять доступное клавиатурное состояние. После успешной замены или удаления T-Bank connection клиент MUST инвалидировать список счетов.
|
||||
Клиент SHALL загружать список при открытии защищённого маршрута `/accounts` без ручного обновления и без автоматического refetch при возврате фокуса. Он MUST использовать успешный Client Cached Snapshot списка в течение 30 минут. Открытый экран Accounts MUST перечитывать локальный backend-результат каждые 30 минут и MUST NOT запускать импорт T-Bank. Клиент MUST показывать наблюдаемые freshness, полноту, время последнего успешного результата, последнюю часовую попытку и доступность из `meta` понятным текстом или иконкой с доступным именем, а не только цветом. При уже имеющемся успешном списке клиент MUST сохранять последний успешный Client Cached Snapshot видимым во время фонового HTTP-перечитывания и после его ошибки, ясно отделяя это состояние от свежего backend-результата. При успешном непустом ответе клиент MUST отобразить адаптивный одноколоночный список широких интерактивных карточек счетов с локализованными типом и, если доступна, датой открытия без времени; каждая карточка MUST ясно показывать тип, имя, финансовую сводку, вторичные метаданные и заметное действие открытия счёта, вести на `/accounts/:accountId` по всей области и сохранять доступное клавиатурное состояние. После успешной замены или удаления T-Bank connection клиент MUST инвалидировать список счетов.
|
||||
|
||||
Для доступной `portfolioSummary` карточка MUST показывать не более двух Money-итогов в исходных валютах и обозначать наличие остальных валют, если они есть; она MUST NOT показывать или подразумевать FX-конвертацию, межвалютный агрегированный итог либо Cash Balance как отдельный второй итог. При `portfolioSummary: null` карточка MUST показывать локальное безопасное состояние временной недоступности сводки, сохраняя метаданные счёта и возможность открыть его. При доступной пустой сводке карточка MUST не выдавать отсутствие итогов за временную ошибку.
|
||||
|
||||
@@ -69,17 +77,29 @@
|
||||
- **WHEN** клиент получает успешный пустой массив
|
||||
- **THEN** клиент показывает штатное сообщение об отсутствии инвестиционных счетов T-Bank
|
||||
|
||||
#### Scenario: Последняя попытка неуспешна после полного списка
|
||||
- **WHEN** клиент получает полный `data` списка и `meta` с неуспехом последней часовой попытки
|
||||
- **THEN** клиент сохраняет список видимым, показывает его исходное время и текстовое либо доступно именованное предупреждение о неуспешной попытке
|
||||
|
||||
#### Scenario: Первая загрузка списка выполняется или неуспешна
|
||||
- **WHEN** клиент получает `data: null`, а `meta` обозначает выполнение первой загрузки либо её неуспех
|
||||
- **THEN** клиент показывает соответственно доступное состояние загрузки либо ошибки без карточек счетов и без представления частичного списка как полного
|
||||
|
||||
#### Scenario: Список берётся из свежего frontend-кэша
|
||||
- **WHEN** User повторно открывает список в течение 60 секунд после успешной загрузки
|
||||
- **WHEN** User повторно открывает список в течение 30 минут после успешной загрузки
|
||||
- **THEN** клиент показывает сохранённые счета без нового API-запроса
|
||||
|
||||
#### Scenario: Устаревший список загружается повторно
|
||||
- **WHEN** User открывает список после истечения 60 секунд frontend-кэша и уже существует последний успешный список
|
||||
- **THEN** клиент оставляет этот список видимым во время нового запроса и ясно обозначает фоновое обновление
|
||||
- **WHEN** User открывает список после истечения 30 минут frontend-кэша и уже существует последний успешный список
|
||||
- **THEN** клиент оставляет этот список видимым во время нового HTTP-чтения и ясно обозначает фоновое обновление
|
||||
|
||||
#### Scenario: Открытый экран перечитывает список
|
||||
- **WHEN** экран Accounts остаётся открытым 30 минут после последнего успешного HTTP-чтения
|
||||
- **THEN** клиент выполняет фоновое HTTP-перечитывание backend-результата, сохраняет видимый snapshot и не запускает импорт T-Bank
|
||||
|
||||
#### Scenario: Ошибка фонового обновления не скрывает snapshot
|
||||
- **WHEN** обновление устаревшего списка завершается ошибкой при наличии последнего успешного Client Cached Snapshot
|
||||
- **THEN** клиент сохраняет snapshot видимым, безопасно сообщает о неуспешном обновлении и предоставляет применимое действие повторной загрузки
|
||||
- **WHEN** фоновое HTTP-перечитывание списка завершается ошибкой при наличии последнего успешного Client Cached Snapshot
|
||||
- **THEN** клиент сохраняет snapshot видимым, безопасно сообщает о неуспешном HTTP-чтении и предоставляет применимое действие повторной загрузки
|
||||
|
||||
#### Scenario: Карточка ограничивает валютные итоги
|
||||
- **WHEN** доступная сводка счёта содержит итоги в трёх или более валютах
|
||||
@@ -94,15 +114,15 @@
|
||||
- **THEN** он показывает нейтральное состояние отсутствия финансовых итогов, а не состояние временной недоступности
|
||||
|
||||
### Requirement: Клиентские состояния ошибки списка счетов
|
||||
Клиент MUST NOT автоматически повторять неуспешный запрос списка. При `TBANK_NOT_CONNECTED` клиент SHALL показать объяснение отсутствия подключения и действие «Открыть настройки T-Bank». При `INVALID_TBANK_TOKEN` клиент SHALL показать объяснение недействительного token и то же действие. При `TBANK_UNAVAILABLE` клиент SHALL показать безопасное объяснение временной недоступности, действие повторной загрузки и действие открытия настроек T-Bank. При иной ошибке клиент MUST показать общий безопасный error-state только с действием повторной загрузки. Если последний успешный Client Cached Snapshot отсутствует, эти состояния MUST NOT показывать список; если snapshot существует во время фонового обновления, клиент MUST сохранить его видимым и показать соответствующее безопасное предупреждение и только применимые действия.
|
||||
Клиент MUST NOT автоматически повторять неуспешное HTTP-чтение списка. При `TBANK_NOT_CONNECTED` клиент SHALL показать объяснение отсутствия подключения и действие «Открыть настройки T-Bank». При `INVALID_TBANK_TOKEN` клиент SHALL показать объяснение недействительного token и то же действие. При `TBANK_UNAVAILABLE` клиент SHALL показать безопасное объяснение временной недоступности, действие повторного HTTP-чтения и действие открытия настроек T-Bank. При иной ошибке клиент MUST показать общий безопасный error-state только с действием повторного HTTP-чтения. Повтор MUST перечитывать backend-результат и MUST NOT запускать импорт T-Bank. Если последний успешный Client Cached Snapshot отсутствует, эти состояния MUST NOT показывать список; если snapshot существует во время фонового обновления, клиент MUST сохранить его видимым и показать соответствующее безопасное предупреждение и только применимые действия.
|
||||
|
||||
#### Scenario: Подключение требуется
|
||||
- **WHEN** клиент получает `TBANK_NOT_CONNECTED`
|
||||
- **THEN** клиент не показывает список и предлагает открыть настройки T-Bank
|
||||
|
||||
#### Scenario: Известная ошибка не повторяется автоматически
|
||||
- **WHEN** запрос списка завершается известной ошибкой
|
||||
- **THEN** клиент не выполняет дополнительные запросы, пока User не выберет действие повторной загрузки
|
||||
- **WHEN** HTTP-чтение списка завершается известной ошибкой
|
||||
- **THEN** клиент не выполняет дополнительные запросы, пока User не выберет действие повторного HTTP-чтения
|
||||
|
||||
#### Scenario: Token требует внимания
|
||||
- **WHEN** клиент получает `INVALID_TBANK_TOKEN`
|
||||
@@ -110,12 +130,12 @@
|
||||
|
||||
#### Scenario: Временная ошибка T-Bank
|
||||
- **WHEN** клиент получает `TBANK_UNAVAILABLE`
|
||||
- **THEN** клиент не показывает список и предлагает повторить загрузку либо открыть настройки T-Bank
|
||||
- **THEN** клиент не показывает список и предлагает повторить HTTP-чтение либо открыть настройки T-Bank
|
||||
|
||||
#### Scenario: Неизвестная ошибка загрузки
|
||||
- **WHEN** клиент получает ошибку без известного кода списка счетов
|
||||
- **THEN** клиент не показывает список и предлагает только повторить загрузку
|
||||
- **THEN** клиент не показывает список и предлагает только повторить HTTP-чтение
|
||||
|
||||
#### Scenario: Известная ошибка при сохранённом snapshot
|
||||
- **WHEN** фоновое обновление списка с последним успешным Client Cached Snapshot получает `TBANK_UNAVAILABLE`
|
||||
- **THEN** клиент сохраняет snapshot видимым, сообщает о временной недоступности и предлагает повторить загрузку либо открыть настройки T-Bank
|
||||
- **WHEN** фоновое HTTP-перечитывание списка с последним успешным Client Cached Snapshot получает `TBANK_UNAVAILABLE`
|
||||
- **THEN** клиент сохраняет snapshot видимым, сообщает о временной недоступности и предлагает повторить HTTP-чтение либо открыть настройки T-Bank
|
||||
|
||||
@@ -9,28 +9,20 @@
|
||||
## Requirements
|
||||
|
||||
### Requirement: Client Cached Snapshot остаётся доступным при обновлении Accounts и Portfolio
|
||||
Frontend MUST сохранять Client Cached Snapshot списка Brokerage Accounts или
|
||||
Portfolio на экране, если для этого же набора данных уже был успешно получен
|
||||
ответ. Начальная загрузка без такого ответа может показывать блокирующее
|
||||
состояние загрузки. Client Cached Snapshot MUST оставаться видимым во время
|
||||
фонового обновления и после его ошибки; он не является server-side fallback и MUST NOT
|
||||
изменять или интерпретировать поле `Portfolio.isStale`.
|
||||
Frontend MUST сохранять Client Cached Snapshot списка Brokerage Accounts или Portfolio на экране, если для того же набора данных уже был успешно получен ответ. Начальная загрузка без такого ответа может показывать блокирующее состояние загрузки. Для каждого личного resource response клиент MUST представлять freshness, полноту, время последнего успешного результата, последнюю часовую попытку и доступность из `meta` текстом либо доступно именованным элементом, а не только цветом; он MUST различать отсутствие попытки, выполнение, полный успех, частичный успех, неуспех, доступный пустой полный результат и отсутствие полного результата. Client Cached Snapshot MUST оставаться видимым во время фонового HTTP-перечитывания и после его ошибки; он не является server-side fallback и MUST NOT изменять или интерпретировать поле `Portfolio.isStale`. Повторное действие User MUST повторять только HTTP-чтение и MUST NOT запускать личную синхронизацию. Открытый инвестиционный экран MAY периодически перечитывать backend-результат, но MUST NOT создавать импорт T-Bank.
|
||||
|
||||
#### Scenario: Список Brokerage Accounts обновляется в фоне
|
||||
- **WHEN** экран Accounts уже показывает Client Cached Snapshot и начинается
|
||||
фоновое обновление того же списка
|
||||
- **THEN** frontend оставляет список видимым и показывает только
|
||||
неблокирующий индикатор обновления
|
||||
- **WHEN** экран Accounts уже показывает Client Cached Snapshot и начинается фоновое HTTP-перечитывание того же списка
|
||||
- **THEN** frontend оставляет список видимым и показывает только неблокирующий индикатор перечитывания
|
||||
|
||||
#### Scenario: Обновление Portfolio завершается ошибкой после успешного ответа
|
||||
- **WHEN** экран Brokerage Account уже показывает Client Cached Snapshot
|
||||
Portfolio, а последующее обновление этого же Portfolio завершается ошибкой
|
||||
- **THEN** frontend сохраняет Portfolio видимым, показывает локализованное
|
||||
неблокирующее предупреждение о показе последних полученных данных и
|
||||
предоставляет повторную попытку обновления
|
||||
- **WHEN** экран Brokerage Account уже показывает Client Cached Snapshot Portfolio, а последующее HTTP-чтение завершается ошибкой
|
||||
- **THEN** frontend сохраняет Portfolio видимым, показывает локализованное неблокирующее предупреждение и предоставляет повтор только HTTP-чтения
|
||||
|
||||
#### Scenario: Server-side fallback и client cached snapshot различаются
|
||||
- **WHEN** backend возвращает Portfolio с `isStale` либо фоновое обновление
|
||||
Client Cached Snapshot завершается ошибкой
|
||||
- **THEN** frontend отображает соответствующее server-side либо client-side
|
||||
предупреждение независимо, не представляя один вид состояния другим
|
||||
- **WHEN** backend возвращает Portfolio с `isStale` либо HTTP-перечитывание Client Cached Snapshot завершается ошибкой
|
||||
- **THEN** frontend отображает соответствующее server-side либо client-side предупреждение независимо, не представляя одно состояние другим
|
||||
|
||||
#### Scenario: Resource сохраняется после неуспешной часовой попытки
|
||||
- **WHEN** response содержит полный `data` и `meta`, обозначающий неуспех последней часовой попытки
|
||||
- **THEN** frontend сохраняет `data` и его исходное время видимыми и отдельно обозначает неуспех попытки
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# hourly-investment-sync Specification
|
||||
|
||||
## Purpose
|
||||
Определяет независимое от открытых экранов часовое обновление личных инвестиционных данных и безопасно наблюдаемый результат последней попытки.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Часовой обход личных инвестиционных данных
|
||||
Система SHALL начинать первый часовой обход после успешного подключения T-Bank и последующие обходы не ранее чем через 60 минут от начала предыдущей попытки этого подключения. После простоя система MUST выполнить не более одного актуального обхода и MUST NOT воспроизводить пропущенные часы. Неуспешная попытка MUST NOT повторяться ранее следующего часового срока. Перекрывающиеся обходы MUST NOT выполняться: срок, наступивший во время выполняющегося обхода, MUST быть пропущен и MUST NOT ставиться в очередь; следующий обход становится допустимым только в следующий 60-минутный срок, отсчитанный от начала выполнявшейся попытки. Обход MUST обновлять доступные счета, Portfolio каждого доступного счёта, изменяемый хвост операций и будущие события по бумагам текущего Portfolio. Он MUST NOT повторно загружать полные прошлые интервалы операций, завершённые свечи или полную рыночную историю и MUST NOT зависеть от открытия frontend-экрана.
|
||||
|
||||
#### Scenario: Первое подключение
|
||||
- **WHEN** User успешно подключает T-Bank между часовыми сроками
|
||||
- **THEN** первый обход начинается без ожидания следующего календарного часа
|
||||
|
||||
#### Scenario: Возобновление после простоя
|
||||
- **WHEN** backend запускается после пропуска нескольких часовых сроков
|
||||
- **THEN** система выполняет один актуальный обход без синтетических прошлых обновлений
|
||||
|
||||
#### Scenario: Перекрытый срок
|
||||
- **WHEN** предыдущий обход всё ещё выполняется в момент очередного срока
|
||||
- **THEN** система не начинает второй обход и не ставит пропущенный обход в очередь
|
||||
|
||||
#### Scenario: Длительный обход пропускает один срок
|
||||
- **WHEN** обход начался в 10:00, всё ещё выполняется в 11:00 и завершился до 12:00
|
||||
- **THEN** система не запускает обход в 11:00 и может начать следующий не ранее 12:00
|
||||
|
||||
### Requirement: Последний результат обхода доступен только для наблюдения
|
||||
Система SHALL предоставлять авторизованному User результат последнего обхода только в границе его текущего подключения: время начала, состояние выполнения, полного успеха, частичного успеха либо неуспеха и безопасный перечень проблемных счетов или разделов. Это состояние MUST быть доступно в `meta` каждого затронутого resource read вместе с freshness, полнотой и доступностью его `data`. Успех одного счёта или раздела MUST сохраняться независимо от ошибки другого; ошибка MUST NOT заменять его последнее успешное состояние или время. Отсутствие состоявшейся попытки MUST отличаться от успешной попытки. Наблюдение результата MUST NOT предоставлять способ начать личную синхронизацию.
|
||||
|
||||
#### Scenario: Частичный успех
|
||||
- **WHEN** Portfolio одного доступного счёта обновился, а другого не обновился
|
||||
- **THEN** новый Portfolio первого сохраняется, прежний успешный Portfolio второго остаётся доступным, а результат обхода обозначает частичный успех и проблемный счёт
|
||||
|
||||
#### Scenario: Наблюдение не запускает обход
|
||||
- **WHEN** User запрашивает результат последнего обхода
|
||||
- **THEN** система возвращает доступный сохранённый результат без нового обращения к T-Bank
|
||||
|
||||
#### Scenario: Старая граница подключения
|
||||
- **WHEN** User заменил либо удалил подключение и запрашивает результат прежнего обхода
|
||||
- **THEN** система не раскрывает результат прежней границы подключения
|
||||
@@ -0,0 +1,680 @@
|
||||
# manual-bank-cards Specification
|
||||
|
||||
## Purpose
|
||||
Позволяет пользователю вручную вести дебетовые банковские карты в рублях:
|
||||
сохранять операции, видеть независимые балансы, исправлять записи и оценивать
|
||||
месячные расходы по категориям с учётом возвратов.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Отдельный раздел ручного учёта банковских карт
|
||||
|
||||
Система SHALL предоставлять авторизованному пользователю раздел банковских
|
||||
карт на `/cards` и просмотр отдельной своей карты на `/cards/:id`.
|
||||
Раздел MUST работать без подключения T-Bank и не зависеть от доступности
|
||||
инвестиционных данных. Банковские карты MUST NOT становиться брокерскими
|
||||
счетами, а банковские суммы и операции MUST NOT включаться в инвестиционный
|
||||
портфель, пассивный доход или прогноз выплат.
|
||||
|
||||
Первая версия SHALL поддерживать только ручной учёт дебетовых карт в RUB.
|
||||
Автоматическая загрузка из банков, кредитные карты, другие валюты, общий
|
||||
денежный баланс нескольких карт, прогнозы и бюджеты не входят в возможность.
|
||||
|
||||
#### Scenario: Работа без инвестиционного подключения
|
||||
|
||||
- **WHEN** пользователь с действующей локальной сессией, но без подключения T-Bank, открывает раздел карт
|
||||
- **THEN** он может просматривать и вести свои банковские карты без перехода к подключению брокера
|
||||
|
||||
#### Scenario: Независимость от инвестиционных данных
|
||||
|
||||
- **WHEN** пользователь сохраняет банковское пополнение или расход, а инвестиционные данные временно недоступны
|
||||
- **THEN** банковская операция сохраняется по правилам ручного учёта и не меняет инвестиционные суммы или пассивный доход
|
||||
|
||||
### Requirement: Ручное добавление независимых банковских карт
|
||||
|
||||
Система SHALL позволять пользователю добавить карту с названием, начальным
|
||||
остатком и датой начала учёта. Название MUST содержать от 1 до 100 символов
|
||||
после удаления внешних пробелов. Название банка необязательно и MUST содержать
|
||||
не более 100 символов после удаления внешних пробелов. Последние четыре цифры
|
||||
карты необязательны; указанное значение MUST состоять ровно из четырёх цифр.
|
||||
Полный номер карты, срок действия, PIN и код безопасности не запрашиваются
|
||||
и не сохраняются как реквизиты этой возможности.
|
||||
|
||||
Каждая карта MUST иметь собственную идентичность, баланс и историю независимо
|
||||
от совпадения банка, названия или последних цифр. Пользователь SHALL иметь
|
||||
возможность изменить указанные сведения о своей карте с теми же ограничениями.
|
||||
Финансовые изменения начального остатка и даты начала подчиняются отдельным
|
||||
требованиям исправления и архива.
|
||||
|
||||
#### Scenario: Добавление карты с начальным остатком
|
||||
|
||||
- **WHEN** пользователь сохраняет карту с допустимым названием, датой начала учёта и начальным остатком 10 000 ₽
|
||||
- **THEN** карта появляется в его списке с балансом 10 000 ₽ и без вручную внесённых операций
|
||||
|
||||
#### Scenario: Необязательные сведения не заполнены
|
||||
|
||||
- **WHEN** пользователь добавляет карту без названия банка и последних четырёх цифр
|
||||
- **THEN** отсутствие этих сведений не препятствует сохранению карты при корректности обязательных значений
|
||||
|
||||
#### Scenario: Несколько карт одного банка
|
||||
|
||||
- **WHEN** пользователь добавляет две карты одного банка с одинаковым названием и начальными остатками 1 000 ₽ и 2 000 ₽
|
||||
- **THEN** система сохраняет две самостоятельные карты с раздельными балансами и историей
|
||||
|
||||
#### Scenario: Некорректные сведения карты
|
||||
|
||||
- **WHEN** название после удаления внешних пробелов пустое или длиннее 100 символов, название банка длиннее 100 символов либо указанные последние цифры не состоят ровно из четырёх цифр
|
||||
- **THEN** система отклоняет сохранение, объясняет ошибку соответствующего поля и не создаёт или не изменяет карту
|
||||
|
||||
### Requirement: Точные денежные значения и проверка до сохранения
|
||||
|
||||
Система MUST принимать денежные значения только в RUB, с точностью не более
|
||||
двух знаков после десятичного разделителя, без округления введённой суммы.
|
||||
Сумма расхода, пополнения, перевода или возврата MUST быть строго положительной
|
||||
и не превышать 92 233 720 368 547 758,07 ₽. Начальный остаток MUST находиться
|
||||
в диапазоне от −92 233 720 368 547 758,08 ₽ до 92 233 720 368 547 758,07 ₽
|
||||
включительно; ноль и отрицательный начальный остаток допустимы.
|
||||
|
||||
Значение, не представляющее корректную денежную сумму, неподдерживаемая
|
||||
валюта, лишние десятичные знаки или выход за диапазон MUST приводить
|
||||
к понятной ошибке без частичного сохранения. Применение ограничения
|
||||
к каждому денежному вводу MUST NOT приводить к округлению, переполнению
|
||||
или ограничению точного результата сложения уже сохранённых сумм.
|
||||
|
||||
#### Scenario: Нулевой и отрицательный начальный остаток
|
||||
|
||||
- **WHEN** пользователь сохраняет отдельные карты с начальными остатками 0 ₽ и −100 ₽
|
||||
- **THEN** система сохраняет обе карты и показывает соответствующие известные балансы
|
||||
|
||||
#### Scenario: Неположительная сумма операции
|
||||
|
||||
- **WHEN** пользователь пытается внести операцию с суммой 0 ₽ или −1 ₽
|
||||
- **THEN** система отклоняет операцию, сохраняя прежние историю, балансы и сводки
|
||||
|
||||
#### Scenario: Точность не исправляется округлением
|
||||
|
||||
- **WHEN** пользователь пытается сохранить начальный остаток или операцию со значением 1,001 ₽
|
||||
- **THEN** система сообщает о допустимых двух десятичных знаках и не заменяет введённое значение на 1 ₽
|
||||
|
||||
#### Scenario: Границы денежных значений
|
||||
|
||||
- **WHEN** пользователь указывает верхнюю допустимую сумму операции или нижний допустимый начальный остаток
|
||||
- **THEN** сами эти граничные значения принимаются, а выход за соответствующую границу на одну копейку отклоняется без изменения данных
|
||||
|
||||
#### Scenario: Некорректная сумма или валюта
|
||||
|
||||
- **WHEN** поступает попытка сохранить нечисловую сумму либо сумму в валюте, отличной от RUB
|
||||
- **THEN** система объясняет ошибку и не сохраняет карту, операцию или изменение существующих данных
|
||||
|
||||
### Requirement: Даты начала учёта и ручных операций
|
||||
|
||||
Дата начала учёта и дата операции MUST быть существующими календарными датами
|
||||
без времени. Сегодня определяется по Europe/Moscow. Дата начала учёта
|
||||
MUST NOT быть позже сегодняшней даты. Начальный остаток означает деньги
|
||||
до всех вручную внесённых операций начиная с указанной даты включительно.
|
||||
|
||||
Дата операции MUST находиться между датой начала учёта затронутой карты
|
||||
и сегодняшним днём включительно. Для перевода ограничение MUST выполняться
|
||||
для обеих карт. Ввод прошедших операций внутри этого интервала SHALL быть
|
||||
доступен. Необязательное описание операции MUST содержать не более
|
||||
500 символов. Сохранённая календарная дата MUST NOT сдвигаться при смене
|
||||
часового пояса устройства.
|
||||
|
||||
#### Scenario: Операция в первый день учёта
|
||||
|
||||
- **WHEN** при начальном остатке 1 000 ₽ на 1 сентября пользователь вносит расход 100 ₽ с датой 1 сентября
|
||||
- **THEN** расход учитывается после начального остатка и баланс становится 900 ₽
|
||||
|
||||
#### Scenario: Прошедшая операция
|
||||
|
||||
- **WHEN** пользователь вносит операцию задним числом не раньше начала учёта и не позже сегодняшнего дня
|
||||
- **THEN** операция сохраняется с указанной датой и изменяет текущий баланс и сводку соответствующего месяца
|
||||
|
||||
#### Scenario: Недопустимая дата или описание
|
||||
|
||||
- **WHEN** дата не существует, находится до начала учёта или в будущем либо описание длиннее 500 символов
|
||||
- **THEN** система объясняет ошибку и не сохраняет изменение
|
||||
|
||||
#### Scenario: Перевод до начала учёта получателя
|
||||
|
||||
- **WHEN** дата перевода допустима для карты-источника, но предшествует началу учёта карты-получателя
|
||||
- **THEN** перевод отклоняется без изменения обеих карт
|
||||
|
||||
#### Scenario: Московская календарная граница
|
||||
|
||||
- **WHEN** на устройстве ещё предыдущий день, а в Europe/Moscow уже наступил следующий
|
||||
- **THEN** допустимая сегодняшняя дата и текущий месяц определяются по Europe/Moscow, а сохранённые даты операций остаются прежними
|
||||
|
||||
### Requirement: Баланс определяется начальным остатком и внесёнными операциями
|
||||
|
||||
Баланс карты MUST равняться начальному остатку плюс денежное влияние всех
|
||||
её сохранённых и не удалённых операций: пополнение и возврат увеличивают
|
||||
баланс, расход уменьшает, перевод уменьшает баланс источника и увеличивает
|
||||
баланс получателя. Начальный остаток MUST учитываться ровно один раз
|
||||
и MUST NOT считаться расходом, возвратом или пополнением.
|
||||
|
||||
Баланс MUST рассчитываться точно и может быть отрицательным. Недостаточный
|
||||
учётный остаток MUST NOT препятствовать сохранению корректного расхода
|
||||
или перевода. Баланс MUST NOT зависеть от выбранного месяца, категории,
|
||||
показанной страницы истории или нахождения карты в архиве.
|
||||
|
||||
Система SHALL представлять баланс как результат ручного учёта и MUST NOT
|
||||
выдавать его за подтверждённый банком остаток или обещать полноту невнесённых
|
||||
операций. Отдельной произвольной операции корректировки баланса SHALL NOT быть.
|
||||
|
||||
#### Scenario: Последовательные расходы с копейками
|
||||
|
||||
- **WHEN** при начальном остатке 1 ₽ сохраняются расходы 0,10 ₽ и 0,20 ₽
|
||||
- **THEN** баланс составляет ровно 0,70 ₽
|
||||
|
||||
#### Scenario: Полное расходование и отрицательный остаток
|
||||
|
||||
- **WHEN** при балансе 100 ₽ пользователь сохраняет расход 100 ₽, затем расход 50 ₽
|
||||
- **THEN** первая операция даёт известный баланс 0 ₽, вторая сохраняется и даёт баланс −50 ₽
|
||||
|
||||
#### Scenario: Начальный остаток и страница истории
|
||||
|
||||
- **WHEN** при начальном остатке 1 000 ₽ и расходах 100 ₽ и 200 ₽ пользователь повторно открывает карту и видит только расход 200 ₽
|
||||
- **THEN** текущий баланс остаётся 700 ₽: учитываются обе операции, а начальный остаток не добавляется повторно
|
||||
|
||||
#### Scenario: Итог превышает границу отдельного ввода
|
||||
|
||||
- **WHEN** на карту с начальным остатком 92 233 720 368 547 758,07 ₽ сохраняется пополнение 0,01 ₽
|
||||
- **THEN** баланс составляет точно 92 233 720 368 547 758,08 ₽ без переполнения, округления или ограничения верхней границей ввода
|
||||
|
||||
### Requirement: Ручное внесение расходов и пополнений
|
||||
|
||||
Система SHALL позволять вручную внести расход или пополнение по своей активной
|
||||
карте с суммой, датой и необязательным описанием. Расход MUST иметь категорию;
|
||||
если пользователь не выбирает другую, применяется его категория по умолчанию.
|
||||
После успешного сохранения операция MUST появиться в истории соответствующей
|
||||
карты и ровно один раз изменить её баланс.
|
||||
|
||||
Расход SHALL уменьшать баланс на свою сумму, пополнение SHALL увеличивать.
|
||||
Категория MUST NOT изменять денежное влияние расхода. Пополнение MUST NOT
|
||||
включаться в расходы, возвраты или пассивный инвестиционный доход.
|
||||
|
||||
#### Scenario: Расход с категорией
|
||||
|
||||
- **WHEN** при балансе 10 000 ₽ пользователь сохраняет расход 500 ₽ с категорией «Кафе»
|
||||
- **THEN** история показывает расход 500 ₽ с этой категорией, а баланс составляет 9 500 ₽
|
||||
|
||||
#### Scenario: Категория по умолчанию
|
||||
|
||||
- **WHEN** пользователь сохраняет расход, не выбрав другую категорию
|
||||
- **THEN** расход сохраняется с принадлежащей пользователю категорией по умолчанию и отображается под её текущим названием
|
||||
|
||||
#### Scenario: Пополнение карты
|
||||
|
||||
- **WHEN** при балансе −500 ₽ пользователь сохраняет пополнение 2 000 ₽
|
||||
- **THEN** баланс становится 1 500 ₽, история содержит пополнение, а расходы и возвраты не меняются
|
||||
|
||||
#### Scenario: Независимость другой карты
|
||||
|
||||
- **WHEN** при балансах A 1 000 ₽ и B 2 000 ₽ пользователь сохраняет расход 125,50 ₽ по A
|
||||
- **THEN** баланс A становится 874,50 ₽, а баланс и история B остаются прежними
|
||||
|
||||
### Requirement: Перевод между своими картами как одно действие
|
||||
|
||||
Система SHALL позволять одним действием внести перевод между двумя разными
|
||||
активными картами одного пользователя. Сумма перевода MUST одинаково уменьшать
|
||||
баланс источника и увеличивать баланс получателя. История каждой стороны
|
||||
MUST показывать направление и другую карту этого перевода.
|
||||
|
||||
Перевод MUST сохраняться, исправляться и удаляться целиком для обеих сторон.
|
||||
Нельзя сохранять только одну сторону, переводить на ту же или чужую карту.
|
||||
Перевод MUST NOT увеличивать расходы или возвраты в сводках одной или всех карт.
|
||||
|
||||
#### Scenario: Перевод между картами
|
||||
|
||||
- **WHEN** при балансах A 5 000 ₽ и B 1 000 ₽ пользователь сохраняет перевод 2 000 ₽ из A в B
|
||||
- **THEN** баланс A становится 3 000 ₽, баланс B — 3 000 ₽, обе истории показывают стороны одного перевода, а расходы не меняются
|
||||
|
||||
#### Scenario: Недостаточный остаток источника
|
||||
|
||||
- **WHEN** при балансах A 100 ₽ и B 0 ₽ пользователь сохраняет перевод 150 ₽ из A в B
|
||||
- **THEN** перевод сохраняется целиком, баланс A становится −50 ₽, а баланс B — 150 ₽
|
||||
|
||||
#### Scenario: Недопустимый получатель
|
||||
|
||||
- **WHEN** получателем указана та же карта или карта другого пользователя
|
||||
- **THEN** система отклоняет перевод без изменения историй, балансов и сводок
|
||||
|
||||
#### Scenario: Сбой сохранения перевода
|
||||
|
||||
- **WHEN** перевод не удаётся сохранить
|
||||
- **THEN** система не подтверждает успех и не оставляет сохранённой только одну его сторону
|
||||
|
||||
### Requirement: Возврат связан с исходным расходом
|
||||
|
||||
Возврат SHALL ссылаться на существующий не удалённый расход той же карты.
|
||||
При внесении возврата пользователь SHALL иметь возможность выбрать исходный
|
||||
расход карты за любой период учёта, независимо от месяца открытого журнала.
|
||||
Частичные и несколько возвратов допустимы; сумма всех не удалённых возвратов
|
||||
MUST NOT превышать текущую сумму исходного расхода. Дата возврата MUST быть
|
||||
не раньше даты расхода и соответствовать общим ограничениям даты карты.
|
||||
При редактировании возврата проверка общей возвращённой суммы MUST заменять
|
||||
его прежнюю сумму новой, не учитывая обе версии одновременно.
|
||||
|
||||
Возврат MUST увеличивать баланс карты и наследовать категорию исходного
|
||||
расхода. Отдельный выбор категории возврата SHALL NOT предоставляться.
|
||||
Изменение категории исходного расхода MUST менять категорию связанных
|
||||
возвратов и результаты затронутых месячных сводок, сохраняя их суммы и даты.
|
||||
|
||||
#### Scenario: Частичные возвраты
|
||||
|
||||
- **WHEN** по расходу 1 000 ₽ пользователь последовательно сохраняет возвраты 300 ₽ и 700 ₽
|
||||
- **THEN** оба возврата связаны с расходом, увеличивают баланс суммарно на 1 000 ₽ и используют его категорию
|
||||
|
||||
#### Scenario: Выбор покупки прошлого месяца
|
||||
|
||||
- **WHEN** пользователь открывает сентябрьский журнал и вносит возврат по августовской покупке
|
||||
- **THEN** форма позволяет найти и выбрать исходный августовский расход этой карты без ограничения сентябрьским журналом
|
||||
|
||||
#### Scenario: Исправление полностью возвращённой покупки
|
||||
|
||||
- **WHEN** по расходу 500 ₽ сохранён единственный возврат 500 ₽ и пользователь изменяет только описание возврата
|
||||
- **THEN** исправление сохраняется: общая возвращённая сумма остаётся 500 ₽, а прежняя и новая версии не складываются как два возврата
|
||||
|
||||
#### Scenario: Превышение возвращаемой суммы
|
||||
|
||||
- **WHEN** при расходе 1 000 ₽ и сохранённом возврате 800 ₽ пользователь пытается сохранить ещё 300 ₽
|
||||
- **THEN** система отклоняет новый возврат без изменения существующих записей и сумм
|
||||
|
||||
#### Scenario: Недопустимая связь или дата возврата
|
||||
|
||||
- **WHEN** возврат ссылается на удалённую операцию, операцию другого вида или другой карты либо датирован раньше расхода
|
||||
- **THEN** система отклоняет возврат без изменения истории, баланса и сводок
|
||||
|
||||
#### Scenario: Изменение категории покупки
|
||||
|
||||
- **WHEN** пользователь меняет категорию расхода с «Покупки» на «Здоровье», а возврат по нему произошёл в другом месяце
|
||||
- **THEN** расход и возврат учитываются в категории «Здоровье» в своих месяцах с прежними денежными суммами
|
||||
|
||||
### Requirement: Общие для пользователя категории расходов
|
||||
|
||||
Система SHALL предоставлять каждому пользователю собственный общий для всех
|
||||
его карт список категорий: «Продукты», «Кафе», «Такси», «Транспорт», «Жильё»,
|
||||
«Здоровье», «Покупки», «Развлечения», «Другое». «Другое» является начальной
|
||||
категорией по умолчанию. Категории MUST быть доступны до первого расхода.
|
||||
Начальный набор MUST создаваться при успешном добавлении первой карты
|
||||
пользователя и не дублироваться при добавлении следующих карт.
|
||||
|
||||
Пользователь SHALL иметь возможность добавлять и переименовывать любые свои
|
||||
категории, включая начальные. Название MUST содержать от 1 до 80 символов
|
||||
после удаления внешних пробелов и быть уникальным среди категорий пользователя
|
||||
без учёта регистра. Переименование MUST сохранять идентичность категории:
|
||||
все старые расходы, возвраты и сводки показывают её текущее название.
|
||||
Переименование категории по умолчанию MUST сохранять её роль.
|
||||
Удаление категорий и вложенные подкатегории SHALL NOT предоставляться в V1.
|
||||
|
||||
#### Scenario: Начальный набор и повторный вход
|
||||
|
||||
- **WHEN** пользователь сохраняет первую карту, затем добавляет следующую карту и повторно входит в приложение
|
||||
- **THEN** после первой карты ему доступен начальный набор категорий до первого расхода, а дальнейшие действия не создают дубликатов
|
||||
|
||||
#### Scenario: Категория доступна на всех своих картах
|
||||
|
||||
- **WHEN** пользователь создаёт категорию «Питомец»
|
||||
- **THEN** она доступна для расходов по всем его картам и не появляется у другого пользователя
|
||||
|
||||
#### Scenario: Переименование используемой начальной категории
|
||||
|
||||
- **WHEN** пользователь переименовывает категорию «Другое» в «Остальное»
|
||||
- **THEN** прежние записи отображаются как «Остальное», суммы не меняются, а новые расходы без другого выбора используют ту же категорию «Остальное»
|
||||
|
||||
#### Scenario: Дублирующееся или недопустимое название
|
||||
|
||||
- **WHEN** пользователь пытается добавить « кафе » при существующей «Кафе», сохранить пустое после удаления пробелов название или название длиннее 80 символов
|
||||
- **THEN** система объясняет ошибку и не изменяет список категорий
|
||||
|
||||
### Requirement: Исправление и удаление операций с согласованным пересчётом
|
||||
|
||||
Пользователь SHALL иметь возможность изменить сумму, дату, описание
|
||||
и применимые реквизиты сохранённой операции либо удалить её. Вид операции
|
||||
и связь возврата с исходным расходом MUST быть неизменны после создания.
|
||||
Все затронутые карты MUST быть активны; каждое исправление MUST соблюдать
|
||||
те же денежные, календарные и пользовательские ограничения, что и создание.
|
||||
|
||||
Обычный расход или пополнение SHALL быть переносимы на другую свою активную
|
||||
карту. Расход с не удалёнными возвратами MUST NOT переноситься на другую карту,
|
||||
удаляться, уменьшаться ниже суммы возвратов или сдвигаться на дату позже
|
||||
любого связанного возврата. Для такого действия сначала исправляются
|
||||
или удаляются мешающие возвраты. Возврат остаётся на карте исходного расхода.
|
||||
|
||||
Исправление или удаление MUST пересчитать балансы, историю и все затронутые
|
||||
месячные сводки; изменение даты переносит влияние между соответствующими
|
||||
месяцами. Исправление сторон перевода MUST учитывать его прежние и новые
|
||||
карты. Удаление перевода MUST удалять обе стороны одним действием.
|
||||
Повторное удаление уже удалённой собственной операции SHALL быть
|
||||
безрезультатным успешным действием без повторного денежного влияния.
|
||||
|
||||
#### Scenario: Изменение суммы и месяца расхода
|
||||
|
||||
- **WHEN** пользователь меняет расход 100 ₽ за август на 150 ₽ за сентябрь
|
||||
- **THEN** баланс дополнительно уменьшается на 50 ₽, август перестаёт учитывать 100 ₽, а сентябрь учитывает 150 ₽
|
||||
|
||||
#### Scenario: Перенос пополнения на другую карту
|
||||
|
||||
- **WHEN** пользователь переносит пополнение 500 ₽ с A на свою активную B с допустимой датой
|
||||
- **THEN** баланс A уменьшается на 500 ₽, баланс B увеличивается на 500 ₽, а запись относится к B
|
||||
|
||||
#### Scenario: Изменение получателя перевода
|
||||
|
||||
- **WHEN** перевод 200 ₽ из A в B исправляется на перевод 300 ₽ из A в C и все три карты активны
|
||||
- **THEN** прежнее влияние на B отменяется, A уменьшается дополнительно на 100 ₽, C увеличивается на 300 ₽, а обе стороны исправленного перевода сохраняются согласованно
|
||||
|
||||
#### Scenario: Исправление расхода ограничено возвратами
|
||||
|
||||
- **WHEN** для расхода 1 000 ₽ существует возврат 600 ₽ и пользователь пытается удалить расход, перенести его на другую карту, уменьшить до 500 ₽ или сдвинуть позже даты возврата
|
||||
- **THEN** система отклоняет действие и объясняет необходимость сначала исправить связанные возвраты
|
||||
|
||||
#### Scenario: Удаление возврата и затем расхода
|
||||
|
||||
- **WHEN** пользователь удаляет все возвраты расхода, а затем сам расход
|
||||
- **THEN** влияние каждой удалённой записи отменяется в балансе и в её месячной сводке, а ограничение удаления исходного расхода больше не действует
|
||||
|
||||
#### Scenario: Удаление перевода из любой стороны
|
||||
|
||||
- **WHEN** пользователь удаляет перевод из истории получателя и затем повторяет удаление
|
||||
- **THEN** обе стороны удаляются при первом действии, денежное влияние отменяется один раз, а повторное удаление ничего не меняет
|
||||
|
||||
#### Scenario: Смена вида или исходного расхода
|
||||
|
||||
- **WHEN** пользователь пытается превратить расход в пополнение либо связать существующий возврат с другим расходом
|
||||
- **THEN** изменение отклоняется без подмены вида операции или её исходной связи
|
||||
|
||||
### Requirement: Исправление начала ручного учёта
|
||||
|
||||
Пользователь SHALL иметь возможность изменить начальный остаток и дату начала
|
||||
учёта активной карты. Изменение MUST проверять всю затронутую сохранённую
|
||||
историю, включая стороны переводов и связанные возвраты, и отклоняться
|
||||
без частичного результата, если после него какая-либо операция нарушает
|
||||
действующие ограничения. Удалённые операции не ограничивают новую дату.
|
||||
|
||||
Изменение начального остатка SHALL пересчитывать текущий баланс, сохраняя
|
||||
суммы, даты и категории операций. Оно MUST NOT создавать отдельную операцию
|
||||
или изменять месячные расходы и возвраты. Пропущенное движение SHALL
|
||||
исправляться внесением реальной операции с допустимой датой.
|
||||
|
||||
#### Scenario: Исправление начального остатка
|
||||
|
||||
- **WHEN** при начальном остатке 1 000 ₽ и расходах 300 ₽ пользователь меняет начальный остаток на 1 200 ₽
|
||||
- **THEN** баланс становится 900 ₽, история операций не пополняется корректировкой, а месячные расходы остаются 300 ₽
|
||||
|
||||
#### Scenario: Новое начало противоречит истории
|
||||
|
||||
- **WHEN** пользователь переносит дату начала учёта с 1 на 10 сентября, а по карте сохранён расход или перевод от 5 сентября
|
||||
- **THEN** система отклоняет изменение и сохраняет прежние дату, баланс и всю историю
|
||||
|
||||
### Requirement: Архив карт сохраняет историю
|
||||
|
||||
Пользователь SHALL иметь возможность архивировать свою карту при любом
|
||||
балансе, включая положительный, нулевой и отрицательный, и вернуть её
|
||||
из архива. Архивация MUST NOT менять деньги или операции. Обычный список
|
||||
SHALL показывать активные карты; переключатель архива SHALL открывать
|
||||
архивные карты и их историю.
|
||||
|
||||
Пока карта архивна, система MUST запрещать создание, редактирование
|
||||
и удаление затрагивающих её операций, а также изменение начального остатка
|
||||
и даты начала учёта. Для исправления или новой операции карту сначала
|
||||
возвращают из архива. Перевод или возврат MUST требовать активного состояния
|
||||
всех затронутых карт. Изменение описательных сведений карты само по себе
|
||||
не является финансовой операцией.
|
||||
|
||||
Архивная карта MUST продолжать участвовать в исторических сводках «Все карты».
|
||||
Безвозвратное удаление карты SHALL NOT предоставляться в первой версии.
|
||||
|
||||
#### Scenario: Архивирование с ненулевым остатком
|
||||
|
||||
- **WHEN** пользователь архивирует карту с балансом −500 ₽ и сохранённой историей
|
||||
- **THEN** карта скрывается из обычного списка, остаётся доступной через переключатель архива с теми же балансом и историей и сохраняет вклад в сводки всех карт
|
||||
|
||||
#### Scenario: Финансовое действие по архивной карте
|
||||
|
||||
- **WHEN** пользователь пытается внести, исправить или удалить операцию либо изменить начало учёта архивной карты
|
||||
- **THEN** система отклоняет действие без изменения данных и объясняет необходимость вернуть карту из архива
|
||||
|
||||
#### Scenario: Возврат или перевод затрагивает архив
|
||||
|
||||
- **WHEN** возврат относится к расходу архивной карты либо хотя бы одна прежняя или новая сторона изменяемого перевода архивна
|
||||
- **THEN** финансовое действие отклоняется до возвращения всех затронутых карт из архива
|
||||
|
||||
#### Scenario: Возвращение карты
|
||||
|
||||
- **WHEN** пользователь возвращает карту из архива
|
||||
- **THEN** она снова видна в обычном списке с сохранёнными данными и допускает финансовые действия по общим правилам
|
||||
|
||||
### Requirement: Месячный журнал и сводка расходов по категориям
|
||||
|
||||
Система SHALL показывать историю и месячную сводку выбранной карты, а также
|
||||
месячную сводку всех банковских карт пользователя. По умолчанию выбирается
|
||||
текущий месяц Europe/Moscow; пользователь SHALL иметь возможность явно выбрать
|
||||
другой месяц. История карты и её сводка MUST использовать один выбранный месяц.
|
||||
Операции SHALL отображаться по убыванию календарной даты со стабильным порядком
|
||||
внутри даты при повторном чтении неизменившихся данных и доступом к следующим
|
||||
страницам истории.
|
||||
|
||||
Сводка MUST показывать расходы, возвраты и итог после возвратов, а также
|
||||
соответствующие суммы по категориям. Расход входит в месяц собственной даты,
|
||||
возврат — в месяц даты возврата, даже если покупка была раньше.
|
||||
Итог MUST равняться расходам минус возвраты и может быть отрицательным.
|
||||
Начальные остатки, пополнения и переводы MUST NOT включаться в эти суммы.
|
||||
|
||||
Сводка MUST учитывать все подходящие не удалённые записи, а не только
|
||||
текущую страницу истории. «Все карты» MUST включать активные и архивные карты
|
||||
владельца. Выбор месяца и охвата MUST NOT изменять текущие балансы.
|
||||
При отсутствии расходов и возвратов SHALL показываться известные нули.
|
||||
|
||||
#### Scenario: Месяц по умолчанию и переключение
|
||||
|
||||
- **WHEN** пользователь открывает карту в сентябре по Europe/Moscow и затем выбирает август
|
||||
- **THEN** сначала история и сводка относятся к сентябрю, после выбора обе относятся к августу, а текущий баланс остаётся прежним
|
||||
|
||||
#### Scenario: Сводка расходов и переводов
|
||||
|
||||
- **WHEN** в месяце сохранены расходы «Кафе» 500 ₽ и «Такси» 300 ₽, пополнение 2 000 ₽ и перевод 1 000 ₽ без возвратов
|
||||
- **THEN** сводка показывает расходы 800 ₽, возвраты 0 ₽, итог 800 ₽ и категории 500 ₽ и 300 ₽ без включения пополнения или перевода
|
||||
|
||||
#### Scenario: Возврат покупки прошлого месяца
|
||||
|
||||
- **WHEN** расход «Покупки» 1 000 ₽ совершён в августе, возврат 600 ₽ — в сентябре и других операций этих видов нет
|
||||
- **THEN** август показывает расходы 1 000 ₽, возвраты 0 ₽ и итог 1 000 ₽, а сентябрь — расходы 0 ₽, возвраты 600 ₽ и итог −600 ₽ в той же категории
|
||||
|
||||
#### Scenario: Все карты включают архивные
|
||||
|
||||
- **WHEN** расходы активной A за месяц составили 800 ₽, архивной B — 200 ₽, а между ними ранее был перевод 1 000 ₽
|
||||
- **THEN** общая сводка показывает расходы 1 000 ₽ и не добавляет перевод, независимо от переключателя архива в списке карт
|
||||
|
||||
#### Scenario: Несколько страниц и равные даты
|
||||
|
||||
- **WHEN** операции выбранного месяца занимают несколько страниц и часть операций имеет одинаковую дату
|
||||
- **THEN** пользователь может просмотреть все страницы в стабильном порядке, а сводка и баланс учитывают все соответствующие записи независимо от открытой страницы
|
||||
|
||||
### Requirement: Изоляция пользовательских финансовых данных
|
||||
|
||||
Карты, категории, операции и связи между ними MUST принадлежать локальному
|
||||
пользователю WealthLedger. Система MUST предоставлять чтение, создание,
|
||||
изменение, удаление, архив и восстановление только владельцу с действующей
|
||||
сессией. Идентификатор сам по себе MUST NOT подтверждать доступ.
|
||||
|
||||
Проверка владельца MUST распространяться на каждую упомянутую карту,
|
||||
категорию и операцию, включая обе стороны перевода, исходный расход возврата
|
||||
и новые связи при редактировании. Отказ MUST NOT раскрывать чужие финансовые
|
||||
сведения или менять данные любой стороны. Сводки MUST включать только
|
||||
данные текущего пользователя.
|
||||
|
||||
#### Scenario: Нет действующей сессии
|
||||
|
||||
- **WHEN** пользователь без действующей сессии пытается прочитать карты, сводку или изменить финансовые данные
|
||||
- **THEN** система отказывает в доступе без раскрытия и изменения сохранённых данных
|
||||
|
||||
#### Scenario: Чужая карта или категория
|
||||
|
||||
- **WHEN** пользователь обращается к чужой карте или пытается назначить своей операции чужую категорию
|
||||
- **THEN** система отклоняет действие, не раскрывает чужие сведения и не меняет баланс, историю или категории
|
||||
|
||||
#### Scenario: Чужая связь перевода или возврата
|
||||
|
||||
- **WHEN** пользователь подставляет чужую карту в перевод или чужой расход в возврат при создании либо редактировании
|
||||
- **THEN** система отклоняет всё действие без сохранения частичного результата и без раскрытия данных другого пользователя
|
||||
|
||||
#### Scenario: Чужие исправления и архив
|
||||
|
||||
- **WHEN** пользователь пытается изменить или удалить чужую операцию, переименовать чужую категорию либо архивировать или восстановить чужую карту
|
||||
- **THEN** система отказывает и сохраняет все данные владельца без изменений
|
||||
|
||||
### Requirement: Согласованные изменения и защита повторной отправки
|
||||
|
||||
Успешное финансовое изменение MUST согласованно сохранять операции, связи,
|
||||
денежное влияние и пересчитанные результаты для всех затронутых карт.
|
||||
Система MUST NOT подтверждать успех несохранённого действия или оставлять
|
||||
частичный результат. Независимые одновременные операции MUST сохранять
|
||||
влияние каждой успешной записи, не затирая друг друга.
|
||||
|
||||
Повторная отправка одного намерения создать карту или операцию с тем же
|
||||
содержанием MUST приводить не более чем к одному соответствующему результату
|
||||
и однократному учёту начального остатка или денежного влияния операции.
|
||||
Иное содержание при повторном использовании того же намерения MUST приводить
|
||||
к конфликту без изменения данных. После исправления или удаления результата
|
||||
поздняя повторная отправка исходного создания MUST NOT восстанавливать
|
||||
старые значения, удалённую запись или её денежное влияние.
|
||||
Ответ на повтор MUST показывать актуальное состояние ранее созданной карты
|
||||
или операции либо сообщать, что операция удалена, не выдавая старые данные
|
||||
за текущий результат. Уже сохранённое намерение MUST NOT проверяться
|
||||
как новая финансовая запись и повторно расходовать доступную сумму возврата.
|
||||
Архивирование ранее созданной карты MUST NOT превращать повтор её создания
|
||||
в создание другой карты или возвращение из архива.
|
||||
|
||||
Редактирование или удаление существующей операции и изменение карты,
|
||||
основанные на её устаревшем состоянии, MUST завершаться понятным конфликтом
|
||||
без перезаписи более свежих изменений. Повторное удаление уже удалённой
|
||||
операции не считается перезаписью и остаётся безрезультатным действием.
|
||||
Проверки суммы возвратов, состояния архива, дат и других ограничений
|
||||
MUST сохраняться при одновременных действиях.
|
||||
|
||||
#### Scenario: Повтор после потерянного ответа
|
||||
|
||||
- **WHEN** создание расхода 100 ₽ успешно сохранено, ответ потерян и пользователь повторяет то же намерение с тем же содержанием
|
||||
- **THEN** история содержит один расход, баланс изменён один раз, а повтор не создаёт дубликат
|
||||
|
||||
#### Scenario: Повтор создания карты после архивации
|
||||
|
||||
- **WHEN** карта с начальным остатком 1 000 ₽ создана, ответ потерян, карта затем архивирована и приходит повтор её исходного создания
|
||||
- **THEN** система показывает актуальную архивную карту, не создаёт вторую карту, не дублирует начальный остаток и не возвращает карту из архива
|
||||
|
||||
#### Scenario: Повтор полного возврата
|
||||
|
||||
- **WHEN** возврат 500 ₽ полностью покрывает расход 500 ₽, успешно сохранён, но ответ потерян и пользователь повторяет то же намерение создания возврата
|
||||
- **THEN** система возвращает сохранённый возврат без новой записи, повторного изменения баланса или ошибки о превышении суммы покупки
|
||||
|
||||
#### Scenario: Изменённое содержание того же намерения
|
||||
|
||||
- **WHEN** после сохранения расхода 100 ₽ повтор того же намерения содержит сумму 200 ₽
|
||||
- **THEN** система сообщает о конфликте и сохраняет исходный результат без дополнительной операции
|
||||
|
||||
#### Scenario: Позднее создание после исправления или удаления
|
||||
|
||||
- **WHEN** сохранённый расход 100 ₽ исправлен на 150 ₽ или удалён, после чего приходит повтор его исходного создания
|
||||
- **THEN** система показывает актуальный расход 150 ₽ либо сообщает об удалении; прежняя сумма или удалённая запись не восстанавливаются и дополнительного денежного влияния нет
|
||||
|
||||
#### Scenario: Два независимых расхода
|
||||
|
||||
- **WHEN** при балансе 1 000 ₽ расходы 100 ₽ и 200 ₽ успешно сохраняются одновременно как разные намерения
|
||||
- **THEN** история содержит оба расхода, а баланс составляет 700 ₽
|
||||
|
||||
#### Scenario: Два возврата превышают покупку вместе
|
||||
|
||||
- **WHEN** для расхода 1 000 ₽ одновременно сохраняются независимые возвраты 600 ₽ и 600 ₽
|
||||
- **THEN** система не допускает общей сохранённой суммы 1 200 ₽: конфликтующее действие отклоняется без частичного результата
|
||||
|
||||
#### Scenario: Исправление по устаревшим данным
|
||||
|
||||
- **WHEN** операция или карта изменилась после того, как пользователь начал её редактирование, а он отправляет изменение прежнего состояния
|
||||
- **THEN** система сообщает о конфликте, сохраняет более свежие данные и позволяет получить актуальное состояние для нового исправления
|
||||
|
||||
#### Scenario: Удаление по устаревшим данным
|
||||
|
||||
- **WHEN** пользователь пытается удалить существующую операцию, которую другой успешный запрос уже изменил
|
||||
- **THEN** система отклоняет устаревшее удаление без потери более свежего исправления
|
||||
|
||||
#### Scenario: Архивирование одновременно с финансовым изменением
|
||||
|
||||
- **WHEN** архивирование карты пересекается с созданием или исправлением затрагивающей её операции
|
||||
- **THEN** результаты соответствуют последовательному выполнению допустимых действий и не допускают финансовую запись, выполненную после вступившего в силу архива
|
||||
|
||||
### Requirement: Сохранённый ручной учёт переживает повторный вход и перезапуск
|
||||
|
||||
Успешно сохранённые карты, начальные остатки, даты начала, операции,
|
||||
категории, архивное состояние и связи возвратов и переводов MUST оставаться
|
||||
доступными владельцу после обновления страницы, выхода, повторного входа
|
||||
и обычного перезапуска приложения. Эти действия MUST NOT создавать
|
||||
дубликаты, восстанавливать удалённые операции или сбрасывать балансы.
|
||||
Защита повторной отправки сохранённых намерений MUST сохранять действие
|
||||
после этих переходов.
|
||||
|
||||
#### Scenario: Повторный вход
|
||||
|
||||
- **WHEN** пользователь выходит и снова входит после сохранения карты с начальным остатком 1 000 ₽ и расходом 100 ₽
|
||||
- **THEN** он видит ту же карту, дату начала, расход с его текущей категорией и баланс 900 ₽
|
||||
|
||||
#### Scenario: Перезапуск с переводом, возвратом и архивом
|
||||
|
||||
- **WHEN** приложение перезапускается после сохранения переводов, связанных возвратов, переименования категорий и архивирования карты
|
||||
- **THEN** владелец получает прежние связи, названия, архивное состояние и рассчитанные суммы без дубликатов или потери истории
|
||||
|
||||
#### Scenario: Повтор создания после перезапуска
|
||||
|
||||
- **WHEN** после перезапуска повторяется намерение создания ранее сохранённой, а затем удалённой операции
|
||||
- **THEN** удалённая операция и её денежное влияние не восстанавливаются
|
||||
|
||||
### Requirement: Доступные формы и различимые состояния без ложного успеха
|
||||
|
||||
Раздел SHALL различать отсутствие карт, отсутствие операций за месяц,
|
||||
известный нулевой баланс, загрузку и ошибку получения данных. Ошибка
|
||||
MUST NOT подменяться пустым списком или нулевой суммой. Пользователь SHALL
|
||||
иметь возможность повторить неуспешную загрузку.
|
||||
|
||||
Формы карт, операций, исправлений и категорий MUST иметь доступные названия
|
||||
полей и действий, управление с клавиатуры и понятные ошибки, связанные
|
||||
с соответствующим полем или действием. Обязательность, направление денег
|
||||
и ошибки MUST NOT передаваться только цветом.
|
||||
|
||||
Во время сохранения, ошибки и конфликта введённый черновик MUST оставаться
|
||||
доступным до явного закрытия или успешного завершения. Пока результат
|
||||
сохранения не подтверждён, система MUST NOT показывать введённую сумму
|
||||
как подтверждённый новый баланс или выдавать действие за успешное.
|
||||
При неопределённом результате после потери ответа повтор SHALL использовать
|
||||
защиту того же намерения создания. После подтверждённого изменения
|
||||
отображаемые история, балансы и сводки MUST согласовываться с сохранёнными
|
||||
данными всех затронутых карт.
|
||||
|
||||
#### Scenario: Пустой список
|
||||
|
||||
- **WHEN** раздел успешно загружен и у пользователя ещё нет карт
|
||||
- **THEN** система показывает штатное пустое состояние с доступным действием добавления карты
|
||||
|
||||
#### Scenario: Нет операций при ненулевом балансе
|
||||
|
||||
- **WHEN** в выбранном месяце нет операций, но текущий баланс карты составляет 10 000 ₽
|
||||
- **THEN** история сообщает об отсутствии операций за месяц, сводка содержит нули, а текущий баланс остаётся 10 000 ₽
|
||||
|
||||
#### Scenario: Ошибка загрузки
|
||||
|
||||
- **WHEN** загрузка карт, истории или сводки завершается ошибкой
|
||||
- **THEN** система показывает ошибку и действие повтора, не выдавая её за отсутствие данных или нулевой результат
|
||||
|
||||
#### Scenario: Доступный ввод и проверка
|
||||
|
||||
- **WHEN** пользователь управляет формой с клавиатуры или средством чтения экрана и вводит некорректную сумму
|
||||
- **THEN** названия полей и действий доступны, ошибка суммы определима без цвета, а введённые значения остаются для исправления
|
||||
|
||||
#### Scenario: Ожидание и неопределённый результат
|
||||
|
||||
- **WHEN** пользователь отправляет перевод и ответ ещё не получен либо потерян
|
||||
- **THEN** черновик сохраняется, неподтверждённые балансы не выдаются за сохранённые, а повтор того же создания не порождает второй перевод
|
||||
|
||||
#### Scenario: Ошибка или конфликт исправления
|
||||
|
||||
- **WHEN** сохранение исправления отклоняется из-за ошибки или устаревшего состояния
|
||||
- **THEN** пользователь видит причину, сохраняет черновик и может получить актуальные данные для следующего действия без перезаписи чужого успешного изменения
|
||||
@@ -8,21 +8,21 @@
|
||||
|
||||
### Requirement: Каждый change содержит полный Delivery Map
|
||||
|
||||
Каждый versioned change MUST оценивать delivery areas `frontend`, `backend`, `specs` и `docs` и для каждой области указывать `affected` либо `N/A` с конкретной причиной. `specs` SHALL быть affected для каждого versioned SDD change. `docs` SHALL быть affected для каждой поставленной пользовательской фичи; для технического или процессного change она MAY быть `N/A` только когда фактическая устойчивая документация не изменяется. `frontend` и `backend` SHALL быть affected только когда меняется их поведение, контракт, реализация или обязательный локальный gate. Пустой коммит MUST NOT использоваться для имитации участия незатронутого репозитория.
|
||||
Каждый legacy `wealth-ledger-sdd` change MUST оценивать delivery areas `frontend`, `backend`, `specs` и `docs` в Design Delivery Map и для каждой области указывать `affected` либо `N/A` с конкретной причиной. Для `wealth-ledger-sdd-v2` Design оценивает implementation impact `main`, `frontend`, `backend` (и `qa`, только если существует repo), а Docs Impact отдельно оценивает `docs`; `specs` владеет SDD artifacts и native sync/archive без фиктивной implementation task. Для обоих workflows `docs` SHALL быть affected для каждой поставленной пользовательской фичи; для технического или процессного change она MAY быть `N/A` только когда фактическая устойчивая документация не изменяется. `frontend` и `backend` SHALL быть affected только когда меняется их поведение, контракт, реализация или обязательный локальный gate. Пустой коммит MUST NOT использоваться для имитации участия незатронутого репозитория.
|
||||
|
||||
#### Scenario: Фича затрагивает клиент и API
|
||||
|
||||
- **WHEN** change меняет пользовательский поток и соответствующее API-поведение
|
||||
- **THEN** Delivery Map отмечает frontend, backend, specs и docs как affected и описывает ожидаемый результат каждой области без реализации по файлам
|
||||
- **THEN** legacy Delivery Map отмечает frontend, backend, specs и docs как affected; v2 Design отмечает frontend/backend, а Docs Impact — docs и отдельный specs artifact не превращается в пустую task
|
||||
|
||||
#### Scenario: Область не затронута
|
||||
|
||||
- **WHEN** поведение, контракт, реализация, guardrails и фактическая документация области не меняются
|
||||
- **THEN** Delivery Map содержит проверяемую причину `N/A`, а delivery не требует пустого коммита этого репозитория
|
||||
- **THEN** соответствующий impact artifact содержит проверяемую причину `N/A`, а delivery не требует пустого коммита этого репозитория
|
||||
|
||||
### Requirement: Частичная multi-repository delivery не считается завершённой
|
||||
|
||||
Change MUST оставаться active, пока каждая affected область не реализована и не прошла свои обязательные локальные gates. Успех одной области MUST NOT позволять архивировать change, если другая affected область отсутствует, не проверена либо не соответствует согласованному поведению. После archive change MUST NOT получить terminal status `done`, пока фактические результаты всех четырёх delivery areas не представлены в Completion Evidence.
|
||||
Change MUST оставаться active, пока каждая affected область не реализована и не прошла свои обязательные локальные gates. Успех одной области MUST NOT позволять архивировать change, если другая affected область отсутствует, не проверена либо не соответствует согласованному поведению. После archive change MUST NOT получить terminal status `done`, пока фактические результаты всех применимых delivery areas выбранной schema не представлены в Completion Evidence.
|
||||
|
||||
#### Scenario: Backend готов, а frontend ещё не реализован
|
||||
|
||||
@@ -36,12 +36,12 @@ Change MUST оставаться active, пока каждая affected обла
|
||||
|
||||
### Requirement: Completion Evidence содержит только наблюдённые результаты
|
||||
|
||||
Completion Evidence MUST создаваться после выполнения работы и MUST связывать canonical change с commit identifier каждого изменённого Git-репозитория, точными выполненными non-mutating verification commands и их наблюдёнными результатами. Для каждой области с `N/A` evidence SHALL сохранять согласованную причину отсутствия изменения. Planned commands, ожидаемые результаты, незавершённые checklist items и предполагаемые commit identifiers MUST NOT представляться как completion evidence.
|
||||
Completion Evidence MUST создаваться после выполнения работы и MUST связывать canonical change с commit identifier каждого изменённого Git-репозитория, точными выполненными non-mutating verification commands и их наблюдёнными результатами. Для каждой оценённой области с `N/A` evidence SHALL сохранять согласованную причину отсутствия изменения. Planned commands, ожидаемые результаты, незавершённые checklist items и предполагаемые commit identifiers MUST NOT представляться как completion evidence.
|
||||
|
||||
#### Scenario: Change готовится к terminal status
|
||||
|
||||
- **WHEN** implementer заявляет, что multi-repository change завершён
|
||||
- **THEN** reviewer может сопоставить Delivery Map с фактическими commits, результатами gates и причинами `N/A` для всех четырёх областей
|
||||
- **THEN** reviewer может сопоставить impact artifacts выбранной schema с фактическими commits, результатами gates и причинами `N/A` для всех оценённых областей
|
||||
|
||||
#### Scenario: Команда ещё не была выполнена
|
||||
|
||||
@@ -64,7 +64,7 @@ Workspace SHALL предоставлять один non-mutating локальн
|
||||
|
||||
### Requirement: Каждая задача объявляет единственную delivery area
|
||||
|
||||
Каждый checkbox Tasks MUST объявлять ровно одного owner из `frontend`, `backend`, `specs`, `docs` или `workspace`. Implementation work, изменяющая несколько Git-репозиториев, MUST быть разделена на repository-owned tasks; `workspace` SHALL использоваться только для cross-area orchestration или verification без владения repository implementation. Для каждой `affected` area Tasks MUST содержать её implementation work и canonical local gate, а task ownership MUST соответствовать утверждённому Delivery Map.
|
||||
Каждый checkbox legacy Tasks MUST объявлять ровно одного owner из `frontend`, `backend`, `specs`, `docs` или `workspace`; `workspace` SHALL использоваться только для cross-area orchestration или verification без владения repository implementation. Каждый checkbox v2 Tasks MUST объявлять ровно одного repository owner из `main`, `frontend`, `backend`, `docs`, `specs` или существующего `qa`; `main` владеет только workspace/Compose/release/configuration, `specs` — genuine repository maintenance, `docs` — approved Docs Impact, `qa` — только реальная automation delivery, но не manual acceptance checkbox. Implementation work, изменяющая несколько Git-репозиториев, MUST быть разделена на repository-owned tasks. Для каждой `affected` implementation area Tasks MUST содержать её implementation work и canonical local gate, а task ownership MUST соответствовать утверждённым impact artifacts выбранной schema.
|
||||
|
||||
#### Scenario: Change затрагивает frontend и backend
|
||||
|
||||
@@ -74,16 +74,16 @@ Workspace SHALL предоставлять один non-mutating локальн
|
||||
#### Scenario: Выполняется общий verification gate
|
||||
|
||||
- **WHEN** задача только оркестрирует проверку нескольких affected areas и не изменяет их implementation
|
||||
- **THEN** task использует owner `workspace` и не подменяет repository-owned implementation или canonical gates
|
||||
- **THEN** legacy task MAY использовать `workspace`; v2 использует `main` только при реальном workspace-owned outcome, не маскируя независимую Validation под implementation checkbox
|
||||
|
||||
#### Scenario: Task ссылается на N/A area
|
||||
|
||||
- **WHEN** checkbox объявляет owner области, которая имеет статус `N/A` в Delivery Map
|
||||
- **THEN** planning validation завершается ошибкой и указывает несогласованную delivery area
|
||||
- **THEN** planning validation завершается ошибкой и указывает несогласованную delivery area выбранной schema
|
||||
|
||||
### Requirement: Tasks трассируются к утверждённым требованиям
|
||||
|
||||
Каждый checkbox Tasks MUST содержать coverage reference: одну или несколько пар из точного capability path и точного Requirement heading либо `N/A` с конкретной причиной для работы, которая не реализует spec-level behavior. Причина `N/A` MUST объяснять отсутствие самостоятельного observable behavior, техническую необходимость задачи и её связь с утверждённым Design или delivery; однословная категория или неразрешённый placeholder не являются достаточной причиной. Каждый Requirement из Delta Specs MUST быть покрыт хотя бы одной task. Planning validation MUST отклонять отсутствующее покрытие, ссылку на несуществующий capability или Requirement, пустую причину `N/A` и неразрешённый placeholder. Содержательная достаточность непустой причины SHALL оцениваться semantic approval и independent review и MUST NOT выдаваться за детерминированный hard guardrail. Traceability SHALL использовать нативную идентичность requirement и MUST NOT вводить параллельный namespace собственных requirement IDs.
|
||||
Каждый checkbox Tasks MUST содержать coverage reference: одну или несколько пар из точного capability path и точного Requirement heading либо `N/A` с конкретной причиной для работы, которая не реализует spec-level behavior. Причина `N/A` MUST объяснять отсутствие самостоятельного observable behavior, техническую необходимость задачи и её связь с утверждённым Design или delivery; для v2 также допустима связь с System Analysis или Docs Impact. Однословная категория или неразрешённый placeholder не являются достаточной причиной. Каждый Requirement из Delta Specs MUST быть покрыт хотя бы одной task; для v2 допустимо явное объяснение отсутствия implementation work. Planning validation MUST отклонять отсутствующее покрытие, ссылку на несуществующий capability или Requirement, пустую причину `N/A` и неразрешённый placeholder. Содержательная достаточность непустой причины SHALL оцениваться semantic approval и independent review и MUST NOT выдаваться за детерминированный hard guardrail. Traceability SHALL использовать нативную идентичность requirement и MUST NOT вводить параллельный namespace собственных requirement IDs.
|
||||
|
||||
#### Scenario: Delta requirement покрыт реализацией
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# online-instrument-information Specification
|
||||
|
||||
## Purpose
|
||||
Определяет online-only получение справочных сведений и результатов поиска инструментов отдельно от сохранённых личных финансовых данных.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Справочные сведения и поиск инструментов получают онлайн
|
||||
Система SHALL получать подробные справочные сведения об Instrument и результаты поиска онлайн при их запросе. Такие сведения MUST NOT становиться самостоятельной долговременной копией справочника. Недоступность либо отсутствие онлайн-сведений MUST NOT задерживать сохранённый финансовый ответ, изменять его свежесть или подтверждать отсутствие Instrument.
|
||||
|
||||
#### Scenario: Медленный online lookup
|
||||
- **WHEN** подробные сведения об Instrument ещё не получены онлайн при чтении сохранённого Portfolio
|
||||
- **THEN** система возвращает доступный сохранённый Portfolio без ожидания lookup
|
||||
|
||||
#### Scenario: Online lookup не найден
|
||||
- **WHEN** online lookup не возвращает Instrument
|
||||
- **THEN** система не интерпретирует этот результат как изменение сохранённых личных финансовых данных
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
### Requirement: SDD-артефакты имеют непересекающиеся полномочия
|
||||
|
||||
Proposal SHALL выражать причину, намерение, границы и ожидаемый эффект change языком владельца и MUST NOT содержать решения по технологиям, структуре кода, файлам, API или зависимостям. Delta Specs SHALL определять только добавленное, изменённое или удалённое наблюдаемое поведение и инварианты системы без описания изменений кода. Design SHALL объяснять архитектурный способ выполнения утверждённых Specs, существенные решения, риски и альтернативы крупными частями и MUST NOT становиться построчной инструкцией реализации. Применимые UI/UX-решения и визуальные материалы SHALL входить в единый canonical Design и MUST NOT образовывать отдельный planning authority или отдельный UI/UX approval. Tasks SHALL быть implementation checklist, достаточным для выполнения Design и Specs, и MUST NOT содержать ещё не наблюдённые completion results, commit identifiers или post-archive bookkeeping. Completion Evidence SHALL фиксироваться только после фактической реализации и верификации и MUST NOT подменять planning artifact. Концептуальные материалы и Discovery SHALL оставаться входным контекстом и MUST NOT считаться утверждёнными OpenSpec artifacts.
|
||||
Proposal SHALL выражать причину, намерение, границы и ожидаемый эффект change языком владельца и MUST NOT содержать решения по технологиям, структуре кода, файлам, API или зависимостям. Delta Specs SHALL определять только добавленное, изменённое или удалённое наблюдаемое поведение и инварианты системы без описания изменений кода. В `wealth-ledger-sdd-v2` System Analysis SHALL определять межсистемные потоки, contracts и semantics до Design; Design SHALL объяснять внутреннюю реализацию approved behavior/contracts, риски и альтернативы, не становясь построчной инструкцией. QA Analysis SHALL определять независимую system verification, не предписывая разработчикам internal tests. Docs Impact SHALL планировать только durable AS-IS knowledge для `docs/`. Применимые UI/UX-решения и визуальные материалы SHALL входить в единый canonical Design и MUST NOT образовывать отдельный planning authority или отдельный UI/UX approval. Tasks SHALL быть implementation checklist из утверждённых upstream artifacts и MUST NOT содержать ещё не наблюдённые completion results, commit identifiers или post-archive bookkeeping. Completion Evidence SHALL фиксироваться только после фактической реализации и верификации и MUST NOT подменять planning artifact. Концептуальные материалы и Discovery SHALL оставаться входным контекстом и MUST NOT считаться утверждёнными OpenSpec artifacts.
|
||||
|
||||
#### Scenario: Product Owner формулирует Proposal
|
||||
|
||||
@@ -16,7 +16,7 @@ Proposal SHALL выражать причину, намерение, границ
|
||||
|
||||
#### Scenario: Агент подготавливает Tasks
|
||||
|
||||
- **WHEN** Proposal, Delta Specs и Design уже утверждены
|
||||
- **WHEN** upstream artifacts выбранной schema утверждены
|
||||
- **THEN** Tasks перечисляет конкретные действия реализации и проверки, но не заявляет будущие результаты уже выполненными
|
||||
|
||||
#### Scenario: Владелец согласует визуальный результат
|
||||
@@ -33,26 +33,31 @@ Proposal SHALL выражать причину, намерение, границ
|
||||
|
||||
### Requirement: Artifact graph применяет последовательные approval gates
|
||||
|
||||
Versioned SDD change SHALL проходить последовательность `Proposal → Delta Specs → Design → Tasks → Apply`. AI-агент MAY подготовить черновик текущего разрешённого артефакта, но MUST NOT считать его утверждённым самостоятельно или создавать downstream artifact до явного решения Change Owner. Для change, определяющего продуктовое поведение, Change Owner SHALL быть Product Owner; для технического или процессного change — Engineering Owner. Каждый из четырёх planning artifacts MUST существовать и быть утверждён до Apply.
|
||||
Каждый change SHALL следовать schema из своего `.openspec.yaml`: существующие active `wealth-ledger-sdd` сохраняют `Proposal → Delta Specs → Design → Tasks → Apply`, а новые `wealth-ledger-sdd-v2` используют `Proposal → Delta Specs → System Analysis → (Design + QA Analysis) → Docs Impact → Tasks → Apply`. В v2 Design и QA Analysis MAY подготавливаться параллельно после approval System Analysis; Docs Impact требует approvals обоих. `skip_specs: true` явно обозначает отсутствие behavior delta, а не фиктивный spec. AI-агент MAY подготовить черновик текущего разрешённого артефакта, но MUST NOT считать его утверждённым самостоятельно или создавать downstream artifact до явного решения Change Owner. Для change, определяющего продуктовое поведение, Change Owner SHALL быть Product Owner; для технического или процессного change — Engineering Owner. Каждый применимый planning artifact выбранной schema MUST существовать и быть явно утверждён до Apply; существующие approvals legacy changes MUST NOT инвалидироваться только из-за смены default schema.
|
||||
|
||||
#### Scenario: Proposal ещё не утверждён
|
||||
|
||||
- **WHEN** AI-агент завершил черновик Proposal, но Change Owner не дал явного согласия
|
||||
- **THEN** Delta Specs, Design и Tasks остаются недоступны для создания
|
||||
- **THEN** downstream artifacts выбранной schema остаются недоступны для создания
|
||||
|
||||
#### Scenario: Delta Specs утверждены
|
||||
|
||||
- **WHEN** Change Owner явно подтверждает Delta Specs после утверждённого Proposal
|
||||
- **THEN** Design становится следующим разрешённым planning artifact, а Tasks и Apply остаются заблокированы
|
||||
- **THEN** для legacy разрешён Design, для v2 — System Analysis; Tasks и Apply остаются заблокированы
|
||||
|
||||
#### Scenario: Active change переживает смену default schema
|
||||
|
||||
- **WHEN** active change закреплён за `wealth-ledger-sdd` и default меняется на `wealth-ledger-sdd-v2`
|
||||
- **THEN** он сохраняет прежний artifact graph и approvals без создания фиктивных v2 artifacts
|
||||
|
||||
### Requirement: Изменение upstream artifact требует каскадной проверки
|
||||
|
||||
Изменение утверждённого Proposal MUST инициировать повторную проверку Delta Specs, Design и Tasks; изменение утверждённых Delta Specs MUST инициировать повторную проверку Design и Tasks; изменение утверждённого Design MUST инициировать повторную проверку Tasks. Изменение Tasks MUST NOT неявно менять scope, наблюдаемое поведение или архитектурные решения upstream artifacts; при таком обнаружении workflow SHALL вернуть работу к самому раннему затронутому upstream artifact и его approval gate. Если несоответствие обнаружено во время Design или Apply, downstream-работа MUST остановиться, а Change Owner MUST получить наблюдаемое основание возврата и решения, требуемые на выбранном уровне. Механическое исправление синтаксиса или формата, которое не меняет утверждённые намерение, поведение, архитектурные решения или состав задач, MUST пройти повторную structural validation и MUST NOT само по себе инвалидировать approvals. Исправление Markdown heading, опечатки без изменения нормативного смысла или выравнивания таблицы MAY считаться механическим; изменение формулировки Requirement, добавление или удаление Scenario, изменение выбранного архитектурного решения либо planned Verification задачи MUST NOT считаться механическим.
|
||||
Семантическое изменение утверждённого artifact MUST возвращаться к самой ранней затронутой planning boundary и инициировать повторную проверку и approval только её зависимых downstream artifacts согласно выбранной schema. Для legacy Proposal → Delta Specs → Design → Tasks; для v2 intent/scope → Proposal, observable behavior → Specs, systems/sequences/contracts/mappings/inter-system errors/semantic observability → System Analysis, internal implementation/storage/technical observability/developer tests → Design, system verification/test data/automation → QA Analysis, durable AS-IS/ADR → Docs Impact, decomposition/ownership/dependencies → Tasks. Изменение Tasks MUST NOT неявно менять upstream semantics. Если несоответствие обнаружено во время Design или Apply, downstream-работа MUST остановиться, а Change Owner MUST получить наблюдаемое основание возврата. Механическое исправление синтаксиса или формата без изменения смысла MUST пройти повторную structural validation и MUST NOT само по себе инвалидировать approvals. Исправление Markdown heading, опечатки без изменения нормативного смысла или выравнивания таблицы MAY считаться механическим; изменение Requirement/Scenario, contract, sequence, архитектурного решения, QA expected result, docs target либо planned Verification задачи MUST NOT считаться механическим.
|
||||
|
||||
#### Scenario: После Design изменилось намерение
|
||||
|
||||
- **WHEN** владелец меняет scope или ожидаемый эффект Proposal
|
||||
- **THEN** зависимые Delta Specs, Design и Tasks теряют прежнее подтверждение до повторной согласованной проверки
|
||||
- **THEN** зависимые artifacts выбранной schema теряют прежнее подтверждение до повторной согласованной проверки
|
||||
|
||||
#### Scenario: Task вводит новое поведение
|
||||
|
||||
@@ -62,7 +67,7 @@ Versioned SDD change SHALL проходить последовательност
|
||||
#### Scenario: Apply выявляет архитектурное расхождение
|
||||
|
||||
- **WHEN** во время реализации обнаружено, что утверждённый Design не позволяет выполнить Specs без другого архитектурного решения
|
||||
- **THEN** Apply останавливается, Tasks теряет прежнее подтверждение, а change возвращается к Design с наблюдаемым основанием и вариантами решения
|
||||
- **THEN** Apply останавливается, затронутые downstream approvals теряют силу, а change возвращается к Design с наблюдаемым основанием и вариантами решения
|
||||
|
||||
#### Scenario: Исправляется только структурный формат
|
||||
|
||||
@@ -107,33 +112,28 @@ Canonical store и policy расположения artifacts SHALL иметь do
|
||||
- **WHEN** project-owned OpenSpec entrypoint допускает выбор nearest-root или global-default
|
||||
- **THEN** verifier завершается ошибкой и указывает non-compliant entrypoint
|
||||
|
||||
### Requirement: OpenSpec tooling имеет проверяемый version contract
|
||||
### Requirement: OpenSpec tooling проверяется по совместимости
|
||||
|
||||
SDD workflow MUST объявлять одну текущую поддерживаемую версию OpenSpec CLI в canonical version contract и проверять фактическую версию до команд, читающих или изменяющих canonical change. Normative checks и проверяемые generated markers MUST получать ожидаемую версию из этого контракта и MUST NOT независимо закреплять конкретный version literal. Несовместимая либо неопределённая версия MUST давать actionable blocking result и MUST NOT приводить к молчаливому изменению artifact graph, templates или project-owned entrypoints. Публикация новой stable версии MUST NOT автоматически менять поддерживаемую версию проекта. Upgrade SHALL изменять version contract только после изолированной проверки совместимости schema, agent workflow, canonical store и hard blockers и SHALL сохранять историческое evidence с фактически использованными ранее версиями.
|
||||
SDD workflow MUST проверять доступный OpenSpec CLI на совместимость с выбранной schema и canonical store без закрепления одной версии CLI. Перед изменением workflow или обновлением CLI SHALL выполняться native validation обеих project-local schemas, затронутых changes и canonical specs; artifact graph, agent workflow, store selection и blocking gates MUST сохранять согласованную семантику. Несовместимый CLI MUST давать actionable blocking result и MUST NOT приводить к молчаливому изменению artifacts или project-owned entrypoints. Фактически использованная версия CLI SHALL сохраняться в observed verification evidence, а не в отдельном version contract.
|
||||
|
||||
#### Scenario: Версия CLI соответствует контракту
|
||||
#### Scenario: Доступный CLI совместим
|
||||
|
||||
- **WHEN** агент начинает работу с canonical change поддерживаемой версией OpenSpec
|
||||
- **THEN** workflow продолжает работу с объявленной схемой и canonical store
|
||||
- **WHEN** проверки выбранной schema и canonical store проходят на установленном OpenSpec CLI
|
||||
- **THEN** workflow продолжает работу без требования совпадения с закреплённым номером версии
|
||||
|
||||
#### Scenario: Версия CLI не соответствует контракту
|
||||
#### Scenario: CLI нарушает совместимость
|
||||
|
||||
- **WHEN** доступная версия OpenSpec не совпадает с объявленным совместимым контрактом
|
||||
- **THEN** локальная preflight-проверка блокирует SDD-операцию и сообщает ожидаемую и фактическую версии
|
||||
- **WHEN** после обновления CLI schema validation, разрешение store или blocking gates перестают работать
|
||||
- **THEN** зависимая SDD-операция останавливается с диагностикой нарушения до исправления несовместимости
|
||||
|
||||
#### Scenario: Опубликована новая stable версия
|
||||
#### Scenario: CLI обновлён
|
||||
|
||||
- **WHEN** upstream публикует новую stable версию, которая ещё не прошла project compatibility assessment
|
||||
- **THEN** текущий version contract остаётся действующим и новая версия не становится неявным требованием для разработки
|
||||
|
||||
#### Scenario: Подготовлен upgrade candidate
|
||||
|
||||
- **WHEN** Engineering Owner рассматривает смену поддерживаемой версии
|
||||
- **THEN** изолированная проверка подтверждает schema, agent workflow, store selection и hard blockers до изменения version contract
|
||||
- **WHEN** Engineering Owner обновляет OpenSpec CLI
|
||||
- **THEN** проверяются обе project-local schemas, затронутые changes, canonical specs и workflow, а фактическая версия и результаты сохраняются в verification evidence
|
||||
|
||||
### Requirement: Structural и semantic verification являются разными gates
|
||||
|
||||
OpenSpec structural validation SHALL проверять schema, artifact graph и формальную корректность specs, но MUST NOT считаться доказательством соответствия реализации. До terminal status change MUST пройти semantic verification реализации против Proposal, Delta Specs, Design и Tasks, а затем независимый read-only review. Если основной agent entrypoint semantic verification недоступен, workflow SHALL использовать versioned эквивалентную процедуру с теми же входными артефактами, категориями проверки и наблюдаемым evidence.
|
||||
OpenSpec structural validation SHALL проверять schema, artifact graph и формальную корректность specs, но MUST NOT считаться доказательством соответствия реализации или approval. До terminal status change MUST пройти semantic verification реализации против применимых approved artifacts выбранной schema, включая v2 System Analysis, QA Analysis и Docs Impact, а затем независимый read-only review. Если основной agent entrypoint semantic verification недоступен, workflow SHALL использовать versioned эквивалентную процедуру с теми же входными артефактами, категориями проверки и наблюдаемым evidence.
|
||||
|
||||
#### Scenario: Structural validation прошла без ошибок
|
||||
|
||||
@@ -146,7 +146,7 @@ OpenSpec structural validation SHALL проверять schema, artifact graph
|
||||
- **THEN** агент выполняет документированную эквивалентную read-only проверку и сохраняет её результат вместо пропуска gate
|
||||
|
||||
### Requirement: Archive блокируется незавершёнными tasks
|
||||
Project-owned archive workflow MUST отклонять archive, если отсутствует требуемый planning artifact, любой обязательный artifact не утверждён, `tasks.md` отсутствует или содержит unchecked task, semantic verification либо независимый read-only review не завершены успешно, или любая affected delivery area не реализована и не прошла обязательные pre-archive gates. Workflow MUST NOT предлагать confirmation override для этих blocking conditions. Post-archive bookkeeping MUST NOT появляться как unchecked task; он SHALL фиксироваться через детерминированное Completion Evidence после успешного archive, после чего связанная delivery может получить terminal status.
|
||||
Project-owned archive workflow MUST отклонять archive, если отсутствует требуемый выбранной schema planning artifact, любой обязательный artifact не утверждён, `tasks.md` отсутствует или содержит unchecked task, semantic verification либо независимый read-only review не завершены успешно, или любая affected delivery area не реализована и не прошла обязательные pre-archive gates. Для v2 также MUST быть подтверждены developer checks, независимая QA/system verification, semantic compatibility backend-generated OpenAPI/runtime interface с approved target contract при изменении API и применение Docs Impact так, чтобы canonical `docs/` отражали resulting AS-IS. Native OpenSpec sync/archive SHALL обновлять canonical behavior specs, не переписывая их implementation details; System Analysis и target contracts остаются в архивном change, а не становятся второй вечной копией current OpenAPI. Workflow MUST NOT предлагать confirmation override для этих blocking conditions. Post-archive bookkeeping MUST NOT появляться как unchecked task; он SHALL фиксироваться через детерминированное Completion Evidence после успешного archive, после чего связанная delivery может получить terminal status.
|
||||
|
||||
#### Scenario: Change имеет unchecked task
|
||||
- **WHEN** implementer запрашивает archive и остаётся хотя бы один unchecked task
|
||||
@@ -181,7 +181,7 @@ Project-owned archive workflow MUST отклонять archive, если отс
|
||||
|
||||
#### Scenario: Downstream artifacts ещё отсутствуют
|
||||
|
||||
- **WHEN** Proposal создан, а Delta Specs, Design и Tasks ещё не существуют
|
||||
- **WHEN** Proposal создан, а downstream artifacts выбранной schema ещё не существуют
|
||||
- **THEN** workflow сообщает результат доступных Proposal checks и не представляет отсутствующую full-change validation как успешную
|
||||
|
||||
### Requirement: Project schema сохраняет заметные самодостаточные правила
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# portfolio-value-history Specification
|
||||
|
||||
## Purpose
|
||||
Определяет сохранение точных оценок Portfolio по счёту и валюте для будущей истории без предоставления публичного чтения этих точек.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Полные оценки Portfolio сохраняются как история
|
||||
Система SHALL сохранять для каждого доступного Brokerage Account точку оценки по каждой исходной валюте только после успешного получения полного Portfolio с вычислимой стоимостью всех его позиций. Точка MUST содержать точное значение без валютной конвертации и фактическое время успешного получения Portfolio. Система MUST NOT подставлять ноль за отсутствующую стоимость и MUST NOT создавать точку для неполной оценки. Повторная публикация одной попытки MUST NOT создавать дубликат точки.
|
||||
|
||||
#### Scenario: Полная мультивалютная оценка
|
||||
- **WHEN** успешный полный Portfolio содержит вычислимые итоги RUB и USD
|
||||
- **THEN** система сохраняет две отдельные точки с исходными значениями и валютами
|
||||
|
||||
#### Scenario: Неполная оценка
|
||||
- **WHEN** в новом полном составе Portfolio у позиции отсутствует вычислимая стоимость
|
||||
- **THEN** система сохраняет новый Portfolio без точки истории оценки
|
||||
|
||||
### Requirement: История оценки скрыта вне подтверждённого доступа
|
||||
Система MUST сохранять историю оценки при замене token или удалении подключения, но MUST NOT раскрывать её без текущего подтверждённого доступа User к тому же Brokerage Account. Успешное новое подключение, подтверждающее доступ к счёту, MUST снова делать его сохранённую историю доступной для будущих поддерживаемых представлений. Система MUST NOT предоставлять отдельное публичное чтение точек истории этим требованием.
|
||||
|
||||
#### Scenario: Удалённое подключение
|
||||
- **WHEN** User удаляет T-Bank connection
|
||||
- **THEN** сохранённые точки оценки этого User не доступны для чтения
|
||||
|
||||
#### Scenario: Повторно подтверждённый счёт
|
||||
- **WHEN** новое успешное подключение подтверждает доступ User к тому же Brokerage Account
|
||||
- **THEN** ранее сохранённые точки этого счёта снова доступны для будущих поддерживаемых представлений
|
||||
@@ -7,13 +7,13 @@
|
||||
## Requirements
|
||||
|
||||
### Requirement: Получение Portfolio выбранного Brokerage Account
|
||||
Система SHALL предоставлять авторизованному User `GET /api/v1/brokerage-accounts/:accountId/portfolio`. Она MUST возвращать нормализованный полный Portfolio Snapshot только для открытого поддерживаемого Brokerage Account, доступного через текущий T-Bank connection User. Ответ MUST содержать `lastSuccessfulAt` в RFC 3339 UTC без миллисекунд, boolean `isStale`, `totalsByCurrency`, `positions` и `cashBalances`.
|
||||
Система SHALL предоставлять авторизованному User `GET /api/v1/brokerage-accounts/:accountId/portfolio`. Она MUST возвращать нормализованный полный Portfolio Snapshot только для открытого поддерживаемого Brokerage Account, доступного через текущий T-Bank connection User. Ответ MUST иметь envelope `{ data, meta }`: `data` MUST содержать сохранённый полный Portfolio Snapshot с `lastSuccessfulAt` в RFC 3339 UTC без миллисекунд, boolean `isStale`, `totalsByCurrency`, `positions` и `cashBalances`; `meta` MUST содержать наблюдаемое состояние resource по `tbank-data-sync`, включая freshness, полноту, время последнего успешного Snapshot, последнюю часовую попытку и доступность. Обычный GET MUST читать сохранённый Snapshot и его наблюдаемое состояние без ожидания T-Bank и MUST NOT запускать обновление личного Portfolio.
|
||||
|
||||
Portfolio Snapshot является результатом наблюдения Portfolio на конкретный момент. Он MUST NOT означать, что WealthLedger хранит Portfolio как долговременный агрегат или подтверждает доступность Brokerage Account вне текущего T-Bank connection.
|
||||
|
||||
#### Scenario: Получение текущего Portfolio
|
||||
- **WHEN** User запрашивает Portfolio доступного Brokerage Account, а обязательные upstream-данные успешно получены
|
||||
- **THEN** система отвечает `200` полным нормализованным Portfolio Snapshot с `isStale: false` и временем успешного получения финансового снимка
|
||||
- **WHEN** User запрашивает Portfolio доступного Brokerage Account, а полный Snapshot текущего подключения успешно сохранён
|
||||
- **THEN** система отвечает `200` с полным нормализованным Portfolio Snapshot в `data`, его `lastSuccessfulAt` и `isStale`, а также наблюдаемым состоянием в `meta`
|
||||
|
||||
#### Scenario: Недоступный Brokerage Account
|
||||
- **WHEN** `accountId` не принадлежит User, закрыт, неподдерживаем или больше не доступен через текущий T-Bank connection
|
||||
@@ -94,144 +94,72 @@ Portfolio Snapshot является результатом наблюдения
|
||||
- **THEN** система передаёт её как строку `"-0.5"`
|
||||
|
||||
### Requirement: Свежесть и fallback полного Portfolio Snapshot
|
||||
Система MUST хранить последний полный успешный Portfolio Snapshot в SQLite
|
||||
с принадлежностью User, экземпляру подключения и Brokerage Account. Обычный
|
||||
GET MUST читать сохранённые данные без ожидания T-Bank; missing/stale источники
|
||||
обновляются в фоне по пользовательскому запросу. Portfolio source свеж 60 секунд.
|
||||
Ошибка обновления MUST сохранять прежний полный Snapshot и его время без
|
||||
пятиминутного лимита fallback. Metadata MUST NOT определять успешность или
|
||||
свежесть финансового Snapshot; при её отсутствии используется доступный fallback.
|
||||
Система MUST хранить последний полный успешно полученный Portfolio Snapshot с принадлежностью User, экземпляру подключения и Brokerage Account. Обычный GET MUST возвращать `{ data, meta }`, где `data` является сохранённым Snapshot, а `meta` соответствует наблюдаемому состоянию resource по `tbank-data-sync`, читать его без ожидания T-Bank и MUST NOT запускать обновление личного Portfolio, даже если Snapshot отсутствует или старше 60 минут; обновлением владеет часовой обход. Portfolio source MUST считаться свежим в течение 60 минут после успешного получения. Ошибка обхода MUST сохранять прежний полный Snapshot и его время без лимита fallback. Если состав получен полностью, но отдельная позиция не имеет цены, система MUST сохранить новый состав с неполной оценкой; metadata MUST NOT определять успешность или свежесть финансового Snapshot. Сохранённая доступная identity позиции MAY оставаться доступной при недоступности online-справочника.
|
||||
|
||||
Система MUST делать прежние личные datasets недоступными после успешной замены
|
||||
или удаления подключения. Поздняя работа прежнего подключения MUST NOT
|
||||
публиковаться в новом. Общие metadata и подтверждённое отсутствие инструмента
|
||||
сохраняются по UID/FIGI на 24 часа; ошибка lookup не подтверждает отсутствие.
|
||||
Система MUST делать прежние личные datasets недоступными после успешной замены или удаления подключения. Поздняя работа прежнего подключения MUST NOT публиковаться в новом. Самостоятельные справочные сведения и результаты lookup MUST запрашиваться онлайн, а не сохраняться как долговременный справочник; ошибка lookup не подтверждает отсутствие инструмента.
|
||||
|
||||
#### Scenario: Свежий Snapshot
|
||||
- **WHEN** User повторно запрашивает тот же Portfolio в течение 60 секунд после успешного получения
|
||||
- **WHEN** User повторно запрашивает тот же Portfolio в течение 60 минут после успешного получения
|
||||
- **THEN** система возвращает свежий Snapshot без нового обращения к T-Bank
|
||||
|
||||
#### Scenario: Медленный lookup инструмента
|
||||
- **WHEN** metadata инструмента ещё отсутствует и его lookup не завершился
|
||||
- **THEN** система не задерживает локальный ответ ради lookup и показывает доступную identity; сохранённая успешная metadata используется последующим чтением
|
||||
- **WHEN** сведения об инструменте ещё запрашиваются онлайн
|
||||
- **THEN** система не задерживает локальный ответ Portfolio ради lookup и показывает сохранённую доступную identity
|
||||
|
||||
#### Scenario: Fallback после временной upstream-ошибки
|
||||
- **WHEN** обновление устаревшего Snapshot неуспешно из-за временной недоступности T-Bank, а в текущей границе подключения есть полный успешный Snapshot
|
||||
- **WHEN** часовой обход Portfolio неуспешен из-за временной недоступности T-Bank, а в текущей границе подключения есть полный успешный Snapshot
|
||||
- **THEN** система отвечает `200` этим Snapshot с `isStale: true` и исходным `lastSuccessfulAt`
|
||||
|
||||
#### Scenario: Недействительный token не маскируется fallback-данными
|
||||
- **WHEN** T-Bank отклоняет token как неаутентифицированный или без прав при обновлении Portfolio
|
||||
- **WHEN** T-Bank отклоняет token как неаутентифицированный или без прав при часовом обходе Portfolio
|
||||
- **THEN** система не возвращает fallback Snapshot и отвечает `400` Problem Details с code `INVALID_TBANK_TOKEN`
|
||||
|
||||
#### Scenario: Неполная оценка при новом составе
|
||||
- **WHEN** успешно получен полный состав позиций, но у одной позиции нет вычислимой цены
|
||||
- **THEN** новый состав становится текущим Portfolio Snapshot с неизвестной стоимостью позиции без подстановки нуля
|
||||
|
||||
### Requirement: Состояния и ошибки Portfolio на клиенте
|
||||
Клиент SHALL загружать Portfolio при открытии защищённого маршрута `/accounts/:accountId`. Он MUST использовать успешный ответ в течение 30 секунд без automatic retry или refetch при возврате фокуса. Открытый экран MUST перечитывать сохранённый результат backend каждые 30 минут без запуска синхронизации T-Bank. Клиент MUST показывать freshness, полноту, время последнего успешного Snapshot, последнюю часовую попытку и доступность из `meta` текстом или доступно именованным элементом, а не только цветом. Клиент MUST NOT предоставлять действие ручной синхронизации Portfolio. При отсутствии ранее успешного ответа он MUST показывать структурный skeleton с доступным обозначением занятости без вымышленных сумм; skeleton MUST NOT быть интерактивным или получать клавиатурный фокус.
|
||||
|
||||
Клиент SHALL загружать Portfolio при открытии защищённого маршрута
|
||||
`/accounts/:accountId`. Он MUST использовать успешный ответ в течение
|
||||
30 секунд, без automatic retry или refetch при возврате фокуса.
|
||||
Polling допускается только для известной работы открытого экрана;
|
||||
data GET наблюдения использует `sync=observe` и MUST NOT запускать новый импорт.
|
||||
Status сообщает state/errorCode без hash notifications и численных progress counters.
|
||||
После ухода или завершения работы polling MUST прекращаться.
|
||||
Истечение срока свежести MUST NOT скрывать Client Cached Snapshot. Во время фонового перечитывания и после его ошибки клиент MUST сохранять ранее полученные данные того же Portfolio, исходное `lastSuccessfulAt` и показывать неблокирующую индикацию либо предупреждение с действием повтора только HTTP-чтения. Сохранённые данные MUST NOT считаться новым успешным снимком или подтверждением текущей доступности Brokerage Account. При `isStale: true` клиент MUST показывать Snapshot, исходное время и нейтральное предупреждение без действия ручного обновления. Server-side stale и ошибка Client Cached Snapshot MUST оставаться раздельными состояниями.
|
||||
|
||||
Предзагрузка всей истории свечей бумаг портфеля MUST NOT выполняться.
|
||||
Новый диапазон свечей загружается при его открытии; initial loading допустим.
|
||||
При загрузке без ранее полученного успешного ответа для этого же 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-состояние только при пустых `positions`
|
||||
и `cashBalances`, без нулевых валютных карточек. Для показываемого состава
|
||||
Portfolio клиент MUST сохранять время успешного получения снимка
|
||||
`lastSuccessfulAt`, форматируемое в UTC. При `isStale: true` клиент MUST
|
||||
показывать Snapshot, его исходное время и нейтральное предупреждение
|
||||
о временной недоступности T-Bank. Это предупреждение само по себе MUST NOT
|
||||
добавлять действие ручного обновления. Server-side stale и ошибка фонового
|
||||
обновления Client Cached Snapshot MUST оставаться раздельными состояниями:
|
||||
ошибка клиента MUST NOT менять или подменять `isStale`, а оба предупреждения
|
||||
и действие повтора клиентского обновления MUST оставаться доступными при
|
||||
одновременном наличии этих состояний.
|
||||
|
||||
При отсутствии ранее успешно полученного Client Cached Snapshot этого же
|
||||
Portfolio и ответе `404` клиент MUST показать состояние недоступного счёта
|
||||
с действием возврата к списку счетов без действия повтора. При отсутствии
|
||||
Snapshot и `TBANK_NOT_CONNECTED`, `INVALID_TBANK_TOKEN`, `TBANK_UNAVAILABLE`
|
||||
или иной ошибке клиент MUST показать безопасное error-состояние с доступными
|
||||
для ошибки действиями; действие повтора разрешено только для временной
|
||||
и неизвестной ошибки.
|
||||
При отсутствии Client Cached Snapshot и ответе `404` клиент MUST показать недоступный счёт с возвратом к списку без действия повтора. При отсутствии Snapshot и `TBANK_NOT_CONNECTED`, `INVALID_TBANK_TOKEN`, `TBANK_UNAVAILABLE` либо иной ошибке клиент MUST показать безопасное error-состояние; повтор HTTP-чтения разрешён только для временной и неизвестной ошибки.
|
||||
|
||||
#### Scenario: Показ устаревшего Portfolio
|
||||
|
||||
- **WHEN** клиент получает успешный ответ с `isStale: true`
|
||||
- **THEN** клиент показывает позиции, остатки, валютные итоги, время
|
||||
последнего успешного снимка и нейтральное предупреждение; само это
|
||||
предупреждение не добавляет действие ручного обновления
|
||||
|
||||
#### Scenario: Недоступный счёт на клиенте
|
||||
|
||||
- **WHEN** для текущего Portfolio отсутствует ранее успешно полученный
|
||||
Client Cached Snapshot и клиент получает `404`
|
||||
- **THEN** клиент не показывает Portfolio и предлагает вернуться к списку
|
||||
Brokerage Account без действия повтора
|
||||
- **THEN** клиент показывает Portfolio, исходное время и нейтральное предупреждение без ручной синхронизации
|
||||
|
||||
#### Scenario: Первичная загрузка Portfolio
|
||||
- **WHEN** клиент ожидает Portfolio без ранее полученного успешного ответа
|
||||
- **THEN** он показывает структурный skeleton без вымышленных или нулевых сумм и без фокусируемых placeholders
|
||||
|
||||
- **WHEN** клиент ожидает Portfolio этого Brokerage Account без ранее
|
||||
полученного успешного ответа
|
||||
- **THEN** он показывает структурный skeleton, включая место сводки,
|
||||
с доступным обозначением загрузки, без вымышленных или нулевых сумм
|
||||
и без фокусируемых placeholders
|
||||
#### Scenario: Неуспешная последняя попытка сохраняет Portfolio
|
||||
- **WHEN** клиент получает полный Portfolio и `meta` с неуспехом последней часовой попытки
|
||||
- **THEN** он сохраняет Portfolio и исходное время видимыми и отдельно показывает текстовое либо доступно именованное предупреждение о неуспехе попытки
|
||||
|
||||
#### Scenario: Недоступный счёт на клиенте
|
||||
- **WHEN** для текущего Portfolio отсутствует Client Cached Snapshot и клиент получает `404`
|
||||
- **THEN** клиент не показывает Portfolio и предлагает вернуться к списку Brokerage Account без действия повтора
|
||||
|
||||
#### Scenario: Обновление после истечения срока свежести
|
||||
|
||||
- **WHEN** срок свежести показанного Portfolio истёк и началось фоновое
|
||||
обновление данных того же Brokerage Account
|
||||
- **THEN** клиент сохраняет прежние валютные итоги и время их успешного
|
||||
получения, показывает неблокирующую индикацию обновления и не заменяет
|
||||
доступный состав и сводку skeleton-состоянием
|
||||
- **WHEN** срок свежести показанного Portfolio истёк и началось фоновое HTTP-перечитывание
|
||||
- **THEN** клиент сохраняет прежние валютные итоги и время, показывает неблокирующую индикацию и не заменяет данные skeleton-состоянием
|
||||
|
||||
#### Scenario: Ошибка обновления после успешного ответа
|
||||
|
||||
- **WHEN** клиент уже показывает Client Cached Snapshot Portfolio,
|
||||
а обновление этих же данных завершается ошибкой
|
||||
- **THEN** клиент сохраняет данные, суммы и время прежнего снимка,
|
||||
показывает неблокирующее предупреждение и действие повтора;
|
||||
ошибка не делает сохранённый снимок свежим и не изменяет `isStale`
|
||||
- **WHEN** Client Cached Snapshot Portfolio уже видим, а повторное HTTP-чтение завершается ошибкой
|
||||
- **THEN** клиент сохраняет прежние данные и время, показывает неблокирующее предупреждение и действие повтора чтения
|
||||
|
||||
#### Scenario: Ошибка клиентского обновления при server-side stale
|
||||
|
||||
- **WHEN** клиент показывает Portfolio с `isStale: true`, а фоновое
|
||||
обновление этого же Portfolio завершается ошибкой
|
||||
- **THEN** прежние суммы и время остаются видимыми одновременно
|
||||
с нейтральным server-side предупреждением, отдельным предупреждением
|
||||
об ошибке клиентского обновления и его действием повтора
|
||||
- **WHEN** клиент показывает Portfolio с `isStale: true`, а фоновое перечитывание завершается ошибкой
|
||||
- **THEN** оба предупреждения остаются различимыми, а действие повтора относится только к HTTP-чтению
|
||||
|
||||
#### Scenario: Повторное обновление успешно
|
||||
|
||||
- **WHEN** после ошибки фонового обновления User запускает повторную
|
||||
попытку и получает успешный Portfolio Snapshot
|
||||
- **THEN** до ответа прежние суммы и время остаются видимыми, затем
|
||||
клиент показывает значения и `lastSuccessfulAt` успешного ответа,
|
||||
убирает предупреждение об ошибке клиентского обновления и показывает
|
||||
server-side предупреждение только в соответствии с новым `isStale`
|
||||
- **WHEN** после ошибки HTTP-чтения User запускает повторное чтение и получает успешный Portfolio Snapshot
|
||||
- **THEN** клиент показывает значения и `lastSuccessfulAt` успешного ответа, убирает предупреждение об ошибке чтения и отображает server-side предупреждение только по новому `isStale`
|
||||
|
||||
#### Scenario: Штатный пустой Portfolio на клиенте
|
||||
|
||||
- **WHEN** успешно полученный Portfolio содержит пустые `positions`
|
||||
и `cashBalances`
|
||||
- **THEN** клиент показывает существующее empty-состояние без валютных
|
||||
карточек с вымышленными нулевыми итогами
|
||||
- **WHEN** успешно полученный Portfolio содержит пустые `positions` и `cashBalances`
|
||||
- **THEN** клиент показывает empty-состояние без валютных карточек с вымышленными нулевыми итогами
|
||||
|
||||
### Requirement: Представление карточек позиций на клиенте
|
||||
Клиент MUST отображать `STOCK` и `BOND` отдельными секциями. Внутри каждой секции MUST сохраняться upstream-порядок позиций. Позиции иных типов MUST оставаться доступными в отдельной общей секции.
|
||||
|
||||
@@ -18,15 +18,15 @@
|
||||
- **THEN** система отвечает `200` с `{ connected: true, connectedAt: <RFC 3339 UTC timestamp> }` без token
|
||||
|
||||
### Requirement: Создание и замена подключения T-Bank
|
||||
Система SHALL позволять авторизованному User создать или заменить единственное подключение через `PUT /api/v1/tbank-connection` с token. Система MUST удалить внешние пробелы token, отклонить пустое значение как ошибку валидации и проверить token запросом T-Bank открытых счетов до сохранения. Пустой список счетов MUST считаться успешной проверкой. Успех MUST вернуть `200` с текущим статусом подключения; успешная замена MUST установить новый `connectedAt`.
|
||||
Система SHALL позволять авторизованному User создать или заменить единственное подключение через `PUT /api/v1/tbank-connection` с token. Система MUST удалить внешние пробелы token, отклонить пустое значение как ошибку валидации и проверить token запросом T-Bank открытых счетов до сохранения. Пустой список счетов MUST считаться успешной проверкой. Успех MUST вернуть `200` с текущим статусом подключения; успешная замена MUST установить новый `connectedAt`. После успешного подключения MUST начинаться первый часовой обход без команды frontend. История стоимости прежних счетов MUST сохраняться, но MUST быть доступна только для счетов, доступ к которым подтверждён новым подключением.
|
||||
|
||||
#### Scenario: Успешное создание подключения без счетов
|
||||
- **WHEN** User отправляет непустой token и T-Bank успешно возвращает пустой список открытых счетов
|
||||
- **THEN** система сохраняет подключение и отвечает `200` с `{ connected: true, connectedAt: <RFC 3339 UTC timestamp> }`
|
||||
- **THEN** система сохраняет подключение, отвечает `200` с `{ connected: true, connectedAt: <RFC 3339 UTC timestamp> }` и не создаёт фиктивные счета
|
||||
|
||||
#### Scenario: Успешная замена подключения
|
||||
- **WHEN** User с подключением отправляет другой token и T-Bank успешно его проверяет
|
||||
- **THEN** система заменяет token подключения, обновляет `connectedAt` и отвечает текущим статусом без token
|
||||
- **THEN** система заменяет token, обновляет `connectedAt`, отвечает текущим статусом без token и начинает первый часовой обход нового подключения
|
||||
|
||||
#### Scenario: Невалидный token не заменяет существующее подключение
|
||||
- **WHEN** T-Bank отклоняет token как неаутентифицированный или не имеющий прав
|
||||
@@ -37,15 +37,15 @@
|
||||
- **THEN** система отвечает `503` Problem Details с code `TBANK_UNAVAILABLE` и не создаёт либо не изменяет подключение
|
||||
|
||||
### Requirement: Удаление подключения T-Bank
|
||||
Система SHALL позволять авторизованному User удалить своё подключение через `DELETE /api/v1/tbank-connection`. Удаление MUST быть идемпотентным и отвечать `204`; после успешного удаления backend MUST прекратить использовать удалённый token и удалить относящиеся к User кэшированные данные T-Bank.
|
||||
Система SHALL позволять авторизованному User удалить своё подключение через `DELETE /api/v1/tbank-connection`. Удаление MUST быть идемпотентным и отвечать `204`; после успешного удаления backend MUST прекратить использовать удалённый token и сделать относящиеся к User текущие личные данные недоступными. История стоимости счёта MUST сохраняться, но MUST NOT раскрываться до подтверждения нового доступа к тому же счёту.
|
||||
|
||||
#### Scenario: Удаление существующего подключения
|
||||
- **WHEN** User удаляет существующее подключение
|
||||
- **THEN** система удаляет token и связанные кэшированные данные User, затем отвечает `204`
|
||||
- **THEN** система удаляет token, скрывает текущие личные данные и историю стоимости и отвечает `204`
|
||||
|
||||
#### Scenario: Повторное удаление подключения
|
||||
- **WHEN** User удаляет уже отсутствующее подключение
|
||||
- **THEN** система отвечает `204`
|
||||
- **THEN** система отвечает `204`, не раскрывая сохранённую историю
|
||||
|
||||
### Requirement: Конфиденциальность token T-Bank
|
||||
Система MUST хранить не более одного token T-Bank для каждого User только в зашифрованном виде. Token MUST расшифровываться только для серверного вызова T-Bank и MUST NOT передаваться в frontend, HTTP-ответах, OpenAPI examples, сообщениях ошибок или application logs.
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# tbank-data-sync Specification
|
||||
|
||||
## Purpose
|
||||
Определяет единственного владельца обновления личных данных T-Bank и узкие
|
||||
исключения первой загрузки данных, которых ещё нет в локальном результате.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Часовой обход владеет обновлением личных данных T-Bank
|
||||
Система MUST обновлять личные инвестиционные данные T-Bank только часовым обходом для текущего подключения User. Обычные resource reads, действия повтора и открытие экранов MUST NOT запускать импорт, ручную личную синхронизацию или обращение к T-Bank. Единственными исключениями являются первая загрузка отсутствующего интервала Account Ledger и впервые открытого диапазона свечей: они MAY начать получение только этого отсутствующего интервала либо диапазона, когда для него нет подтверждённого локального результата. Эти исключения MUST NOT обновлять Accounts, Portfolio, иные интервалы операций, прогноз будущих событий или другой диапазон свечей. После подтверждения полноты запрошенного прошлого интервала или диапазона его повторное чтение MUST оставаться локальным.
|
||||
|
||||
#### Scenario: Обычное чтение не запускает импорт
|
||||
- **WHEN** User открывает Accounts, Portfolio, Account Ledger, будущие события либо повторяет неуспешное HTTP-чтение
|
||||
- **THEN** система читает локальный результат и не начинает личную синхронизацию T-Bank
|
||||
|
||||
#### Scenario: Первый отсутствующий интервал операций
|
||||
- **WHEN** User впервые открывает интервал Account Ledger без подтверждённого локального результата
|
||||
- **THEN** система MAY начинает получение только этого интервала и возвращает его локальное состояние неполноты до подтверждения результата
|
||||
|
||||
#### Scenario: Впервые открытый диапазон свечей
|
||||
- **WHEN** User впервые открывает диапазон свечей, для которого нет подтверждённого локального результата
|
||||
- **THEN** система MAY начинает получение только этого диапазона и не загружает другие диапазоны или полную историю свечей
|
||||
|
||||
### Requirement: Результат личного обновления остаётся наблюдаемым
|
||||
Каждый response resource read личных данных текущего подключения MUST возвращать `{ data, meta }`. `meta` MUST отдельно делать наблюдаемыми freshness и полноту именно `data`, момент последнего успешного полного результата resource, момент и состояние последней часовой попытки, затронувшей resource, и доступность resource. Состояние последней попытки MUST различать отсутствие попытки, выполнение, полный успех, частичный успех и неуспех. Доступность MUST различать доступный полный результат, первую загрузку без полного результата, временную недоступность без полного результата и недоступность из-за отсутствующего либо недействительного подключения. Неполный, продолжающийся или неуспешный результат MUST NOT обозначаться полным или свежим. Успешный пустой Accounts либо иной пустой полный результат MUST обозначаться доступным и полным, а не отсутствующим. При неуспешном обновлении последний подтверждённый полный результат и его время MUST оставаться доступными, когда отдельное требование capability не предписывает недоступность данных; его freshness MUST определяться исходным временем, а `meta` MUST отдельно обозначать неуспех последней попытки. Наблюдение этих состояний MUST NOT предоставлять способ начать синхронизацию.
|
||||
|
||||
#### Scenario: Ошибка после полного результата
|
||||
- **WHEN** очередное обновление набора личных данных завершается ошибкой после ранее подтверждённого полного результата
|
||||
- **THEN** система возвращает предыдущий полный `data`, его исходное время и freshness, а `meta` отдельно обозначает неуспех последней попытки
|
||||
|
||||
#### Scenario: Первая загрузка ещё не завершена
|
||||
- **WHEN** для набора личных данных ещё нет подтверждённого полного результата
|
||||
- **THEN** система возвращает `data: null` и в `meta` обозначает выполнение первой загрузки либо её неуспех без частичного результата, выданного за полный
|
||||
|
||||
#### Scenario: Успешный пустой список счетов
|
||||
- **WHEN** последний полный результат Accounts подтверждает отсутствие открытых поддерживаемых счетов
|
||||
- **THEN** система возвращает `data: []`, а `meta` обозначает доступный полный результат, его время и состояние последней часовой попытки
|
||||
Reference in New Issue
Block a user