diff --git a/AGENTS.md b/AGENTS.md index 2d45af7..8a682f5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,13 +2,23 @@ ## Назначение и authority -Этот репозиторий — единственный versioned SDD authority. Canonical OpenSpec store — `wealth-ledger-specs`, физический root от workspace — `specs/openspec` (локально `openspec`). OpenSpec commands, читающие или изменяющие change, всегда получают `--store wealth-ledger-specs`; nearest-root или default-store fallback запрещён. +Этот репозиторий — единственный versioned SDD authority. Shared SDD policy, +common reading pointers, audit routing и `../references/**` policy задаёт root +`AGENTS.md`. -Proposal, Delta Specs, Design, Tasks, changes и archives хранятся только в `openspec`. Не создавать versioned planning artifacts вне него, включая `superpowers/plans`; `../references/**` read-only. `docs` хранит as-built state и Completion Evidence, но не копию change artifacts. +Canonical OpenSpec store — `wealth-ledger-specs`, физический root от workspace — +`specs/openspec` (локально `openspec`). OpenSpec commands, читающие или +изменяющие change, всегда получают `--store wealth-ledger-specs`. + +Proposal, Delta Specs, Design, Tasks, changes и archives хранятся в `openspec`. +`docs` хранит as-built state и Completion Evidence, но не копию change artifacts. ## Workflow и verification -Поддерживаемая CLI version задана в `openspec/.cli-version`; новые changes используют project-local schema `wealth-ledger-sdd`. Артефакты создаются по одному после approval Change Owner. Общая policy: [`../docs/development/engineering-governance.md`](../docs/development/engineering-governance.md); lifecycle: [`../docs/development/openspec-authority-and-lifecycle.md`](../docs/development/openspec-authority-and-lifecycle.md); audit work: [`../docs/development/audit-remediation.md`](../docs/development/audit-remediation.md). +Поддерживаемая CLI version задана в `openspec/.cli-version`; новые changes +используют project-local schema `wealth-ledger-sdd`. Артефакты создаются по +одному после approval Change Owner. Lifecycle: +[`../docs/development/openspec-authority-and-lifecycle.md`](../docs/development/openspec-authority-and-lifecycle.md). Из workspace canonical pre-archive gate: @@ -32,5 +42,4 @@ Structural validation не заменяет semantic verification и independent - Design содержит Delivery Map `frontend`, `backend`, `specs`, `docs` и условный Solution Assessment. - Specs описывают observable behavior, не изменения кода; Design — architecture, не line-by-line implementation; Tasks — implementation checklist без будущего evidence. -- Не хранить application code и durable ADR в `specs`. -- Для фактической системы и терминов использовать [`../docs/architecture/system-overview.md`](../docs/architecture/system-overview.md) и [`../docs/architecture/domain-glossary.md`](../docs/architecture/domain-glossary.md). +- Application code и durable ADR принадлежат affected application/docs repos. diff --git a/openspec/changes/consolidate-tbank-connection-resolution/.openspec.yaml b/openspec/changes/consolidate-tbank-connection-resolution/.openspec.yaml new file mode 100644 index 0000000..edbbf30 --- /dev/null +++ b/openspec/changes/consolidate-tbank-connection-resolution/.openspec.yaml @@ -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 diff --git a/openspec/changes/consolidate-tbank-connection-resolution/design.md b/openspec/changes/consolidate-tbank-connection-resolution/design.md new file mode 100644 index 0000000..d41bea9 --- /dev/null +++ b/openspec/changes/consolidate-tbank-connection-resolution/design.md @@ -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 + + diff --git a/openspec/changes/consolidate-tbank-connection-resolution/implementation-summary.md b/openspec/changes/consolidate-tbank-connection-resolution/implementation-summary.md new file mode 100644 index 0000000..8afa084 --- /dev/null +++ b/openspec/changes/consolidate-tbank-connection-resolution/implementation-summary.md @@ -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. diff --git a/openspec/changes/consolidate-tbank-connection-resolution/proposal.md b/openspec/changes/consolidate-tbank-connection-resolution/proposal.md new file mode 100644 index 0000000..846f6b0 --- /dev/null +++ b/openspec/changes/consolidate-tbank-connection-resolution/proposal.md @@ -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, который ещё не задокументирован. diff --git a/openspec/changes/consolidate-tbank-connection-resolution/tasks.md b/openspec/changes/consolidate-tbank-connection-resolution/tasks.md new file mode 100644 index 0000000..daaa8fa --- /dev/null +++ b/openspec/changes/consolidate-tbank-connection-resolution/tasks.md @@ -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.