Переносит архив `audits/2026-10-11/` (19 файлов) из codex/audit-remediation-2026-10-11 в develop и добавляет точечный allowlist в .gitignore.
78 KiB
Аудит 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. Это не отказ от аудита из-за отсутствия графа.
Выполненные проверки:
- 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. - Чтение manifests, module registrations, public exports, controllers, application owners, relevant persistence/schema, query consumers и контраргументов; поиск через Read/Glob/Grep из соответствующих repo paths.
- В 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). - Из root:
git diff --no-index --stat "backend/openapi.json" "frontend/src/shared/api/spec/openapi.json". Exit 0, различий файлов на диске нет. Это не свежая генерация и не доказательство runtime schema parity. - Context7: документация TanStack Query
/tanstack/queryпо polling,refetchIntervalи отличию automatic fetching от manualrefetch. Справочный запрос не обращался к прикладным финансовым 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
- Факт: модульный NestJS monolith и локальный frontend FSD profile жизнеспособны. Прослеженные Nest module imports не образуют цикла; frontend dependency-cruiser не обнаружил циклов/forbidden imports по текущим правилам. Не доказана необходимость CQRS, обязательных ports, дополнительного state store или микросервисов.
- Подтверждённый boundary risk / ARCH-002: read orchestration операций
шире разрешённой первой загрузки. Income GET инициирует membership work,
а общий
readPlanсhistory: trueможет загрузить отсутствующую историю вне запрошенного периода. Это конкурирующая refresh policy рядом с hourly owner, а не недостаток слоёв. - Подтверждённый ownership drift / ARCH-003: периодический GET принадлежит
одновременно query owner, глобальному QueryClient и timer в feature
подключения. Разрешённый import
pages → featuresне делает этот owner осмысленным; timer не знает query eligibility. - KEEP / ARCH-005–007: auth coordination, account/connection fences, общая conditional publication и banking transaction/replay boundaries имеют конкретных production consumers. Их удаление не является допустимым сокращением архитектуры.
- UNVERIFIED / ARCH-008–009: перенос event-specific semantics из generic
forecast и удаление persisted
observereference-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. Второй callerreadPlan— 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 callersreadPlanнайдены: 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 — специальный результат
- Modular monolith + concrete providers. AppModule явно собирает
capabilities (
backend/src/app.module.ts:18-36). Установка обязательных ports/adapters на каждый класс не решит найденные initiator conflicts. - Отдельные 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. - Sync repository module ниже coordinator. Его существование обрывает
предполагаемую циклическую зависимость resource → coordinator
(
backend/src/modules/investments/tbank-sync/tbank-sync-repository.module.ts:1-9). Наличие одного класса не делает module лишним. - Общий fenced publication, а не независимый write каждого HTTP reader.
Сохранить проверки dependency versions и fallback на complete result
(
backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:35-46,80-159). - Banking journal остаётся локальной транзакционной capability. Fingerprint, tombstone, version и refund checks имеют разные защитные роли; common transaction сохраняет их совместно — ARCH-007.
- Frontend layer/public API + верхняя page composition. Entity queries переиспользуются account/details pages; большой связный VM не нужно дробить только по размеру. Green dependency graph не выбирает owner policy.
- 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 не требуется. - 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
- ARCH-002 / M: сузить общий operations readPlan до missing targets, убрать Income membership/scheduling. До acceptance явно решить cold historical Income UX; proof должен охватить оба production callers.
- 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 |