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

821 lines
78 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Аудит 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 |