docs(sdd): archive tbank access refactor
This commit is contained in:
+7
@@ -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
|
||||
+175
@@ -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 -->
|
||||
+20
@@ -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.
|
||||
+54
@@ -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.
|
||||
Reference in New Issue
Block a user