diff --git a/openspec/specs/brokerage-account-income-analytics/spec.md b/openspec/specs/brokerage-account-income-analytics/spec.md index 075c588..40117b0 100644 --- a/openspec/specs/brokerage-account-income-analytics/spec.md +++ b/openspec/specs/brokerage-account-income-analytics/spec.md @@ -156,7 +156,7 @@ ### 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: Каждая выплата показана отдельной карточкой diff --git a/openspec/specs/portfolio-view/spec.md b/openspec/specs/portfolio-view/spec.md index a234991..92646ae 100644 --- a/openspec/specs/portfolio-view/spec.md +++ b/openspec/specs/portfolio-view/spec.md @@ -94,20 +94,29 @@ Portfolio Snapshot является результатом наблюдения - **THEN** система передаёт её как строку `"-0.5"` ### 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 -- **WHEN** User повторно запрашивает тот же Portfolio в течение 30 секунд после успешного получения +- **WHEN** User повторно запрашивает тот же Portfolio в течение 60 секунд после успешного получения - **THEN** система возвращает свежий Snapshot без нового обращения к T-Bank #### Scenario: Медленный lookup инструмента - **WHEN** metadata инструмента ещё отсутствует и его lookup не завершился -- **THEN** система ожидает lookup в пределах gRPC deadline и возвращает name при успешном ответе upstream +- **THEN** система не задерживает локальный ответ ради lookup и показывает доступную identity; сохранённая успешная metadata используется последующим чтением #### Scenario: Fallback после временной upstream-ошибки -- **WHEN** обновление устаревшего Snapshot неуспешно из-за временной недоступности T-Bank, а последний полный успешный Snapshot получен не более пяти минут назад +- **WHEN** обновление устаревшего Snapshot неуспешно из-за временной недоступности T-Bank, а в текущей границе подключения есть полный успешный Snapshot - **THEN** система отвечает `200` этим Snapshot с `isStale: true` и исходным `lastSuccessfulAt` #### Scenario: Недействительный token не маскируется fallback-данными @@ -118,7 +127,14 @@ Portfolio Snapshot является результатом наблюдения Клиент SHALL загружать Portfolio при открытии защищённого маршрута `/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 Account клиент MUST показывать структурное skeleton-состояние ожидаемого содержимого, включая сводку, без вымышленных сумм. Область загрузки MUST diff --git a/openspec/specs/user-authentication/spec.md b/openspec/specs/user-authentication/spec.md index 7f77af7..577ff4f 100644 --- a/openspec/specs/user-authentication/spec.md +++ b/openspec/specs/user-authentication/spec.md @@ -53,11 +53,21 @@ #### Scenario: Истёкшая сессия - **WHEN** клиент отправляет refresh-cookie Session, не обновлявшейся более 7 дней -- **THEN** система удаляет refresh-cookie и отвечает `401` Problem Details с code `UNAUTHORIZED` +- **THEN** система отвечает `401` Problem Details с code `UNAUTHORIZED`, не изменяя cookie неуспешным refresh #### Scenario: Повторное использование refresh token - **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: Завершение сессии Система SHALL позволять завершить текущую Session через logout. Logout MUST отозвать Session, если refresh-cookie соответствует активной Session, и MUST удалить refresh-cookie. Logout MUST быть идемпотентным и не раскрывать существование Session.