spec: simplify runtime requirements
Specs security / security (push) Failing after 8s
Specs verify / verify (push) Successful in 10s
Conventional commits / commits (pull_request) Successful in 12s
Specs security / security (pull_request) Failing after 8s
Specs verify / verify (pull_request) Successful in 10s

Preserve identity isolation and financial integrity while accepting
cross-tab reauthentication and on-demand candle history.

Align portfolio freshness with persisted reads and keep income
consistency at the backend owner.
This commit is contained in:
2026-10-03 11:38:08 +03:00
parent 4d580f145a
commit d861de5ead
3 changed files with 35 additions and 9 deletions
@@ -156,7 +156,7 @@
### Requirement: Income payments remain traceable to exact Ledger Entries ### Requirement: Income payments remain traceable to exact Ledger Entries
В разделе «Выплаты» страница MUST показывать единый список карточек точных исполненных Ledger Entries применённого периода в исходном серверном порядке entries. Каждая выплата MUST иметь собственную сразу видимую карточку с описанием, датой, доступной identity Instrument и signed Money в исходной валюте. Страница MUST NOT добавлять внешние группировочные карточки или заголовки по Instrument либо валюте, subtotals инструмента или количество выплат инструмента. Список MUST сохранять отрицательные корректировки и повторяющиеся operation IDs как отдельные записи и MUST NOT включать записи вне аналитического итога. Отказ от группировочного UI MUST NOT отменять проверки принадлежности entries к instrumentKey, валюты и receipt count ответа или приводить к пересчёту денежных итогов на клиенте. В разделе «Выплаты» страница MUST показывать единый список карточек точных исполненных Ledger Entries применённого периода в исходном серверном порядке entries. Каждая выплата MUST иметь собственную сразу видимую карточку с описанием, датой, доступной identity Instrument и signed Money в исходной валюте. Страница MUST NOT добавлять внешние группировочные карточки или заголовки по Instrument либо валюте, subtotals инструмента или количество выплат инструмента. Список MUST сохранять отрицательные корректировки и повторяющиеся operation IDs как отдельные записи и MUST NOT включать записи вне аналитического итога. Backend MUST обеспечивать согласованность entries, instrumentKey, валюты и receipt count при формировании ответа из одного набора записей. Frontend SHALL доверять типизированному contract без второго runtime consistency validator и MUST NOT пересчитывать денежные итоги.
#### Scenario: Каждая выплата показана отдельной карточкой #### Scenario: Каждая выплата показана отдельной карточкой
+22 -6
View File
@@ -94,20 +94,29 @@ Portfolio Snapshot является результатом наблюдения
- **THEN** система передаёт её как строку `"-0.5"` - **THEN** система передаёт её как строку `"-0.5"`
### Requirement: Свежесть и fallback полного Portfolio Snapshot ### Requirement: Свежесть и fallback полного Portfolio Snapshot
Система MUST кэшировать полный успешный Portfolio Snapshot отдельно для каждой пары User и Brokerage Account в памяти на 30 секунд. После истечения свежего кэша система MUST получить новый полный снимок; при временной upstream-недоступности она SHALL вернуть последний успешный полный снимок, если ему не более пяти минут, с `isStale: true`. Metadata инструмента MUST NOT влиять на успешность или свежесть Portfolio Snapshot, но MUST быть получено до формирования нового Snapshot, чтобы при доступном upstream-ответе вернуть отображаемое имя инструмента. Система 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 после успешной замены или удаления его T-Bank connection. Успешные metadata инструмента и подтверждённое отсутствие инструмента MUST кэшироваться глобально по FIGI на 24 часа; ошибка metadata lookup MUST NOT кэшироваться. Система MUST делать прежние личные datasets недоступными после успешной замены
или удаления подключения. Поздняя работа прежнего подключения MUST NOT
публиковаться в новом. Общие metadata и подтверждённое отсутствие инструмента
сохраняются по UID/FIGI на 24 часа; ошибка lookup не подтверждает отсутствие.
#### Scenario: Свежий Snapshot #### Scenario: Свежий Snapshot
- **WHEN** User повторно запрашивает тот же Portfolio в течение 30 секунд после успешного получения - **WHEN** User повторно запрашивает тот же Portfolio в течение 60 секунд после успешного получения
- **THEN** система возвращает свежий Snapshot без нового обращения к T-Bank - **THEN** система возвращает свежий Snapshot без нового обращения к T-Bank
#### Scenario: Медленный lookup инструмента #### Scenario: Медленный lookup инструмента
- **WHEN** metadata инструмента ещё отсутствует и его lookup не завершился - **WHEN** metadata инструмента ещё отсутствует и его lookup не завершился
- **THEN** система ожидает lookup в пределах gRPC deadline и возвращает name при успешном ответе upstream - **THEN** система не задерживает локальный ответ ради lookup и показывает доступную identity; сохранённая успешная metadata используется последующим чтением
#### Scenario: Fallback после временной upstream-ошибки #### Scenario: Fallback после временной upstream-ошибки
- **WHEN** обновление устаревшего Snapshot неуспешно из-за временной недоступности T-Bank, а последний полный успешный Snapshot получен не более пяти минут назад - **WHEN** обновление устаревшего Snapshot неуспешно из-за временной недоступности T-Bank, а в текущей границе подключения есть полный успешный Snapshot
- **THEN** система отвечает `200` этим Snapshot с `isStale: true` и исходным `lastSuccessfulAt` - **THEN** система отвечает `200` этим Snapshot с `isStale: true` и исходным `lastSuccessfulAt`
#### Scenario: Недействительный token не маскируется fallback-данными #### Scenario: Недействительный token не маскируется fallback-данными
@@ -118,7 +127,14 @@ Portfolio Snapshot является результатом наблюдения
Клиент SHALL загружать Portfolio при открытии защищённого маршрута Клиент SHALL загружать Portfolio при открытии защищённого маршрута
`/accounts/:accountId`. Он MUST использовать успешный ответ в течение `/accounts/:accountId`. Он MUST использовать успешный ответ в течение
30 секунд, без automatic retry, polling или refetch при возврате фокуса. 30 секунд, без automatic retry или refetch при возврате фокуса.
Polling допускается только для известной работы открытого экрана;
data GET наблюдения использует `sync=observe` и MUST NOT запускать новый импорт.
Status сообщает state/errorCode без hash notifications и численных progress counters.
После ухода или завершения работы polling MUST прекращаться.
Предзагрузка всей истории свечей бумаг портфеля MUST NOT выполняться.
Новый диапазон свечей загружается при его открытии; initial loading допустим.
При загрузке без ранее полученного успешного ответа для этого же Brokerage При загрузке без ранее полученного успешного ответа для этого же Brokerage
Account клиент MUST показывать структурное skeleton-состояние ожидаемого Account клиент MUST показывать структурное skeleton-состояние ожидаемого
содержимого, включая сводку, без вымышленных сумм. Область загрузки MUST содержимого, включая сводку, без вымышленных сумм. Область загрузки MUST
+12 -2
View File
@@ -53,11 +53,21 @@
#### Scenario: Истёкшая сессия #### Scenario: Истёкшая сессия
- **WHEN** клиент отправляет refresh-cookie Session, не обновлявшейся более 7 дней - **WHEN** клиент отправляет refresh-cookie Session, не обновлявшейся более 7 дней
- **THEN** система удаляет refresh-cookie и отвечает `401` Problem Details с code `UNAUTHORIZED` - **THEN** система отвечает `401` Problem Details с code `UNAUTHORIZED`, не изменяя cookie неуспешным refresh
#### Scenario: Повторное использование refresh token #### Scenario: Повторное использование refresh token
- **WHEN** клиент отправляет refresh token, заменённый предыдущим успешным refresh - **WHEN** клиент отправляет refresh token, заменённый предыдущим успешным refresh
- **THEN** система удаляет refresh-cookie и отвечает `401` Problem Details с code `UNAUTHORIZED`, не отзывая связанную Session только из-за этого запроса - **THEN** система отвечает `401` Problem Details с code `UNAUTHORIZED`, не изменяет cookie и не отзывает связанную Session только из-за этого запроса
### Requirement: Межвкладочная координация не требуется
Frontend MUST сохранять изоляцию данных текущей identity внутри вкладки.
Он MUST NOT требовать cross-tab synchronization для auth flow. После смены
auth-состояния в другой вкладке допускаются reload или повторная авторизация.
Неуспешный refresh MUST NOT удалять cookie успешного конкурентного запроса.
#### Scenario: Конкурентный refresh проигрывает в другой вкладке
- **WHEN** вкладка получает `401` после ротации общей cookie другой вкладкой
- **THEN** она может перейти на login; cookie победителя остаётся доступной для последующего восстановления
### Requirement: Завершение сессии ### Requirement: Завершение сессии
Система SHALL позволять завершить текущую Session через logout. Logout MUST отозвать Session, если refresh-cookie соответствует активной Session, и MUST удалить refresh-cookie. Logout MUST быть идемпотентным и не раскрывать существование Session. Система SHALL позволять завершить текущую Session через logout. Logout MUST отозвать Session, если refresh-cookie соответствует активной Session, и MUST удалить refresh-cookie. Logout MUST быть идемпотентным и не раскрывать существование Session.