Переносит архив `audits/2026-10-11/` (19 файлов) из codex/audit-remediation-2026-10-11 в develop и добавляет точечный allowlist в .gitignore.
88 KiB
Аудит 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
- Факт: наибольшая подтверждённая лишняя координация в живом frontend —
useScreenSync: страницы передают descriptor и массив resources в отдельный timer, хотя query-owner уже управляет lifecycle. Все изученные production callers используют только его побочный эффект. CPLX-001 / SIMPLIFY / P2. - Факт: живой Ledger сначала вычисляет интервал, превращает его обратно
в строки screen intent, прогоняет через многоцелевой planner и извлекает
только operations/screen intervals. Для этого caller достаточно имеющихся
operationIntervalsиreadPlan. CPLX-002 / SIMPLIFY / P2. Удаление всего planner не следует из этого вывода. - Факт, небольшая стоимость: двухcallback presentation VM скринера не владеет state, fetching, lifecycle или отдельным consumer. Локальная замена сохраняет accessibility; это необязательный cleanup. CPLX-003 / SIMPLIFY / P3.
- Риск и недоказанная возможность упрощения: forecast связывает request window, persisted identity и выбор fallback; online/observe инструментов обслуживают разные модели; income read содержит асинхронное планирование импорта. Смешение моделей доказано, но безопасное удаление всех ветвей — UNVERIFIED, поэтому CPLX-004–006 / INVESTIGATE, а не массовый delete.
- Факт: conditional publication, source-version fences, точная денежная арифметика, durable reservation, банковские fingerprints/CAS/tombstones и стабильные screener snapshots имеют конкретные текущие сценарии. CPLX-007–010 / KEEP. Их сокращение до «обычного GET/CRUD/timer» создаст больше проблем, чем снимет сложности.
- Итог: подтверждены 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, accountAccountPage.vm.ts:78–84, operationsAccountOperationsPage.vm.ts:96–99, eventsAccountEventsPage.vm.ts:67–75, incomeAccountIncomePage.vm.ts:66–69, bondsBondsPage.vm.ts:13, bondBondPage.vm.ts:45–55, stockStockPage.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. Manualrefetch: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 plannerforce=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.gettbank-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 testbackend/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принимают publicsync=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–422replay/collision/delete,:462–489refund/replay,:622–653race и 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_ACCESSinstruments.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 HTTPbackend/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; использовать установленный QueryrefetchIntervalс 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
- CPLX-002: локально убрать Ledger → screen-planner round-trip с сохранением
operationIntervals/readPlanи current options. Самая небольшая содержательная change boundary; обязательна проверка exact interval/cursor semantics. - CPLX-003: при ближайшем изменении Pagination убрать translation-only hook. Небольшая стоимость/выгода; не делать отдельный архитектурный рефакторинг.
Решения, требующие policy/test подтверждения
- 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 |