docs: archive technical audit 2026-10-11 in develop (#36)

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