From 9a8687feda24edcc6ee6412313a844afce50f5eb Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Wed, 26 Aug 2026 15:03:48 +0300 Subject: [PATCH] docs(specs): archive improved ledger event rows --- .../.openspec.yaml | 8 + .../design.md | 151 ++++++++++++++++++ .../proposal.md | 46 ++++++ .../specs/brokerage-account-ledger/spec.md | 54 +++++++ .../tasks.md | 43 +++++ .../specs/brokerage-account-ledger/spec.md | 26 ++- 6 files changed, 325 insertions(+), 3 deletions(-) create mode 100644 openspec/changes/archive/2026-08-26-improve-ledger-event-rows/.openspec.yaml create mode 100644 openspec/changes/archive/2026-08-26-improve-ledger-event-rows/design.md create mode 100644 openspec/changes/archive/2026-08-26-improve-ledger-event-rows/proposal.md create mode 100644 openspec/changes/archive/2026-08-26-improve-ledger-event-rows/specs/brokerage-account-ledger/spec.md create mode 100644 openspec/changes/archive/2026-08-26-improve-ledger-event-rows/tasks.md diff --git a/openspec/changes/archive/2026-08-26-improve-ledger-event-rows/.openspec.yaml b/openspec/changes/archive/2026-08-26-improve-ledger-event-rows/.openspec.yaml new file mode 100644 index 0000000..97ae6af --- /dev/null +++ b/openspec/changes/archive/2026-08-26-improve-ledger-event-rows/.openspec.yaml @@ -0,0 +1,8 @@ +schema: wealth-ledger-sdd +created: 2026-08-26 +goal: Сделать записи журнала операций и последние записи на обзоре счёта визуально понятнее. +affected_areas: + - frontend + - backend + - specs + - docs diff --git a/openspec/changes/archive/2026-08-26-improve-ledger-event-rows/design.md b/openspec/changes/archive/2026-08-26-improve-ledger-event-rows/design.md new file mode 100644 index 0000000..933ad5e --- /dev/null +++ b/openspec/changes/archive/2026-08-26-improve-ledger-event-rows/design.md @@ -0,0 +1,151 @@ +## Context + +Account Ledger already preserves the signed upstream payment and the operation +identifiers, but the operations response has no instrument brand metadata. The +portfolio resolves the same metadata from the existing server-side T-Bank +boundary and uses the brand file name to render an image with an initial-based +fallback. The account overview and the full ledger currently render separate, +text-heavy operation layouts; the full ledger also keeps page data and JSX in +one component instead of the finance view-model boundary. + +The public OpenAPI contract is the frontend authority. T-Bank rate limiting, +authentication and upstream failure handling remain owned by the backend. + +## Goals / Non-Goals + +**Goals:** + +- Supply nullable instrument brand metadata with each Ledger Entry without + altering its monetary or business meaning. +- Reuse one accessible, responsive presentation of a Ledger Entry in the + account overview and full ledger. +- Present the already-signed payment with a textual directional cue, a sign and + semantic positive/negative/neutral styling. +- Preserve local error, loading, empty, filter and cursor states. + +**Non-Goals:** + +- No new ledger categories, financial calculations, historical balance or + currency conversion. +- No changes to the T-Bank integration boundary, access rules, pagination or + query semantics. +- No logo enrichment for Future Cash Events or Portfolio Positions outside the + existing supplied metadata. + +## Decisions + +### Enrich Ledger Entries through a shared instrument-metadata resolver + +The backend will extract the existing Portfolio instrument lookup and its +short-lived metadata caching into a reusable resolver. After obtaining a ledger +page, the service will resolve unique operation instrument identities through +that resolver and attach nullable `logoName` metadata to its entries. Lookup by +UID remains preferred; FIGI is the fallback. An unavailable or unresolvable +instrument remains a successful Ledger Entry with `logoName: null`; enrichment +must not hide a ledger page or change its money, category or identifiers. + +This preserves the existing server-side T-Bank boundary and its rate limiting, +deduplicates repeated instruments in one page and avoids a separate visual-data +API. The shared resolver replaces Portfolio's private copy so the two consumers +have identical lookup and failure semantics. + +### Make the additive contract explicit and nullable + +`BrokerageOperationDto` gains the nullable logo file name as presentation +metadata. The generated backend OpenAPI and committed frontend snapshot remain +the only contract path. Existing consumers can ignore the additive field; +updated consumers must treat absence as normal and use the text fallback. + +### Use a shared Ledger Entry presentation component + +The frontend will expose a focused entity-level presentation component for a +Ledger Entry and use it both in the account overview and the full journal. It +will show the logo image when available, otherwise a deterministic initial +fallback; the visible operation and instrument text remains the primary label. +The existing image URL formatter is reused. Logo loading failure falls back in +place without moving the row. + +The full ledger page is brought to the existing `container → ViewModel → view` +boundary while retaining its current query and interaction state. The ViewModel +prepares collection-level presentation data; the shared row owns only one +entry's display. This prevents duplication while keeping data fetching outside +presentation views. + +### Convey money direction independently of colour + +The row derives direction only from the sign already present in `payment.value`: +positive is a labelled `+` «Поступление», negative is a labelled `−` «Списание», +and missing payment is the existing neutral «Без движения денег» state. Semantic +Mantine theme tokens supply positive, negative and neutral tones; the sign and +visible Russian label make the state understandable without colour. Amounts +remain formatted from their decimal string and are not converted to JavaScript +numbers for the direction decision. + +The row uses a three-part hierarchy: instrument identity on the left, operation +and date/category as secondary metadata, and a right-aligned amount/direction +group. At narrow widths the metadata may wrap below the primary line while the +amount stays associated with its entry; the list never requires horizontal +scrolling. + +## Delivery Map + +| Area | Status | Architectural outcome or N/A reason | +| --- | --- | --- | +| frontend | `affected` | Generated contract update, reusable accessible Ledger Entry row, and aligned overview/full-ledger layouts through the finance VM/view boundary. | +| backend | `affected` | Shared server-side instrument metadata resolver and nullable logo metadata on each operations response. | +| specs | `affected` | Approved delta requirements become the versioned behavioural contract. | +| docs | `affected` | Account Ledger as-built documentation describes the delivered metadata and directional row semantics. | + +### Contract Gate Decision + +| Gate | Status | Reason | +| --- | --- | --- | +| runtime-openapi | `required` | Backend adds public response metadata and frontend consumes the generated contract. | + +## Solution Assessment + +The change extends an existing external T-Bank metadata lookup and introduces a +shared cache consumer, so it evaluates the available approaches. + +| Option | Fit and maturity | Security and operating cost | Decision | +| --- | --- | --- | --- | +| Resolve every operation independently | Uses existing client but repeats calls for the same instrument and exposes journal latency to upstream volume. | Rate limiter prevents excess but adds queueing and avoidable upstream load. | Rejected. | +| Reuse Portfolio's private cache by duplicating it in the ledger | Works locally but creates divergent TTL, failure and fallback behaviour. | Low initial cost, growing maintenance and inconsistent cache pressure. | Rejected. | +| Extract a bounded, short-lived shared resolver around the existing client | Reuses mature authenticated boundary and gives both consumers one lookup policy. | No new credential or storage; cached public brand metadata is returned only after account access. Bounded memory and non-fatal failures limit operating risk. | Chosen. | + +No third-party package or new external integration is justified: the T-Bank +client already supplies the required brand metadata and resilience controls. + +## Risks / Trade-offs + +- A page with many distinct instruments can still need several metadata lookups + on a cold cache. Deduplication, bounded cache lifetime and the existing + limiter trade initial completeness latency for upstream protection. +- Some past operations do not identify an instrument or have no brand asset. + The mandatory text fallback keeps these rows legible without inventing data. +- An additive nullable OpenAPI field requires atomic runtime-contract and + frontend-snapshot alignment. Until the compatible frontend is delivered, + old consumers safely ignore the field. +- Green/red alone can exclude users; sign and text are required alongside + semantic colours, and images remain supplementary to visible text. + +## Migration Plan + +1. Introduce the shared resolver and the nullable response metadata while + preserving the operations payload and failure behaviour. +2. Publish and sync the generated OpenAPI contract, then consume it in the + frontend presentation boundary. +3. Replace the duplicated overview and journal row rendering with the shared + responsive component and update the as-built feature documentation after + verification. + +### Rollback Contract + +| Field | Decision | +| --- | --- | +| Status | `required` | +| Reason | The change adds a public API response field consumed by the frontend. | +| Trigger | Runtime OpenAPI mismatch, unacceptable additional ledger latency, or a regression that hides ledger entries. | +| Actions | Revert the frontend to the prior row presentation and revert the backend response enrichment; retain the pre-change response shape when rollback is complete. | +| Data safety | No persistent data, migration or credential changes occur. In-memory metadata and page caches can expire or be discarded safely. | +| Verification | Re-run affected canonical gates, the runtime OpenAPI contract gate, and the ledger scenarios for signed, unsigned and missing-logo entries. | diff --git a/openspec/changes/archive/2026-08-26-improve-ledger-event-rows/proposal.md b/openspec/changes/archive/2026-08-26-improve-ledger-event-rows/proposal.md new file mode 100644 index 0000000..89686f8 --- /dev/null +++ b/openspec/changes/archive/2026-08-26-improve-ledger-event-rows/proposal.md @@ -0,0 +1,46 @@ +## Why + +Сейчас последние записи на обзоре счёта и строки полного журнала воспринимаются +как плотный текст: инструмент не имеет наглядного визуального якоря, а денежное +движение трудно быстро отличить как поступление или списание. Это замедляет +просмотр недавней активности и разрывает визуальную согласованность с уже +оформленными позициями акций и облигаций. + +## What Changes + +- Сделать каждую Ledger Entry в «Последних событиях» и в «Журнале операций» + компактной структурированной строкой с визуальной идентичностью инструмента, + понятным fallback при её отсутствии и читаемой группировкой основных и + второстепенных сведений. +- Явно обозначать направление денежного движения: поступления и списания + должны различаться знаком, текстовой семантикой и цветом; запись без движения + денег должна оставаться нейтральной и ясно подписанной. +- Сохранить одинаковые смысл, порядок данных и визуальную иерархию для полной + истории и пяти последних записей на обзоре счёта, с адаптацией плотности к + узкому экрану. +- Не изменять состав исполненных операций, их порядок, фильтрацию, пагинацию, + расчёт денежных значений, категории, навигацию или правила доступа к счёту. +- Не добавлять в журнал расчёт доходности, баланса после операции, валютную + конвертацию или трактовку категории операции как финансового результата. + +## Capabilities + +### New Capabilities + +Нет. + +### Modified Capabilities + +- `brokerage-account-ledger`: записи журнала и блок последних записей получают + доступную presentation metadata инструмента и явно отображают направление + денежного движения без изменения факта или суммы операции. + +## Impact + +- Пользователь быстрее распознаёт инструмент и видит, была ли конкретная + операция поступлением, списанием или движением без денег. +- Затрагиваются отображение последних записей, полный журнал и данные, + необходимые для их визуальной идентичности. +- Документация Account Ledger после поставки будет уточнена как фактическое + состояние; публичное поведение журнала требует согласованного изменения + frontend и backend. diff --git a/openspec/changes/archive/2026-08-26-improve-ledger-event-rows/specs/brokerage-account-ledger/spec.md b/openspec/changes/archive/2026-08-26-improve-ledger-event-rows/specs/brokerage-account-ledger/spec.md new file mode 100644 index 0000000..40b8e3f --- /dev/null +++ b/openspec/changes/archive/2026-08-26-improve-ledger-event-rows/specs/brokerage-account-ledger/spec.md @@ -0,0 +1,54 @@ +## MODIFIED Requirements + +### Requirement: Ledger entries preserve broker operation meaning +Система MUST представлять одну upstream-операцию одной записью журнала и MUST передавать её timestamp, тип операции, категорию, описание, доступные идентификаторы инструмента, доступную presentation metadata инструмента, денежную выплату, количество, цену, НКД и комиссию. Денежные поля MUST сохранять исходную валюту и точное десятичное значение. + +#### Scenario: Отображение купонной выплаты +- **WHEN** T-Bank возвращает исполненную купонную операцию с выплатой и данными инструмента +- **THEN** запись журнала определяет её как income и передаёт исходную сумму выплаты, валюту и данные инструмента + +#### Scenario: Отображение комиссии сделки без синтетической записи +- **WHEN** T-Bank возвращает исполненную сделку, содержащую выплату и комиссию +- **THEN** журнал содержит одну запись сделки с комиссией в качестве её детали + +#### Scenario: Отображение движения бумаг без денег +- **WHEN** T-Bank возвращает исполненную операцию, изменяющую состав инструментов, но без денежной выплаты +- **THEN** запись журнала передаёт доступное количество и не содержит искусственной денежной выплаты с нулевым значением + +#### Scenario: Доступна визуальная идентичность инструмента +- **WHEN** для инструмента исполненной операции доступна presentation metadata бренда +- **THEN** запись журнала передаёт её вместе с доступными идентификаторами инструмента, не меняя остальные сведения операции + +### Requirement: Account overview includes recent ledger events +Обзор счёта MUST показывать пять последних исполненных записей журнала этого счёта и действие перехода к его полному журналу. Каждая показанная запись MUST иметь ту же визуальную идентичность инструмента и семантику денежного движения, что и запись полного журнала. Неуспешный или пустой запрос последних записей MUST оставаться локальным состоянием блока последних событий и MUST NOT заменять успешно отображённый обзор Portfolio. + +#### Scenario: Отображение последних событий +- **WHEN** загружается обзор счёта и его журнал содержит исполненные операции +- **THEN** обзор показывает не более пяти последних записей и ссылку на полный журнал + +#### Scenario: Последние записи недоступны +- **WHEN** обзор счёта успешно загружается, но запрос последних записей завершается ошибкой +- **THEN** обзор Portfolio остаётся доступным, а блок последних событий отображает собственное состояние восстановления + +#### Scenario: Последняя запись не содержит visual metadata инструмента +- **WHEN** одна из последних записей не имеет доступной presentation metadata инструмента +- **THEN** обзор показывает для неё понятный текстовый fallback и сохраняет дату, тип, инструмент и денежное изменение записи доступными для чтения + +### Requirement: Full ledger presents compact rows +Страница полного журнала MUST показывать для каждой записи дату, тип операции, инструмент и денежное изменение в исходной валюте в компактной структурированной строке. При доступной presentation metadata инструмента строка MUST показывать его визуальную идентичность; при её отсутствии MUST использовать понятный текстовый fallback. Для положительной денежной выплаты строка MUST явно обозначать поступление знаком, текстовой семантикой и положительным цветом; для отрицательной — списание знаком, текстовой семантикой и отрицательным цветом. Страница MUST обозначать записи без движения денег нейтрально и MUST NOT показывать цену или количество. + +#### Scenario: Отображение записи только с инструментом +- **WHEN** полный журнал отображает запись без денежной выплаты +- **THEN** страница определяет её как запись без движения денег + +#### Scenario: Отображение поступления +- **WHEN** полный журнал отображает запись с положительной денежной выплатой +- **THEN** строка явно показывает поступление и его денежное изменение в исходной валюте, не полагаясь только на цвет + +#### Scenario: Отображение списания +- **WHEN** полный журнал отображает запись с отрицательной денежной выплатой +- **THEN** строка явно показывает списание и его денежное изменение в исходной валюте, не полагаясь только на цвет + +#### Scenario: Отсутствует visual metadata инструмента +- **WHEN** полный журнал отображает запись без доступной presentation metadata инструмента +- **THEN** строка показывает понятный текстовый fallback без подмены или сокрытия доступных данных инструмента diff --git a/openspec/changes/archive/2026-08-26-improve-ledger-event-rows/tasks.md b/openspec/changes/archive/2026-08-26-improve-ledger-event-rows/tasks.md new file mode 100644 index 0000000..5120ec5 --- /dev/null +++ b/openspec/changes/archive/2026-08-26-improve-ledger-event-rows/tasks.md @@ -0,0 +1,43 @@ +## 1. Backend contract and metadata delivery + +- [x] 1.1 [backend] Извлечь из Portfolio общий bounded resolver metadata инструмента, применить его к уникальным идентификаторам страницы журнала и добавить nullable `logoName` в Ledger Entry без изменения исходных сумм, категорий или ошибок журнала. + - **Covers:** `brokerage-account-ledger` — `Ledger entries preserve broker operation meaning` + - **Verification:** Добавить risk-based Jest-проверки UID/FIGI lookup, дедупликации, nullable fallback и сохранения содержимого Ledger Entry; выполнить `npm run verify` в `backend`. + +- [x] 1.2 [backend] Опубликовать additive nullable metadata в runtime OpenAPI и проверить совместимость существующего ответа операций. + - **Covers:** `N/A` — реализация Contract Gate Decision из утверждённого Design; самостоятельного дополнительного observable requirement не создаёт. + - **Verification:** Выполнить backend runtime preflight и подтвердить наличие nullable `logoName` в `BrokerageOperationDto` опубликованного OpenAPI до frontend sync. + +## 2. Frontend ledger presentation + +- [x] 2.1 [frontend] Синхронизировать committed OpenAPI snapshot после опубликованного backend contract и обновить типизированное потребление Ledger Entry. + - **Covers:** `N/A` — обязательное связывание frontend с изменённым публичным contract из Design, без нового отдельного behavior. + - **Verification:** Выполнить `npm run openapi:sync` в `frontend`, проверить generated diff на nullable `logoName` и выполнить `npm run openapi:check`. + +- [x] 2.2 [frontend] Создать единое presentation отображение Ledger Entry с логотипом или текстовым fallback, русскими подписями «Поступление»/«Списание», знаком и семантическими тонами; применить его к пяти последним записям обзора и полному журналу. + - **Covers:** `brokerage-account-ledger` — `Account overview includes recent ledger events`; `brokerage-account-ledger` — `Full ledger presents compact rows` + - **Verification:** Добавить риск-ориентированные Vitest-проверки положительной, отрицательной, безденежной и безлоготипной записи в обоих потребителях; вручную проверить узкую ширину, клавиатурный фокус и читаемость без цветового восприятия. + +- [x] 2.3 [frontend] Перевести полный журнал на finance boundary `container → ViewModel → view`, сохранив фильтрацию, cursor-пагинацию и все loading/error/empty состояния. + - **Covers:** `N/A` — техническая структура нужна для повторного использования строки и соблюдения утверждённого UI Pattern, не расширяя Delta Specs. + - **Verification:** Выполнить `npm run verify` в `frontend`. + +## 3. Documentation and SDD delivery + +- [x] 3.1 [docs] Обновить as-built Account Ledger documentation после поставки: отразить nullable visual metadata и русскую семантику поступления, списания и отсутствия движения денег без дублирования change artifacts. + - **Covers:** `N/A` — docs фиксирует только проверенное поставленное состояние, определённое Delivery Map. + - **Verification:** Выполнить `npm run verify` в `docs` и проверить локальные ссылки изменённого feature document. + +- [x] 3.2 [specs] Выполнить strict validation change и Delta Specs в canonical store после завершения planning artifacts. + - **Covers:** `N/A` — specs владеет versioned SDD contract и его structural validation, а application behavior реализуют frontend и backend. + - **Verification:** Выполнить `openspec validate improve-ledger-event-rows --type change --strict --store wealth-ledger-specs`, `openspec validate --specs --strict --store wealth-ledger-specs` и `git diff --check` в `specs`. + +## 4. Cross-area contract and completion + +- [x] 4.1 [workspace] Выполнить required runtime OpenAPI gate после backend publication и frontend snapshot sync. + - **Covers:** `N/A` — workspace координирует Contract Gate Decision между двумя владельцами, не реализуя отдельное behavior. + - **Verification:** Выполнить `bash specs/scripts/verify-runtime-openapi.sh` из workspace root. + +- [x] 4.2 [workspace] Сверить поставленное поведение с Proposal, Delta Specs и Design, затем запустить canonical dispatcher и независимый read-only review перед archive. + - **Covers:** `N/A` — workspace владеет только межрепозиторной semantic verification и review, а не реализацией требований. + - **Verification:** Выполнить `bash specs/scripts/verify-workspace-change.sh improve-ledger-event-rows` из workspace root и подтвердить отсутствие Critical/Important findings в independent review. diff --git a/openspec/specs/brokerage-account-ledger/spec.md b/openspec/specs/brokerage-account-ledger/spec.md index 6c104cd..e8a610c 100644 --- a/openspec/specs/brokerage-account-ledger/spec.md +++ b/openspec/specs/brokerage-account-ledger/spec.md @@ -29,7 +29,7 @@ - **THEN** система возвращает следующую страницу без повторения записей предыдущей страницы ### Requirement: Ledger entries preserve broker operation meaning -Система MUST представлять одну upstream-операцию одной записью журнала и MUST передавать её timestamp, тип операции, категорию, описание, доступные идентификаторы инструмента, денежную выплату, количество, цену, НКД и комиссию. Денежные поля MUST сохранять исходную валюту и точное десятичное значение. +Система MUST представлять одну upstream-операцию одной записью журнала и MUST передавать её timestamp, тип операции, категорию, описание, доступные идентификаторы инструмента, доступную presentation metadata инструмента, денежную выплату, количество, цену, НКД и комиссию. Денежные поля MUST сохранять исходную валюту и точное десятичное значение. #### Scenario: Отображение купонной выплаты - **WHEN** T-Bank возвращает исполненную купонную операцию с выплатой и данными инструмента @@ -43,8 +43,12 @@ - **WHEN** T-Bank возвращает исполненную операцию, изменяющую состав инструментов, но без денежной выплаты - **THEN** запись журнала передаёт доступное количество и не содержит искусственной денежной выплаты с нулевым значением +#### Scenario: Доступна визуальная идентичность инструмента +- **WHEN** для инструмента исполненной операции доступна presentation metadata бренда +- **THEN** запись журнала передаёт её вместе с доступными идентификаторами инструмента, не меняя остальные сведения операции + ### Requirement: Account overview includes recent ledger events -Обзор счёта MUST показывать пять последних исполненных записей журнала этого счёта и действие перехода к его полному журналу. Неуспешный или пустой запрос последних записей MUST оставаться локальным состоянием блока последних событий и MUST NOT заменять успешно отображённый обзор Portfolio. +Обзор счёта MUST показывать пять последних исполненных записей журнала этого счёта и действие перехода к его полному журналу. Каждая показанная запись MUST иметь ту же визуальную идентичность инструмента и семантику денежного движения, что и запись полного журнала. Неуспешный или пустой запрос последних записей MUST оставаться локальным состоянием блока последних событий и MUST NOT заменять успешно отображённый обзор Portfolio. #### Scenario: Отображение последних событий - **WHEN** загружается обзор счёта и его журнал содержит исполненные операции @@ -54,13 +58,29 @@ - **WHEN** обзор счёта успешно загружается, но запрос последних записей завершается ошибкой - **THEN** обзор Portfolio остаётся доступным, а блок последних событий отображает собственное состояние восстановления +#### Scenario: Последняя запись не содержит visual metadata инструмента +- **WHEN** одна из последних записей не имеет доступной presentation metadata инструмента +- **THEN** обзор показывает для неё понятный текстовый fallback и сохраняет дату, тип, инструмент и денежное изменение записи доступными для чтения + ### Requirement: Full ledger presents compact rows -Страница полного журнала MUST показывать для каждой записи дату, тип операции, инструмент и денежное изменение в исходной валюте. Она MUST обозначать записи без движения денег и MUST NOT показывать цену или количество. +Страница полного журнала MUST показывать для каждой записи дату, тип операции, инструмент и денежное изменение в исходной валюте в компактной структурированной строке. При доступной presentation metadata инструмента строка MUST показывать его визуальную идентичность; при её отсутствии MUST использовать понятный текстовый fallback. Для положительной денежной выплаты строка MUST явно обозначать поступление знаком, текстовой семантикой и положительным цветом; для отрицательной — списание знаком, текстовой семантикой и отрицательным цветом. Страница MUST обозначать записи без движения денег нейтрально и MUST NOT показывать цену или количество. #### Scenario: Отображение записи только с инструментом - **WHEN** полный журнал отображает запись без денежной выплаты - **THEN** страница определяет её как запись без движения денег +#### Scenario: Отображение поступления +- **WHEN** полный журнал отображает запись с положительной денежной выплатой +- **THEN** строка явно показывает поступление и его денежное изменение в исходной валюте, не полагаясь только на цвет + +#### Scenario: Отображение списания +- **WHEN** полный журнал отображает запись с отрицательной денежной выплатой +- **THEN** строка явно показывает списание и его денежное изменение в исходной валюте, не полагаясь только на цвет + +#### Scenario: Отсутствует visual metadata инструмента +- **WHEN** полный журнал отображает запись без доступной presentation metadata инструмента +- **THEN** строка показывает понятный текстовый fallback без подмены или сокрытия доступных данных инструмента + ### Requirement: Кэш страниц журнала ограничивает локальные ресурсы Система SHALL кэшировать не более 100 недавно использованных страниц Account Ledger в каждом backend process не более чем на 30 секунд. Cache key MUST включать User, Brokerage Account, нормализованные период и categories, opaque cursor и limit; порядок или дублирование categories MUST NOT создавать отдельную cache identity, а отсутствующий конец периода MUST использовать текущий 30-секундный bucket. Просроченная страница MUST NOT возвращаться, а при переполнении cache MUST удалять наименее недавно использованную страницу.