diff --git a/openspec/changes/archive/2026-09-01-harden-tbank-connection-cache-boundary/.openspec.yaml b/openspec/changes/archive/2026-09-01-harden-tbank-connection-cache-boundary/.openspec.yaml new file mode 100644 index 0000000..50da4a6 --- /dev/null +++ b/openspec/changes/archive/2026-09-01-harden-tbank-connection-cache-boundary/.openspec.yaml @@ -0,0 +1,2 @@ +schema: wealth-ledger-sdd +created: 2026-08-31 diff --git a/openspec/changes/archive/2026-09-01-harden-tbank-connection-cache-boundary/design.md b/openspec/changes/archive/2026-09-01-harden-tbank-connection-cache-boundary/design.md new file mode 100644 index 0000000..d0f318b --- /dev/null +++ b/openspec/changes/archive/2026-09-01-harden-tbank-connection-cache-boundary/design.md @@ -0,0 +1,241 @@ +## Context + +Frontend использует один `QueryClient`, а `openapi-react-query` формирует ключи +GET-запросов как кортежи с HTTP method, canonical API path и, при наличии, +params. Все пользовательские финансовые выборки текущего T-Bank connection +находятся в `/api/v1/brokerage-accounts` subtree. Сейчас successful PUT и DELETE +connection обновляют status cache, но сбрасывают только список Brokerage +Accounts; сохранённые и выполняющиеся query остальных выборок могут пережить +смену connection. + +Backend также владеет connection-dependent cache. Account Ledger хранит страницы +по User, Brokerage Account и query без идентичности текущего connection; Income +Summary и Income Analytics используют те же страницы. Portfolio очищается при +PUT и DELETE, но старый in-flight request может завершиться после очистки и +снова записать snapshot. Browser `AbortSignal` прекращает потребление HTTP +response, но не является гарантией отмены уже запущенного backend T-Bank RPC. +Поэтому один frontend reset не может обеспечить запрет на данные предыдущей +границы. + +Каждый successful PUT сохраняет заново зашифрованный token с новым AES-GCM +nonce, включая замену token тем же plaintext value. Persisted encrypted payload +тем самым уже является сменяемой идентичностью connection; DELETE удаляет её. +Это позволяет изолировать server-side cache без нового поля БД, transport DTO +или API version. Обычная ошибка frontend background refetch по-прежнему +сохраняет Client Cached Snapshot, а logout или потеря auth session очищает весь +`QueryClient` независимо от connection boundary. + +## Goals / Non-Goals + +**Goals:** + +- сделать successful create, replace и delete общей границей frontend и backend + cache reuse для connection-dependent финансовых данных; +- исключить чтение сохранённого либо поздно завершившегося результата + предыдущего connection независимо от порядка in-flight completions; +- оставить failed mutation в текущем generation и сохранить Client Cached + Snapshot только для текущего generation; +- сохранить status-first frontend commit, refetch активных consumers и + независимую полную session cleanup при logout; +- удержать cache isolation внутри существующих QueryClient, connection access и + cache-owning backend layers без изменения публичного API и storage schema. + +**Non-Goals:** + +- менять observable Requirements, backend endpoints, transport DTO или OpenAPI + contract; +- добавлять persisted connection version, data migration, persisted financial + cache или новый глобальный frontend state container; +- привязывать к User connection generation общие reference caches инструментов, + логотипов и корпоративных событий, которые не содержат account snapshot; +- менять stale times, retry policies или UI-семантику обычного background + refetch; +- сужать logout cleanup до T-Bank data, менять auth boundary либо сериализовать + конкурентные replacements между распределёнными backend instances. + +## Decisions + +### Одна connection boundary координирует два cache plane + +Successful mutation создаёт одну логическую границу с двумя владельцами. +Frontend feature `tbank-connection` коммитит подтверждённый status и сбрасывает +все financial query текущего `QueryClient`. Backend connection access сообщает +cache owners opaque identity текущего persisted connection, а каждый +connection-dependent cache разрешает reuse только внутри этой identity. + +Обе защиты обязательны. Frontend reset немедленно удаляет данные из UI и +останавливает reuse browser responses; backend isolation не позволяет новому +frontend fetch получить server response, вычисленный до границы. Failed mutation +не меняет persisted connection identity и не запускает frontend commit, поэтому +данные текущей границы сохраняются. + +### Feature-owned frontend commit использует native reset brokerage subtree + +Модель `tbank-connection` остаётся единственным владельцем frontend commit после +successful PUT или DELETE. PUT использует status из response, DELETE — известный +disconnected status. Оба paths вызывают одну операцию, которая сначала записывает +status в существующий status query, затем применяет `QueryClient.resetQueries` с +feature-owned predicate ко всем generated GET keys, чей canonical path равен +`/api/v1/brokerage-accounts` или начинается с +`/api/v1/brokerage-accounts/`. Connection status и несвязанные query в эту family +не входят. + +Native reset отменяет browser requests через существующий `AbortSignal`, +возвращает matching query в initial state без предыдущих данных и запускает +refetch active observers. Inactive query получает данные при следующем +потреблении. Refetch не удерживает mutation UX в pending: обязательный status и +cache commit завершается синхронно, а получение нового response продолжается +независимо. + +`invalidateQueries` отклонён, потому что оставляет предыдущие данные читаемыми до +successful refetch. `removeQueries` отклонён для active observers: reset +напрямую переводит их в состояние без старых данных и использует штатный +TanStack Query lifecycle. + +### Backend connection access выдаёт opaque cache generation + +Ответственный connection access layer вместе с расшифрованным token получает +persisted encrypted payload и вычисляет из него фиксированный non-reversible +digest средствами Node runtime. Digest используется только как internal cache +generation: он не сохраняется отдельно, не возвращается через API и не попадает +в logs. Новый random nonce при каждом успешном PUT создаёт новое generation даже +для того же plaintext token; DELETE оставляет последующие requests без +generation до нового успешного PUT. + +Generation определяется из persisted record при обслуживании request, поэтому +все backend instances, читающие одну запись, получают одну identity, а +process-local invalidation не является условием корректности. `connectedAt` +отклонён как единственный generation key: timestamp имеет пользовательскую +семантику и конечную точность. Отдельный UUID column также подходит, но добавляет +schema migration и lifecycle нового persisted field без необходимого +compatibility gap. + +### Connection-dependent backend cache принимает только своё generation + +Account Ledger page key расширяется backend generation. Проверка connection и +доступа к Brokerage Account остаётся до cache reuse; Income Summary и Income +Analytics автоматически получают ту же изоляцию через общий collection path. +Старая page, записанная после boundary поздним RPC, остаётся помеченной старым +generation и недоступна новому request; существующие TTL и LRU ограничивают её +жизнь и память. + +Portfolio разрешает cache hit только после разрешения текущего persisted +connection и только когда snapshot принадлежит его generation. Cached fallback +при временной upstream error также ограничен тем же generation. Snapshot старого +in-flight request может завершиться после PUT или DELETE, но его generation не +совпадёт с новым connection, а при отсутствии connection cache вообще не +проверяется. Existing explicit invalidation может оставаться best-effort memory +eviction, но не служит correctness boundary. + +Brokerage Accounts не хранит полный server-side account snapshot. Events каждый +раз начинает с current connection и current account positions; его общие +instrument/dividend reference caches остаются connection-independent, поскольку +не содержат пользовательскую account selection или financial snapshot. Любой +будущий backend cache готового response из brokerage subtree обязан при +добавлении классифицироваться как connection-dependent либо получить конкретное +обоснование независимости. + +### Connection cleanup и auth cleanup остаются раздельными + +Connection boundary выполняется только после successful PUT или DELETE. Failed +mutation не меняет status, generation и financial cache. Обычный background +refetch использует существующие query policies и может сохранить последний +successful Client Cached Snapshot текущего generation. Logout и auth failure +продолжают вызывать полный `QueryClient.clear()`, включая connection status, +financial и любые другие session-scoped query. + +Regression coverage проверяет observable boundary и race ordering: successful +create/replace/delete сбрасывает весь parameterized brokerage subtree; +отменённый frontend response не возвращается; cached и late backend results +предыдущего generation не обслуживают новый request; DELETE не допускает cache +hit без connection; failed mutation, current-generation background snapshot и +logout сохраняют свои независимые outcomes. + +## Delivery Map + +| Area | Status | Architectural outcome or N/A reason | +| --- | --- | --- | +| frontend | `affected` | `tbank-connection` централизует successful status/cache commit и через native QueryClient lifecycle сбрасывает весь brokerage query subtree, сохраняя failure, background snapshot и logout semantics. | +| backend | `affected` | Connection access выдаёт opaque generation текущей persisted connection; Ledger/Income и Portfolio допускают cache reuse и fallback только внутри неё, поэтому local invalidation и browser cancellation не являются correctness boundary. | +| specs | `affected` | Canonical change фиксирует пересмотренный двухсторонний Design, зависимые Tasks и lifecycle artifacts без изменения утверждённого observable Requirement. | +| docs | `affected` | После поставки as-built connection, Portfolio и Account Ledger cache descriptions отражают generation isolation, frontend reset и отличие от current-generation Client Cached Snapshot. | + +### Contract Gate Decision + +| Gate | Status | Reason | +| --- | --- | --- | +| runtime-openapi | `N/A` | Frontend и backend меняют только internal cache orchestration; endpoints, request/response schemas и generated OpenAPI contract не меняются ни на одной стороне. | + +## Solution Assessment + +Change существенно меняет caching и concurrency boundary, поэтому рассмотрены +native и custom alternatives. + +| Option | Fit | Maturity and security | Operating constraints | Migration cost | +| --- | --- | --- | --- | --- | +| TanStack Query reset плюс backend generation из digest persisted encrypted payload | Закрывает browser cache, server cache и late in-flight responses; один persisted connection определяет generation во всех processes | Использует установленный TanStack Query lifecycle, Node crypto и существующее AES-GCM storage; digest не раскрывает token и не создаёт новый public surface | Portfolio выполняет lightweight persisted connection lookup до cache reuse; cache owners обязаны различать connection-dependent и shared reference data | Низкая: без schema/API migration, локальные изменения ответственных cache layers | +| Только frontend `resetQueries` | Закрывает browser state, но новый fetch может получить старый backend response | Native и безопасен на frontend | Browser abort не отменяет уже выполняющийся backend RPC | Низкая, но нарушает Requirement | +| Fan-out `clear()`/`invalidate()` из connection mutation во все backend caches | Удаляет уже сохранённые local entries | Простая process-local операция | Late request может повторно записать старые данные после clear; другой instance не получает invalidation | Низкая, но не является correctness solution | +| Explicit persisted UUID generation column | Даёт ту же строгую generation isolation и ясную storage identity | Зрелый стандартный механизм, без token-derived coupling | Требует schema ownership, migration существующих records и rollback data contract | Средняя и непропорциональная при уже сменяемом encrypted payload | +| Отключить connection-dependent backend caches | Строго исключает server reuse предыдущей границы | Наименьшая cache logic surface | Увеличивает T-Bank RPC load, latency и rate-limit pressure для Portfolio, Ledger и Income | Низкая code migration, высокая постоянная operating cost | + +Выбран первый вариант. Он использует существующую persisted connection identity +и native cache lifecycle, а custom logic ограничивает одной доменной операцией: +сравнением opaque generation перед reuse. Это устраняет compatibility gap, +который нельзя закрыть одним frontend reset или invalidation fan-out, без нового +storage field и без отказа от полезного backend caching. + +## Risks / Trade-offs + +- Generation зависит от свойства AES-GCM storage создавать новый encrypted + payload при каждом successful PUT. Если token storage перестанет + re-encrypt-иться, перед таким изменением потребуется явная generation identity; + regression boundary должна сделать нарушение наблюдаемым. +- Opaque digest является security-sensitive internal identifier: его нельзя + возвращать, логировать или включать в errors, хотя он не позволяет восстановить + token. +- Persisted connection lookup перед Portfolio cache reuse добавляет локальную DB + работу, но сохраняет экономию дорогих T-Bank Portfolio/Positions calls и + обеспечивает DELETE/replace isolation между processes. +- Old-generation Ledger pages остаются в bounded LRU до TTL, а поздний Portfolio + completion может временно вытеснить current entry. Generation check сохраняет + корректность, но редкая гонка способна вызвать дополнительный refetch. +- Active frontend view после смены connection временно переходит в initial + loading/error state. Это намеренный trade-off: snapshot предыдущей границы не + показывается, пока current-generation response ещё не получен. +- Если current-generation refetch завершается ошибкой сразу после successful + mutation, новый status уже виден, а данные предыдущего generation не + восстанавливаются. Recovery использует обычный error path без ложного + snapshot. +- Path-subtree classification включает будущие brokerage subresources на + frontend, но новый backend response cache всё равно требует явного решения о + generation ownership. + +## Migration Plan + +1. Сначала в backward-compatible backend delivery добавить opaque generation в + connection access context и ограничить ею Portfolio, Ledger и производные + Income cache paths; existing endpoints и responses оставить без изменений. +2. Перевести frontend successful PUT и DELETE на единый status/cache commit с + reset всего brokerage query subtree. +3. Зафиксировать boundary-level regression coverage для cached и in-flight + ordering на обеих сторонах, failed mutation, current-generation background + snapshot и независимого logout cleanup. +4. После подтверждённой реализации обновить as-built connection, Portfolio и + Account Ledger cache documentation. + +Backend поставляется первым, потому что он совместим с текущим frontend и сразу +делает новые reads generation-safe. Frontend delivery затем включает немедленную +UI boundary. Data migration, compatibility window и OpenAPI regeneration не +требуются. + +### Rollback Contract + +| Field | Decision | +| --- | --- | +| Status | `required` | +| Reason | Change укрепляет isolation пользовательских финансовых данных после replace/delete; rollback не должен возвращать cross-generation cache reuse или cache hit без current connection. | +| Trigger | Generation resolution ошибочно блокирует current connection либо вызывает системную деградацию доступности cache-backed финансовых выборок после delivery. | +| Actions | Сохранить frontend reset и current connection checks; временно отключить reuse затронутого backend cache или перейти на uncached current-connection reads, затем исправить generation logic. Не откатывать систему к cache keys без generation. | +| Data safety | Persisted token и финансовые данные не мигрируют; затронуты только in-memory entries, которые можно безопасно отбросить. | +| Verification | Current connection обслуживает Brokerage Accounts, Portfolio, Operations, Events и Income; create/replace/delete не используют previous-generation entries, а failed mutation сохраняет current-generation data. | diff --git a/openspec/changes/archive/2026-09-01-harden-tbank-connection-cache-boundary/proposal.md b/openspec/changes/archive/2026-09-01-harden-tbank-connection-cache-boundary/proposal.md new file mode 100644 index 0000000..4824541 --- /dev/null +++ b/openspec/changes/archive/2026-09-01-harden-tbank-connection-cache-boundary/proposal.md @@ -0,0 +1,45 @@ +## Why + +После успешного создания, замены или удаления T-Bank connection пользователь +может продолжить видеть финансовые данные, полученные через предыдущее +подключение. Обновление только списка Brokerage Accounts не защищает Portfolio, +Operations, Events и Income от повторного использования данных с уже +недействительной границы владения. Это подтверждённый высокоприоритетный дефект +когерентности данных, поэтому его нужно устранить до менее критичных findings +последнего frontend-аудита. + +## What Changes + +- Успешное создание, замена или удаление T-Bank connection становится границей, + после которой ни один следующий потребитель T-Bank-derived финансовых данных + не использует данные, загруженные через предыдущее подключение. +- Результат успешной операции по-прежнему сразу определяет отображаемый статус + connection, а Accounts, Portfolio, Operations, Events, Income Summary и Income + Analytics при следующем обращении отражают только текущую границу connection. +- Неуспешная операция сохраняет текущий статус connection и доступные данные, + относящиеся к нему. +- Обычное фоновое обновление продолжает сохранять Client Cached Snapshot при + временной ошибке, а logout независимо очищает данные пользовательской сессии. +- Change не меняет состав и смысл доступных финансовых данных, не добавляет + новые финансовые возможности и не включает остальные findings + frontend-аудита. + +## Capabilities + +### New Capabilities + +Нет. + +### Modified Capabilities + +- `frontend-query-coherence`: расширить инвариант когерентности T-Bank + connection с Brokerage Accounts на все финансовые данные, полученные через + текущее подключение. + +## Impact + +Изменение затрагивает пользователей, которые создают, заменяют или удаляют +T-Bank connection, а также все пользовательские представления Accounts, +Portfolio, Operations, Events и Income, зависящие от текущего подключения. +Audit remediation workflow должен зафиксировать этот scope как отдельное +исправление, не смешивая его с оставшимися findings. diff --git a/openspec/changes/archive/2026-09-01-harden-tbank-connection-cache-boundary/specs/frontend-query-coherence/spec.md b/openspec/changes/archive/2026-09-01-harden-tbank-connection-cache-boundary/specs/frontend-query-coherence/spec.md new file mode 100644 index 0000000..200c96b --- /dev/null +++ b/openspec/changes/archive/2026-09-01-harden-tbank-connection-cache-boundary/specs/frontend-query-coherence/spec.md @@ -0,0 +1,28 @@ +## MODIFIED Requirements + +### Requirement: Frontend сохраняет когерентность кэша T-Bank connection +Frontend MUST считать успешное создание, замену или удаление T-Bank connection новой границей повторного использования финансовых данных. Статус connection MUST сразу отражать результат успешной операции, а ни один последующий потребитель Brokerage Accounts, Portfolio, Operations, Events, Income Summary или Income Analytics MUST NOT использовать данные, загруженные до этой границы. Неуспешная операция MUST сохранять текущий статус connection и доступные данные его границы. Временная ошибка обычного фонового обновления MUST сохранять Client Cached Snapshot текущей границы, а logout MUST независимо очищать статус connection и связанные с ним данные пользовательской сессии. + +#### Scenario: Успешное создание подключения обновляет status и границу данных +- **WHEN** User успешно создаёт T-Bank connection в настройках +- **THEN** frontend показывает статус из успешного ответа, и следующий потребитель любой связанной финансовой выборки не использует данные, загруженные до создания connection + +#### Scenario: Успешная замена подключения обновляет status и accounts +- **WHEN** User успешно заменяет T-Bank connection в настройках +- **THEN** frontend показывает статус из успешного ответа, и следующий потребитель любой связанной финансовой выборки не использует данные, загруженные до замены connection + +#### Scenario: Успешное удаление подключения обновляет status и accounts +- **WHEN** User подтверждает успешное удаление T-Bank connection +- **THEN** frontend показывает состояние без подключения, и следующий потребитель любой связанной финансовой выборки не использует данные, загруженные до удаления connection + +#### Scenario: Неуспешная операция не меняет границу connection +- **WHEN** создание, замена или удаление T-Bank connection завершается ошибкой +- **THEN** frontend сохраняет текущий статус connection и доступные Brokerage Accounts, Portfolio, Operations, Events, Income Summary и Income Analytics его границы + +#### Scenario: Временная ошибка фонового обновления сохраняет snapshot +- **WHEN** обычное фоновое обновление финансовой выборки текущего T-Bank connection завершается временной ошибкой +- **THEN** frontend сохраняет её Client Cached Snapshot как данные текущей границы connection + +#### Scenario: Logout очищает данные пользовательской сессии +- **WHEN** User выходит из текущей сессии +- **THEN** frontend очищает статус T-Bank connection и все связанные с ним финансовые данные этой пользовательской сессии diff --git a/openspec/changes/archive/2026-09-01-harden-tbank-connection-cache-boundary/tasks.md b/openspec/changes/archive/2026-09-01-harden-tbank-connection-cache-boundary/tasks.md new file mode 100644 index 0000000..49ec4a7 --- /dev/null +++ b/openspec/changes/archive/2026-09-01-harden-tbank-connection-cache-boundary/tasks.md @@ -0,0 +1,47 @@ +## 1. Backend connection generation and cache isolation + +- [x] 1.1 [backend] По выбранному в утверждённом Solution Assessment варианту расширить internal connection access context opaque generation из digest persisted encrypted payload средствами Node runtime; generation не сохранять отдельно, не включать в API, logs или errors и закрепить regression proof смены identity при каждом successful PUT, включая тот же plaintext token. + - **Covers:** `N/A` — internal generation не создаёт самостоятельного observable behavior, но является выбранной в утверждённом Design технической основой строгой cache isolation без schema или API migration. + - **Verification:** Targeted backend tests подтверждают одинаковую generation для одной persisted connection record, новую generation после successful replacement и отсутствие public/logged token-derived identifier. +- [x] 1.2 [backend] Включить current connection generation в Account Ledger page cache boundary, сохранив connection/account access checks до reuse, и закрепить regression proof, что cached и поздно завершившиеся страницы прежней generation недоступны Operations и производным Income Summary/Analytics после replace или delete. + - **Covers:** `frontend-query-coherence` — `Requirement: Frontend сохраняет когерентность кэша T-Bank connection` + - **Verification:** Targeted Account Ledger и Income tests моделируют cached hit и late completion вокруг successful PUT/DELETE и подтверждают только current-generation reuse при неизменных TTL/LRU limits. +- [x] 1.3 [backend] Ограничить Portfolio cache hit и cached fallback текущей persisted connection generation, разрешая cache reuse только после connection resolution, и закрепить regression proof для replace/delete и старого in-flight completion без ослабления current-generation fallback. + - **Covers:** `frontend-query-coherence` — `Requirement: Frontend сохраняет когерентность кэша T-Bank connection` + - **Verification:** Targeted Portfolio tests подтверждают current-generation hit/fallback, отсутствие hit без connection и недоступность cached либо late previous-generation snapshot после successful PUT/DELETE. +- [x] 1.4 [backend] Выполнить canonical backend gate после generation-scoped cache implementation и обязательного concurrency/regression coverage. + - **Covers:** `N/A` — gate не добавляет самостоятельного observable behavior, а подтверждает affected backend delivery из утверждённого Design. + - **Verification:** `nvm use --silent && npm run verify` из `backend`. + +## 2. Frontend connection boundary + +- [x] 2.1 [frontend] Реализовать в `tbank-connection` единую операцию commit подтверждённого status и native reset всех generated GET query из `/api/v1/brokerage-accounts` subtree; перевести successful PUT и DELETE на эту операцию и добавить regression proof для root, parameterized и in-flight query без очистки несвязанного cache. + - **Covers:** `frontend-query-coherence` — `Requirement: Frontend сохраняет когерентность кэша T-Bank connection` + - **Verification:** `nvm use --silent && npm run test:run -- src/features/tbank-connection`; observable assertions подтверждают новый status, отсутствие данных предыдущей границы для create/replace/delete, отмену позднего старого browser response и сохранение несвязанного query. +- [x] 2.2 [frontend] Дополнить regression coverage сохранения текущей границы при failed connection mutation, Client Cached Snapshot при обычной ошибке фонового refetch и полной независимой очистки status и financial cache при logout. + - **Covers:** `frontend-query-coherence` — `Requirement: Frontend сохраняет когерентность кэша T-Bank connection` + - **Verification:** `nvm use --silent && npm run test:run -- src/features/tbank-connection src/features/auth/model/auth.test.tsx src/entities/brokerage-account/api/useBrokerageAccounts.test.tsx src/entities/portfolio/api/usePortfolio.test.tsx`; observable assertions покрывают failure, current-generation background snapshot и logout scenarios. +- [x] 2.3 [frontend] Выполнить canonical frontend gate после implementation и regression coverage. + - **Covers:** `N/A` — gate не добавляет самостоятельного observable behavior, а подтверждает affected frontend delivery из утверждённого Design. + - **Verification:** `nvm use --silent && npm run verify` из `frontend`. + +## 3. As-built documentation + +- [x] 3.1 [docs] После подтверждённой backend и frontend delivery обновить as-built описания T-Bank connection, Portfolio и Account Ledger cache semantics: successful mutation создаёт общую frontend/backend generation boundary, failed mutation и current-generation Client Cached Snapshot сохраняются, а logout остаётся отдельной полной session cleanup. + - **Covers:** `N/A` — документация не создаёт самостоятельного поведения, но поставляет обязательный affected docs outcome для реализованной cache boundary по утверждённому Design. + - **Verification:** Scoped review затронутых feature/cache documents подтверждает current-state generation isolation, frontend reset и lifecycle cases без копирования Proposal, Specs, Design или Tasks. +- [x] 3.2 [docs] Выполнить canonical docs gate после as-built обновления. + - **Covers:** `N/A` — gate проверяет обязательную affected docs delivery и не меняет observable product behavior. + - **Verification:** `nvm use --silent && npm run verify` из `docs`. + +## 4. Cross-area readiness and canonical traceability + +- [x] 4.1 [workspace] Семантически сверить delivered backend, frontend и docs outcomes с утверждёнными Requirement и Design, включая create/replace/delete, failed mutation, cached и in-flight ordering, current-generation background snapshot, logout и отсутствие storage/API contract changes. + - **Covers:** `N/A` — cross-area verification координирует affected repositories и подтверждает approved scope, но не владеет repository implementation. + - **Verification:** Scenario-to-evidence review для `frontend-query-coherence` и Delivery Map не оставляет непокрытых scenarios, unchecked affected delivery или незапланированных schema/OpenAPI diffs. +- [x] 4.2 [specs] После repository delivery и semantic review отметить в canonical Tasks только действительно завершённые items, сохранив traceability утверждённых Requirement, Design и affected areas. + - **Covers:** `N/A` — versioned SDD traceability является обязательным specs outcome утверждённого delivery, но не добавляет observable product behavior. + - **Verification:** Scoped review `tasks.md` сопоставляет каждый checked item с repository evidence и не оставляет завершённую работу отмеченной без подтверждения. +- [x] 4.3 [specs] Выполнить canonical strict change gate перед передачей в independent Validation. + - **Covers:** `N/A` — structural gate подтверждает affected specs delivery и не заменяет semantic verification реализации. + - **Verification:** `openspec validate harden-tbank-connection-cache-boundary --type change --strict --store wealth-ledger-specs` из `specs` под Node 22. diff --git a/openspec/specs/frontend-query-coherence/spec.md b/openspec/specs/frontend-query-coherence/spec.md index 0669292..8bd06ff 100644 --- a/openspec/specs/frontend-query-coherence/spec.md +++ b/openspec/specs/frontend-query-coherence/spec.md @@ -7,15 +7,31 @@ ## Requirements ### Requirement: Frontend сохраняет когерентность кэша T-Bank connection -Frontend MUST использовать идентичность кэша, с которой загружается статус T-Bank connection, когда успешная замена или удаление connection обновляет этот статус. После каждого успешного создания, замены или удаления connection frontend MUST инвалидировать идентичность кэша списка Brokerage Accounts, с которой этот список загружается. +Frontend MUST считать успешное создание, замену или удаление T-Bank connection новой границей повторного использования финансовых данных. Статус connection MUST сразу отражать результат успешной операции, а ни один последующий потребитель Brokerage Accounts, Portfolio, Operations, Events, Income Summary или Income Analytics MUST NOT использовать данные, загруженные до этой границы. Неуспешная операция MUST сохранять текущий статус connection и доступные данные его границы. Временная ошибка обычного фонового обновления MUST сохранять Client Cached Snapshot текущей границы, а logout MUST независимо очищать статус connection и связанные с ним данные пользовательской сессии. + +#### Scenario: Успешное создание подключения обновляет status и границу данных +- **WHEN** User успешно создаёт T-Bank connection в настройках +- **THEN** frontend показывает статус из успешного ответа, и следующий потребитель любой связанной финансовой выборки не использует данные, загруженные до создания connection #### Scenario: Успешная замена подключения обновляет status и accounts - **WHEN** User успешно заменяет T-Bank connection в настройках -- **THEN** frontend показывает статус из успешного ответа и следующий потребитель списка Brokerage Accounts не использует данные, загруженные до замены connection +- **THEN** frontend показывает статус из успешного ответа, и следующий потребитель любой связанной финансовой выборки не использует данные, загруженные до замены connection #### Scenario: Успешное удаление подключения обновляет status и accounts - **WHEN** User подтверждает успешное удаление T-Bank connection -- **THEN** frontend показывает состояние без подключения и следующий потребитель списка Brokerage Accounts не использует данные, загруженные до удаления connection +- **THEN** frontend показывает состояние без подключения, и следующий потребитель любой связанной финансовой выборки не использует данные, загруженные до удаления connection + +#### Scenario: Неуспешная операция не меняет границу connection +- **WHEN** создание, замена или удаление T-Bank connection завершается ошибкой +- **THEN** frontend сохраняет текущий статус connection и доступные Brokerage Accounts, Portfolio, Operations, Events, Income Summary и Income Analytics его границы + +#### Scenario: Временная ошибка фонового обновления сохраняет snapshot +- **WHEN** обычное фоновое обновление финансовой выборки текущего T-Bank connection завершается временной ошибкой +- **THEN** frontend сохраняет её Client Cached Snapshot как данные текущей границы connection + +#### Scenario: Logout очищает данные пользовательской сессии +- **WHEN** User выходит из текущей сессии +- **THEN** frontend очищает статус T-Bank connection и все связанные с ним финансовые данные этой пользовательской сессии ### Requirement: Generated API queries сохраняют транспортную и cache identity Frontend MUST сохранять сгенерированную API-клиентом идентичность для запросов Brokerage Accounts, операций Brokerage Account, событий Brokerage Account, Portfolio и T-Bank connection. Потребитель, которому требуется прочитать или инвалидировать один из этих кэшей вне hook, MUST получать его идентичность из владельца соответствующего query, а не формировать отдельный ключ.