Files
main/audits/2026-10-11/reports/01-architecture-and-module-boundaries-report.md
codex-bot d07fbfabb1 docs: archive technical audit 2026-10-11 in develop (#36)
Переносит архив `audits/2026-10-11/` (19 файлов) из codex/audit-remediation-2026-10-11 в develop и добавляет точечный allowlist в .gitignore.
2026-10-17 19:38:03 +03:00

78 KiB
Raw Permalink Blame History

Аудит 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, а не предположение по названиям директорий.

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). Реальное направление:

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<TbankClient, 'validateToken'> (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<T> одновременно 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