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

88 KiB
Raw Permalink Blame History

Аудит 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

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

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

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

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

StocksPageView: useTranslation
→ useStocksPagePresentationVm: useTranslation
  → два typed translation callbacks
← callbacks object
Pagination получает callback references

After

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 подтверждения

  1. 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