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

917 lines
88 KiB
Markdown
Raw Permalink 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.
# Аудит 02 — Complexity & Overengineering
## 1. Audit metadata
- **Audit ID/name:** 02 / Complexity & Overengineering.
- **Дата:** 2026-10-11.
- **Исполнитель:** независимый исполнитель направления 02; другие сабагенты
не запускались. Выводы других новых отчётов не читались.
- **Workspace:** `/Users/ksv741/Project/vibe/WealthLedger`.
- **Режим:** исследование, read-only для application source, конфигураций,
тестов, specs, docs и root source/config. Единственная запись этого аудита —
`audit-results/02-complexity-and-overengineering-report.md` через `apply_patch`.
| Checkout | Абсолютный путь | Проверенный HEAD | Начальный status |
| --- | --- | --- | --- |
| root | `/Users/ksv741/Project/vibe/WealthLedger` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | clean |
| frontend | `/Users/ksv741/Project/vibe/WealthLedger/frontend` | `c8821200ce699dab1ce65360d1968d5877a2e78b` | clean |
| backend | `/Users/ksv741/Project/vibe/WealthLedger/backend` | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` | clean |
| specs | `/Users/ksv741/Project/vibe/WealthLedger/specs` | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` | clean |
| docs | `/Users/ksv741/Project/vibe/WealthLedger/docs` | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` | clean |
SHA получены самостоятельно командой `git rev-parse HEAD` из каждого checkout;
clean означает пустой вывод
`git status --porcelain=v1 --untracked-files=all`, а не отсутствие ignored files.
Root allowlist исключает audit-results и nested repos:
`.gitignore:1–2`. Поэтому новый отчёт проверяется отдельно от tracked diff.
**Инструкции и skills.** Сначала загружен установленный `ponytail-audit`
через Skill tool, прочитан полный протокол
`audit-prompts/02-complexity-and-overengineering.md:1–67`.
Использованы его ladder/hunt, теги `delete:`, `native:`, `yagni:`, `shrink:`
и ранжирование по возможному сокращению сложности. Полный формат отчёта задан
пользовательским протоколом; численная оценка net LOC не выдумывается.
Прочитаны AGENTS каждого checkout, local architecture/engineering и
активированные frontend UI/data/TS/testing, backend HTTP/data/integrations/
security/errors/observability/testing conventions. Root conventions применены
только к размещению и проверке отчёта. Загружен
`verification-before-completion` для завершающей проверки.
`graphify` загружен, но существующий graph отсутствует: прямое чтение
`graphify-out/graph.json` в root и каждом из четырёх checkout вернуло
File not found. Graph extraction, query CLI, установка и создание artifacts
не выполнялись. Исследование продолжено статическими трассировками.
**Что выполнялось:** специализированные Read/Grep/Glob, поиск callers из
директорий соответствующих repo, read-only Git-команды для SHA/status;
при проверке native-замены прочитан уже установленный dependency source.
Завершающие проверки перечислены в разделе 9.
**Что не выполнялось:** install, codegen, migrations, build, full-suite,
targeted runtime tests, запуск приложения, browser/network сценарии,
обращения к реальным внешним API, commit, PR. Старый
`audit-results/technical-audit-2026-10-11.md` прочитан только как список
гипотез; его findings, оценки LOC и результаты проверок не перенесены как
собственные измерения. Остальные новые отчёты не открывались.
**Статус доказательств:** High подтверждает механизм и callers по текущему
source, а не runtime-воспроизведение. `UNVERIFIED` означает, что конкретная
потеря гарантии или безопасный объём удаления ещё не доказаны. У всех оценок
выигрыша — `Impact unmeasured`, если отдельно не указано иное.
## 2. Executive verdict
1. **Факт:** наибольшая подтверждённая лишняя координация в живом frontend —
`useScreenSync`: страницы передают descriptor и массив resources в отдельный
timer, хотя query-owner уже управляет lifecycle. Все изученные production
callers используют только его побочный эффект. **CPLX-001 / SIMPLIFY / P2.**
2. **Факт:** живой Ledger сначала вычисляет интервал, превращает его обратно
в строки screen intent, прогоняет через многоцелевой planner и извлекает
только operations/screen intervals. Для этого caller достаточно имеющихся
`operationIntervals` и `readPlan`. **CPLX-002 / SIMPLIFY / P2.** Удаление
всего planner не следует из этого вывода.
3. **Факт, небольшая стоимость:** двухcallback presentation VM скринера
не владеет state, fetching, lifecycle или отдельным consumer. Локальная
замена сохраняет accessibility; это необязательный cleanup.
**CPLX-003 / SIMPLIFY / P3.**
4. **Риск и недоказанная возможность упрощения:** forecast связывает request
window, persisted identity и выбор fallback; online/observe инструментов
обслуживают разные модели; income read содержит асинхронное планирование
импорта. Смешение моделей доказано, но безопасное удаление всех ветвей —
**UNVERIFIED**, поэтому **CPLX-004–006 / INVESTIGATE**, а не массовый delete.
5. **Факт:** conditional publication, source-version fences, точная денежная
арифметика, durable reservation, банковские fingerprints/CAS/tombstones и
стабильные screener snapshots имеют конкретные текущие сценарии.
**CPLX-007–010 / KEEP.** Их сокращение до «обычного GET/CRUD/timer» создаст
больше проблем, чем снимет сложности.
6. **Итог:** подтверждены **3 actionable findings: P0=0, P1=0, P2=2, P3=1**;
отдельно **KEEP=4**, **INVESTIGATE=3**. Новые слои, общий workflow engine,
generic repository или новая dependency для решения не нужны. Это
risk-/cost-based выборка, а не сертификат отсутствия других проблем.
## 3. Inventory & coverage
### 3.1. Фактическая карта и метод
Проверены независимые checkout и composition roots. Backend — NestJS modular
monolith, Prisma/SQLite, конкретные application providers; frontend —
React/TypeScript/Vite, TanStack Query, Mantine, generated OpenAPI types.
Основания: `backend/src/app.module.ts:18–38`,
`backend/package.json:40–66`, `frontend/package.json:39–62`,
`frontend/src/app/ui/App.tsx:13–44`. Архитектурная convention сама по себе
не использована как доказательство целесообразности реализации.
Отбор выполнялся по распределению transitions, глубине делегирования,
вариантам режима чтения и числу преобразований одного input. Затем проверялись
entrypoint, runtime consumer, source-owner, persistence и контрпример.
Ни LOC, ни наличие отдельного файла не являлись критерием дефекта.
Выборка репрезентативна для сложной investment coordination и финансовых writes:
есть browser query lifecycle, periodic producer, conditional persistence,
cursor/history, multi-period analytics, online reference, command snapshot
и transactional replay. Auth refresh, все integration parsers, все UI forms,
весь банковский read layer и каждый controller exhaustively не трассировались.
Мёртвый код, DRY, tooling/test infrastructure и performance исследовались
только до передачи signal соответствующему направлению. Generated/vendor
деревья не сканировались как собственные production abstractions проекта.
**Метод чисел в этом отчёте:**
- 8 flows — ручная нумерация F1–F8 ниже, по различным production entrypoints.
Это не автоматически рассчитанный coverage percentage.
- 8 production call sites `useScreenSync` — уникальные page VM из поиска
`useScreenSync` в `frontend/src`, без import/export и test matches; все
восемь VM прочитаны. Это accounts, account, account-operations,
account-events, account-income, bonds, bond, stock.
- 2 effects и 1 interval в hook — декларации в
`frontend/src/features/tbank-connection/model/screen-sync.ts:23–35`.
Это локальные конструкции, а не число browser timers во всей системе.
- 2 callbacks presentation VM — декларации строк 9 и 13; 3 consumers forecast
— UpcomingEventsService, StockIncomeService, BondIncomeService по imports
и вызовам `forecasts.result` в production source.
- Counts findings/severity получаются из consolidation table. Число ветвей
всего repo, cyclomatic complexity, milliseconds, CI minutes, token savings,
bundle size и net LOC не измерялись.
### 3.2. Восемь живых production flows
| Flow / owner | Реальный entrypoint и цепочка | Поддерживаемые сценарии и состояния | Сопоставление сложности со сценарием |
| --- | --- | --- | --- |
| **F1. Обзор счёта и перечитывание**; page VM + entity queries | Route `/accounts/:accountId`: `frontend/src/app/routes/AppRoutes/AppRoutes.tsx:203–210` → `pages/account/ui/AccountPage/AccountPage.tsx:9–34` → VM `:66–135` → `entities/portfolio/api/usePortfolio.ts:19–25` → `shared/api/resource-query.ts:99–149` → `backend/src/modules/investments/portfolio/presentation/portfolio.controller.ts:25–31` → `application/portfolio.service.ts:113–231` → `infrastructure/portfolio.repository.ts:25–47` | 5 resources в overview; без content loading/error, успешный empty/loaded, partial metadata, cached content при background read | Query/session/resource projection и локальный server read оправданы. Отдельный screen timer дублирует query scheduling; scope не влияет на него. CPLX-001 |
| **F2. Часовой обход**; scheduler и source owners | `backend/src/app.module.ts:35` → `tbank-sync/tbank-sync.module.ts:24–25` → `tbank-hourly.scheduler.ts:48–87` → `TbankSyncRepository.reserveHourly` `tbank-sync.repository.ts:174–220` → membership/portfolio/operations/events `scheduler:113–175` → settle `:178–218` | Первая/последующая попытка, overlap, restart, changed connection, failed/partial/succeeded; проход membership → portfolios → operations → events | Нативный timer только предлагает работу; durable row и process flag имеют разные горизонты жизни. Polling отдельных workers и completion promises различаются по owner. KEEP координации; ошибка lifecycle передана audit 13 |
| **F3. Сохранённый прогноз событий**; UpcomingEvents/Forecast owners | `backend/src/modules/investments/upcoming-events/upcoming-events.controller.ts:22–38` → `upcoming-events.service.ts:54–173,200–239` → local portfolio + saved calendars → `portfolio/application/forecast.service.ts:18–95` → `portfolio/infrastructure/forecast.repository.ts:24–175` | One account/all, известные/неизвестные monetary values, complete candidate, прежний full result при failed dependencies, другое UTC-окно | Фильтрация/расчёт и publication fence — разные обязанности. Парсинг окна из строкового ключа и поиск пересекающегося fallback смешивают read selection и publication. CPLX-004; fences CPLX-007 |
| **F4. Ledger, cursor и первая загрузка интервала**; Operations owner | `backend/src/modules/investments/operations/presentation/brokerage-operations.controller.ts:22–38` → `application/brokerage-operations.service.ts:69–152` → screen planner → `application/brokerage-operations-sync.service.ts:46–126` → domain interval/coverage → repository → saved page и cursor `service:152–388` | One account/all, explicit/default period, instrument/categories, first/cursor page, complete/missing coverage, historical cold read, moving tail | Подпись cursor и interval normalization сохраняются. Date → ISO → screen → source union → intervals является обходным маршрутом для уже выбранного интервала. CPLX-002 |
| **F5. Income summary/analytics**; Income + Operations owners | `backend/src/modules/investments/income/income-analytics.controller.ts:24–79` → `income-analytics.service.ts:45–165` → `operations/application/brokerage-operations.service.ts:391–574` → local snapshot, optional membership.done → schedule → readPlan → totals/comparisons/buckets | Summary выбранного периода, analytics selected/previous/year, неизвестная граница истории, missing/partial/complete, observe/default read | Несколько comparison periods и точные суммы нужны. Два read modes с discovery/schedule расширяют lifecycle обычного чтения; удалить без решения cold history нельзя. CPLX-006 |
| **F6. Online поиск/detail/ticker инструмента**; Catalog owner | `frontend/src/pages/bonds/ui/BondsPage/BondsPage.vm.ts:9–39` → `entities/bond/api/queries.ts:97–104` → `backend/src/modules/investments/instruments/bonds/presentation/bond.controller.ts:55–89` → `application/bond-catalog.service.ts:25–88` → `instruments/application/catalog-discovery.service.ts:84–337` → TbankClient; `sync=observe` идёт через saved discovery/enrichment | Поиск, UID detail, ticker ambiguity/not found, online и saved/observe variants; bond/share mapping | Catalog service не passthrough: переводит provider money/dates/fields. Legacy observe path доступен через публичный query flag, поэтому отсутствие нового frontend caller не доказывает мёртвость backend ветви. CPLX-005 |
| **F7. Скринер акций**; Screener owner | Route `/stocks`: `frontend/src/app/routes/AppRoutes/AppRoutes.tsx:176–183` → `pages/stocks/ui/StocksPage/StocksPage.vm.ts:29–115` → POST/GET `backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts:85–109` → `application/stock-screener.service.ts:65–139` | Fresh search, stable pages, empty success, expiry, foreign user/revision, invalid page/input, поздний frontend ответ | Snapshot identity, bounds и latest-request guard нужны. Отдельная translation-only presentation VM не добавляет поведения. CPLX-003, CPLX-010 |
| **F8. Создание/изменение/повтор банковской операции**; BankOperations owner | `backend/src/modules/banking/bank-cards/presentation/bank-cards.controller.ts:133–166` → `application/bank-card-input.ts:125–142` → `application/bank-operations.service.ts:94–330` → `infrastructure/bank-cards.repository.ts:35–73` → SQLite transaction/constraints | Expense/topup/transfer/refund, replay same ID, changed intent, stale version, dependent refunds, delete/tombstone, rollback | Проверки денег/input, replay, ownership, CAS исходной покупки и транзакция защищают разные инварианты. Они не лишние fallback/guards. CPLX-009 |
Пути в цепочках, сокращённые до `application/...`, относятся к module,
названному в той же строке; полные evidence основных решений приведены ниже.
Эти flows живы по routes/module wiring/callers; выполнение в production
или latency не наблюдались.
### 3.3. Проверенные seams, guards, flags и контраргументы
| Кандидат / качественный сигнал | Независимая проверка | Итог |
| --- | --- | --- |
| Framework interfaces/strategy, конкретные repositories | `backend/src/prisma/prisma.service.ts` и scheduler используют lifecycle contracts; архитектура и DI roots не требуют port на каждый service. Поиск `interface`, `implements`, factory/strategy/registry в `backend/src` не выявил подтверждённого generic plugin engine в выбранных flows | Не объявлять framework seam ненужным из-за одной реализации |
| `StockScreenerService` / `TbankAccess` / `TBANK_ACCESS` | Узкий access type `stock-screener.service.ts:50–63`; production `useExisting` `instruments.module.ts:24`; test seam `stock-screener.service.spec.ts:25–38` | Один production provider не означает бесполезность seam; не создавать второй adapter и не считать это actionable |
| `GetStockCompanyUseCase` похож на wrapper | `get-stock-company.use-case.ts:25–38,41–88` выбирает sector-dependent metrics, currency/finite value rules и wire projection | Meaningful boundary conversion; не объединять с gRPC client |
| Generic `ForecastService` | Три production callers: `upcoming-events.service.ts:160`, `stock-income.service.ts:85`, `bond-income.service.ts:116`; общие publication guarantees | Generic здесь обслуживает существующие разные payloads; не переписывать в три независимых persistence flow |
| Generic resource/session wrappers | `frontend/src/shared/api/authenticated-query.ts:34–58`, `resource-query.ts:37–67,99–102,129–149` обслуживают несколько endpoint queries и confirmed access loss | Session gate, cache purge и resource envelope сохранять при CPLX-001/005 |
| Presentation VM выглядит формальным | Stocks VM — только labels; `CardPagePresentation.vm.ts:18–53,65–73` имеет настоящий scroll/layout lifecycle | CPLX-003 только для Stocks; не обобщать на все presentation hooks |
| Flags `ticking`, durable `state`, `processRunId`, `attemptId` | Tick guard `scheduler:56–71`; durable/restart logic `repository:189–201`; publication identities `portfolio.repository.ts:122–147` | Не одинаковое состояние: обход процесса, lifetime попытки, fencing старой работы |
| Public metadata `freshness`, `completeness`, `availability`, `lastAttempt` | `investment-resource.mapper.ts:18–68`; spec `tbank-data-sync/spec.md:24–37` | Один общий boolean уничтожит различие старого полного результата и новой неуспешной попытки. Derived completeness не доказывает, что его можно убрать из wire contract |
| Internal `meta.sync` и новые публичные states | `investment-resource.mapper.ts:65–68` задаёт non-enumerable compatibility property; её читают `forecast.service.ts:69–75`, enrichment `:323–328` | Migration bridge имеет runtime consumers; нельзя просто удалить property как «не сериализуется» |
| Fallback после неуспеха | `forecast.repository.ts:78–97,130–159` и тест `upcoming-events.e2e-spec.ts:294–318,344–363` | Old complete snapshot и rejection устаревшего candidate нужны; подозрителен выбор identity/window, не само сохранение fallback |
| Catch → payload fallback | `forecast.service.ts:98–123` | Parsed string key и generic payload скрывают причинность; malformed saved data/ключи не исследованы runtime. Потенциальное упрощение требует CPLX-004, не механического удаления guards |
| Calendar `stable/quote` policy | `catalog-enrichment.service.ts:189–193,679–692`, hourly caller `scheduler:166–175` | Корректность refresh policy — audit 13/07, не доказательство ненужности source-owner |
| Runtime guards поверх типов | Screener `item` `:112–127`, money `rub-money.ts:12–31`, company metrics `get-stock-company.use-case.ts:71–87` | DTO/TS types не гарантируют shape внешних records и persisted values. KEEP |
| Config switches | `backend/src/config/env.validation.ts:9–19,21–114` ограничивает rate limit, endpoint/CA и logging; реальные integration/client inputs | Не найдено подтверждения speculative compatibility registry; эксплуатационные knobs не удаляются по одному отсутствующему override |
### 3.4. Complexity justification table — специальные результаты
| Flow | Supported scenarios | Safety invariants | Suspect complexity | Removal candidate | Risk of change | Decision |
| --- | --- | --- | --- | --- | --- | --- |
| F1 overview/periodic read | Mounted resources, changing IDs, cached success/error | Enabled/session gate, canonical key, AbortSignal/cache reset, read-only retry | Timer + resourcesRef + screen descriptor над query lifecycle | Собственный scheduler hook после переноса cadence к query options; без новой registry | Medium: background/focus/reconnect и reset cadence не полностью эквивалентны | SIMPLIFY / CPLX-001 |
| F2 hourly | First/due/restart/overlap, independent sections | Durable single-attempt, connection isolation, safe terminal outcome | In-memory и durable state рядом; разные completion APIs | Только несущественные presentation/helpers после отдельного lifecycle proof; timer/reservation не удалять | High: restart/late result/partial result | KEEP / CPLX-008 |
| F3 forecast | Complete candidate, missing calendar, saved fallback, window/all | Exact Money, atomic dependency validation, membership/connection fence | Window спрятано в scopeKey; generic read selection и publication в одной resolve | Локально разделить latest complete selection и request filter после решения window identity | High: stored data compatibility и сохранённый calculatedAt | INVESTIGATE / CPLX-004; KEEP / CPLX-007 |
| F4 ledger | Account/all, default/explicit dates, cursor, first missing range | Exact [from,to), normalization/month splitting, authorization, cursor binding | Range → ISO screen → Source union → intervals | Вызов broad planner в этом reader; напрямую использовать существующий normalizer | Medium: end-inclusive/exclusive, movingEnd и unfiltered source coverage | SIMPLIFY / CPLX-002 |
| F5 income | Selected/previous/year, cold/incomplete history, default/observe | Exact per-currency sums, known vs unknown, current connection | Snapshot → membership.done → deferred schedule → readPlan → repeat read | Request-driven membership/schedule только после определения cold-history owner | High: исчезновение первого доступного результата | INVESTIGATE / CPLX-006 |
| F6 online instruments | Search/detail/ticker, ambiguity, observe cache | Public shape, unknown fields, source-vs-personal isolation | Online и durable/saved reference paths под одним bool | Legacy observe backend branch только после consumer/contract decision; не calendars/candles | High: публичный flag и persisted consumers | INVESTIGATE / CPLX-005 |
| F7 screener | New search, stable page, empty/expired/foreign result | Search ownership/revision, bounds, stable order, latest frontend intent | Translation-only VM; кажущаяся лишней input allowlist | Только Stocks presentation hook; snapshot и raw-body rejection оставить | Low для callbacks; High для snapshots/validation | SIMPLIFY / CPLX-003; KEEP / CPLX-010 |
| F8 banking write/replay | Create/update/delete/refund, lost response, collision | Kopecks exactness, transaction rollback, CAS, original fingerprint/tombstone | Много guards и replay-before-validation | Подтверждённых removal candidates в write path нет | High: двойное денежное влияние/over-refund | KEEP / CPLX-009 |
### 3.5. Калибровка
**«Сложно, но нужно», случай A — forecast publication (CPLX-007).**
Реальный scenario: quantity/source revision меняется между расчётом и записью.
Owner повторно проверяет membership, portfolios и calendar versions внутри
transaction (`forecast.repository.ts:35–49,80–145`). Изученный SQLite test
меняет portfolio до настоящего `resolve`
(`backend/test/upcoming-events.e2e-spec.ts:344–363`), а другой меняет
dataRevision (`:490–513`). Простое `upsert(candidate)` уменьшает код, но
публикует смесь наблюдений. Сохранять fence независимо от решения об окне.
**«Сложно, но нужно», случай B — banking replay и refund (CPLX-009).**
Реальный scenario: полный refund сохранён, HTTP-ответ потерян, одновременно
идёт второй refund или исправление покупки. Сначала replay идентичного
намерения, затем validation нового действия; CAS покупки и refund sum остаются
в transaction (`bank-operations.service.ts:152–196,214–240`). Проверки
`bank-cards.e2e-spec.ts:462–489,622–653` различают эти исходы. Проверять
refund limit до replay или использовать только unique ID проще внешне,
но теряются successful retry и collision protection.
**«Просто, но рискованно» — убрать raw-body allowlist скринера, полагаясь
на `forbidNonWhitelisted` (CPLX-010).**
`backend/src/main.ts:31–38` сначала применяет global transform/whitelist без
forbidNonWhitelisted. Локальный body pipe
`stock.controller.ts:91` работает уже после global pipe; explicit
`Object.keys(request.body)` `:94–97` смотрит исходное тело.
Порядок проверен по установленному Nest source:
`backend/node_modules/@nestjs/core/router/router-execution-context.js:161–170`
соединяет `pipes.concat(paramPipes)`,
`backend/node_modules/@nestjs/common/pipes/validation.pipe.js:68–85,100–101`
преобразует body в entity и возвращает entity для argument.
Следовательно, декларация local forbid не является достаточным доказательством
rejection неизвестного original field. Изученный configured HTTP test
`backend/test/stocks.e2e-spec.ts:73–87` требует 400 для unknown field.
Этот test не запускался, но существует конкретный механизм, объясняющий
guard. Не удалять guard как redundant; глобальное изменение pipe policy
ради одного endpoint увеличит scope и сложность проверки.
## 4. Findings
Actionable здесь означает только `DELETE`, `SIMPLIFY` или `FIX`.
KEEP — явно защищённая текущая сложность, INVESTIGATE — требование evidence
перед изменением. Severity у KEEP=P3 обозначает отсутствие установленной
проблемы текущего механизма; серьёзность возможного неправильного удаления
описана в Guarantees at risk и не подменяет severity finding.
### CPLX-001 — native: перенести периодическое чтение к query-owner
- **Decision:** SIMPLIFY.
- **Severity:** P2.
- **Confidence:** High.
- **Evidence:** `frontend/src/features/tbank-connection/model/screen-sync.ts:7–61`;
`frontend/src/shared/api/authenticated-query.ts:34–58`;
`frontend/src/entities/portfolio/api/usePortfolio.ts:19–25`;
`frontend/src/entities/bond/api/queries.ts:97–104`;
`frontend/src/app/ui/App.tsx:13–19`.
- **Runtime consumer:** eight page VM calls: accounts `AccountsPage.vm.ts:23`,
account `AccountPage.vm.ts:78–84`, operations `AccountOperationsPage.vm.ts:96–99`,
events `AccountEventsPage.vm.ts:67–75`, income `AccountIncomePage.vm.ts:66–69`,
bonds `BondsPage.vm.ts:13`, bond `BondPage.vm.ts:45–55`,
stock `StockPage.vm.ts:45–55` под `frontend/src/pages/<page>/ui/<Component>/`.
Все восемь используют вызов как statement, не читают возвращённый model.
- **Claim:** один read lifecycle сейчас разнесён по query options и собственному
page-level timer с mutable ref; это подтверждённая лишняя coordination boundary.
- **Current mechanism:** каждый mounted caller ставит interval; отдельный effect
обновляет resourcesRef; timer вызывает ручной refetch каждого текущего resource.
`_scope` никак не используется. В том же hook рассчитываются refresh/retry/
source/transport flags, которые эти production callers не используют.
- **Impact / actual cost:** для диагностики cadence нужно проследить page VM,
hook, entity policy и App defaults. Проверка enabled находится в query-owner,
но timer вызывает manual refetch без неё. **Impact unmeasured**: число лишних
запросов, runtime latency и трудозатраты не измерены.
- **Why now:** текущие страницы уже перешли к периодическому read-only чтению,
descriptor больше не обозначает команду синхронизации.
- **Minimum change:** использовать существующий `refetchInterval` на нужных
entity query observers вместо timer/ref координации; убрать hook/call
descriptors, когда все текущие callers переведены. Не включать polling
глобально для всех API и не создавать registry. Явно согласовать текущие
focus/reconnect/stale policies у тех же владельцев.
- **Guarantees at risk:** auth/session enabled, смена connection/key,
cached snapshot/retry, HTTP-only read, отсутствие запросов с пустым UID,
прежняя политика background-tab и общий cadence.
- **Counterarguments / trade-offs:** единый timer даёт общий момент перечитывания
нескольких resources; native timers привязаны к observers и могут менять фазу.
Однако текущий hook не выполняет barrier или атомарный snapshot для группы.
Native option существует в установленной dependency: `frontend/package.json:46`;
`node_modules/@tanstack/query-core/src/queryObserver.ts:371–430` проверяет enabled
при scheduling и отдельно управляет background/focus. Manual `refetch`
`:279–285,340–362` идёт другим путём. Один refetchInterval **сам по себе не
ограничивает** focus/mount/reconnect reads до 30 минут. Переход требует решения
о политике, не нового scheduler.
- **Verification or rollback:** существующий screen-sync test `:21–80` сохраняет
HTTP-only semantics, но не доказывает timer boundary. Перед изменением добавить
минимальную MSW/fake-time проверку meaningful/disabled input, смены key,
unmount и background/refetch policy; сохранить access/cache tests. Rollback —
revert локальной migration, без schema/данных.
- **Effort:** M.
- **Related finding IDs:** CPLX-005.
### CPLX-002 — shrink: Ledger не нужен round-trip через общий screen planner
- **Decision:** SIMPLIFY.
- **Severity:** P2.
- **Confidence:** High.
- **Evidence:** `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:74–151`;
`backend/src/modules/investments/investment-sync-plan.ts:77–105,121–140,271–284,301–327`;
`backend/src/modules/investments/operations/domain/operations-sync-plan.ts:18–64`;
`backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:46–126`.
- **Runtime consumer:** оба живых routes через
`backend/src/modules/investments/operations/presentation/brokerage-operations.controller.ts:22–38`
вызывают `BrokerageOperationsService.read`; после planner consumer берёт только
operations источники с priority=screen.
- **Claim:** source union и screen abstraction здесь не представляют бизнес-
возможность caller; уже выбранный interval временно расширяется до screen
plan и снова сужается до того же operations interval.
- **Current mechanism:** `ledgerRange` вычисляет [from,to); reader превращает
from/to в ISO, вычитает 1 ms из exclusive end, вызывает planner с пустыми
instruments и known-membership flag. Planner снова вызывает `ledgerRange`,
добавляет membership/history sources, нормализует operations и сортирует
priorities. Reader отбрасывает всё, кроме operations/screen, и передаёт readPlan.
- **Impact / actual cost:** изменение first-load boundary требует понимания
обширного screen vocabulary, priority фильтра и обратимого date conversion.
Это engineering/cognitive cost, не доказанный performance bottleneck.
**Impact unmeasured.**
- **Why now:** live caller уже account/range-based, а screen planner обслуживает
модель orchestration, которую read endpoint не исполняет целиком.
- **Minimum change:** построить intervals из уже выбранных `selected`, `from`,
`to` при known membership и `from < to`, передать их в существующий
`operationIntervals`, затем существующий `sync.readPlan`. Сохранить текущий
unfiltered source import: instrument/categories ограничивают read result,
не должны незаметно сужать completeness базового источника.
- **Guarantees at risk:** [from,to) и inclusive HTTP end, subsecond cursor end,
empty/all scopes, membership unknown, month splitting, movingEnd,
authorization/revision, coverage. Политику history/backfill не менять этим cleanup.
- **Counterarguments / trade-offs:** screen planner централизует expansion для
другого caller `tbank-sync.service.ts:143–180`; его полное удаление не предлагается.
Нормализацию нельзя заменить простым единственным диапазоном: она группирует,
объединяет и делит по месяцам (`operations-sync-plan.ts:21–61`).
Default planner `force=false` уже не передаёт instrumentUid в import
(`investment-sync-plan.ts:139`); это поведение следует сохранить явно.
- **Verification or rollback:** сравнить observable first-load intervals до/после
для known/unknown membership, one/all, explicit/default end и cursor;
использовать изученные SQLite scenarios
`backend/test/tbank-ledger-local.e2e-spec.ts:113–137,138–228,229–268`
для точности/cursor/local repeat. Они не запускались. Rollback — revert
только caller simplification; shared normalizer/repository остаются прежними.
- **Effort:** S.
- **Related finding IDs:** CPLX-006, CPLX-008.
### CPLX-003 — yagni: translation-only Stocks presentation VM
- **Decision:** SIMPLIFY.
- **Severity:** P3.
- **Confidence:** High.
- **Evidence:** `frontend/src/pages/stocks/ui/StocksPage/StocksPagePresentation.vm.ts:1–20`;
`frontend/src/pages/stocks/ui/StocksPage/StocksPage.view.tsx:205–208,259–268`;
`frontend/.ai/conventions/ui.md:58–89`.
- **Runtime consumer:** единственный обнаруженный production import/call —
`StocksPage.view.tsx:28,208`. Поиск имени по frontend source не обнаружил
другого consumer или самостоятельного test/story seam этого hook.
- **Claim:** отдельная VM содержит только два translation callbacks для
Pagination labels; собственной lifecycle/data/form responsibility нет.
- **Current mechanism:** view уже вызывает `useTranslation`, затем ещё один
hook вызывает translation и возвращает getControlProps/getItemProps.
- **Impact / actual cost:** дополнительный import/file переход для изменения
доступного названия; стоимость мала. **Impact unmeasured**. Не заявляется
React performance improvement или необходимость менять всю page architecture.
- **Why now:** подходящий cleanup при следующем касании pagination, не отдельная
срочная задача и не основание дробить большой view.
- **Minimum change:** оставить два типизированных callback references рядом
с Pagination в существующем presentation owner, используя уже доступный `t`;
убрать отдельный hook/file. Не убирать aria-label и не создавать shared helper.
- **Guarantees at risk:** translation update, имена previous/next/page,
presentation-only boundary, отсутствие inline functions в JSX props.
- **Counterarguments / trade-offs:** local convention предпочитает actions в VM
и extraction helpers; формальная uniformity может быть важнее маленького
выигрыша. Решение допустимо как узкое UI-only упрощение с именованными const
callbacks; если правило трактуется как запрет любых таких callbacks во view,
сначала согласовать узкую convention exemption, а не переносить этот код в
data VM. `CardPagePresentation.vm.ts:18–53,65–73` — контрпример с настоящим
layout lifecycle, его не объединять по аналогии.
- **Verification or rollback:** semantic review labels + существующая
`StocksPage.test.tsx`; простая проверка keyboard/pagination names при будущем
изменении. Новый unit suite только для декларации callback не нужен.
Rollback — вернуть файл и import без изменения endpoint/state.
- **Effort:** S.
- **Related finding IDs:** CPLX-010.
### CPLX-004 — request window, persisted identity и forecast fallback смешаны
- **Decision:** INVESTIGATE.
- **Severity:** P2.
- **Confidence:** Medium.
- **Evidence:** `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:152–173`;
`backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:50–79,179–186`;
`backend/src/modules/investments/portfolio/application/forecast.service.ts:77–93,98–123`;
`backend/prisma/schema.prisma:133–145`.
- **Runtime consumer:** F3 HTTP reads и hourly `events.get`
`tbank-hourly.scheduler.ts:166–175`; тот же ForecastService обслуживает
`stock-income.service.ts:85–110` и `bond-income.service.ts:116–140`.
- **Claim:** вычисленный request scopeKey используется как PK, query filter
и протокол разбора window. Это делает simple saved-read policy зависимой
от generic persistence parsing и cross-window fallback. Безопасность замены
latest-result моделью — **UNVERIFIED**.
- **Current mechanism:** exact-key row → parse requested key → scan matching
events keys → first overlapping/account match → membership fallback →
candidate/no-candidate branches → version validation → dependency equality
→ upsert → повторный parse/filter/re-total в service.
- **Impact / actual cost:** read/publication semantics труднее объяснить и
менять независимо; catch возвращает исходный payload, если parsing/filter
падает. **Impact unmeasured**; не заявляется частота wrong-window response.
- **Why now:** новые specs требуют saved calculatedAt/original window при
другом request window, включая пустое пересечение
`specs/openspec/specs/brokerage-account-upcoming-events/spec.md:10–20,63–76`.
- **Minimum change:** пока только определить identity последнего полного
результата и разделение read filter/publication в существующих owners.
После proof возможна локальная замена cross-window parser/selection;
schema migration и универсальный forecast engine не назначаются заранее.
- **Guarantees at risk:** сохранённый calculatedAt, window/empty success,
source/portfolio version fencing, membership isolation, старые rows,
stock/bond payloads и per-currency exact totals.
- **Counterarguments / trade-offs:** scope keys нужны для независимых payloads
и старых windows. ADR 018 `docs/adr/018-minimal-runtime-coordination.md:42–45`
объясняет rejection incompatible candidate; SQLite test
`backend/test/upcoming-events.e2e-spec.ts:321–339` требует data=null для
непересекающегося window, тогда как новая spec требует empty saved result.
Это реальный конфликт evidence, а не разрешение удалить все fallback.
`:619–640` отдельно защищает unchanged calculatedAt при узком окне.
- **Verification or rollback:** cheapest proof — controlled two successful
day/window publications, narrower/disjoint GET, changed dependency перед
publish и one/all scope в existing SQLite suite после решения семантики.
Проверить representative persisted keys/payloads на disposable данных.
Runtime proof и data inspection не выполнялись. До этого rollback-план
для изменения identity нельзя считать определённым.
- **Effort:** M.
- **Related finding IDs:** CPLX-007, CPLX-008.
### CPLX-005 — online/observe compatibility раздваивает reference lifecycle
- **Decision:** INVESTIGATE.
- **Severity:** P2.
- **Confidence:** Medium.
- **Evidence:** `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:84–140,143–217,220–337`;
`backend/src/modules/investments/instruments/bonds/presentation/bond.controller.ts:55–89`;
`backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:233–330`;
`frontend/src/shared/api/resource-query.ts:69–127,144`.
- **Runtime consumer:** `BondController.search/get/getByTicker` и аналогичные
`StockController:58–82,112–119` принимают public `sync=observe`. Новые frontend
queries обычно вызывают default online mode; frontend `.observe()` найден
только в `resource-query.test.tsx:63,167`, не в production callers.
- **Claim:** один public reference use case поддерживает online answer и
persisted/durable resource model с иной error/availability semantics.
Нужность сохранять backend compatibility branch сегодня — **UNVERIFIED**.
- **Current mechanism:** false → online token/search/details/prices; true →
saved discovery/source reads, dependency composition, sync metadata и alias
verification. Frontend observation helper отдельно меняет transport key,
отменяет старый query и пишет envelope в canonical cache.
- **Impact / actual cost:** два набора branches/metadata/state policies при
изменении search/detail/company; труднее определить владельца freshness.
**Impact unmeasured**. RPC fan-out/latency не оценивались в этом направлении.
- **Why now:** `specs/openspec/specs/online-instrument-information/spec.md:8–17`
и ADR 019 `:22–26` описывают online-only reference, но публичный flag и
persistence consumers ещё существуют.
- **Minimum change:** получить consumer/compatibility decision для конкретных
reference endpoints. При доказанном отсутствии обязательства сохранить old
behavior — локально оставить online path и удалить лишь его legacy alternative.
Frontend unused observation export передан audit 04, не объявлен отдельным
delete finding здесь.
- **Guarantees at risk:** public HTTP contract, offline clients, сохранённые
labels/calendar/candle dependencies, canonical cache identity и confirmed
access invalidation. Не удалять CatalogEnrichment/Repository целиком: F3
использует saved calendar sources.
- **Counterarguments / trade-offs:** backend branch имеет реальный entrypoint;
отсутствие вызова в SPA не доказывает отсутствие внешнего consumer. Shared
repository также обслуживает личные forecast dependencies, а старые FK
защищают свечи. Generated transport/DTO mapping не является лишним wrapper.
- **Verification or rollback:** inventory callers/опубликованного query contract,
targeted default-vs-observe HTTP cases на controlled upstream; отдельно
проверить calendar/candle source readers. Никаких реальных API calls для proof
не требуется. Сохранять совместимость до ответа; data deletion требует
отдельного disposable migration/rollback proof и в этот audit не входит.
- **Effort:** M.
- **Related finding IDs:** CPLX-001, CPLX-007.
### CPLX-006 — income read содержит собственную асинхронную import coordination
- **Decision:** INVESTIGATE.
- **Severity:** P2.
- **Confidence:** Medium.
- **Evidence:** `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:391–436`;
`backend/src/modules/investments/income/income-analytics.service.ts:45–93`;
`backend/src/modules/investments/income/income-analytics.controller.ts:24–79`;
`backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:46–126`.
- **Runtime consumer:** оба summary и оба analytics routes передают
`query.sync === 'observe'`; обычный GET выбирает branch с schedule.
- **Claim:** read result и запуск работы соединены через snapshot reads,
membership completion и deferred schedule. Это дополнительный owner
lifecycle личных данных; возможность удалить его целиком без ухудшения
cold history — **UNVERIFIED**.
- **Current mechanism:** local access → readPage → define schedule → default
membership.request → discovery.done.then(schedule) либо await schedule →
repeat readPage → periods/coverage/meta → exact income aggregation.
- **Impact / actual cost:** response может быть вычислен до завершения
deferred work; изменение period/source policy нужно согласовывать с Ledger
и hourly owner. **Impact unmeasured**, без оценки RPC/latency.
- **Why now:** `specs/openspec/specs/tbank-data-sync/spec.md:9–22` оставляет
исключение первой загрузки только Ledger/candles; ADR 019 `:9–16` описывает
ту же границу. В live read code сохранилась более широкая координация.
- **Minimum change:** определить, кто и как делает отсутствующий history
доступным income. Если это уже Ledger/source owner, убрать request-driven
membership/schedule из этого reader, сохранив periods/coverage/unknown results.
Не создавать новый IncomeSyncService до необходимости.
- **Guarantees at risk:** cold-start выбранного и comparison periods,
неполнота не превращается в ноль, membership/revision isolation,
exact totals и passive observe semantics.
- **Counterarguments / trade-offs:** обычное income чтение сейчас может быть
единственным действием пользователя для получения старых операций;
hourly обновляет mutable tail (`scheduler:154–160`), а не всякий history
interval. Механическое удаление ветки оставит данные неполными дольше.
Сложность агрегирования по валютам/периодам не является этим finding.
- **Verification or rollback:** один controlled cold income GET без observe
на пустой coverage и один после Ledger load; проверить lastSuccessfulAt,
selected/previous/year, отсутствие membership RPC и old complete reads.
Нужен product/behavior answer о доступности истории. Режим работы
непроверен runtime; откат локального reader возможен, но UX меняется.
- **Effort:** M.
- **Related finding IDs:** CPLX-002, CPLX-008.
### CPLX-007 — сохранить точность и conditional forecast publication
- **Decision:** KEEP.
- **Severity:** P3.
- **Confidence:** High.
- **Evidence:** `backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:35–49,80–145,154–174`;
`backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:119–130,282–299`;
`backend/src/common/decimal.ts:19–48,59–83`;
`backend/test/upcoming-events.e2e-spec.ts:294–318,344–363,490–513`.
- **Runtime consumer:** F3 плюс stock dividends/bond coupons forecast callers;
pure decimal helpers используются для candidate и output totals.
- **Claim:** guards отвергают stale/несогласованный candidate; точный Money
сохраняет currency и неизвестное значение. Это подтверждённая необходимая
сложность, независимо от спорного scope/window selection.
- **Current mechanism:** transaction fence connection/membership, portfolio
version set, source revision+fetchedAt, прежний result при mismatch,
exact bigint+scale calculations, publication только согласованного candidate.
- **Impact / actual cost:** сопровождение dependency lists и decimal types
стоит внимания; **Impact unmeasured**. Потеря fence создаст неправильную
смесь количества и календаря; Number-замена теряет exact decimal semantics.
- **Why now:** эти guards находятся рядом с CPLX-004 и особенно уязвимы для
ошибочного удаления вместе с parser/fallback logic.
- **Minimum change:** оставить эти механизмы; при будущем window cleanup
сохранять существующую transaction/version validation boundary.
- **Guarantees at risk:** financial accuracy, cross-currency separation,
unknown≠zero, immutable successful calculatedAt, connection isolation.
- **Counterarguments / trade-offs:** «проверили access до расчёта» не защищает
от изменения до записи; test seams здесь доходят до real repository.
Batch reads source versions также учитывают SQLite parameter limit
(`forecast.repository.ts:99–128`), не заменять без proof одним giant query.
- **Verification or rollback:** существующие cited controlled publication
scenarios и decimal cases при любом изменении. Они изучены, но не запущены.
Удаление protections не предлагается; rollback не требуется этому audit.
- **Effort:** S.
- **Related finding IDs:** CPLX-004, CPLX-005, CPLX-009.
### CPLX-008 — сохранить durable reservation и source-owner boundaries
- **Decision:** KEEP.
- **Severity:** P3.
- **Confidence:** High.
- **Evidence:** `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:28–31,48–111,113–175`;
`backend/src/modules/investments/tbank-sync/tbank-sync.repository.ts:174–242`;
`backend/src/modules/investments/portfolio/application/portfolio.service.ts:244–306`;
`backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts:116–147`;
`backend/prisma/schema.prisma:232–262`.
- **Runtime consumer:** registered scheduler F2 и portfolio/operations/events
source owners; timer не является источником личного HTTP-read lifecycle.
- **Claim:** process-level flags/pending maps и durable attempt identity
защищают разные случаи. Замена всего на один interval и Promise.all не
эквивалентна существующему безопасному publication contract.
- **Current mechanism:** tick guard → transaction reservation по current
connection/hourly scope → process restart distinction → sequence sources
→ own worker completion → conditional publication → durable settlement.
- **Impact / actual cost:** диагностика требует нескольких owners;
**Impact unmeasured**. Удаление лишит restart/late response fencing и
независимого сохранения успеха одного ресурса при сбое другого.
- **Why now:** это самая видимая orchestration chain, но она обслуживает
текущий hourly requirement и не должна становиться жертвой LOC cleanup.
- **Minimum change:** оставить source owners и короткие DB fences; lifecycle
ошибки исправлять в них по audit 13, не введением общей distributed workflow
платформы. Single-process предел уже отмечен `ponytail:` в source.
- **Guarantees at risk:** no overlapping attempts, cancelled old work,
current revision ownership, transactions вне network calls, error isolation.
- **Counterarguments / trade-offs:** текущий deployment single-process,
поэтому новый distributed lease заранее не оправдан. KEEP не означает,
что все catch/poll/settle branches корректны: `scheduler:108–110,187–218`
дают отдельный reliability signal, который не лечится удалением reservation.
- **Verification or rollback:** будущие controlled clock/restart/supersession
и persistence-failure checks на durable boundary. Full suites и scheduler
runtime не запускались; отсутствие ошибки не утверждается.
- **Effort:** S.
- **Related finding IDs:** CPLX-002, CPLX-006, CPLX-007.
### CPLX-009 — сохранить banking intent/CAS/tombstone/rollback
- **Decision:** KEEP.
- **Severity:** P3.
- **Confidence:** High.
- **Evidence:** `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:152–196,214–240,251–330`;
`backend/src/modules/banking/bank-cards/application/bank-card-input.ts:74–81,125–142`;
`backend/src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts:35–73`;
`backend/src/modules/banking/bank-cards/domain/rub-money.ts:12–38`;
`backend/prisma/schema.prisma:310–333`.
- **Runtime consumer:** F8 create/update/delete HTTP routes
`bank-cards.controller.ts:133–166`.
- **Claim:** fingerprint, mutable version, original-purchase CAS и deletedAt
не являются несколькими способами обозначить одну state; они защищают
collision, concurrent edits, refund serialization и resurrection отдельно.
- **Current mechanism:** normalize exact input → original fingerprint →
transaction → replay existing result before new-operation validation →
ownership/links/date checks → purchase CAS/refund sum → write/version/tombstone.
- **Impact / actual cost:** guards делают write path длиннее;
**Impact unmeasured**. Их удаление допускает второе денежное влияние,
over-refund или восстановление deleted record после lost response.
- **Why now:** эти механизмы легко ошибочно принять за слишком defensive
CRUD, но они требуются `manual-bank-cards/spec.md:527–589` и различаются
в studied test outcomes.
- **Minimum change:** оставить защищающую transaction и exact arithmetic;
не вводить generic repository/IdempotencyService вокруг этой локальной модели.
- **Guarantees at risk:** exact RUB kopecks, replay/collision protection,
lost update, refund ceiling, ownership, atomic rollback и delete persistence.
- **Counterarguments / trade-offs:** unique ID отсекает duplicate row, но
сам по себе не различает изменённый intent и replay после edit/delete.
Mutex только в приложении не заменит durable version/restart semantics.
Retry transaction может повторять callback; держать внешний side effect
вне callback — обязательный предел модели.
- **Verification or rollback:** `backend/test/bank-cards.e2e-spec.ts:391–422`
replay/collision/delete, `:462–489` refund/replay,
`:622–653` race и rollback rejected source lock. Тесты прочитаны, не запущены.
При будущем изменении нужен real isolated SQLite proof, не mock call count.
- **Effort:** S.
- **Related finding IDs:** CPLX-007.
### CPLX-010 — сохранить identity и runtime границы screener результата
- **Decision:** KEEP.
- **Severity:** P3.
- **Confidence:** High.
- **Evidence:** `backend/src/modules/investments/instruments/stocks/application/stock-screener.service.ts:49–63,65–139`;
`backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts:85–109`;
`backend/src/main.ts:31–38`;
`frontend/src/pages/stocks/ui/StocksPage/StocksPage.vm.ts:58–101`.
- **Runtime consumer:** F7 POST creates searchId; GET page reads именно этот
bounded snapshot, а frontend выбирает результат последнего user intent.
- **Claim:** snapshot/ownership/revision/page validation и local late-result
guard представляют текущие сценарии; они не оправдывают лишнюю presentation
VM, но сами не являются overengineering.
- **Current mechanism:** full selected result один раз → bounded Map/TTL →
stable paging по searchId → user/current-revision/bounds checks; raw body
allowlist поверх transformed DTO; requestId guard на поздние frontend results.
- **Impact / actual cost:** bounded memory и дополнительный search identity;
**Impact unmeasured**. «Проще запрашивать каталог каждой страницей» теряет
стабильность, а удаление guards теряет observable input/owner contract.
- **Why now:** этот flow содержит и actionable CPLX-003, и необходимые seams;
требуется не смешивать простую presentation simplification с state deletion.
- **Minimum change:** сохранить snapshot/guards; не добавлять Redis, cursor
service или adapter с единственной реализацией. Узкий `TBANK_ACCESS`
`instruments.module.ts:24` имеет test seam и не требует cleanup отдельно.
- **Guarantees at risk:** непересекающиеся стабильные страницы, successful
empty result, чужой user/revision, expiry, unknown-input rejection,
latest frontend search after out-of-order responses.
- **Counterarguments / trade-offs:** память process-local, restart теряет
search; TTL/limit делают этот предел явным. Default query library нельзя
механически поставить вместо command search model. Raw body guard не
избыточен при порядке global/local pipes — калибровка 3.5.
- **Verification or rollback:** existing
`stock-screener.service.spec.ts:40–96` (stable/foreign/revision/invalid cases),
configured HTTP `backend/test/stocks.e2e-spec.ts:65–105`.
Frontend latest-result proof проверять с completion barrier, не по уже
видимому newer result. Все эти tests в этом аудите не запускались.
- **Effort:** S.
- **Related finding IDs:** CPLX-003.
## 5. DELETE / SIMPLIFY / KEEP / INVESTIGATE
| Decision | IDs | Что сокращать или сохранять | Граница решения |
| --- | --- | --- | --- |
| DELETE | — | Самостоятельных подтверждённых DELETE findings в scope 02 нет | Отсутствие consumers у manual sync и frontend observe передано 04, не пересчитано как complexity finding |
| SIMPLIFY | CPLX-001 | Screen timer/ref/descriptor → existing query scheduling | Только текущие mounted investment reads; auth/cache guards оставить |
| SIMPLIFY | CPLX-002 | Date/screen/source round-trip → existing operation interval owner | Не удалять весь planner или менять coverage/history policy |
| SIMPLIFY | CPLX-003 | Translation-only VM → local presentation callbacks | Aria labels, typed references и presentation isolation сохранить |
| KEEP | CPLX-007 | Exact arithmetic и conditional forecast publication | Даже при изменении identity/window |
| KEEP | CPLX-008 | Durable attempt/source-owner coordination | Reliability fixes не равны удалению fence |
| KEEP | CPLX-009 | Banking intent fingerprints/CAS/tombstones/transactions | Replay до validation нового refund обязателен |
| KEEP | CPLX-010 | Stable bounded screener snapshot, input/owner guards и late-response fence | Не полагаться на local forbid после global whitelist |
| INVESTIGATE | CPLX-004 | Forecast window/identity/read selection | UNVERIFIED safe replacement; specs/test expectation conflict |
| INVESTIGATE | CPLX-005 | Legacy observe reference lifecycle | UNVERIFIED external consumer/compatibility obligation |
| INVESTIGATE | CPLX-006 | Request-driven income schedule | UNVERIFIED cold-history behavior после удаления |
**Ponytail shortlist, от большего ожидаемого сокращения coordination к меньшему:**
- `native:` убрать собственный screen read scheduler; использовать установленный
Query `refetchInterval` с owner policy. [CPLX-001;
`frontend/src/features/tbank-connection/model/screen-sync.ts:22–61`]
- `shrink:` убрать Ledger round-trip через screen plan; использовать существующий
interval normalizer. [CPLX-002;
`backend/src/modules/investments/operations/application/brokerage-operations.service.ts:120–151`]
- `yagni:` объединить translation-only presentation hook с его view;
сохранить оба accessible callbacks. [CPLX-003;
`frontend/src/pages/stocks/ui/StocksPage/StocksPagePresentation.vm.ts:1–20`]
**net:** сокращение строк не измерено: implementation diff не создавался;
подтверждённых dependency-кандидатов на удаление — **0**. Это число означает
результат рассматриваемых simplifications, не полный dependency audit.
## 6. Cost & benefit
| ID / группа | Ожидаемый класс экономии | Фактическое измерение | Обязательные регрессии / предел |
| --- | --- | --- | --- |
| CPLX-001 | Cognitive load, maintenance, меньше собственного scheduling/state code | not measured; runtime/requests/CI minutes/agent tokens не измерены | Enabled/session, changing key, background policy, cached result, retry только GET; нельзя обещать общий 30-minute throttle одним interval |
| CPLX-002 | Cognitive load, навигация, maintenance диапазонов | not measured; performance не оценивалась | Date bounds/subseconds/all/unknown membership, normalization, coverage и cursor |
| CPLX-003 | Малое сокращение code navigation и maintenance | not measured | Translation/accessibility; отсутствие роста generic UI abstraction |
| CPLX-004–006 | Возможное уменьшение mixed lifecycle/compatibility cost | not measured; выгода условна до verification | Public contract, cold history, saved forecast identity и source-owner boundaries |
| CPLX-007–010 | Сохранение защищающих гарантий; положительная cleanup экономия не заявляется | not measured; изученный source/test intent не равен runtime proof | Financial accuracy, CAS/transaction/replay/owner isolation, stable paging, lifecycle |
Не заявлены проценты ускорения, число удаляемых LOC, CI minutes или экономия
agent tokens. Снижение навигации — качественное следствие удаления лишнего
перехода, а не измерение productivity.
### 6.1. Before/After logic outline — три подтверждённых случая
Это варианты изменения, **не внесённые исправления**.
#### A. CPLX-001 — periodic investment reads
**Before**
```text
page создаёт entity queries
page передаёт scope и resources в useScreenSync
effect обновляет mutable resourcesRef
другой effect держит timer до unmount
tick вручную вызывает refetch каждого resource
App/каждый query отдельно задают focus/reconnect/stale policy
hook вычисляет ещё refresh/retry/status model, который page не читает
```
**After**
```text
page создаёт те же entity queries
owner options задают periodic read interval для этих observers
Query enabled/session/key/cleanup владеют запуском и отменой
view продолжает читать data/meta/query error
retry существующего владельца повторяет только HTTP read
focus/reconnect/stale policy согласована у тех же query owners
```
**Потери/добавленная сложность:** у query owners появляются явные interval/
background options; observer timers имеют иную фазу, чем один page timer.
Если нужно opt-in только для части callers, потребуется минимальный existing
query option input. Не создавать новый polling registry или второй scheduler;
если обязательна атомарная групповая свежесть, одной native option недостаточно,
но текущий hook тоже её не обеспечивает.
#### B. CPLX-002 — Ledger first-load intervals
**Before**
```text
access.local → selected accounts
ledgerRange(query) → from/to (exclusive end)
cursor при наличии задаёт прежний to
Date → ISO; to - 1ms → screen intent
planInvestmentScreen(operations, empty instruments, membershipKnown)
→ membership source + normalized screen operations + history sources
→ priority sort
filter kind=operations, priority=screen; снова собрать intervals
sync.readPlan(intervals, current options)
saved page/coverage/cursor
```
**After**
```text
access.local → те же selected accounts
тот же ledgerRange и cursor end
при membershipKnown и from < to:
operationIntervals(selected accounts × already computed [from,to))
sync.readPlan(intervals, те же options)
тот же saved page/coverage/cursor
```
**Потери/добавленная сложность:** reader должен явно сохранить membership
guard и unfiltered source-import semantics. Оставить общую date policy и
normalization у прежних helpers; иначе локальное сокращение превратится в
новую конкурирующую interval implementation. Изменение history budget не
включать в этот before/after.
#### C. CPLX-003 — Pagination labels
**Before**
```text
StocksPageView: useTranslation
→ useStocksPagePresentationVm: useTranslation
→ два typed translation callbacks
← callbacks object
Pagination получает callback references
```
**After**
```text
StocksPageView: useTranslation
два typed callback references рядом с presentation
Pagination получает те же aria-label values
```
**Потери/добавленная сложность:** небольшое исключение uniform VM layout и
локальные closures в view. Если convention запрещает их абсолютно, выигрыш
мал и правило нужно сначала согласовать; не вводить shared utility/data-VM
зависимость ради устранения одного файла. Accessibility не упрощается.
## 7. Top actions
Только подтверждённые actionable направления; investigation не выдаётся за
готовое действие удаления. Ранжирование — value/risk/effort, не severity alone.
### Quick wins
1. **CPLX-002:** локально убрать Ledger → screen-planner round-trip с сохранением
`operationIntervals/readPlan` и current options. Самая небольшая содержательная
change boundary; обязательна проверка exact interval/cursor semantics.
2. **CPLX-003:** при ближайшем изменении Pagination убрать translation-only hook.
Небольшая стоимость/выгода; не делать отдельный архитектурный рефакторинг.
### Решения, требующие policy/test подтверждения
3. **CPLX-001:** заменить page timer existing Query scheduling и согласовать
focus/reconnect/background/key-change policy. Перед implementation нужен
маленький controlled boundary proof; ADR нужен только если меняется
продуктовый cadence contract, а не ради применения query option.
CPLX-004–006 требуют ответов раздела 9 до назначения cleanup. KEEP-механизмы
не помещены в список «улучшений», чтобы не увеличивать backlog без необходимости.
## 8. Cross-audit signals
Каждый пункт — передача темы владельцу, без второго полноценного аудита.
| Тема | Evidence | Рекомендуемый audit ID |
| --- | --- | --- |
| Нет найденного production consumer `TbankSyncService.request`; controller предоставляет только status, shared planner всё ещё нужен Ledger | `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:54–311`; `backend/src/modules/investments/tbank-sync/tbank-sync.controller.ts:34–40`; поиск `TbankSyncService` по backend source | 04 — Dead Code & Legacy |
| Frontend `.observe()` consumer обнаружен только в tests; backend observe route при этом жив | `frontend/src/shared/api/resource-query.ts:69–127`; `frontend/src/shared/api/resource-query.test.tsx:63,167`; `backend/src/modules/investments/instruments/bonds/presentation/bond.controller.ts:55–89` | 04 — Dead Code & Legacy |
| Две publicInstrument projections требуют отдельного DRY ownership review | `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:499–530`; `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:646–677` | 03 — Duplication & Reusability |
| Общий catch/settle/poll hourly lifecycle и stable calendar refresh policy | `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:108–110,187–218`; `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:189–193,679–692` | 13 — Reliability & Security |
| Default income GET/source history ownership и readPlan backfill выходят за complexity cleanup | `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:408–435`; `backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:102–124`; `specs/openspec/specs/tbank-data-sync/spec.md:9–22` | 06 — Backend Architecture; 07 — API/Data Flow |
| Overview source metadata не передаётся в loaded view | `frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:125–135`; `frontend/src/pages/account/ui/AccountPage/AccountPage.tsx:24–34` | 05 — Frontend Architecture |
| Search detail RPC fan-out и banking list operationView повторные reads | `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:308–317`; `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:41–68,361–364` | 07 — API/Data Flow Efficiency |
| Forecast old-range expectation конфликтует с новой saved-window spec; документация читает разные поколения owner model | `backend/test/upcoming-events.e2e-spec.ts:321–331`; `specs/openspec/specs/brokerage-account-upcoming-events/spec.md:18–20`; `docs/architecture/containers.md:142–148`; `docs/adr/019-hourly-reads-and-online-instrument-reference.md:9–16` | 08 — Test Quality; 15 — Specs & Documentation |
Ни один signal не включён в actionable count 02 как отдельное исправление
correctness/security/performance/dead code/tests/tooling.
## 9. Open questions / verification plan
| Вопрос, действительно меняющий решение | Самый дешёвый способ проверки | Влияние на finding |
| --- | --- | --- |
| Интервал 30 минут относится только к periodic tick или также mount/focus/reconnect; нужна ли background вкладка? | Согласовать смысл `docs/adr/019-hourly-reads-and-online-instrument-reference.md:27–29`, затем controlled Query/MSW fake-time case с существующими policies | CPLX-001: native замена подтверждена, точная cadence policy ещё требует решения |
| Ledger нормализация должна быть upstream-unfiltered при UI instrument filter? | Сравнить current planner `force=false` и existing normalizer outputs на one/all, default dates, subsecond cursor и unknown membership | CPLX-002: ограничивает безопасный минимальный diff |
| Допустимы ли два trivial presentation callbacks во view при текущей convention? | Узкое semantic решение по `frontend/.ai/conventions/ui.md:58–89`; проверить typed callback refs и aria labels | CPLX-003: может оставить P3 cleanup отложенным без новой abstraction |
| Что является identity последнего полного event forecast; какой новый полный результат заменяет старый window? | Согласовать current specs/test conflict; two-window publication + narrow/disjoint read + dependency race в existing SQLite suite | CPLX-004: до ответа safe simplification UNVERIFIED |
| Есть ли обязательные внешние consumers saved `sync=observe` reference endpoints? | Проверить предоставленный public contract и сведения владельца о consumers; targeted HTTP default/observe case | CPLX-005: отсутствие SPA consumer не разрешает breaking backend delete |
| Кто обеспечивает cold historical income после отказа от request-driven membership/schedule? | Один cold income GET и один после Ledger interval load на isolated fixture; уточнить UX owner истории | CPLX-006: отсутствие полного результата нельзя заменять нулём |
**Минимальные будущие проверки, не запущенные в этом аудите:** существующие
frontend MSW/RTL boundary cases для CPLX-001/003 и isolated SQLite scenarios
для CPLX-002/004/006; published contract/consumers для CPLX-005. Не требуется
новая audit test infrastructure. Статический audit не доказывает отсутствие
временных race или актуальность реальных данных.
### 9.1. Проверка выполнения самого аудита
- Полный протокол прочитан; сохранены 9 общих разделов.
- В inventory: 8 live flows с entrypoints/owners/callers, complexity justification
table, seams/guards/fallback/states/compatibility assessment.
- Калибровка: два «сложно, но нужно» и один «просто, но рискованно».
- Findings CPLX-001–010 имеют все обязательные поля; actionable/KEEP/INVESTIGATE
и unmeasured/UNVERIFIED разделены.
- Before/After: три подтверждённых случая и явная added-complexity цена каждой
рекомендации. Финальная consolidation table содержит только эти 10 IDs.
- Boy Scout semantic review отчёта: проверены scope 02, конкретные владельцы,
реальные consumers, контраргументы, preservation точности/transactions/CAS/
replay/auth/cache/lifecycle; source cleanup не выполнялся.
- После записи выполнены `git rev-parse HEAD`,
`git status --porcelain=v1 --untracked-files=all`, `git diff --check` и
`git diff --cached --check` из каждого из пяти checkout: HEAD совпадает
с таблицей metadata, status/diff checks без вывода. Все source checkout
остались clean; staged/unstaged/new source изменений не появилось.
- `git check-ignore -v audit-results/02-complexity-and-overengineering-report.md`
подтвердил root ignore `.gitignore:2`. Сам новый файл проверен отдельно:
`git diff --no-index --check /dev/null audit-results/02-complexity-and-overengineering-report.md`
без whitespace errors.
- Read/Grep нового отчёта подтвердили разделы 1–9, special results и финальную
таблицу. `rg --count` по heading pattern вернул 10 findings; по field-label
pattern — 150 полей (14 обязательных + Runtime consumer у каждого из 10).
Таблица вручную сверена с полями; aggregation учитывает только 10 строк
consolidation, не дополнительные сводные таблицы выше.
**Ограничения:** static risk-based выборка, без existing graph, без runtime
tests/build/full-suite, без реальной сети и persisted production data inspection.
Generated schema/lockfiles/tools не менялись; архивы/references/dated audits
не изменялись. Package manifests и installed dependency source изучались,
но dependency/security/performance audit не выполнялся. Некоторые initial
path guesses отсутствовали; последующие evidence взяты только из реально
прочитанных paths, не из предположений о layout.
## Machine-readable consolidation table
| ID | Decision | Severity | Confidence | Repo | Files | One-line impact | Effort | Related IDs |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| CPLX-001 | SIMPLIFY | P2 | High | frontend | src/features/tbank-connection/model/screen-sync.ts; src/entities/portfolio/api/usePortfolio.ts; src/entities/bond/api/queries.ts; src/app/ui/App.tsx | Page timer/ref добавляет второй read lifecycle; эффект не измерен | M | CPLX-005 |
| CPLX-002 | SIMPLIFY | P2 | High | backend | src/modules/investments/operations/application/brokerage-operations.service.ts; src/modules/investments/investment-sync-plan.ts; src/modules/investments/operations/domain/operations-sync-plan.ts | Ledger разворачивает выбранный interval через общий screen plan; эффект не измерен | S | CPLX-006,CPLX-008 |
| CPLX-003 | SIMPLIFY | P3 | High | frontend | src/pages/stocks/ui/StocksPage/StocksPagePresentation.vm.ts; src/pages/stocks/ui/StocksPage/StocksPage.view.tsx | Два translation callbacks требуют отдельного файла; эффект не измерен | S | CPLX-010 |
| CPLX-004 | INVESTIGATE | P2 | Medium | backend | src/modules/investments/portfolio/infrastructure/forecast.repository.ts; src/modules/investments/portfolio/application/forecast.service.ts; src/modules/investments/upcoming-events/upcoming-events.service.ts | Window identity смешана с fallback/publication; безопасная замена UNVERIFIED | M | CPLX-007,CPLX-008 |
| CPLX-005 | INVESTIGATE | P2 | Medium | backend,frontend | backend/src/modules/investments/instruments/application/catalog-discovery.service.ts; backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts; frontend/src/shared/api/resource-query.ts | Online/observe сохраняют два lifecycle; обязательство compatibility UNVERIFIED | M | CPLX-001,CPLX-007 |
| CPLX-006 | INVESTIGATE | P2 | Medium | backend | src/modules/investments/operations/application/brokerage-operations.service.ts; src/modules/investments/income/income-analytics.service.ts; src/modules/investments/income/income-analytics.controller.ts | Income read также планирует импорт; безопасное удаление cold-history пути UNVERIFIED | M | CPLX-002,CPLX-008 |
| CPLX-007 | KEEP | P3 | High | backend | src/modules/investments/portfolio/infrastructure/forecast.repository.ts; src/common/decimal.ts; src/modules/investments/upcoming-events/upcoming-events.service.ts | Точная арифметика и conditional publication защищают согласованный monetary snapshot | S | CPLX-004,CPLX-005,CPLX-009 |
| CPLX-008 | KEEP | P3 | High | backend | src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts; src/modules/investments/tbank-sync/tbank-sync.repository.ts; src/modules/investments/portfolio/infrastructure/portfolio.repository.ts | Durable attempts/source fences защищают restart и позднюю публикацию | S | CPLX-002,CPLX-006,CPLX-007 |
| CPLX-009 | KEEP | P3 | High | backend | src/modules/banking/bank-cards/application/bank-operations.service.ts; src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts; src/modules/banking/bank-cards/domain/rub-money.ts | Intent/CAS/tombstones/transactions защищают точные replay/refund semantics | S | CPLX-007 |
| CPLX-010 | KEEP | P3 | High | backend,frontend | backend/src/modules/investments/instruments/stocks/application/stock-screener.service.ts; backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts; frontend/src/pages/stocks/ui/StocksPage/StocksPage.vm.ts | Snapshot и owner/input/late-result guards имеют текущие сценарии и consumers | S | CPLX-003 |