Переносит архив `audits/2026-10-11/` (19 файлов) из codex/audit-remediation-2026-10-11 в develop и добавляет точечный allowlist в .gitignore.
917 lines
88 KiB
Markdown
917 lines
88 KiB
Markdown
# Аудит 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 |
|