# Аудит 01 — Architecture & Module Boundaries ## 1. Audit metadata - **Audit ID/name:** 01 / Architecture & Module Boundaries. - **Дата:** 2026-10-11. - **Исполнитель:** один независимый исполнитель направления 01. Другие субагенты не запускались, новые отчёты других направлений не читались. - **Workspace:** `/Users/ksv741/Project/vibe/WealthLedger`. - **Режим:** исследование read-only; единственная запись — этот отчёт вне четырёх самостоятельных checkout. | Checkout | Абсолютный путь | Проверенный HEAD | Начальный status | Итоговый status после записи отчёта | | --- | --- | --- | --- | --- | | root | `/Users/ksv741/Project/vibe/WealthLedger` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | clean | clean | | frontend | `/Users/ksv741/Project/vibe/WealthLedger/frontend` | `c8821200ce699dab1ce65360d1968d5877a2e78b` | clean | clean | | backend | `/Users/ksv741/Project/vibe/WealthLedger/backend` | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` | clean | clean | | specs | `/Users/ksv741/Project/vibe/WealthLedger/specs` | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` | clean | clean | | docs | `/Users/ksv741/Project/vibe/WealthLedger/docs` | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` | clean | clean | SHA/status получены самостоятельно командами `git rev-parse HEAD` и `git status --short --untracked-files=all` из каждого владельца. Пустой вывод status означает отсутствие staged, unstaged и видимых untracked изменений; ignored файлы этим не инвентаризируются. **Инструкции и skills.** Первым загружен установленный `ponytail-audit`. Применён его complexity-only проход: поиск недоступной orchestration, лишней indirection и механизма, заменяемого возможностью установленного stack. Correctness-сигналы отделены от этого прохода. Полная структура отчёта следует протоколу `audit-prompts/01-architecture-and-module-boundaries.md`, прочитанному целиком. Прочитаны AGENTS каждого checkout, местные architecture/engineering и активированные data, auth/security, integrations, HTTP/errors, testing, frontend TypeScript/React и UI conventions. Root conventions использованы только для root и сохранения отчёта. Дополнительно применены `feature-sliced-design` с reference `layer-structure.md` и `verification-before-completion`. Локальный FSD profile не заменён общими рекомендациями skill. **Graphify limitation.** Skill загружен, но существующий `graphify-out/graph.json` отсутствует в root, frontend, backend, specs и docs по проверке известных абсолютных путей. Query существующего графа невозможен. Графы и дополнительные artifacts не генерировались; выполнена статическая трассировка source/imports/DI. Это не отказ от аудита из-за отсутствия графа. **Выполненные проверки:** 1. Git HEAD/status в пяти checkout в начале, перед сохранением и после сохранения; SHA совпадают, source/config trees чистые. `git diff --check` в каждом checkout — без ошибок. Сам ignored отчёт отдельно проверен `git diff --no-index --check /dev/null audit-results/01-architecture-and-module-boundaries-report.md` — без whitespace errors. 2. Чтение manifests, module registrations, public exports, controllers, application owners, relevant persistence/schema, query consumers и контраргументов; поиск через Read/Glob/Grep из соответствующих repo paths. 3. В frontend, после активации местной Node: `source "$HOME/.nvm/nvm.sh" && nvm use --silent && ./node_modules/.bin/depcruise src packages --config dependency-cruiser.cjs --output-type err-long`. Exit 0: **420 modules, 1773 dependencies, no dependency violations**. Это прямой запуск установленного анализатора; `npm run deps` не запускался, потому что его pre-step собирает packages (`frontend/package.json:24-26,33`). 4. Из root: `git diff --no-index --stat "backend/openapi.json" "frontend/src/shared/api/spec/openapi.json"`. Exit 0, различий файлов на диске нет. Это не свежая генерация и не доказательство runtime schema parity. 5. Context7: документация TanStack Query `/tanstack/query` по polling, `refetchInterval` и отличию automatic fetching от manual `refetch`. Справочный запрос не обращался к прикладным финансовым API. **Уточнение scope:** dead-code и documentation/index вопросы перенесены в короткие Cross-audit signals для направлений 04 и 15. При корректировке перечитан и изменён только этот отчёт; проверки source graph и OpenAPI из первичного аудита повторно не запускались. Counts, related IDs и таблицы относятся к оставшимся архитектурным findings. **Не запускались:** install, build, codegen/OpenAPI generation, migrations, application startup, full-suite/verify, SQLite/E2E/unit tests, нагрузочные проверки, commit/PR и реальные внешние API. `references/**`, dated audits, архивы и исходники не изменялись. Предыдущий `audit-results/technical-audit-2026-10-11.md` прочитан только как реестр гипотез; его результаты измерений не использованы вместо текущих проверок. Все `path:line` ниже отсчитываются от workspace root и относятся к указанным HEAD. Ссылка на тест означает статически просмотренный сценарий либо будущую проверку, но не результат запуска теста в этом аудите. ## 2. Executive verdict 1. **Факт:** модульный NestJS monolith и локальный frontend FSD profile жизнеспособны. Прослеженные Nest module imports не образуют цикла; frontend dependency-cruiser не обнаружил циклов/forbidden imports по текущим правилам. Не доказана необходимость CQRS, обязательных ports, дополнительного state store или микросервисов. 2. **Подтверждённый boundary risk / ARCH-002:** read orchestration операций шире разрешённой первой загрузки. Income GET инициирует membership work, а общий `readPlan` с `history: true` может загрузить отсутствующую историю вне запрошенного периода. Это конкурирующая refresh policy рядом с hourly owner, а не недостаток слоёв. 3. **Подтверждённый ownership drift / ARCH-003:** периодический GET принадлежит одновременно query owner, глобальному QueryClient и timer в feature подключения. Разрешённый import `pages → features` не делает этот owner осмысленным; timer не знает query eligibility. 4. **KEEP / ARCH-005–007:** auth coordination, account/connection fences, общая conditional publication и banking transaction/replay boundaries имеют конкретных production consumers. Их удаление не является допустимым сокращением архитектуры. 5. **UNVERIFIED / ARCH-008–009:** перенос event-specific semantics из generic forecast и удаление persisted `observe` reference-path требуют проверки product semantics/совместимости. Наличие нескольких consumers и публичного параметра — реальные контраргументы против механического удаления. **Подсчёт:** actionable = `DELETE`, `SIMPLIFY`, `FIX` в разделе 4. **P0: 0; P1: 0; P2: 2; P3: 0. KEEP: 3; INVESTIGATE: 2.** Severity не превращена в порядок cleanup. Для KEEP использован P3, поскольку текущий дефект не заявляется; важность защищаемых инвариантов описана отдельно. Для INVESTIGATE severity — уровень рассмотрения гипотезы, не подтверждение сбоя. Runtime/performance impact не измерялся. ## 3. Inventory & coverage ### 3.1. Фактический layout и слои **Backend.** Manifest объявляет NestJS 12, TypeScript 6, Prisma 7 и better-sqlite3; schema задаёт SQLite, а не Postgres (`backend/package.json:40-66,104-105`, `backend/prisma/schema.prisma:1-9`). Прочитаны регистрации всех найденных `src/**/*.module.ts`: composition root, Auth, BankCards, BrokerageAccounts, Portfolio, Operations, Income, UpcomingEvents, Instruments, InstrumentSources, TbankConnection, TbankSync, TbankSyncRepository, Tbank/Moex/Ncr, Prisma и Logging. Это DI inventory, а не предположение по названиям директорий. ```text AppModule ├─ Auth / BankCards ├─ BrokerageAccounts → Portfolio ├─ Income → Operations ├─ Instruments → InstrumentSources + Portfolio ├─ UpcomingEvents → InstrumentSources + Portfolio ├─ TbankConnection → TbankSync └─ TbankSync → Portfolio + Operations + UpcomingEvents + InstrumentSources Personal source owners → TbankSyncRepositoryModule (нижний durable store) Business/source owners → TbankModule → gRPC client / access / membership InstrumentSources → MoexModule + NcrModule Global infrastructure: ConfigModule, PrismaModule, LoggingModule ``` Контекст legacy DI: зарегистрированный status service содержит также screen-command orchestration и зависимости source owners (`backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:42-52,54-311`), но HTTP controller вызывает только status (`backend/src/modules/investments/tbank-sync/tbank-sync.controller.ts:31-40`). Это часть фактической dependency map; решение о dead-code closure принадлежит направлению 04. Intended ownership сравнивался с принятым ADR 019: hourly walk обновляет personal sources, обычные GET локальны, Ledger/candles имеют узкие first-load исключения (`docs/adr/019-hourly-reads-and-online-instrument-reference.md:9-29`). Current ownership показан production traces ниже; расхождения read initiators оценены в ARCH-002. Актуальность docs/index рассматривается направлением 15. Важный контрпример предполагаемому циклу: `TbankSyncModule → PortfolioModule → TbankSyncRepositoryModule` не возвращается в `TbankSyncModule`. Store module регистрирует только repository (`backend/src/modules/investments/tbank-sync/tbank-sync-repository.module.ts:1-9`). Также `investment-sync-plan → income.api` достигает только pure `planIncomePeriod`, не `IncomeAnalyticsService` (`backend/src/modules/investments/income/income.api.ts:1`). Source import cycle, Nest module cycle и runtime DI cycle не отождествляются. Полный backend AST/DI graph checker не запускался; отсутствие всех возможных транзитивных source cycles не заявляется. **Frontend.** Manifest задаёт React/Vite/TypeScript, TanStack Query, openapi-fetch/openapi-react-query и private local packages (`frontend/package.json:39-62,112-116,128-130`). Реальное направление: ```text app/providers/routes → pages/{account,accounts,income,events,operations,...} pages → features/{auth,tbank-connection,instrument-search,instrument-details} pages → entities/{portfolio,income,broker-operation,brokerage-event,bond,...} pages → widgets/portfolio-structure features/entities → shared/{api,lib,i18n,ui} src consumers → packages/ui,icons; packages/ui → packages/tokens ``` Routes lazy-import page public APIs (`frontend/src/app/routes/AppRoutes/AppRoutes.tsx:9-103`), account VM собирает entity queries через публичные index (`frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:6-18,66-84`). Query options принадлежат entities; shared/api владеет transport, session-aware eligibility и resource projection. Пример: `frontend/src/entities/portfolio/api/usePortfolio.ts:11-25` → `frontend/src/shared/api/resource-query.ts:85-149` → `frontend/src/shared/api/authenticated-query.ts:34-50` → `frontend/src/shared/api/client.ts:53-119,284-290`. Размеры 420/1773 получены из единственного текущего запуска depcruise на `src packages`, с `doNotFollow: node_modules`, TS pre-compilation dependencies, source aliases и исключением package dist (`frontend/dependency-cruiser.cjs:78-87`, `frontend/tsconfig.dependency-cruiser.json:1-12`). Они включают объекты графа, в том числе test/story source, и не означают 420 runtime business modules. Правила проверяют imports, public APIs, cycles и direction (`frontend/dependency-cruiser.cjs:1-76`), но не ownership refresh policy. ### 3.2. Сквозные production traces | Capability | Controller/query → application → rules → storage/integration | Проверенная boundary | | --- | --- | --- | | Auth/session | App/AuthProvider → auth VM → shared authenticatedFetch; AuthController → AuthService → Session/JWT | В браузере один session owner и callback-based transport; cookie пишет controller; rotation CAS остаётся у AuthService. Evidence: `frontend/src/features/auth/model/auth.vm.ts:29-151`; `backend/src/modules/auth/presentation/auth.controller.ts:81-118`; `backend/src/modules/auth/application/auth.service.ts:81-114,166-201` | | Accounts/portfolio | Account VM → usePortfolio → PortfolioController → PortfolioService.read/saved → PortfolioRepository; отдельно hourly → PortfolioService.request → gRPC → mapper → publish | HTTP read локален; production producer и publisher сохранены отдельно от read composition. Evidence: `frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:66-84`; `backend/src/modules/investments/portfolio/presentation/portfolio.controller.ts:19-32`; `backend/src/modules/investments/portfolio/application/portfolio.service.ts:55-59,113-232,234-284` | | Instrument discovery | BondsPage VM → useBondSearch → BondCatalogService → CatalogDiscoveryService.onlineSearch → TbankClient; source calendars отдельно через CatalogEnrichmentService | Default reference read online; persisted observe branch остаётся доступен. Evidence: `frontend/src/pages/bonds/ui/BondsPage/BondsPage.vm.ts:9-13`; `frontend/src/entities/bond/api/queries.ts:97-104`; `backend/src/modules/investments/instruments/bonds/application/bond-catalog.service.ts:25-55`; `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:84-94,295-337` | | Sync/forecast | Native tick → reserveHourly → membership → portfolio → operations → events.refresh/get → ForecastService → ForecastRepository | Coordinator задаёт порядок, owners выполняют sources. Event GET также рассчитывает candidate и проходит write-capable resolve. Evidence: `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:48-65,76-110,127-175`; `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:54-173`; `backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:24-46,170-175` | | Banking operations | Frontend bank mutation → BankCardsController → BankOperationsService → operationInput/validateOperation → BankCardsRepository.write → SQLite; summary → GetBankSummaryUseCase → pure bankSummary | Banking не зависит от broker integration; transaction охватывает idempotency, refund validation, versioned write и response read. Evidence: `frontend/src/entities/bank-card/api/mutations.ts:29-82`; `backend/src/modules/banking/bank-cards/presentation/bank-cards.controller.ts:127-166`; `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:94-240`; `backend/src/modules/banking/bank-cards/application/get-bank-summary.use-case.ts:12-35` | ### 3.3. Ownership map — специальный результат Пять центральных capabilities ниже разделены по решениям/состояниям/записям, чтобы несколько технических participants не выдавать за нескольких owners. | Capability / решение и состояние | Current owner | Competing owner | Recommended owner | Evidence | | --- | --- | --- | --- | --- | | Auth/session: локальная identity, refresh hash, rotation | Backend AuthService; browser auth VM — только клиентская session | Конкурирующий session writer в прослеженных путях не найден; shared client получает callbacks | Сохранить это разделение; shared client не становится auth domain owner | `backend/src/modules/auth/application/auth.service.ts:81-114,166-201`; `frontend/src/features/auth/model/auth.vm.ts:29-115,143-151` | | Accounts/portfolio: создание/replacement connection и начального membership | TbankConnectionService.put, одна транзакция новой revision | TbankAccessService.publishMembership — последующее подтверждение текущей revision, а не competing replacement | Lifecycle подключения остаётся в connection module; последующие membership writes у существующего access owner | `backend/src/modules/investments/tbank-connection/tbank-connection.service.ts:31-72`; `backend/src/integrations/tbank/tbank-access.service.ts:148-237` | | Accounts/portfolio: право видеть счёт и membership freshness | TbankAccessService.local; source producer TbankMembershipService; refresh cadence у hourly | Income collectRanges инициирует refresh membership | Hourly инициирует повторное membership чтение; personal GET использует только local boundary | `backend/src/integrations/tbank/tbank-access.service.ts:260-288`; `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:113-125`; `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:428-435` | | Accounts/portfolio: positions, точные totals, сохранённый portfolio | PortfolioService/PortfolioRepository; AccountsService только строит summary | Legacy screen dispatcher содержит portfolio branch без найденного production entrypoint; это DI-контекст, не текущий refresh initiator | Portfolio source owner сохраняется; инициирование — hourly, projection — существующие readers | `backend/src/modules/investments/portfolio/application/portfolio.service.ts:179-198,234-284`; `backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts:116-184`; `backend/src/modules/investments/brokerage-accounts/brokerage-accounts.service.ts:16-39`; `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:99-115` | | Instrument discovery: поиск/identity и публичная projection | CatalogDiscoveryService + stock/bond catalog services | Default online и persisted observe представляют разные поколения read policy | Default online owner сохранить; удаление persisted compatibility пока INVESTIGATE | `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:84-167,220-292`; `backend/src/modules/investments/instruments/bonds/application/bond-catalog.service.ts:25-88` | | Instrument discovery: сохранённые calendars/quotes/candles | CatalogEnrichmentService/CatalogDiscoveryRepository и CandleService/CandleRepository | Metadata lookup хранит другую identity projection; само по себе не competing calendar owner | Сохранить source-specific owners, не сливать financial sources с online reference answers | `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:100-160,164-230`; `backend/src/modules/investments/instruments/application/candle.service.ts:101-124`; `backend/prisma/schema.prisma:148-229` | | Sync/forecast: момент запуска личной работы и durable attempt | TbankHourlyScheduler + TbankSyncRepository; Ledger — узкое first-load исключение | Income scheduling и readPlan history expansion — реальные read initiators; TbankSyncService.request/dispatch — legacy DI-контекст | Hourly — единственный repeat-refresh initiator; Ledger запускает только missing requested interval | `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:76-175`; `backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:92-124`; `specs/openspec/specs/tbank-data-sync/spec.md:9-22` | | Sync/forecast: window, event amounts, filtering и complete publication | UpcomingEventsService рассчитывает события; ForecastService фильтрует events; ForecastRepository выбирает overlapping window и публикует | Event semantics распределена между capability и generic portfolio forecast owner; generic publication имеет ещё stock/bond consumers | Сохранить один общий fenced publisher; event window/filter policy предпочтительно у Events, но перенос после semantic proof | `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:119-173,271-299`; `backend/src/modules/investments/portfolio/application/forecast.service.ts:98-123`; `backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:50-79,179-186` | | Banking operations: intent, refund eligibility, journal write, version | BankOperationsService внутри BankCards module; repository владеет общей DB transaction boundary | UI хранит draft/intent, но не authority для refund limit; competing server writer не найден | Сохранить cohesive transactional owner и pure money rules; не дробить transaction по картам/категориям/возвратам | `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:94-240,251-330`; `backend/src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts:35-66`; `backend/src/modules/banking/bank-cards/domain/rub-money.ts:12-38` | ### 3.4. Dependency/Boundary matrix — специальный результат | From → To | Фактическая зависимость и consumer | Сопоставление с intended boundary / оценка | | --- | --- | --- | | Frontend app → pages/features | Lazy routes и AuthProvider: `frontend/src/app/routes/AppRoutes/AppRoutes.tsx:9-103`; `frontend/src/app/ui/App.tsx:10-37` | Корректная верхняя composition root | | Frontend pages → entities/features | Account VM queries и screen timer: `frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:6-18,66-84` | Public imports соблюдены; semantic owner timer требует ARCH-003 | | Frontend entities → shared/api | usePortfolio/useBondSearch: `frontend/src/entities/portfolio/api/usePortfolio.ts:1-25`; `frontend/src/entities/bond/api/queries.ts:97-104` | Transport общий, endpoint identity/policy локальны; сохранить | | Frontend features/auth → shared transport | Callback registration: `frontend/src/features/auth/model/auth.vm.ts:143-151`; `frontend/src/shared/api/client.ts:28-35,122-125` | Обратный callback не является forbidden source import shared → feature | | Frontend src → private packages | App theme и exports: `frontend/src/app/ui/App.tsx:7,25-28`; `frontend/packages/ui/package.json:9-19,26-33` | Реальные boundaries для app/catalog consumers; не дополнительные runtime services | | Frontend packages → src | Запрет `frontend/dependency-cruiser.cjs:18-23` | В текущем source graph нарушений не найдено | | Backend HTTP → application | Portfolio/Income/Bank controllers: `backend/src/modules/investments/portfolio/presentation/portfolio.controller.ts:17-31`; `backend/src/modules/investments/income/income-analytics.controller.ts:24-79`; `backend/src/modules/banking/bank-cards/presentation/bank-cards.controller.ts:127-166` | Controller делегирует, identity из verified user; сохраняется | | Backend Income → Operations | `backend/src/modules/investments/income/income.module.ts:3-12`; `backend/src/modules/investments/income/income-analytics.service.ts:45-93` | Аналитика должна читать ledger data; скрытая scheduling branch — ARCH-002 | | Backend UpcomingEvents/Instruments → Portfolio forecast | `backend/src/modules/investments/upcoming-events/upcoming-events.module.ts:5-15`; `backend/src/modules/investments/instruments/stocks/application/stock-income.service.ts:85-94`; `backend/src/modules/investments/instruments/bonds/application/bond-income.service.ts:116-125` | Общее publication/coherence имеет несколько consumers; event-specific window protocol — ARCH-008 | | Backend TbankSync → resource owners | `backend/src/modules/investments/tbank-sync/tbank-sync.module.ts:14-25`; scheduler `:113-175` | Направление coordinator → owners обосновано; broad legacy DI описана как контекст inventory | | Backend resource owners → sync store | Portfolio/Operations/Events imports: `backend/src/modules/investments/portfolio/application/portfolio.service.ts:20-23`; `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:20-23`; `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:23-26` | Store module экспортирует repository и не импортирует coordinator. Цикл не доказан. Прямой файл вместо `*.api.ts` отступает от общего documented import profile, но это реально экспортированный provider, не доказательство плохого owner | | Backend business → Tbank integration | `backend/src/integrations/tbank/tbank.module.ts:10-25`; `backend/src/modules/investments/portfolio/portfolio.module.ts:3-21` | Client/cache/limiter регистрируются у одного integration owner | | Backend integration → business modules | Просмотр imports в integrations и `backend/src/integrations/tbank/tbank-access.service.ts:9-15` | В просмотренных import paths обратного обращения к business module нет; Prisma access — documented technical persistence, не HTTP/domain dependency | | Backend BankCards → Auth/Prisma/domain | `backend/src/modules/banking/bank-cards/bank-cards.module.ts:3-19`; `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:3-29` | Cohesive CRUD service использует tx client по локальному profile; обязательный repository wrapper на каждый query не нужен | | Backend domain → framework/storage | ESLint guard: `backend/eslint.config.mjs:126-168`; pure RUB rules `backend/src/modules/banking/bank-cards/domain/rub-money.ts:1-38` | Прямые imports ограничены; эта проверка не доказывает транзитивный граф или DI. Не выдаётся новый blanket lint verdict | | Backend → frontend contract | Nest metadata → предоставленный JSON → generated frontend types: `backend/scripts/generate-openapi.cjs:19-35`; `frontend/scripts/check-openapi.mjs:10-26` | Передаётся artifact, не shared TypeScript source соседнего checkout. Файлы snapshot на диске совпадают; fresh parity UNVERIFIED | | Specs/docs → application | Specs behavioral input; docs объясняют architecture: `specs/AI_INDEX.md:3-13`; `docs/README.md:52-67` | Не runtime/source dependency; intended policy сопоставлена с production ownership, documentation/index maintenance относится к audit 15 | Обнаруженные прямые imports экспортированных backend providers не приравниваются к доказанным DI/circular failures. Например, `backend/src/modules/investments/instruments/stocks/application/stock-screener.service.ts:10-13` читает integration implementation entry вместо `tbank.api.ts`; provider зарегистрирован в TbankModule, альтернативная implementation не заявлена. Смысл boundaries проверен по consumers, а не по наличию barrel-файла. ### 3.5. SRP, cohesion, DIP/ISP, OCP/LSP - **SRP/cohesion:** Auth session, connection lifecycle, source membership, portfolio publication и banking journal имеют разные причины изменения и реальные write boundaries. BankCardsService, BankOperationsService, ExpenseCategoriesService и summary use case в одном module не требуют отдельных Nest modules. Legacy screen dispatcher соединяет те же source owners на уровне DI; dead-code решение вынесено в cross-audit signal 04. - **Coupling:** Income зависит от read service, который также инициирует membership/full-history work; фронтенд-страницы зависят от connection feature ради периодических resource reads. Это проверенные связи, не вывод по имени класса. Forecast window меняется сразу в Events и generic portfolio persistence/service — ARCH-008. - **DIP/ISP:** Concrete clients/repositories допустимы и имеют test seams. `TbankConnectionService` использует `Pick` (`backend/src/modules/investments/tbank-connection/tbank-connection.service.ts:11-19`), а screener получает narrow access contract через `useExisting` (`backend/src/modules/investments/instruments/stocks/application/stock-screener.service.ts:50-62`; `backend/src/modules/investments/instruments/instruments.module.ts:24`). Это не второй client instance и не доказательство ненужности интерфейса. - **OCP/LSP:** not applicable для исследованных бизнес-границ: сменяемых production implementations одного domain contract не установлено. Stock/bond services — разные capabilities, а не заявленные взаимозаменяемые subclasses. Нарушение LSP по различию их ответов не придумано. ### 3.6. Границы покрытия Выборка risk-/cost-based: пять central capabilities, все Nest registrations, frontend import graph, три подробных page/query consumers (Account, Bonds, AccountEvents), auth/connection boundaries и banking write workflow. Основные гипотезы проверены по caller, source mechanism и контрпримеру. Не подсчитывались LOC savings, complexity scores или количество API calls. Не исследовались подробно UI state/rendering, duplicate fragments, test effectiveness, scheduler error correctness, API latency, dependency security, migrations и эксплуатационные данные. Эти области не закрываются данным audit 01. ## 4. Findings ### ARCH-002 — Ограничить read orchestration операции запрошенным missing interval - **Decision:** FIX. - **Severity:** P2. - **Confidence:** High. - **Evidence:** `backend/src/modules/investments/income/income-analytics.controller.ts:24-79`; `backend/src/modules/investments/income/income-analytics.service.ts:45-93`; `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:120-150,391-435`; `backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:46-124`; `specs/openspec/specs/tbank-data-sync/spec.md:9-22`; `specs/openspec/specs/brokerage-account-ledger/spec.md:131-144`. - **Runtime consumer:** frontend income queries не передают `sync=observe` (`frontend/src/entities/income/api/useIncome.ts:14-44`). IncomeController передаёт `observe=false`, затем summary/analytics вызывают `collectRanges`. Второй caller `readPlan` — production Ledger read через `backend/src/modules/investments/operations/presentation/brokerage-operations.controller.ts:22-38`. - **Claim:** разрешение читать personal result сейчас включает право инициировать более широкую личную синхронизацию. Граница first-load не удерживается общим application owner. - **Current mechanism:** при обычном Income GET `collectRanges` запрашивает membership, а после завершения запускает readPlan с `history: true`. Ledger тоже передаёт `history: true`. Общий readPlan сначала загружает missing target, затем вычисляет `accountHistory` и ставит в работу `remaining` вне targets. Это отдельная ветвь execution, не только metadata. Все production callers `readPlan` найдены: Ledger и collectRanges. - **Impact / actual cost:** экран аналитики может инициировать membership refresh; узкий Ledger interval может инициировать постороннюю историю счёта. Ответ продолжает возвращаться локально, но ownership работы и объём разрешённого side effect отличаются от narrow exception. **Impact unmeasured:** число фактических RPC, latency и частота этого состояния не измерялись. - **Why now:** source owners уже вызваны hourly coordinator (`backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:113-175`). Предыдущая broad-history policy пережила переход и создаёт вторую причину выполнения work. - **Minimum change:** убрать membership/scheduling из обычного income read; оставить ему local operations projection. В общем readPlan убрать branch расширения на `accountHistory`, сохранив coverage, first missing target, revision/access checks и source request. Повторный refresh mutable tail остаётся у hourly. Cold historical income должен честно возвращать incomplete/unavailable; если нужна самостоятельная first-load возможность для Income, принять это явно как изменение существующего исключения, не спрятать в generic read helper. - **Guarantees at risk:** полнота selected/previous/year comparisons, account access, cursor binding, atomic publication, невыдача частичных totals за полный результат. Удалять весь BrokerageOperationsSyncService, staging или first-load Ledger exception нельзя. - **Counterarguments / trade-offs:** history prefetch делает Income cold start удобнее; это реальная продуктовая польза, а не бессмысленная indirection. Полный сохранённый прошлый target сам по себе не повторно загружается: `planOperationRead` добавляет только missing fragments (`backend/src/modules/investments/operations/domain/operations-sync-plan.ts:174-195`). Поэтому finding не утверждает безусловный refresh всех интервалов. Старое ledger requirement всё ещё описывает upstream TTL (`specs/openspec/specs/brokerage-account-ledger/spec.md:97-110`), а новое `:131-140` задаёт first-load/local reads: конфликт нужно сделать явным. - **Verification or rollback:** controlled integration case с membership старше TTL и narrow missing historical interval: Income GET не создаёт membership work; Ledger work ограничен этим interval; complete previous interval остаётся local; incomplete income не превращается в ноль. Проверить оба callers общего helper, не только IncomeController. Откат source policy совместим со schema; product clarification — в существующем ADR/spec. - **Effort:** M. - **Related finding IDs:** ARCH-006. ### ARCH-003 — Вернуть периодическое чтение владельцу query - **Decision:** SIMPLIFY. - **Severity:** P2. - **Confidence:** High. - **Evidence:** `frontend/src/features/tbank-connection/model/screen-sync.ts:7-35,37-61`; `frontend/src/features/tbank-connection/index.ts:1-2`; `frontend/src/entities/bond/api/queries.ts:97-104`; `frontend/src/entities/portfolio/api/usePortfolio.ts:19-25`; `frontend/src/app/ui/App.tsx:13-21`. - **Runtime consumer:** Account VM `:78-84`, BondsPage VM `:9-13` и AccountEvents VM `:62-75` по путям из раздела 3. Остальные найденные consumers — Accounts, AccountIncome, AccountOperations, Stock и Bond page VMs. Это прямые вызовы feature public API, а не timer в тестовом коде. - **Claim:** connection feature владеет cadence всех переданных resource GET, хотя их inputs/eligibility/cache policy принадлежат queries. Это competing lifecycle owner, который можно убрать возможностями уже установленного TanStack Query. - **Current mechanism:** hook игнорирует `_scope`, сохраняет resources в ref и каждые 30 минут вызывает manual refetch всех ресурсов. В resource contract нет `isEnabled`; при этом useResourceQuery это поле предоставляет (`frontend/src/shared/api/resource-query.ts:132-146`). Query owners задают enabled/staleTime, App включает focus/reconnect refetch. Timer и эти policy owners действуют независимо. - **Impact / actual cost:** review inputs/query eligibility требует перехода из entity в стороннюю connection feature; Bonds query, отключённый для короткого поиска, всё равно передан timer. Manual refetch не следует automatic enabled policy. **Impact unmeasured:** число нежелательных requests, влияние на backend и время сопровождения не измерены. - **Why now:** screen descriptor больше не выбирает server work; сохранять screen-sync boundary для обычного чтения нет основания. Depcruise это пропускает, потому что `pages → features` разрешён. - **Minimum change:** перенести polling в options существующих query owners через `refetchInterval` и убрать page timer calls/неиспользуемые descriptors. Connection feature оставить владельцем только PUT/DELETE и успешного connection cache reset. Retry отдельного query остаётся в page VM. Если остаётся реальный общий retry use case, сохранить только его cohesion, не переносить timer целиком в новый общий hook. - **Guarantees at risk:** no polling для disabled/unauthenticated queries, отсутствие old-session/old-connection данных, сохранение cached successful result, background-tab semantics и ограничение частоты. Нельзя выдавать механическую замену таймера за полностью эквивалентный scheduling contract. - **Counterarguments / trade-offs:** общий screen timer сейчас синхронно предлагает чтение нескольких resources, что удобно для page composition. Query-native polling по observers может иначе вести себя в background tab и не делает multi-resource read атомарным. Source cadence и согласованность resource results принадлежат backend; общий frontend timer их всё равно не гарантирует. Product правило «не чаще 30 минут» нужно согласовать с mount/focus/reconnect, а не просто добавить второй interval. - **Verification or rollback:** fake-time/MSW через существующую boundary: короткий поиск и отсутствующий account/uid не вызываются, active query повторяется в принятом режиме, session/connection switch отменяет старый результат, повтор не инициирует backend screen command. Проверить focus и hidden-tab behavior. Откат query options/page imports, schema не меняется. - **Effort:** M. - **Related finding IDs:** ARCH-002, ARCH-005, ARCH-006. ### ARCH-005 — Сохранить разделение session owner и authenticated transport - **Decision:** KEEP. - **Severity:** P3. - **Confidence:** High. - **Evidence:** `frontend/src/features/auth/model/auth.vm.ts:29-115,121-151`; `frontend/src/shared/api/client.ts:28-35,53-119,122-125`; `frontend/src/shared/api/authenticated-query.ts:34-50`; `backend/src/modules/auth/application/auth.service.ts:166-201`; `backend/src/modules/auth/presentation/auth.controller.ts:81-118`. - **Runtime consumer:** App монтирует AuthProvider (`frontend/src/app/ui/App.tsx:31-37`); все generated authenticated requests проходят shared fetch, session-aware queries используют его session snapshot. Backend refresh endpoint вызывает CAS rotation и после успеха пишет cookie. - **Claim:** queue/single-flight/callback boundary здесь защищает реальные session transitions; это не пустой wrapper над fetch. - **Current mechanism:** auth VM владеет in-memory access token, cookie-write queue, refresh promise и generation; transport добавляет Bearer и делает ограниченный auth retry через callback. Backend conditional update сравнивает прежний refresh hash и требует одного победителя. - **Impact / actual cost:** frontend feature и shared transport имеют разные причины изменения без source import shared → feature. Сохранена граница session policy/HTTP mechanics. **Impact unmeasured:** runtime savings или полная race coverage здесь не измерялись. - **Why now:** при ARCH-003 нельзя одновременно упростить native polling и удалить session-aware eligibility/late-response protection. - **Minimum change:** не менять auth owner/queue ради сокращения файлов; не добавлять refresh loops в pages/entities. - **Guarantees at risk:** identity isolation, один refresh winner, недопущение old-session response, cookie ordering и logout cache clearing. - **Counterarguments / trade-offs:** эта модель ограничена текущей вкладкой; сама по себе не решает межвкладочную coordination. Это documented ceiling, а не основание добавлять distributed token store/Web Locks. - **Verification or rollback:** при будущих изменениях — существующие `frontend/src/features/auth/model/auth.test.tsx`, `frontend/src/shared/api/client.test.ts` и configured backend auth/SQLite scenarios. В этом аудите tests не запускались; rollback не требуется. - **Effort:** S (сохранение existing boundary, без текущего patch). - **Related finding IDs:** ARCH-003, ARCH-006. ### ARCH-006 — Сохранить singleton source owners и fenced publication - **Decision:** KEEP. - **Severity:** P3. - **Confidence:** High. - **Evidence:** `backend/src/integrations/tbank/tbank.module.ts:10-25`; `backend/src/modules/investments/instruments/instrument-sources.module.ts:12-21`; `backend/src/modules/investments/tbank-sync/tbank-sync-repository.module.ts:1-9`; `backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts:116-184`; `backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:35-46,80-159`; `backend/src/modules/investments/tbank-connection/tbank-connection.service.ts:31-72`. - **Runtime consumer:** hourly вызывает source owners, personal readers используют их snapshots/status, Events/StockIncome/BondIncome используют общий forecast publisher. Connection replacement — отдельный producer новой revision, а не обход publication owner. - **Claim:** DI/store split и conditional publication защищают реальные source lifecycles. Удалять их вместе с legacy dispatcher нельзя. - **Current mechanism:** Tbank client/access/membership зарегистрированы у одного module owner; source work имеет user/revision/attemptId; portfolio publication claim и write находятся в transaction; forecast проверяет membership/portfolio/source versions. Sync repository module отделяет durable store от coordinator, устраняя обратный module edge. - **Impact / actual cost:** сохраняется общий client/limiter и один publication fence вместо переносов cache/private state в consumers. **Impact unmeasured:** производительность и полнота lifecycle correctness не подтверждены этим архитектурным проходом. - **Why now:** ARCH-002 ограничивает read initiators, а не независимые source owners; ARCH-008 не должен размножить publisher по каждому forecast. - **Minimum change:** сохранить concrete owners, module registration и DB fences; исправления scheduler/policy выполнять внутри текущих границ. Не вводить interface/adapter на каждый provider и не сливать user connection lifecycle с low-level RPC client. - **Guarantees at risk:** connection isolation, CAS/conditional publication, complete snapshot preservation, shared retry/limiter и process lifecycle. - **Counterarguments / trade-offs:** global Prisma provider и concrete DI не равны идеальной строгой layer inversion, но соответствуют существующей single-process monolith topology. ProcessRunId не distributed lease; scheduler прямо фиксирует ceiling (`backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:28-31`). Multi-process deployment потребовал бы отдельного решения, не текущего cleanup. - **Verification or rollback:** будущий patch обязан доказать late publication после replacement, superseded attempt и rollback на изолированной SQLite; сохранить target source-owner contracts. В аудите — static verification, не proof реальных races; rollback не требуется. - **Effort:** S (без изменения защищённой boundary). - **Related finding IDs:** ARCH-002, ARCH-003, ARCH-005, ARCH-008, ARCH-009. ### ARCH-007 — Сохранить cohesive banking transaction и точные money rules - **Decision:** KEEP. - **Severity:** P3. - **Confidence:** High. - **Evidence:** `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:94-240,251-330`; `backend/src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts:35-66,96-123`; `backend/src/modules/banking/bank-cards/domain/rub-money.ts:12-38`; `backend/src/modules/banking/bank-cards/application/get-bank-summary.use-case.ts:12-35`; `frontend/src/entities/bank-card/api/mutations.ts:29-82`. - **Runtime consumer:** BankCardsController вызывает journal operations и summary use case; frontend mutations передают intent и после server outcome invalidируют bank GET subtree. - **Claim:** DB transaction callback с concrete Prisma tx client и локальные domain helpers — достаточная граница для банковского use case. Декомпозиция каждой validation/query в отдельный service не даст доказанной пользы. - **Current mechanism:** create intent fingerprint сравнивается после owner check, tombstone не позволяет replay recreate; refund validation и version claim выполняются внутри той же write boundary. RUB normalization переводит decimal string в bigint-копейки с проверками currency/scale/range. Summary получает согласованные inputs через repository и вызывает pure calculation. - **Impact / actual cost:** сохраняется локально обозримый атомарный workflow без broker coupling. **Impact unmeasured:** DB latency, maintenance cost и фактические concurrent outcomes не измерялись. - **Why now:** audit complexity не должен объявлять fingerprints/CAS/tx лишней indirection или переносить journal authority во frontend. - **Minimum change:** сохранить transactional owner и money boundary; не вводить generic repository, port на каждый query или module на каждую banking entity. Read-side optimizations — отдельный предмет audit 07. - **Guarantees at risk:** точность денег, refund limit, rollback, user ownership, lost-response replay protection, immutable operation identity и versions. - **Counterarguments / trade-offs:** repository сейчас также возвращает application conflicts (`card(active)`), а CRUD service делает direct queries. Это documented local compromise, не чистая domain-only модель. Перенос ради одинаковой структуры без самостоятельного consumer только создаст passthrough layer и может разорвать transaction. - **Verification or rollback:** при изменении — существующие configured banking SQLite cases для refund, intent replay, versions/rollback и pure money boundary. Отсутствие запуска здесь явно сохраняется как limitation; текущий patch/rollback не нужен. - **Effort:** S (сохранить, без нового кода). - **Related finding IDs:** —. ### ARCH-008 — Уточнить event-specific boundary общего forecast publisher - **Decision:** INVESTIGATE. - **Severity:** P2. - **Confidence:** Medium. - **Evidence:** `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:152-173,271-299`; `backend/src/modules/investments/portfolio/application/forecast.service.ts:18-65,98-123`; `backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:50-79,179-186`; `backend/src/modules/investments/instruments/stocks/application/stock-income.service.ts:85-94`; `backend/src/modules/investments/instruments/bonds/application/bond-income.service.ts:116-125`. - **Runtime consumer:** EventController → Events.get и scheduler refreshEvents → Events.get; ещё два независимых consumers generic publisher — StockIncome и BondIncome. - **Claim:** распределение event semantics между Events, generic ForecastService и repository доказано. Выгода отдельной write/read boundary и конкретное правило latest/window selection — **UNVERIFIED**. - **Current mechanism:** Events кодирует account/window в scopeKey; repository раскодирует его для overlapping fallback; ForecastService снова раскодирует для event filtering и пересчитывает currency totals. `result` одновременно resolve/publish и response projection. GET сохраняет candidate, хотя не делает upstream RPC. - **Impact / actual cost:** изменение смысла event window требует учитывать три owners/файла, generic storage знает private wire-like scope protocol. **Impact unmeasured:** стоимость поддержки и runtime failure rate не измерены; это не основание объявлять общий publisher бесполезным. - **Why now:** hourly owner и local GET нуждаются в понятном разграничении «публиковать новый полный forecast» и «отфильтровать сохранённый». Оно важнее механического перемещения файлов portfolio → events. - **Minimum change:** сначала определить latest-complete forecast identity и window semantics. Если подтверждено, перенести только event window/filter policy в существующий Events owner и дать repository явный selection input; общий conditional publisher и stock/bond consumers сохранить. Новый ForecastModule/ports/CQRS не обоснован. - **Guarantees at risk:** partial result fallback, membership/source-version coherence, точные currency totals, account/all scope и late publication. - **Counterarguments / trade-offs:** общий publisher реально обслуживает несколько forecast capabilities и содержит защитные checks. Local calculation/write внутри GET не равнозначны personal sync/RPC. Repository может обоснованно выбирать fallback по persistence identity; сначала нужно доказать, что текущая window policy не сама intended contract. - **Verification or rollback:** самый дешёвый решающий proof — controlled два последовательных complete forecasts с новым днём, затем narrow и non-overlap GET; проверить время, totals, account/all и preservation при partial update. Correctness вопрос selection передан audit 06. После согласования boundary — reversible local source diff, без удаления schema. - **Effort:** M. - **Related finding IDs:** ARCH-006. ### ARCH-009 — Не удалять persisted observe path без compatibility evidence - **Decision:** INVESTIGATE. - **Severity:** P2. - **Confidence:** Medium. - **Evidence:** `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:66-94,143-174,220-267`; `backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts:58-82,112-119`; `frontend/src/shared/api/resource-query.ts:69-127,132-149`; `specs/openspec/specs/online-instrument-information/spec.md:8-17`; `docs/adr/019-hourly-reads-and-online-instrument-reference.md:22-26`. - **Runtime consumer:** HTTP `sync=observe` переводит instrument reads в persisted branch; обычный frontend Bonds query не передаёт этот параметр. Backend financial calendars/candles действительно используют source repository независимо от публичного reference search. - **Claim:** dual reference read policy существует, но отсутствие внешних consumers observe/status и безопасный объём удаления — **UNVERIFIED**. Нельзя считать все persisted instrument sources dead из-за online default. - **Current mechanism:** default detail/search/ticker работают online, observe читает catalog discovery/enrichment snapshots. Frontend resource wrapper всё ещё предоставляет специальный observe method с transport-mode substitution. - **Impact / actual cost:** поддерживаются два пути identity/metadata ответа. **Impact unmeasured:** размер реально устранимого графа, LOC, память и dependency savings не измерены. - **Why now:** online default и public observe используют разные read paths. Для выбора boundary нужно отличить reference compatibility от source данных, необходимых текущим calendars/forecasts/candles. - **Minimum change:** собрать compatibility evidence для public reference observe contract; при подтверждении отсутствия необходимости удалить только его старую discovery orchestration/transport mode. Не удалять общую schema, source rows, financial calendar owner или candle FK без отдельного решения. - **Guarantees at risk:** existing public query contract, instrument identity, persisted financial sources и стабильные labels личных snapshots. - **Counterarguments / trade-offs:** tests и HTTP parameter являются реальными consumers/contract surface, даже если текущая SPA использует online default. Accepted ADR прямо требует отдельной совместимой миграции старых reference rows; отсутствие production call site внутри SPA не доказывает отсутствие других клиентов или FK consumers. - **Verification or rollback:** offline inventory public contract и provider call sites, подтверждение владельцем списка внешних consumers; затем targeted reference HTTP cases и source calendar/candle proof. Production telemetry/реальные API в аудите не запрашивались. Пока решение INVESTIGATE, deletion/rollback не выполняются. - **Effort:** M. - **Related finding IDs:** ARCH-006. ## 5. DELETE / SIMPLIFY / KEEP / INVESTIGATE | Decision | IDs | Краткий итог | | --- | --- | --- | | DELETE | — | Подтверждённых архитектурных deletion findings в собственном scope нет | | SIMPLIFY | ARCH-003 | Native polling у query owner вместо screen timer в connection feature | | FIX | ARCH-002 | Narrow first-load boundary операций | | KEEP | ARCH-005 | Session owner, single-flight/cookie queue, callback authenticated transport, backend rotation CAS | | KEEP | ARCH-006 | Singleton source owners, lower durable store module, connection/attempt fences и общий conditional publisher | | KEEP | ARCH-007 | Banking transaction/replay/version boundary и decimal/bigint money rules | | INVESTIGATE | ARCH-008 | Event-specific window/selection/read-write ownership; generic publisher имеет реальные consumers | | INVESTIGATE | ARCH-009 | Public observe compatibility и persisted reference orchestration; financial sources не объявлены dead | ### Valid architectural decisions — специальный результат 1. **Modular monolith + concrete providers.** AppModule явно собирает capabilities (`backend/src/app.module.ts:18-36`). Установка обязательных ports/adapters на каждый класс не решит найденные initiator conflicts. 2. **Отдельные connection lifecycle и integration.** PUT валидирует upstream до DB transaction и атомарно публикует новую revision/membership (`backend/src/modules/investments/tbank-connection/tbank-connection.service.ts:31-72`). Это не тот же owner, что shared gRPC protocol/client. 3. **Sync repository module ниже coordinator.** Его существование обрывает предполагаемую циклическую зависимость resource → coordinator (`backend/src/modules/investments/tbank-sync/tbank-sync-repository.module.ts:1-9`). Наличие одного класса не делает module лишним. 4. **Общий fenced publication, а не независимый write каждого HTTP reader.** Сохранить проверки dependency versions и fallback на complete result (`backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:35-46,80-159`). 5. **Banking journal остаётся локальной транзакционной capability.** Fingerprint, tombstone, version и refund checks имеют разные защитные роли; common transaction сохраняет их совместно — ARCH-007. 6. **Frontend layer/public API + верхняя page composition.** Entity queries переиспользуются account/details pages; большой связный VM не нужно дробить только по размеру. Green dependency graph не выбирает owner policy. 7. **Private UI/tokens foundation для app и catalog.** Два production/tooling consumers используют один theme entry: `frontend/src/app/ui/App.tsx:7,25-28` и `frontend/.storybook/preview.tsx:14,27-35`. Package boundary здесь оправдана; independent registry/runtime service не требуется. 8. **Artifact вместо shared source между repo.** Backend владеет исполняемым Nest contract; frontend использует предоставленный JSON и offline types, specs — behavioral requirements, docs — explanations (`backend/scripts/generate-openapi.cjs:19-35`; `frontend/scripts/check-openapi.mjs:10-26`; `specs/AI_INDEX.md:11-13`). Добавление соседнего checkout в CI не требуется. ## 6. Cost & benefit | ID | Ожидаемый класс экономии / пользы | Фактическое измерение | Обязательная regression boundary | | --- | --- | --- | --- | | ARCH-002 | Maintenance/предсказуемость side effects; потенциально runtime upstream budget | not measured; реальная frequency/RPC/latency неизвестны | Narrow first-load, access, complete coverage, no partial income totals | | ARCH-003 | Cognitive load, lifecycle ownership; потенциально меньше ненужных GET | not measured; нельзя обещать ускорение или уменьшение requests в процентах | Enabled/session eligibility, polling cadence, cached result/late response | | ARCH-005–007 | Сохранение correctness/security/data guarantees без новых layers | not measured; экономия не заявляется | Auth CAS, connection fencing, banking atomicity/replay/money | | ARCH-008–009 | Возможное уменьшение смешанных semantics/compatibility paths | not measured; решение UNVERIFIED | Complete forecast, public contract, financial source/FK consumers | CI-minute saving, dependency removal и runtime ускорение не подтверждены. Никаких новых dependencies для предложенных boundary changes не требуется. Все количества findings подсчитаны по decision rows раздела 4; остальные числа — вывод depcruise или constants исходников с указанными путями. **Complexity-only проход ponytail-audit в архитектурном scope:** - `native:` screen GET timer/descriptor. Замена: `refetchInterval` существующих query owners, с явной cadence policy. [`frontend/src/features/tbank-connection/model/screen-sync.ts:15-35`, ARCH-003] `stdlib:`, `yagni:` и `shrink:` не добавлены ради заполнения списка: одна implementation, небольшой public API или concrete repository сами по себе не являются достаточным основанием удалить boundary. Correctness-related readPlan ограничение находится в ARCH-002, не выдано за вывод skill. **net:** возможное сокращение строк **не измерено**; кандидаты на удаление dependencies **не установлены**. Числовой `-N lines` без подготовленного diff был бы выдуманной метрикой. ## 7. Top actions Подтверждённые действия сгруппированы по value/risk/effort, а не severity cleanup. Новые архитектурные слои для них не нужны. ### Quick wins Отдельных low-risk quick wins в оставшихся архитектурных findings не установлено: оба actionable изменения требуют boundary proof. ### Решения с test/product confirmation 1. **ARCH-002 / M:** сузить общий operations readPlan до missing targets, убрать Income membership/scheduling. До acceptance явно решить cold historical Income UX; proof должен охватить оба production callers. 2. **ARCH-003 / M:** передать cadence query owners и убрать connection timer. Согласовать focus/reconnect/mount/hidden-tab смысл правила 30 минут и проверить disabled/session cases существующим MSW/fake-time boundary. ARCH-008/009 не представлены как подтверждённые deletion actions: сначала дешёвый semantic/compatibility proof из раздела 9. ### Minimal boundary changes — специальный результат | ID | Текущая boundary | Минимальная достаточная альтернатива | Что сохраняется | | --- | --- | --- | --- | | ARCH-002 | Resource read неявно разрешает membership и broad history scheduling | Income — read-only; Ledger — missing requested interval; hourly — repeat refresh | Existing read service, sync worker, coverage, staging/CAS | | ARCH-003 | Connection feature инициирует timer для unrelated query resources | Query options владеют periodic GET; page VM выбирает resources/inputs; connection feature — lifecycle mutation/reset | Generated keys, shared transport, existing session/connection boundaries | | ARCH-008 (условно) | Generic portfolio forecast знает events window/filter semantics | Events задаёт event selection/projection input; existing publisher сохраняет common fences | Несколько forecast consumers; изменение после semantic proof | | ARCH-009 (условно) | Public reference observe смешан с persisted source machinery | Удалить только доказанно ненужную compatibility orchestration | Financial calendars, candles, private snapshot labels и FK | Цена предложений — local source diffs и targeted boundary proof. Дополнительных runtime containers, очередей, generic scheduler, CQRS, обязательных interfaces и новых CI analyzers не предлагается. ## 8. Cross-audit signals - **Audit 04 (бывший ARCH-001):** dead-code closure manual dispatcher — `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:54-311`; production HTTP entry предоставляет только status: `backend/src/modules/investments/tbank-sync/tbank-sync.controller.ts:31-40`. - **Audit 15 (бывший ARCH-004):** stale Container/read-index относительно hourly ownership — `docs/architecture/containers.md:142-148`, `specs/AI_INDEX.md:15-31`; intended policy: `docs/adr/019-hourly-reads-and-online-instrument-reference.md:9-29`. - **Audit 06:** forecast next-day/fallback selection и GET publication semantics — `backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:50-79`; architectural ownership отмечен ARCH-008, correctness здесь не воспроизводился. - **Audit 06:** hourly termination/persistence outcome — `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:93-110,178-218`; отдельный implementation/lifecycle вопрос внутри сохраняемого owner. - **Audit 06/07:** calendar refresh force/TTL policy — `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:182-239`, `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:164-193`; не основание удалять source boundary. - **Audit 07:** online search fan-out и banking list loader — `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:295-317`, `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:333-365`; performance не измерялся. - **Audit 08:** ordinary Income GET и scheduling proof вместо одного observe path — `backend/test/tbank-income-local.e2e-spec.ts:249-278`; эффективность suites отдельно не оценивалась. - **Audit 05/13:** timer manual-refetch eligibility при коротком поиске и смене session — `frontend/src/features/tbank-connection/model/screen-sync.ts:28-35`, `frontend/src/entities/bond/api/queries.ts:97-104`; архитектурный owner — ARCH-003, UI/security последствия требуют своего proof. ## 9. Open questions / verification plan | Вопрос, который меняет решение | Самая дешёвая решающая проверка | Как меняется решение | | --- | --- | --- | | Может ли cold historical Income самостоятельно загружать прошлые периоды? | Product confirmation существующего tbank-data-sync исключения; controlled test ordinary Income GET с cold coverage/stale membership | ARCH-002 остаётся FIX; либо read-only Income, либо явно расширенное узкое first-load requirement, без membership/full-history prefetch | | Что означает 30-minute client cadence для focus/reconnect/mount/background? | Согласовать формулировку ADR 019 `:27-29`; fake-time/MSW active/disabled query и visibility cases | Определяет query options ARCH-003; не оправдывает connection-owned timer | | Как выбрать latest complete event forecast независимо от запрошенного окна? | Два successive-day complete publications, narrow/non-overlap GET и partial update; проверить account/all totals и timestamps | ARCH-008 может стать SIMPLIFY/FIX с переносом только event-specific policy; общий fenced publisher KEEP | | Нужен ли public instrument `sync=observe` внешнему consumer/старому сохранённому work? | Offline inventory OpenAPI/callers и подтверждение владельцем consumer compatibility; targeted reference/status cases | ARCH-009 становится DELETE/SIMPLIFY только для совместимо удаляемого path; source schema не удаляется автоматически | | Свеж ли предоставленный OpenAPI artifact относительно этих HEAD? | В отдельной разрешённой implementation/verification сессии штатная backend generation/parity и frontend offline check | Здесь сравнение только existing files; новые checker/shared-source dependency не предлагаются | **Review completeness перед завершением:** подтверждены девять общих разделов, Ownership map, Dependency/Boundary matrix, valid architectural decisions, minimal boundary changes и финальная таблица. По scoped `rg -c` найдено семь finding headers; каждое из четырнадцати обязательных полей присутствует семь раз, распределение по findings сверено вручную. Actionable findings — только ARCH-002 и ARCH-003; KEEP — ARCH-005–007, INVESTIGATE — ARCH-008–009. Внутренние related IDs ссылаются только на эти семь findings; вынесенные вопросы 04/15 не входят в counts и consolidation. Сверка source evidence/consumer/counterarguments проведена статически; неподтверждённые решения отмечены UNVERIFIED. После сохранения повторены HEAD/status и `git diff --check` во всех пяти checkout: source/config не изменились. Новый ignored отчёт отдельно прошёл whitespace-проверку; routing затрагивает только root audit artifact. Boy Scout review текста не выявил новых source changes, новых механизмов или неподтверждённых числовых оценок экономии. Application build/full-suite для этого artifact не запускались. ## Final machine-readable consolidation table | ID | Decision | Severity | Confidence | Repo | Files | One-line impact | Effort | Related IDs | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | ARCH-002 | FIX | P2 | High | backend | src/modules/investments/operations/application/brokerage-operations.service.ts; src/modules/investments/operations/application/brokerage-operations-sync.service.ts; src/modules/investments/income/income-analytics.service.ts | Personal reads могут инициировать membership и историю вне requested interval | M | ARCH-006 | | ARCH-003 | SIMPLIFY | P2 | High | frontend | src/features/tbank-connection/model/screen-sync.ts; src/entities/bond/api/queries.ts; src/entities/portfolio/api/usePortfolio.ts; src/app/ui/App.tsx | Connection timer конкурирует с query lifecycle owner и не учитывает eligibility | M | ARCH-002,ARCH-005,ARCH-006 | | ARCH-005 | KEEP | P3 | High | frontend,backend | frontend/src/features/auth/model/auth.vm.ts; frontend/src/shared/api/client.ts; backend/src/modules/auth/application/auth.service.ts | Session/transport split сохраняет single-flight, identity isolation и rotation CAS | S | ARCH-003,ARCH-006 | | ARCH-006 | KEEP | P3 | High | backend | src/integrations/tbank/tbank.module.ts; src/modules/investments/tbank-sync/tbank-sync-repository.module.ts; src/modules/investments/portfolio/infrastructure/portfolio.repository.ts; src/modules/investments/portfolio/infrastructure/forecast.repository.ts | Singleton owners и fenced publication сохраняют source lifecycle и connection isolation | S | ARCH-002,ARCH-003,ARCH-005,ARCH-008,ARCH-009 | | ARCH-007 | KEEP | P3 | High | frontend,backend | backend/src/modules/banking/bank-cards/application/bank-operations.service.ts; backend/src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts; backend/src/modules/banking/bank-cards/domain/rub-money.ts; frontend/src/entities/bank-card/api/mutations.ts | Cohesive transaction защищает money, refunds, versions и replay | S | - | | ARCH-008 | INVESTIGATE | P2 | Medium | backend | src/modules/investments/upcoming-events/upcoming-events.service.ts; src/modules/investments/portfolio/application/forecast.service.ts; src/modules/investments/portfolio/infrastructure/forecast.repository.ts | UNVERIFIED: event semantics распределена; новая write/read boundary требует semantic proof | M | ARCH-006 | | ARCH-009 | INVESTIGATE | P2 | Medium | backend,frontend | backend/src/modules/investments/instruments/application/catalog-discovery.service.ts; backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts; frontend/src/shared/api/resource-query.ts | UNVERIFIED: удаление persisted observe path требует compatibility/FK proof | M | ARCH-006 |