Переносит архив `audits/2026-10-11/` (19 файлов) из codex/audit-remediation-2026-10-11 в develop и добавляет точечный allowlist в .gitignore.
821 lines
78 KiB
Markdown
821 lines
78 KiB
Markdown
# Аудит 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 |
|