# Аудит 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//ui//`. Все восемь используют вызов как 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 |