docs(sdd): archive tbank access refactor

This commit is contained in:
2026-08-25 12:35:00 +03:00
parent 79faa29ac0
commit 736885fe65
5 changed files with 0 additions and 0 deletions
@@ -0,0 +1,7 @@
schema: wealth-ledger-sdd
created: 2026-08-25
goal: Consolidate repeated T-Bank connection and account access rules without changing observable API behavior.
skip_specs: true
affected_areas:
- backend
- specs
@@ -0,0 +1,175 @@
# consolidate-tbank-connection-resolution — Design
## Context
Backend T-Bank flows сейчас повторяют один knowledge cluster: чтение сохранённой
T-Bank connection по User, расшифровку token, запрос открытых accounts,
отсечение неподдерживаемых account types, проверку accountId для account-scoped
flows и перевод `INVALID_TBANK_TOKEN` в публичную ошибку API. Полная
account-scoped последовательность есть в Portfolio, Account Ledger и Future Cash
Events; список Brokerage Accounts использует тот же persisted connection и
supported-account rules без проверки конкретного accountId; T-Bank connection
flow отдельно валидирует candidate token и использует то же crypto/error
knowledge.
Поставленное поведение менять нельзя. Для account-scoped read flows отсутствие
connection остаётся `409 TBANK_NOT_CONNECTED`, invalid token остаётся
`400 INVALID_TBANK_TOKEN`, недоступный или неподдерживаемый account остаётся
`404`, а upstream availability errors сохраняют текущие `503`/throw-through
semantics. Account Ledger уже документирует, что connection и account access
проверяются до выдачи cached page; этот порядок должен сохраниться.
Ограничения backend: NestJS module/provider model, Node 22, public HTTP API
`/api/v1`, generated OpenAPI как frontend contract authority, canonical gate
`npm ci && npm run verify`.
## Goals / Non-Goals
**Goals:**
- Сконцентрировать persisted T-Bank connection lookup, token decrypt,
supported-account policy, account access check и invalid-token translation в
одном backend module.
- Сохранить public API behavior, DTO shape, routes, status codes и Problem
Details codes.
- Сохранить service-specific responsibilities: Portfolio normalizes portfolio
data and snapshot fallback, Account Ledger maps operations and pagination,
Future Cash Events calculates projected events, Brokerage Accounts maps the
account list.
- Сделать новый module осмысленной test surface для shared access behavior,
сократив дублирование tests в feature services до orchestration и
feature-specific behavior.
**Non-Goals:**
- Новые T-Bank account types или изменение supported-account semantics.
- Frontend changes, OpenAPI snapshot changes, generated client updates или
runtime contract changes.
- Изменения Portfolio snapshot cache, Account Ledger page cache, Upcoming
Events dividend/instrument caches или metadata cache semantics.
- Durable storage, token versioning, account history или cache invalidation
after token replacement.
- Замена T-Bank gRPC client или добавление другой integration library.
## Decisions
**D1. Ввести один T-Bank access module с двумя public use cases.**
Module предоставляет list-oriented use case для supported Brokerage Accounts и
account-scoped use case для flows, которым нужен доступ к конкретному Brokerage
Account. Module владеет persisted connection lookup, token decrypt,
`getOpenAccounts`, supported-account filtering и inaccessible-account decision.
Feature services получают resolved token/account context или supported account
list и не повторяют access sequence.
*Alternative:* извлечь только маленький `supportedAccounts` constant и crypto
helper. Отклонено: access ordering и error translation остаются размазанными по
services, поэтому future account policy changes всё ещё могут разойтись.
**D2. Оборачивать account-scoped flow execution для централизованного
invalid-token translation.** Простой `resolveAccount(userId, accountId) ->
{ token, account }` не покрывает invalid-token errors, которые могут быть
выброшены более поздними T-Bank calls в том же service flow. Поэтому
account-scoped use case выполняет feature handler внутри translation boundary
access module. Missing connection, account lookup и handler failures используют
одно правило translation для `INVALID_TBANK_TOKEN`; unrelated exceptions
сохраняют существующее propagation.
*Alternative:* оставить `try/catch INVALID_TBANK_TOKEN` в каждом feature service
после shared resolver. Отклонено: это уменьшает duplication, но оставляет
behavior-critical string comparison в нескольких call sites.
**D3. Оставить T-Bank connection management вне account access, разделяя только
crypto/error primitives.** Сохранение или удаление T-Bank connection валидирует
candidate token и не разрешает persisted account. Этот flow не должен
принудительно проходить через account-scoped access use case. Shared module
может предоставить или переиспользовать crypto provider и invalid-token
translator, но connection management сохраняет текущую ответственность за token
validation, encryption и Portfolio snapshot invalidation.
**D4. Единая supported-account policy.** Supported upstream account types и их
normalized Investment Account Type/fallback display name живут в одной policy,
которой владеет T-Bank access module. Account list use case и account-scoped
validation оба опираются на эту policy, поэтому добавление или удаление
supported account type становится one-policy change.
**D5. Сохранить Account Ledger cache ordering.** Account Ledger должен
по-прежнему проверять current connection и account access до обращения к LRU
page cache. Implementation order: сначала access module, затем page cache,
затем upstream operations call при cache miss. Это сохраняет documented behavior
и 30-секундный token-replacement nuance, уже отражённый в as-built docs.
**D6. Tests переезжают к shared interface плюс тонкое service coverage.** Новые
tests покрывают observable branches access module: missing connection, invalid
token during account resolution, inaccessible/unsupported account, supported
account filtering и invalid token из wrapped account-scoped handler.
Существующие service tests корректируются так, чтобы проверять service-specific
behavior и факт использования shared access module без повторного тестирования
одной access matrix в каждом service.
## Delivery Map
| Area | Status | Architectural outcome or N/A reason |
| --- | --- | --- |
| frontend | `N/A` | Frontend behavior, generated client и OpenAPI snapshot не меняются. |
| backend | `affected` | T-Bank access module централизует connection lookup, token decrypt, supported-account policy, account access check и invalid-token translation; feature services сохраняют только feature-specific data retrieval, mapping и cache behavior. |
| specs | `affected` | Этот SDD change хранит Proposal, skipped Delta Specs, Design и Tasks для behavior-preserving refactor. |
| docs | `N/A` | As-built product behavior намеренно не меняется; docs обновляются только если implementation обнаружит ещё не задокументированный behavior nuance. |
### Contract Gate Decision
| Gate | Status | Reason |
| --- | --- | --- |
| runtime-openapi | `N/A` | Public API contract, DTO shape и generated frontend snapshot не меняются. |
### Solution Assessment
Формальная build-versus-buy assessment не triggered. Change не добавляет и не
заменяет auth, storage, external integration, observability, caching,
concurrency control или infrastructure. Он сохраняет существующий T-Bank gRPC
client и переносит project-owned access policy за более глубокий in-process
module.
## Risks / Trade-offs
- **Behavior drift during refactor.** Refactor затрагивает shared access
behavior нескольких endpoints. Mitigation: сохранить existing public status
codes/codes, добавить shared access tests для common matrix и оставить
feature-service tests для endpoint-specific behavior.
- **Over-wide callback interface.** Wrapping feature handlers централизует
invalid-token translation, но может создать впечатление, что access module
отвечает за feature logic. Mitigation: handler context ограничен resolved
token/account data; normalization, pagination, projections и caches остаются
в feature services.
- **Account Ledger cache ordering regression.** Перенос access code может
случайно начать отдавать cached pages до проверки current connection/account
access. Mitigation: Design требует access-before-cache ordering, а Tasks
должны включить focused regression test или semantic verification для этого
behavior.
- **T-Bank connection management partial duplication.** Connection settings flow
не может использовать account-scoped resolution, потому что валидирует
candidate token. Mitigation: разделить crypto/error primitives, но сохранить
connection management отдельно, не искажая account access interface.
## Migration Plan
Поставка выполняется одной backend-focused последовательностью без runtime
migration:
1. Добавить shared T-Bank access module и tests для common access matrix.
2. Перенести supported-account policy в shared module.
3. Refactor account-scoped flows так, чтобы они выполнялись через shared access
module, сохраняя feature-specific mapping, cache и fallback behavior каждого
flow.
4. Refactor Brokerage Accounts list на использование shared supported-account
policy/use case.
5. Оставить T-Bank connection management behavior неизменным, разделив только
безопасные crypto/error primitives, если это полезно.
6. Запустить backend canonical gate, semantic verification против этого Design
и Proposal, затем workspace change verification перед archive.
Rollback — обычный code revert: schema, data, contract или cache migration не
вводятся.
## Open Questions
<!-- none -->
@@ -0,0 +1,20 @@
# consolidate-tbank-connection-resolution — Implementation Summary
## Semantic Verification
Date: 2026-08-25
Checked areas:
- HTTP routes and controllers: no diff in `backend/src/tbank/*controller.ts`.
- Response/request DTOs and Swagger decorators: no diff in `backend/src/tbank/*dto.ts`.
- OpenAPI/frontend contract: unchanged by code diff because controllers and DTOs did not change.
- Frontend behavior: unchanged; no frontend files were modified by this change.
- Supported account types: preserved in shared `tbank-account-policy` and verified by policy/access/accounts tests.
- Cache semantics: Portfolio `freshForMs`, `fallbackForMs`, `metadataForMs`; Account Ledger `freshForMs` and `maxCachedPages`; Upcoming Events `positionsBatchSize`, `dividendCacheTtlMs`, and `instrumentCacheTtlMs` remain unchanged.
- Token replacement/account access nuance: Account Ledger still executes `TbankAccessService.withAccount` before LRU page cache lookup, covered by the cached-page access regression test.
- T-Bank connection settings: candidate token validation, upsert/delete behavior, and Portfolio snapshot invalidation remain covered by focused tests.
Conclusion:
The implementation matches the approved Proposal and Design as a behavior-preserving backend refactor. No Delta Specs were required because no capability, route, DTO, status-code contract, frontend behavior, or durable storage behavior changed.
@@ -0,0 +1,54 @@
## Why
T-Bank-backed flows сейчас опираются на одни и те же правила подключения,
валидности token и поддерживаемых счетов в нескольких местах. Поставленное
поведение должно быть согласованным: отсутствие connection, невалидный token,
недоступный account и неподдерживаемый account type должны обрабатываться
одинаково, когда User открывает счета, Portfolio, Account Ledger или Future
Cash Events.
Текущая реализация повторяет это знание между backend flows. Из-за этого
будущие изменения supported-account rules или обработки invalid-token легко
применить не полностью, создав тихое расхождение поведения между экранами,
которые должны разделять одну access model. Review от 25 авг 2026 подтвердил
дублирование и отметил code-level fix как требующий этого SDD path.
## What Changes
- Консолидировать backend rules для T-Bank connection и supported-account
access так, чтобы каждый T-Bank-backed flow использовал одну внутренне
согласованную policy.
- Сохранить существующее observable behavior для users и frontend callers:
успешные ответы, missing-connection errors, invalid-token errors,
unavailable-upstream errors и inaccessible-account errors остаются
совместимыми с текущим delivered contract.
- Сохранить локальную MVP boundary: frontend продолжает обращаться только к
backend, T-Bank credentials остаются server-side, а runtime caches остаются
in-memory.
- Non-goal: вводить новые T-Bank account types, менять HTTP routes, менять
форму response DTO, менять token storage semantics, добавлять historical
storage или менять frontend UI behavior.
- Non-goal: исправлять несвязанные frontend architecture findings, включая
auth-gating в hooks или форму account page VM/view.
## Capabilities
### New Capabilities
_Нет._
### Modified Capabilities
_Нет._
Это behavior-preserving backend refactor. Change отказывается от Delta Specs
через `skip_specs: true`, потому что spec-level behavior не должно меняться.
Любое обнаруженное изменение поведения должно вернуться к Proposal/Delta Specs
до Design.
## Impact
Users и frontend callers не должны увидеть изменённого product behavior.
Затронутая implementation area — backend T-Bank access logic; specs отслеживает
SDD lifecycle для refactor. Documentation changes ожидаются только если Design
обнаружит as-built behavior nuance, который ещё не задокументирован.
@@ -0,0 +1,42 @@
# consolidate-tbank-connection-resolution — Tasks
## 1. Backend: основа shared access module
- [x] 1.1 Создать `backend/src/tbank/tbank-token.service.ts` и зарегистрировать его в `TbankConnectionModule`. Service должен владеть `TbankCrypto.fromBase64(config.getOrThrow('TOKEN_ENCRYPTION_KEY'))`, предоставлять `encrypt(value: string): string`, `decrypt(payload: string): string` и один helper, который переводит `Error('INVALID_TBANK_TOKEN')` в `BadRequestException({ code: 'INVALID_TBANK_TOKEN' })`, а остальные ошибки пробрасывает без изменений. Verification: `npm test -- tbank/tbank-token.service.spec.ts --runInBand` в `backend`.
- [x] 1.2 Добавить focused tests в `backend/src/tbank/tbank-token.service.spec.ts`: encrypt/decrypt roundtrip через service, перевод invalid-token в `BadRequestException`, неизменённое propagation для `ServiceUnavailableException({ code: 'TBANK_UNAVAILABLE' })`. Verification: `npm test -- tbank/tbank-token.service.spec.ts --runInBand` в `backend`.
- [x] 1.3 Создать `backend/src/tbank/tbank-account-policy.ts` с единой supported upstream account policy, которая сейчас дублируется между services. Policy должна маппить supported upstream types в normalized `type` и fallback display `name`, предоставлять supported-account filtering для list flows и account-access matching для account-scoped flows. Verification: `npm test -- tbank/tbank-account-policy.spec.ts --runInBand` в `backend`.
- [x] 1.4 Добавить focused tests в `backend/src/tbank/tbank-account-policy.spec.ts`: supported brokerage/IIS/invest-box/money-market accounts маппятся в текущие normalized values и fallback names; unsupported upstream accounts исключаются; account-scoped matching отклоняет unsupported account даже при совпавшем account id. Verification: `npm test -- tbank/tbank-account-policy.spec.ts --runInBand` в `backend`.
## 2. Backend: T-Bank access service
- [x] 2.1 Создать `backend/src/tbank/tbank-access.service.ts` и зарегистрировать его в `TbankConnectionModule`. Service должен использовать `PrismaService`, `TbankTokenService`, `TbankClient` и `tbank-account-policy`, предоставляя list-oriented use case для supported accounts и account-scoped wrapper use case. Verification: `npm test -- tbank/tbank-access.service.spec.ts --runInBand` в `backend`.
- [x] 2.2 Добавить access-service tests для list behavior: missing connection возвращает `ConflictException({ code: 'TBANK_NOT_CONNECTED' })`; saved token расшифровывается до `getOpenAccounts`; unsupported accounts фильтруются через shared policy; invalid token из `getOpenAccounts` становится `BadRequestException`; `ServiceUnavailableException` пробрасывается без изменений. Verification: `npm test -- tbank/tbank-access.service.spec.ts --runInBand` в `backend`.
- [x] 2.3 Добавить access-service tests для account-scoped behavior: inaccessible account возвращает `NotFoundException`; accessible supported account передаёт resolved `{ token, account }` в feature handler; `INVALID_TBANK_TOKEN`, выброшенный handler, становится `BadRequestException`; unrelated handler errors пробрасываются без изменений. Verification: `npm test -- tbank/tbank-access.service.spec.ts --runInBand` в `backend`.
## 3. Backend: refactor feature services
- [x] 3.1 Refactor Portfolio flow на использование `TbankAccessService` для account-scoped access; удалить direct connection lookup, direct token decrypt, local `supportedAccounts` и local invalid-token string catch из `backend/src/tbank/portfolio.service.ts`. Fresh snapshot short-circuit и stale snapshot fallback semantics должны сохраниться без изменений. Verification: `npm test -- tbank/portfolio.service.spec.ts --runInBand` в `backend`.
- [x] 3.2 Обновить `backend/src/tbank/portfolio.service.spec.ts`, чтобы повторяющиеся access branches мокали `TbankAccessService`, а не Prisma/config/open-account setup. Сохранить service-specific coverage для normalization, metadata fallback, fresh snapshot reuse, fallback only on temporary upstream errors и invalid token not masked by stale snapshot. Verification: `npm test -- tbank/portfolio.service.spec.ts --runInBand` в `backend`.
- [x] 3.3 Refactor Account Ledger flow на использование `TbankAccessService` для account-scoped access; удалить direct connection lookup, direct token decrypt, local `supportedAccounts` и local invalid-token string catch из `backend/src/tbank/brokerage-operations.service.ts`. Порядок должен сохраниться: access wrapper выполняется до page cache lookup, затем page cache lookup, затем `getOperationsByCursor` при cache miss. Verification: `npm test -- tbank/brokerage-operations.service.spec.ts --runInBand` в `backend`.
- [x] 3.4 Обновить `backend/src/tbank/brokerage-operations.service.spec.ts`, сохранив focused service-specific tests для date boundaries, category filtering, empty future ranges, pagination/cache keys, LRU eviction и regression, что cached pages не bypass current account access checks. Verification: `npm test -- tbank/brokerage-operations.service.spec.ts --runInBand` в `backend`.
- [x] 3.5 Refactor Future Cash Events flow на использование `TbankAccessService` для account-scoped access; удалить direct connection lookup, direct token decrypt, local `supportedAccounts` и local invalid-token string catch из `backend/src/tbank/upcoming-events.service.ts`. Event projection, batching, source failure behavior и existing caches должны сохраниться. Verification: `npm test -- tbank/upcoming-events.service.spec.ts --runInBand` в `backend`.
- [x] 3.6 Обновить `backend/src/tbank/upcoming-events.service.spec.ts`, чтобы access resolution мокался один раз, а service-specific tests покрывали projection, inaccessible account short-circuit через access service, ticker/instrument lookup behavior, skipped cancelled dividends, cache TTL reuse и no partial result on source failure. Verification: `npm test -- tbank/upcoming-events.service.spec.ts --runInBand` в `backend`.
## 4. Backend: list и connection settings flows
- [x] 4.1 Refactor Brokerage Accounts list на использование list-oriented `TbankAccessService` use case и shared account policy. Удалить local `accountTypes`, direct connection lookup, direct token decrypt и local invalid-token string catch из `backend/src/tbank/brokerage-accounts.service.ts`. Verification: `npm test -- tbank/brokerage-accounts.service.spec.ts --runInBand` в `backend`.
- [x] 4.2 Обновить `backend/src/tbank/brokerage-accounts.service.spec.ts`, сохранив list-specific coverage: no T-Bank call without connection, supported account normalization, unsupported account exclusion, invalid token translated to BadRequest, empty upstream list unchanged и unavailable upstream response propagated. Verification: `npm test -- tbank/brokerage-accounts.service.spec.ts --runInBand` в `backend`.
- [x] 4.3 Refactor T-Bank connection settings на использование `TbankTokenService` для encryption и invalid-token translation, сохранив candidate-token validation, upsert/delete behavior и Portfolio snapshot invalidation в `backend/src/tbank/tbank-connection.service.ts`. Verification: `npm test -- tbank/tbank-connection.service.spec.ts --runInBand` в `backend`.
- [x] 4.4 Обновить `backend/src/tbank/tbank-connection.service.spec.ts`: invalid candidate tokens не persist, valid tokens encrypt через `TbankTokenService`, delete остаётся idempotent и вызывает Portfolio invalidation. Verification: `npm test -- tbank/tbank-connection.service.spec.ts --runInBand` в `backend`.
## 5. Backend: integration checks и cleanup
- [x] 5.1 Удалить now-unused imports, constructor parameters, local `supportedAccounts` declarations, local `accountTypes` declarations и local `TbankCrypto.fromBase64` instantiation из affected services. Verification: `npm run lint && npm run typecheck:app && npm run typecheck:test` в `backend`.
- [x] 5.2 Запустить полный backend Jest suite для cross-service regressions в T-Bank behavior. Verification: `npm test -- --runInBand` в `backend`.
- [x] 5.3 Запустить backend canonical gate под Node 22. Verification: `npm ci && npm run verify` в `backend`.
## 6. Specs и workspace verification
- [x] 6.1 Выполнить semantic verification против Proposal и Design: подтвердить, что HTTP routes, DTO fields, OpenAPI output, frontend behavior, supported account types, cache TTLs и token replacement semantics не изменились. Verification: записать checked areas и conclusion в implementation summary перед archive.
- [x] 6.2 Проверить OpenSpec change структурно. Verification: `openspec validate consolidate-tbank-connection-resolution --type change --strict --store wealth-ledger-specs` из `specs`.
- [x] 6.3 После завершения affected tasks запустить workspace change dispatcher под Node 22. Verification: `bash specs/scripts/verify-workspace-change.sh consolidate-tbank-connection-resolution` из workspace root.