docs: archive technical audit 2026-10-11 in develop (#36)
Conventional commits / commits (pull_request) Successful in 13s

This commit is contained in:
2026-10-17 19:30:38 +03:00
parent 0bdc409ed0
commit 2b63bde934
20 changed files with 10749 additions and 0 deletions
@@ -0,0 +1,820 @@
# Аудит 01 — Architecture & Module Boundaries
## 1. Audit metadata
- **Audit ID/name:** 01 / Architecture & Module Boundaries.
- **Дата:** 2026-10-11.
- **Исполнитель:** один независимый исполнитель направления 01. Другие
субагенты не запускались, новые отчёты других направлений не читались.
- **Workspace:** `/Users/ksv741/Project/vibe/WealthLedger`.
- **Режим:** исследование read-only; единственная запись — этот отчёт вне
четырёх самостоятельных checkout.
| Checkout | Абсолютный путь | Проверенный HEAD | Начальный status | Итоговый status после записи отчёта |
| --- | --- | --- | --- | --- |
| root | `/Users/ksv741/Project/vibe/WealthLedger` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | clean | clean |
| frontend | `/Users/ksv741/Project/vibe/WealthLedger/frontend` | `c8821200ce699dab1ce65360d1968d5877a2e78b` | clean | clean |
| backend | `/Users/ksv741/Project/vibe/WealthLedger/backend` | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` | clean | clean |
| specs | `/Users/ksv741/Project/vibe/WealthLedger/specs` | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` | clean | clean |
| docs | `/Users/ksv741/Project/vibe/WealthLedger/docs` | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` | clean | clean |
SHA/status получены самостоятельно командами `git rev-parse HEAD` и
`git status --short --untracked-files=all` из каждого владельца. Пустой вывод
status означает отсутствие staged, unstaged и видимых untracked изменений;
ignored файлы этим не инвентаризируются.
**Инструкции и skills.** Первым загружен установленный `ponytail-audit`.
Применён его complexity-only проход: поиск недоступной orchestration,
лишней indirection и механизма, заменяемого возможностью установленного stack.
Correctness-сигналы отделены от этого прохода. Полная структура отчёта следует
протоколу `audit-prompts/01-architecture-and-module-boundaries.md`, прочитанному
целиком. Прочитаны AGENTS каждого checkout, местные architecture/engineering
и активированные data, auth/security, integrations, HTTP/errors, testing,
frontend TypeScript/React и UI conventions. Root conventions использованы
только для root и сохранения отчёта. Дополнительно применены
`feature-sliced-design` с reference `layer-structure.md` и
`verification-before-completion`. Локальный FSD profile не заменён общими
рекомендациями skill.
**Graphify limitation.** Skill загружен, но существующий
`graphify-out/graph.json` отсутствует в root, frontend, backend, specs и docs
по проверке известных абсолютных путей. Query существующего графа невозможен.
Графы и дополнительные artifacts не генерировались; выполнена статическая
трассировка source/imports/DI. Это не отказ от аудита из-за отсутствия графа.
**Выполненные проверки:**
1. Git HEAD/status в пяти checkout в начале, перед сохранением и после
сохранения; SHA совпадают, source/config trees чистые.
`git diff --check` в каждом checkout — без ошибок. Сам ignored отчёт
отдельно проверен `git diff --no-index --check /dev/null audit-results/01-architecture-and-module-boundaries-report.md` — без whitespace errors.
2. Чтение manifests, module registrations, public exports, controllers,
application owners, relevant persistence/schema, query consumers и
контраргументов; поиск через Read/Glob/Grep из соответствующих repo paths.
3. В frontend, после активации местной Node:
`source "$HOME/.nvm/nvm.sh" && nvm use --silent && ./node_modules/.bin/depcruise src packages --config dependency-cruiser.cjs --output-type err-long`.
Exit 0: **420 modules, 1773 dependencies, no dependency violations**.
Это прямой запуск установленного анализатора; `npm run deps` не запускался,
потому что его pre-step собирает packages
(`frontend/package.json:24-26,33`).
4. Из root:
`git diff --no-index --stat "backend/openapi.json" "frontend/src/shared/api/spec/openapi.json"`.
Exit 0, различий файлов на диске нет. Это не свежая генерация и не
доказательство runtime schema parity.
5. Context7: документация TanStack Query `/tanstack/query` по polling,
`refetchInterval` и отличию automatic fetching от manual `refetch`.
Справочный запрос не обращался к прикладным финансовым API.
**Уточнение scope:** dead-code и documentation/index вопросы перенесены в
короткие Cross-audit signals для направлений 04 и 15. При корректировке
перечитан и изменён только этот отчёт; проверки source graph и OpenAPI из
первичного аудита повторно не запускались. Counts, related IDs и таблицы
относятся к оставшимся архитектурным findings.
**Не запускались:** install, build, codegen/OpenAPI generation, migrations,
application startup, full-suite/verify, SQLite/E2E/unit tests, нагрузочные
проверки, commit/PR и реальные внешние API. `references/**`, dated audits,
архивы и исходники не изменялись. Предыдущий
`audit-results/technical-audit-2026-10-11.md` прочитан только как реестр
гипотез; его результаты измерений не использованы вместо текущих проверок.
Все `path:line` ниже отсчитываются от workspace root и относятся к указанным
HEAD. Ссылка на тест означает статически просмотренный сценарий либо будущую
проверку, но не результат запуска теста в этом аудите.
## 2. Executive verdict
1. **Факт:** модульный NestJS monolith и локальный frontend FSD profile
жизнеспособны. Прослеженные Nest module imports не образуют цикла;
frontend dependency-cruiser не обнаружил циклов/forbidden imports по
текущим правилам. Не доказана необходимость CQRS, обязательных ports,
дополнительного state store или микросервисов.
2. **Подтверждённый boundary risk / ARCH-002:** read orchestration операций
шире разрешённой первой загрузки. Income GET инициирует membership work,
а общий `readPlan` с `history: true` может загрузить отсутствующую историю
вне запрошенного периода. Это конкурирующая refresh policy рядом с hourly
owner, а не недостаток слоёв.
3. **Подтверждённый ownership drift / ARCH-003:** периодический GET принадлежит
одновременно query owner, глобальному QueryClient и timer в feature
подключения. Разрешённый import `pages → features` не делает этот owner
осмысленным; timer не знает query eligibility.
4. **KEEP / ARCH-005–007:** auth coordination, account/connection fences,
общая conditional publication и banking transaction/replay boundaries
имеют конкретных production consumers. Их удаление не является
допустимым сокращением архитектуры.
5. **UNVERIFIED / ARCH-008–009:** перенос event-specific semantics из generic
forecast и удаление persisted `observe` reference-path требуют проверки
product semantics/совместимости. Наличие нескольких consumers и публичного
параметра — реальные контраргументы против механического удаления.
**Подсчёт:** actionable = `DELETE`, `SIMPLIFY`, `FIX` в разделе 4.
**P0: 0; P1: 0; P2: 2; P3: 0. KEEP: 3; INVESTIGATE: 2.**
Severity не превращена в порядок cleanup. Для KEEP использован P3, поскольку
текущий дефект не заявляется; важность защищаемых инвариантов описана отдельно.
Для INVESTIGATE severity — уровень рассмотрения гипотезы, не подтверждение
сбоя. Runtime/performance impact не измерялся.
## 3. Inventory & coverage
### 3.1. Фактический layout и слои
**Backend.** Manifest объявляет NestJS 12, TypeScript 6, Prisma 7 и
better-sqlite3; schema задаёт SQLite, а не Postgres
(`backend/package.json:40-66,104-105`, `backend/prisma/schema.prisma:1-9`).
Прочитаны регистрации всех найденных `src/**/*.module.ts`:
composition root, Auth, BankCards, BrokerageAccounts, Portfolio, Operations,
Income, UpcomingEvents, Instruments, InstrumentSources, TbankConnection,
TbankSync, TbankSyncRepository, Tbank/Moex/Ncr, Prisma и Logging.
Это DI inventory, а не предположение по названиям директорий.
```text
AppModule
├─ Auth / BankCards
├─ BrokerageAccounts → Portfolio
├─ Income → Operations
├─ Instruments → InstrumentSources + Portfolio
├─ UpcomingEvents → InstrumentSources + Portfolio
├─ TbankConnection → TbankSync
└─ TbankSync → Portfolio + Operations + UpcomingEvents + InstrumentSources
Personal source owners → TbankSyncRepositoryModule (нижний durable store)
Business/source owners → TbankModule → gRPC client / access / membership
InstrumentSources → MoexModule + NcrModule
Global infrastructure: ConfigModule, PrismaModule, LoggingModule
```
Контекст legacy DI: зарегистрированный status service содержит также
screen-command orchestration и зависимости source owners
(`backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:42-52,54-311`),
но HTTP controller вызывает только status
(`backend/src/modules/investments/tbank-sync/tbank-sync.controller.ts:31-40`).
Это часть фактической dependency map; решение о dead-code closure принадлежит
направлению 04.
Intended ownership сравнивался с принятым ADR 019: hourly walk обновляет
personal sources, обычные GET локальны, Ledger/candles имеют узкие first-load
исключения (`docs/adr/019-hourly-reads-and-online-instrument-reference.md:9-29`).
Current ownership показан production traces ниже; расхождения read initiators
оценены в ARCH-002. Актуальность docs/index рассматривается направлением 15.
Важный контрпример предполагаемому циклу:
`TbankSyncModule → PortfolioModule → TbankSyncRepositoryModule` не возвращается
в `TbankSyncModule`. Store module регистрирует только repository
(`backend/src/modules/investments/tbank-sync/tbank-sync-repository.module.ts:1-9`).
Также `investment-sync-plan → income.api` достигает только pure
`planIncomePeriod`, не `IncomeAnalyticsService`
(`backend/src/modules/investments/income/income.api.ts:1`).
Source import cycle, Nest module cycle и runtime DI cycle не отождествляются.
Полный backend AST/DI graph checker не запускался; отсутствие всех возможных
транзитивных source cycles не заявляется.
**Frontend.** Manifest задаёт React/Vite/TypeScript, TanStack Query,
openapi-fetch/openapi-react-query и private local packages
(`frontend/package.json:39-62,112-116,128-130`). Реальное направление:
```text
app/providers/routes → pages/{account,accounts,income,events,operations,...}
pages → features/{auth,tbank-connection,instrument-search,instrument-details}
pages → entities/{portfolio,income,broker-operation,brokerage-event,bond,...}
pages → widgets/portfolio-structure
features/entities → shared/{api,lib,i18n,ui}
src consumers → packages/ui,icons; packages/ui → packages/tokens
```
Routes lazy-import page public APIs
(`frontend/src/app/routes/AppRoutes/AppRoutes.tsx:9-103`), account VM собирает
entity queries через публичные index
(`frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:6-18,66-84`).
Query options принадлежат entities; shared/api владеет transport, session-aware
eligibility и resource projection. Пример:
`frontend/src/entities/portfolio/api/usePortfolio.ts:11-25` →
`frontend/src/shared/api/resource-query.ts:85-149` →
`frontend/src/shared/api/authenticated-query.ts:34-50` →
`frontend/src/shared/api/client.ts:53-119,284-290`.
Размеры 420/1773 получены из единственного текущего запуска depcruise на
`src packages`, с `doNotFollow: node_modules`, TS pre-compilation dependencies,
source aliases и исключением package dist
(`frontend/dependency-cruiser.cjs:78-87`,
`frontend/tsconfig.dependency-cruiser.json:1-12`). Они включают объекты графа,
в том числе test/story source, и не означают 420 runtime business modules.
Правила проверяют imports, public APIs, cycles и direction
(`frontend/dependency-cruiser.cjs:1-76`), но не ownership refresh policy.
### 3.2. Сквозные production traces
| Capability | Controller/query → application → rules → storage/integration | Проверенная boundary |
| --- | --- | --- |
| Auth/session | App/AuthProvider → auth VM → shared authenticatedFetch; AuthController → AuthService → Session/JWT | В браузере один session owner и callback-based transport; cookie пишет controller; rotation CAS остаётся у AuthService. Evidence: `frontend/src/features/auth/model/auth.vm.ts:29-151`; `backend/src/modules/auth/presentation/auth.controller.ts:81-118`; `backend/src/modules/auth/application/auth.service.ts:81-114,166-201` |
| Accounts/portfolio | Account VM → usePortfolio → PortfolioController → PortfolioService.read/saved → PortfolioRepository; отдельно hourly → PortfolioService.request → gRPC → mapper → publish | HTTP read локален; production producer и publisher сохранены отдельно от read composition. Evidence: `frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:66-84`; `backend/src/modules/investments/portfolio/presentation/portfolio.controller.ts:19-32`; `backend/src/modules/investments/portfolio/application/portfolio.service.ts:55-59,113-232,234-284` |
| Instrument discovery | BondsPage VM → useBondSearch → BondCatalogService → CatalogDiscoveryService.onlineSearch → TbankClient; source calendars отдельно через CatalogEnrichmentService | Default reference read online; persisted observe branch остаётся доступен. Evidence: `frontend/src/pages/bonds/ui/BondsPage/BondsPage.vm.ts:9-13`; `frontend/src/entities/bond/api/queries.ts:97-104`; `backend/src/modules/investments/instruments/bonds/application/bond-catalog.service.ts:25-55`; `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:84-94,295-337` |
| Sync/forecast | Native tick → reserveHourly → membership → portfolio → operations → events.refresh/get → ForecastService → ForecastRepository | Coordinator задаёт порядок, owners выполняют sources. Event GET также рассчитывает candidate и проходит write-capable resolve. Evidence: `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:48-65,76-110,127-175`; `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:54-173`; `backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:24-46,170-175` |
| Banking operations | Frontend bank mutation → BankCardsController → BankOperationsService → operationInput/validateOperation → BankCardsRepository.write → SQLite; summary → GetBankSummaryUseCase → pure bankSummary | Banking не зависит от broker integration; transaction охватывает idempotency, refund validation, versioned write и response read. Evidence: `frontend/src/entities/bank-card/api/mutations.ts:29-82`; `backend/src/modules/banking/bank-cards/presentation/bank-cards.controller.ts:127-166`; `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:94-240`; `backend/src/modules/banking/bank-cards/application/get-bank-summary.use-case.ts:12-35` |
### 3.3. Ownership map — специальный результат
Пять центральных capabilities ниже разделены по решениям/состояниям/записям,
чтобы несколько технических participants не выдавать за нескольких owners.
| Capability / решение и состояние | Current owner | Competing owner | Recommended owner | Evidence |
| --- | --- | --- | --- | --- |
| Auth/session: локальная identity, refresh hash, rotation | Backend AuthService; browser auth VM — только клиентская session | Конкурирующий session writer в прослеженных путях не найден; shared client получает callbacks | Сохранить это разделение; shared client не становится auth domain owner | `backend/src/modules/auth/application/auth.service.ts:81-114,166-201`; `frontend/src/features/auth/model/auth.vm.ts:29-115,143-151` |
| Accounts/portfolio: создание/replacement connection и начального membership | TbankConnectionService.put, одна транзакция новой revision | TbankAccessService.publishMembership — последующее подтверждение текущей revision, а не competing replacement | Lifecycle подключения остаётся в connection module; последующие membership writes у существующего access owner | `backend/src/modules/investments/tbank-connection/tbank-connection.service.ts:31-72`; `backend/src/integrations/tbank/tbank-access.service.ts:148-237` |
| Accounts/portfolio: право видеть счёт и membership freshness | TbankAccessService.local; source producer TbankMembershipService; refresh cadence у hourly | Income collectRanges инициирует refresh membership | Hourly инициирует повторное membership чтение; personal GET использует только local boundary | `backend/src/integrations/tbank/tbank-access.service.ts:260-288`; `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:113-125`; `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:428-435` |
| Accounts/portfolio: positions, точные totals, сохранённый portfolio | PortfolioService/PortfolioRepository; AccountsService только строит summary | Legacy screen dispatcher содержит portfolio branch без найденного production entrypoint; это DI-контекст, не текущий refresh initiator | Portfolio source owner сохраняется; инициирование — hourly, projection — существующие readers | `backend/src/modules/investments/portfolio/application/portfolio.service.ts:179-198,234-284`; `backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts:116-184`; `backend/src/modules/investments/brokerage-accounts/brokerage-accounts.service.ts:16-39`; `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:99-115` |
| Instrument discovery: поиск/identity и публичная projection | CatalogDiscoveryService + stock/bond catalog services | Default online и persisted observe представляют разные поколения read policy | Default online owner сохранить; удаление persisted compatibility пока INVESTIGATE | `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:84-167,220-292`; `backend/src/modules/investments/instruments/bonds/application/bond-catalog.service.ts:25-88` |
| Instrument discovery: сохранённые calendars/quotes/candles | CatalogEnrichmentService/CatalogDiscoveryRepository и CandleService/CandleRepository | Metadata lookup хранит другую identity projection; само по себе не competing calendar owner | Сохранить source-specific owners, не сливать financial sources с online reference answers | `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:100-160,164-230`; `backend/src/modules/investments/instruments/application/candle.service.ts:101-124`; `backend/prisma/schema.prisma:148-229` |
| Sync/forecast: момент запуска личной работы и durable attempt | TbankHourlyScheduler + TbankSyncRepository; Ledger — узкое first-load исключение | Income scheduling и readPlan history expansion — реальные read initiators; TbankSyncService.request/dispatch — legacy DI-контекст | Hourly — единственный repeat-refresh initiator; Ledger запускает только missing requested interval | `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:76-175`; `backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:92-124`; `specs/openspec/specs/tbank-data-sync/spec.md:9-22` |
| Sync/forecast: window, event amounts, filtering и complete publication | UpcomingEventsService рассчитывает события; ForecastService фильтрует events; ForecastRepository выбирает overlapping window и публикует | Event semantics распределена между capability и generic portfolio forecast owner; generic publication имеет ещё stock/bond consumers | Сохранить один общий fenced publisher; event window/filter policy предпочтительно у Events, но перенос после semantic proof | `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:119-173,271-299`; `backend/src/modules/investments/portfolio/application/forecast.service.ts:98-123`; `backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:50-79,179-186` |
| Banking operations: intent, refund eligibility, journal write, version | BankOperationsService внутри BankCards module; repository владеет общей DB transaction boundary | UI хранит draft/intent, но не authority для refund limit; competing server writer не найден | Сохранить cohesive transactional owner и pure money rules; не дробить transaction по картам/категориям/возвратам | `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:94-240,251-330`; `backend/src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts:35-66`; `backend/src/modules/banking/bank-cards/domain/rub-money.ts:12-38` |
### 3.4. Dependency/Boundary matrix — специальный результат
| From → To | Фактическая зависимость и consumer | Сопоставление с intended boundary / оценка |
| --- | --- | --- |
| Frontend app → pages/features | Lazy routes и AuthProvider: `frontend/src/app/routes/AppRoutes/AppRoutes.tsx:9-103`; `frontend/src/app/ui/App.tsx:10-37` | Корректная верхняя composition root |
| Frontend pages → entities/features | Account VM queries и screen timer: `frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:6-18,66-84` | Public imports соблюдены; semantic owner timer требует ARCH-003 |
| Frontend entities → shared/api | usePortfolio/useBondSearch: `frontend/src/entities/portfolio/api/usePortfolio.ts:1-25`; `frontend/src/entities/bond/api/queries.ts:97-104` | Transport общий, endpoint identity/policy локальны; сохранить |
| Frontend features/auth → shared transport | Callback registration: `frontend/src/features/auth/model/auth.vm.ts:143-151`; `frontend/src/shared/api/client.ts:28-35,122-125` | Обратный callback не является forbidden source import shared → feature |
| Frontend src → private packages | App theme и exports: `frontend/src/app/ui/App.tsx:7,25-28`; `frontend/packages/ui/package.json:9-19,26-33` | Реальные boundaries для app/catalog consumers; не дополнительные runtime services |
| Frontend packages → src | Запрет `frontend/dependency-cruiser.cjs:18-23` | В текущем source graph нарушений не найдено |
| Backend HTTP → application | Portfolio/Income/Bank controllers: `backend/src/modules/investments/portfolio/presentation/portfolio.controller.ts:17-31`; `backend/src/modules/investments/income/income-analytics.controller.ts:24-79`; `backend/src/modules/banking/bank-cards/presentation/bank-cards.controller.ts:127-166` | Controller делегирует, identity из verified user; сохраняется |
| Backend Income → Operations | `backend/src/modules/investments/income/income.module.ts:3-12`; `backend/src/modules/investments/income/income-analytics.service.ts:45-93` | Аналитика должна читать ledger data; скрытая scheduling branch — ARCH-002 |
| Backend UpcomingEvents/Instruments → Portfolio forecast | `backend/src/modules/investments/upcoming-events/upcoming-events.module.ts:5-15`; `backend/src/modules/investments/instruments/stocks/application/stock-income.service.ts:85-94`; `backend/src/modules/investments/instruments/bonds/application/bond-income.service.ts:116-125` | Общее publication/coherence имеет несколько consumers; event-specific window protocol — ARCH-008 |
| Backend TbankSync → resource owners | `backend/src/modules/investments/tbank-sync/tbank-sync.module.ts:14-25`; scheduler `:113-175` | Направление coordinator → owners обосновано; broad legacy DI описана как контекст inventory |
| Backend resource owners → sync store | Portfolio/Operations/Events imports: `backend/src/modules/investments/portfolio/application/portfolio.service.ts:20-23`; `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:20-23`; `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:23-26` | Store module экспортирует repository и не импортирует coordinator. Цикл не доказан. Прямой файл вместо `*.api.ts` отступает от общего documented import profile, но это реально экспортированный provider, не доказательство плохого owner |
| Backend business → Tbank integration | `backend/src/integrations/tbank/tbank.module.ts:10-25`; `backend/src/modules/investments/portfolio/portfolio.module.ts:3-21` | Client/cache/limiter регистрируются у одного integration owner |
| Backend integration → business modules | Просмотр imports в integrations и `backend/src/integrations/tbank/tbank-access.service.ts:9-15` | В просмотренных import paths обратного обращения к business module нет; Prisma access — documented technical persistence, не HTTP/domain dependency |
| Backend BankCards → Auth/Prisma/domain | `backend/src/modules/banking/bank-cards/bank-cards.module.ts:3-19`; `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:3-29` | Cohesive CRUD service использует tx client по локальному profile; обязательный repository wrapper на каждый query не нужен |
| Backend domain → framework/storage | ESLint guard: `backend/eslint.config.mjs:126-168`; pure RUB rules `backend/src/modules/banking/bank-cards/domain/rub-money.ts:1-38` | Прямые imports ограничены; эта проверка не доказывает транзитивный граф или DI. Не выдаётся новый blanket lint verdict |
| Backend → frontend contract | Nest metadata → предоставленный JSON → generated frontend types: `backend/scripts/generate-openapi.cjs:19-35`; `frontend/scripts/check-openapi.mjs:10-26` | Передаётся artifact, не shared TypeScript source соседнего checkout. Файлы snapshot на диске совпадают; fresh parity UNVERIFIED |
| Specs/docs → application | Specs behavioral input; docs объясняют architecture: `specs/AI_INDEX.md:3-13`; `docs/README.md:52-67` | Не runtime/source dependency; intended policy сопоставлена с production ownership, documentation/index maintenance относится к audit 15 |
Обнаруженные прямые imports экспортированных backend providers не
приравниваются к доказанным DI/circular failures. Например,
`backend/src/modules/investments/instruments/stocks/application/stock-screener.service.ts:10-13`
читает integration implementation entry вместо `tbank.api.ts`; provider
зарегистрирован в TbankModule, альтернативная implementation не заявлена.
Смысл boundaries проверен по consumers, а не по наличию barrel-файла.
### 3.5. SRP, cohesion, DIP/ISP, OCP/LSP
- **SRP/cohesion:** Auth session, connection lifecycle, source membership,
portfolio publication и banking journal имеют разные причины изменения
и реальные write boundaries. BankCardsService, BankOperationsService,
ExpenseCategoriesService и summary use case в одном module не требуют
отдельных Nest modules. Legacy screen dispatcher соединяет те же source
owners на уровне DI; dead-code решение вынесено в cross-audit signal 04.
- **Coupling:** Income зависит от read service, который также инициирует
membership/full-history work; фронтенд-страницы зависят от connection feature
ради периодических resource reads. Это проверенные связи, не вывод по имени
класса. Forecast window меняется сразу в Events и generic portfolio
persistence/service — ARCH-008.
- **DIP/ISP:** Concrete clients/repositories допустимы и имеют test seams.
`TbankConnectionService` использует `Pick<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 |