Files
main/audits/2026-10-11/reports/07-api-and-data-flow-efficiency-report.md
T
codex-bot d07fbfabb1 docs: archive technical audit 2026-10-11 in develop (#36)
Переносит архив `audits/2026-10-11/` (19 файлов) из codex/audit-remediation-2026-10-11 в develop и добавляет точечный allowlist в .gitignore.
2026-10-17 19:38:03 +03:00

971 lines
87 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Аудит 07 — API & Data Flow Efficiency
## 1. Audit metadata
- **Дата:** 2026-10-11. **Результат:** независимый статический аудит полного пути
React Query → HTTP → Nest → embedded SQLite/Prisma либо T-Bank/MOEX → UI.
- Workspace: `/Users/ksv741/Project/vibe/WealthLedger`.
- Единственное изменение: новый
`audit-results/07-api-and-data-flow-efficiency-report.md` в root workspace.
Исходники, конфигурации, тесты, public artifacts и история четырёх repo read-only.
- Начальные `git rev-parse HEAD && git status --short`:
| Repo / путь относительно указанного workspace | HEAD | Начальный status |
| --- | --- | --- |
| root `/Users/ksv741/Project/vibe/WealthLedger` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | clean |
| `frontend` | `c8821200ce699dab1ce65360d1968d5877a2e78b` | clean |
| `backend` | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` | clean |
| `specs` | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` | clean |
| `docs` | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` | clean |
**Инструкции и skills.** В самом начале загружен установленный `ponytail-audit`:
complexity-pass ищет минимальное сокращение coordination, а performance/correctness
исследуются отдельно по заданию 07. Применены `performance-optimization`,
`graphify`, `verification-before-completion`. Прочитаны собственные AGENTS всех
repo; frontend engineering/architecture/data/auth/testing/TS/UI, backend
engineering/architecture/data/http/integrations/security/testing/errors/observability.
Root conventions применены только к итоговому Markdown.
**Graphify:** существующего `graphify-out/graph.json` не найдено в root и в четырёх
repo. Проверены directory listings root/frontend/backend и отдельные repo-local
Glob. Попытки Glob по отсутствующим frontend/backend `graphify-out` дали ошибку
поиска; последующий поиск от существующих repo roots — `No files found`.
Граф, extraction, scripts и дополнительные artifacts не создавались. Все edges
ниже подтверждены source trace, а не предполагаемым графом.
**Источники гипотез:** `technical-audit-2026-10-11.md` прочитан исключительно как
реестр направлений проверки. Все выводы ниже заново сверены с callers, source,
конфигурацией и intended semantics. Отчёты аудитов 05/06 и другие результаты
консолидации не читались.
**Метод и исполненные проверки:** Read/Glob/Grep с абсолютными repo paths;
Git metadata в пяти cwd; byte-for-byte сравнение имеющихся public artifacts:
```text
cwd = /Users/ksv741/Project/vibe/WealthLedger
git diff --no-index --exit-code "backend/openapi.json" "frontend/src/shared/api/spec/openapi.json"
result = exit 0, output empty
```
Это подтверждает равенство файлов на диске, **не** свежую генерацию runtime schema.
Проверены пути operations, bond search и screener в snapshot:
`frontend/src/shared/api/spec/openapi.json:2356,3336,4194,6066`.
**Library semantics:** Context7 `/tanstack/query` подтвердил ручной `refetch` при
`enabled: false`, stale mount/focus/reconnect и независимость interval от staleTime;
документация также различает автоматический dedup и императивный cancel/refetch.
Источники: [disabling queries](https://tanstack.com/query/latest/docs/framework/react/guides/disabling-queries),
[important defaults](https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults),
[useQuery](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery).
Установлен Query `5.102.8`, `openapi-react-query 0.5.4`:
`frontend/package.json:46,54`. Context7 lookup openapi-react-query не дал точного
пакета; вместо подмены другим generator прочитан установленный source
`frontend/node_modules/openapi-react-query/src/index.ts:194–219`:
key = method/path/init, queryFn передаёт AbortSignal.
Context7 `/prisma/web` и официальный transactions URL сейчас выдают Prisma 8,
тогда как проект использует `7.10.0` (`backend/package.json:50–51,64`).
Version-specific SQL expansion/parallelism **UNVERIFIED**; бюджет ниже считает
явные Prisma model calls, не обещает «include = один SQL» и не предлагает API v8.
Nest guard/controller/DTO поведение установлено по исходникам, спорной
library-specific Nest семантики в recommendations нет.
**Не запускалось:** приложение/порты, live bank/broker/MOEX/NCR API, install,
build, full-suite, codegen, migrations, production benchmark, staging/commit/PR.
Тесты не запускались: достаточен static proof механизмов; штатные frontend test
scripts запускают build packages, а SQLite suites создают/изменяют БД. Чтение
тестов — evidence их intent, не утверждение об их текущем прохождении.
## 2. Executive verdict
1. **Факт:** Accounts и Portfolio читаются локально; Events не делает upstream RPC,
но может вычислять и публиковать forecast в SQLite на GET. Основной read-model
оправдан и должен сохраниться.
2. **Подтверждённая граница лишней работы:** обычные Income GET инициируют
membership/operations, а Ledger дополнительно планирует отсутствующую историю
вне выбранного периода. Это второй владелец работы рядом с hourly walk,
FLOW-001. Даже HTTP `sync=observe` у Ledger не является passive переключателем.
3. **Подтверждённый fan-out, не измеренная задержка:** online bond search =
`1 + N + I(N>0)` logical RPC, `0≤N≤30`; details параллельны между собой,
prices ждёт их завершения. Detail-поля действительно нужны карточке;
безопаснее сначала убрать независимую waterfall, FLOW-003.
4. **Подтверждённые усилители работы:** timer вручную refetch-ит disabled queries
(FLOW-002); неизвестный заранее invalid broker token превращается в HTTP 500
и повторяется frontend transport до трёх раз (FLOW-004).
5. **Факт структуры DB cost:** banking history выполняет `3 + Σ(1+t+s+c+r)`
model reads на страницу; embedded SQLite устраняет сетевую DB latency, но не
стоимость повторного materialization/queries, FLOW-005.
6. **Fresh/consistent risk по коду:** hourly reuse стабильного bond calendar и
пересекающийся fallback forecast способны удерживать старый результат после
новой попытки, FLOW-006/007. Это не аргумент удалять CAS или complete publication.
7. **Непроверенные гипотезы:** общий выбор 30 секунд / 30 минут, полная hydration
всех portfolio snapshots и объём income DTO требуют policy/scale evidence,
FLOW-008/009. Их нет в confirmed actionable severity.
**Counts:** actionable **7**: P0=0, P1=0, P2=7, P3=0.
Отдельно **KEEP=1**, **INVESTIGATE=2**. Runtime latency, throughput, bytes,
production rate exhaustion и ускорения **UNVERIFIED**; для всех findings
**Impact unmeasured**. Severity основана на подтверждённом механизме и локальном
риске, не на выдуманных p95 или процентах.
## 3. Inventory & coverage
### 3.1. Метод подсчёта и область применимости
Изучены 8 сценариев A–H, включая все пять обязательных. Выборка risk-based:
личные reads, online fan-out, cursor/history, аналитика, scheduler/calendar,
financial read/write projection и catalog pagination. Это не endpoint census
всего приложения и не аудит frontend composition/backend domain style.
- **HTTP:** один GET/POST/PUT к приложению — один attempt. Query cache hit = 0;
shared in-flight Query key = один автоматический fetch, а не число observers.
- **RPC:** `TbankClient` method invocation — logical RPC. Transport attempts
отдельно: 1–2 при допущенном RPC; queue rejection = 0 сетевых attempts.
MOEX HTTP отдельно от gRPC, NCR HTTP отдельно от обоих.
- **DB:** считаются явно вызванные Prisma model reads. `$transaction`, BEGIN,
COMMIT, relation SQL expansion, disk I/O, hydration time не приравниваются к
отдельному удалённому round-trip. Нужен SQL trace для actual SQL count.
- Сценарии счёта имеют действующую сессию/connection/membership. JWT validate не
читает DB (`backend/src/modules/auth/infrastructure/jwt.strategy.ts:21–25`).
Auth bootstrap/refresh, logos CDN и действия пользователя вне одного сценария
вынесены за основной бюджет. Ошибки access могут оборвать граф раньше.
- «Типовой» ниже означает **явно заданную условную ветку**, не measured median.
Произвольная сессия с focus/reconnect/таймерами не имеет конечного общего max.
Обозначения:
| Symbol | Значение |
| --- | --- |
| `A` | число доступных счетов текущего подключения |
| `N` | выбранные search results после дедуп ISIN/UID и limit, 0…30 |
| `R_T` | недостающие account/month intervals выбранного Ledger/Income scope |
| `R_H` | недостающие history intervals вне выбранного scope |
| `p_j` | cursor pages interval j, ≥1 при завершённой загрузке даже пустого interval; upper unknown |
| `m` | membership logical RPC, 0 или 1 при новом source attempt |
| `K` | actual distinct metadata producer calls, 1–2 на producer (UID/FIGI fallback) |
| `M(X)` | metadata DB reads: `ceil(U/400)+ceil(F/400)+ceil(V/400)` для distinct UID/FIGI и найденных canonical UID |
| `b` | непустые calendar batches: `ceil(S/100)+ceil(B/100)` для unique stock/bond UID в scope |
| `J` | дополнительные DB reads awaited source reservation branches; зависит от missing coverage/active work |
| `Q_F` | DB reads forecast.resolve на выбранной ветке; формула ниже |
M не равен количеству positions: identifiers дедуплицируются, matched UID может
отсутствовать или быть неоднозначным. Источник: metadata repository `44–112`.
### 3.2. Статический request graph с actual callers
```text
App QueryClient (focus/reconnect on; Query retries off)
→ session-aware query → generated [method,path,init] / AbortSignal
→ authenticatedFetch → GET transport: deadline + ≤2 transient retries
→ Nest JWT guard / DTO validation / user.sub
Accounts → Portfolio.saved → access.local + SQLite snapshots + hourly
Portfolio → saved + saved metadata → точная projection → UI
Operations → local + readPlan → local page + metadata → UI cursor page
↘ missing chosen scope AND history backfill → worker RPC
Income → collectRanges → local period union → exact totals/entries → UI
↘ membership.request → readPlan(history:true) → worker RPC
Events → local Portfolio + saved calendar → forecast.resolve (conditional write)
Bonds search → onlineToken → FindInstrument → N BondBy → GetLastPrices
→ public projection → card fields → UI
Hourly native tick → durable reservation → membership → portfolios → operations
→ calendar sources → local forecast calculation/publication
```
Query/transport owners: `frontend/src/app/ui/App.tsx:13–21`,
`frontend/src/shared/api/authenticated-query.ts:15–50`,
`frontend/src/shared/api/client.ts:53–119,127–151,205–249`.
Backend composition inspected in portfolio/operations/income/instruments/
instrument-sources/tbank-sync module files; the source providers are shared,
not redeclared per consuming module.
### 3.3. Flow Request Ledger
Числа относятся к одной успешной попытке queryFn без transport retries;
retry multipliers и background work раскрыты отдельно. Cache min=0 требует
fresh cache для **всех** relevant keys и отсутствия imperative refetch.
| Flow / scenario + source | HTTP min / conditional typical / max одной загрузки | Foreground DB reads | Upstream logical / critical path | Conditions / cache / enabled |
| --- | --- | --- | --- | --- |
| **A Accounts list**: AccountsPage.vm `21–24` + AccountsController `19–22` | 0 / 1 cold / 1 | 6 при valid connection | 0 / local DB + DTO | accounts staleTime 1 800 000 ms; shell observers share same key; session required |
| **B Account overview/Portfolio**: AccountPage.vm `66–84` | 0 / 4 если accounts fresh, иначе 5 / 5 distinct GET | `6 + (4+M(P)) + (10+M(L)+J) + (7+M(P)+M(S)+3b+Q_F) + income branch` | local Portfolio=0 RPC; total может включать D/E background; foreground GET параллельны | portfolio/operations/events/income enabled accountId; operations limit5; same account key title/breadcrumb dedup |
| **C Online bond search**: BondsPage.vm `9–13`, BondController `55–65`, discovery `295–337` | 0 disabled/fresh / 1 valid cold / 1 | 1 onlineToken read | empty=1; nonempty=N+2 (3…32); path Search → slowest Detail → Prices | debounce300ms; trimmed q≥2; Query staleTime30s; backend uppercase normalizes; frontend case-different keys не дедупятся |
| **D Brokerage Ledger**: AccountOperationsPage.vm `77–99`, history hook `12–30`, OperationsController `22–38` | 0 / 1 page GET + shared accounts GET если cold / 2 включая shell | `10 + M(page) + J`, cursor ещё +5; SQL expansion unknown | background `Σ p_j`, j∈dedup(R_T∪R_H); foreground не ждёт RPC | page100; backend max1000, take limit+1; filters/categories/cursor server-side; full coverage chosen period не отменяет missing history |
| **E Income analytics**: AccountIncomePage.vm `64–69`, IncomeController `66–79` | 0 / 1 analytics + accounts GET если cold / 2 включая shell | observe=6; ordinary fully covered/fresh membership=20 + one write fence; extra `J` при missing work | `m + Σ p_j` over missing period union + outside history; foreground обычно local, membership.done schedule detached | selected/previous/yearAgo ≤3 calendar periods, unlimited duration; frontend НЕ передаёт observe; summary на overview тот же owner с 1 периодом |
| **F Hourly refresh + forecast**: scheduler `56–175` | 0 browser requests; 0 Nest HTTP инициаторов | variable reservations/staging/publication; `q` poll iterations → q work reads; finite max unknown | `1+2A+Σp_i+K+D_c+S_c` T-Bank плюс MOEX; sequential owner phases | one process native tick60s, due60min, no overlap reservation; missing/invalid connection gates; detail/dividend/bond-event stable cache |
| **G Manual bank card history**: CardPage.vm `35–45`, bank-card queries `76–144` | 0 session disabled / 4 cold or stale mount GET (card/cards/history/summary) / 4; all mode=3+P_h | history `3+Σ(1+t+s+c+r)`; other endpoints separately | 0 RPC; all mode sequential HTTP pages | default staleTime0 revalidates on mount; UI page10; choices5/10/20/50/100/all; backend limit100; all loop100/page until hasNext=false |
| **H Stock screener**: StocksPage.vm `29–55,65–101`, StockScreenerService `65–109` | idle0 / explicit search1 POST /1; next page1 GET | search1 current read; next page1 local read | search1 Shares RPC; page0 RPC | outside Query: no Query cache/dedup; snapshot TTL5min/max100/page30/user+revision; GET retry applies only to page, POST not transient retried |
Paths в строках с сокращёнными именами раскрываются в findings и owner tables.
Полные frontend sources flows A–H:
`frontend/src/pages/accounts/ui/AccountsPage/AccountsPage.vm.ts:21–24`,
`frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:66–84`,
`frontend/src/pages/bonds/ui/BondsPage/BondsPage.vm.ts:9–13`,
`frontend/src/pages/account-operations/ui/AccountOperationsPage/AccountOperationsPage.vm.ts:77–99`,
`frontend/src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.vm.ts:64–69`,
`frontend/src/pages/card/ui/CardPage/CardPage.vm.ts:35–45`,
`frontend/src/pages/stocks/ui/StocksPage/StocksPage.vm.ts:29–55,65–101`.
Flow F инициируется только
`backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:56–175`.
Для B сумма DB — foreground endpoints одного холодного overview, а не transaction
всего экрана; J/background не измерены. Для D/E общий session max — unknown;
invalid cursor может дать дополнительный fetch первой страницы
(`frontend/src/entities/broker-operation/model/useBrokerageOperationHistory.ts:32–59`).
Для G `P_h=max(1,ceil(T/100))` только при стабильном наборе T операций; при
конкурентных изменениях offset dataset точный total requests unknown.
**Дополнительный detail flow.** BondPage.vm `23–55`: detail → после получения UID
quote/metrics/candles/position/coupons/operations (6 dependent queries). Title
`frontend/src/app/routes/DocumentTitle/DocumentTitle.tsx:62–66` использует тот же
detail key, не дополнительный GET. Для успешной холодной загрузки без redirect
7 distinct GET; UID→ticker canonical navigation (`BondPage.tsx:10–12`, VM `62–65`)
может добавить новый detail key/GET, итого 8. Detail by UID =1 RPC, by ticker =
Search→Detail=2 RPC (`discovery:227–249`). Остальные endpoints могут планировать
source workers; их общий count зависит от coverage, calendar/source cache и
пагинации, **unknown**, не «7 HTTP = 7 RPC». Quote/metrics разные keys, но
overlapping source work coalesces в одном enrichment owner. Online detail не
публикуется в shared source cache, поэтому не заменяет cold worker detail.
### 3.4. Cache & Refresh Ownership Matrix
| Data/cache | Identity / lifetime | Owner / trigger | Dedup / isolation / замечание |
| --- | --- | --- | --- |
| frontend accounts | generated method/path; stale30min | account entity; mount/focus/reconnect + screen timer | page/title/breadcrumb/switch разделяют Query; timer только page |
| frontend portfolio/ledger/income/events | generated path+account+period/categories/cursor/limit; stale30s | entity + page useScreenSync interval30min | нет дедуп по похожему payload или разному limit; manual refetch игнорирует enabled |
| frontend bond/stock detail/search/sections | identifier/period/q в generated key; stale30s | entity, detail-dependent enabled; page timer | case q differs while backend normalizes uppercase; online requests не coalesce на backend |
| frontend bank queries | generated key, all mode добавляет `all`; default stale0 | entity; mutations invalidate related subtree | placeholder only same card/period/limit; all mode включает собственный pagination loop |
| session/connection cache | Query cache reset/clear, token in memory | auth + connection commit | generated keys без user/revision допустимы только с этой boundary; KEEP |
| saved membership/portfolio | user/revision/account; freshness60min | hourly membership force + portfolio force | pending/reservation coalesces; GET local; missing differs from confirmed empty |
| operations/coverage | user/revision/account/time/instrument; exact intervals | hourly mutable current-month tail; first missing reads + legacy history | page cursor binds owner/filter/revisions; background missing range dedup; FLOW-001 |
| instrument metadata | provider+UID/FIGI lookups; `fresh=complete` | portfolio metadata worker; savedMany reads local | batching400, producer identity shared; no credential/holding in public records |
| online search/detail/company | no persistent online result | discovery/section read request | synchronous upstream; no server single-flight shown; online-only requirement, не автоматически defect |
| quote/comparison/trading status/bond-current/yield | instrument/source/kind/range; TTL60min | enrichment requestMany quote policy | prices batch up to30, shared source producers; ordinary quote/metrics GET can create work |
| dividend/coupon/bond-events/detail/rating source | stable after successful publish; dividends/coupons range key | enrichment + hourly event caller | bonds no calendar range key; dividends hourly exact window changes daily; FLOW-006 |
| candles | instrument/range coverage, complete past immutable | first uncovered read; queued gaps, no all-history prefetch | window split, same-scope producer; today tail/quote comparison отдельны; don't remove completeness |
| forecast | user/revision/scopeKey incl window; dependency CAS | ForecastService.result from events/coupons GET and hourly get | GET can write; no RPC in events get; full fallback only; FLOW-007 |
| screener snapshot | searchId + user/revision, TTL5min, max100 | explicit POST; GET pages readonly | coherent page30; separate from Query; KEEP |
| NCR registry | one Promise entry, TTL6h; failure TTL60s | NcrRatingsClient.rating | public registry single-flight + negative failure cache; not personal data |
Owners/evidence: frontend query files перечислены в ledger;
`frontend/src/features/tbank-connection/model/screen-sync.ts:20–51`;
`backend/src/integrations/tbank/tbank-source.policy.ts:3–21`;
`instrument-metadata.repository.ts:295–329`;
`catalog-enrichment.service.ts:178–227,496–579,596–625,679–697`;
`candle.service.ts:101–124,264–325`;
`stock-screener.service.ts:19–26,84–109,131–138`;
`backend/src/integrations/ncr/ncr-ratings.client.ts:12–55`.
**Duplicate-query вывод:** несколько useBrokerageAccounts consumers не означают
несколько HTTP. Title `DocumentTitle.tsx:24–32`, breadcrumbs
`AccountBreadcrumbs.tsx:16–25`, switch `AccountScopeSwitch.vm.ts:8–20`, page
используют один entity hook. Query promise/cache работает на same key.
Императивные timer.refetch, direct screener API, разные limit/period/q и backend
source work — за пределами этого автоматического dedup. Нет production
hydrate/dehydrate/persistQueryClient пути в просмотренном `frontend/src`;
`useResourceQuery:129–148` только проецирует envelope, не создаёт второй cache.
### 3.5. Upstream call budget table
| Source / условный сценарий | Logical calls без retry | Network attempts / границы | Total vs critical path |
| --- | --- | --- | --- |
| Search пустой | 1 FindInstrument | 1…2 если admitted | один RPC |
| Search N>0 | `1+N+1`, max32 | до `2(N+2)` при recoverable retry каждого RPC | Search → details cohort → Prices; не N последовательных деталей |
| Search transient response/body timeout, browser ≤3 attempts | до `3(N+2)` logical если каждый server attempt дошёл до всех stages | до `6(N+2)`; при N30 условная граница192 | разные server attempts могут перекрываться; это не measured traffic |
| Search invalid token на FindInstrument | 1 на backend invocation; browser3 →3 | auth gRPC НЕ retryable | FLOW-004, mapping убирает повтор, не исходный auth check |
| Invalid token только на detail cohort | `1+N`, prices не начинается; ≤3(1+N) при трёх HTTP | queue admission/failure может уменьшить count | Promise.all отвергает весь request, уже запущенные sibling details не отменяет |
| UID detail / unambiguous ticker detail | 1 / 2 | ≤2 / ≤4 | ticker сначала search, затем detail; consumer действительно требует identity |
| Company with assetUid | 1 detail +2 asset calls | ≤6 | detail → parallel(description,fundamentals); absent assetUid=1 |
| Hourly successful baseline без new sources | `1 + 2A + Σp_i` | до удвоения T-Bank calls при transient recovery | membership → Σ account portfolio pair(max of2) → Σ operations pages → events |
| Hourly new metadata/calendar | добавка `K + D_c + S_c`; K=1…2 на producer; D_c=new instrument detail; S_c=new stock dividend window | ≤2× добавка T-Bank | metadata queue может работать в фоне, enrichment drain сериализует source producers |
| Hourly new bond calendar | MOEX `Σ_b(c_b+o_b+1)` HTTP | retry не добавляется; каждая request timeout10s | parallel(coupons pages,offers pages,description), page цепочки последовательны |
| Ledger first missing + outside history | `Σ_{j∈dedup(R_T∪R_H)} p_j` | ≤2Σp_j на завершённых admitted pages | worker one page/turn, cursor chain sequential; count не зависит от UI limit5/100 |
| Income обычный GET | `m + Σ_{j∈dedup(R_T∪R_H)} p_j` | ≤2(m+Σp_j) за один successful worker pass | membership.done → schedule; response не ждёт worker completion |
| Local Accounts/Portfolio/Events GET | 0 T-Bank, 0 MOEX | 0 | local read/DTO; events forecast CAS write возможен |
| Screener POST / page GET | 1 Shares /0 | ≤2 /0 | весь online catalog на явное действие; pagination local snapshot |
| NCR rating | cache miss1 HTTP; same pending/cache0 | timeout10s, failure cooldown60s | registry shared across ISIN, отдельный от hourly events источник |
`D_c/S_c` считаются по effective producers после shared cache/coalescing, не по
сумме позиций счетов. `c_b/o_b≥1` для нормальных успешных MOEX page walks;
без runtime payload верх неизвестен. T-Bank metadata/service details:
`tbank.client.ts:261–315,358–448,504–585`;
MOEX `moex.client.ts:83–93,119–177`.
**Retry/deadline budget.** Frontend GET: ≤3 attempts, 15s на fetch **и чтение body**,
1s+2s паузы (`client.ts:15–18,127–151,205–249`). Если срабатывают все три
deadline, programmed component ≈48s, не guaranteed wall-clock SLA.
Query retries globally false. При UNAUTHORIZED возможен один auth refresh и
protected replay; консервативная compositional граница ≤7 HTTP attempts
(два read phases до3 плюс один refresh), но сочетание зависит от того, какая
attempt вернула 401. Сетевые server-side effects не начинаются при auth reject.
Known InvalidTbankToken/Forbidden/NotFound не transport retried.
T-Bank queue starts `r`/second (default5), waiting capacity50, RPC deadline15s
назначается **после** queue wait, transient retry один через1s. Queue wait не
входит в RPC deadline; default не означает фактический production config.
Search32 занимает `ceil(32/r)` rate windows при чистом бюджете; при default r5
это7 окон, не обещание7 секунд. Уже есть limiter, добавлять pool/retry вокруг
него не нужно. HTTP abort не передаётся controller→TbankClient; client request
возвращает Promise, handle не сохраняется для cancel (`tbank.client.ts:571–585`).
Поэтому новые HTTP attempts могут выполнять тот же online fan-out, пока старый
ещё жив. Actual timeout incidence **UNVERIFIED**. Durable first-load work
coalesces; **нельзя** безусловно умножать весь history import на3.
### 3.6. DB Read Pattern table
| Path | Source-inferred model-read count | Pattern / limit / interpretation |
| --- | --- | --- |
| access.local | 1 model call with accounts relation | `tbank-access.service.ts:262–288`; access gate, SQL child queries unknown |
| hourly metadata | 2 when valid connection | `tbank-sync.repository.ts:245–259`; current revision + work |
| Portfolio.saved | 1 access +1 snapshot relation read +2 hourly =4 | `portfolio.service.ts:55–59`, `portfolio.repository.ts:25–47`; loads ALL account payloads before service filter |
| Accounts.list | saved4 + repeated hourly2 =6 | `brokerage-accounts.service.ts:17,39`; projects summaries, no upstream; repeated lookup real, significance unmeasured |
| Portfolio.read | `4+M(positions)` | saved metadata batch400; no per-position remote/detail call |
| Ledger readPage | 5, plus relation SQL unknown | connection,coverage,work,membershipWork,rows `operations.repository.ts:82–218`; `take limit+1`, stable three-field order |
| Ledger foreground | `10+M(page)+J`; cursor extra5 | `operations.service.ts:74,119,145–153,214`; first readPage validates binding before scheduling, preserve validation-before-side-effect |
| Income observe | 1 access+5 readPage=6 | exact period union, categories income, no `take`; not a row N+1 |
| Income ordinary full warm path | 20 reads +1 updateMany fence, no worker launch | initial access1+page5; membership reserve read1; schedule access1+page5+readPlan(access1+coverage1); final page5; source scheduling J if missing |
| Events foreground | `7+M(portfolio positions)+M(supported positions)+3b+Q_F` | get: access1+portfolio4+hourly2; saved metadata again; observe readMany(access/source/work)=3 per batch |
| forecast.resolve | no candidate:2+I(fallback lookup); candidate early fallback:1+I(fallback lookup); candidate validation:2+I(fallback lookup)+ceil(sourceVersions/100) reads | write fence/upsert counted separately; `forecast.repository.ts:35–174`; preserve parameter-limit batches and atomic source validation |
| Bank history | `3+Σ(1+t+s+c+r)` | card ownership + count +page, then per-row loaders; LIMIT≤100 + offset. Valid income1 / transfer2 / expense3 / refund4 extra reads per row |
| Bank all mode | sum history cost over P_h HTTP pages | full hydration deliberately user-selected; no runtime scale bound in frontend all loop |
| Screener search/page | 1 /1 model read | selected result stored in process Map, no DB per instrument |
Ledger base10 assumes selected nonempty intervals: access1 + readPlan access1/
coverage1 + readPage5 +hourly2. Empty selected scope skips readPlan and subtracts2.
`J` excludes detached worker staging/publish reads. Income ordinary20 assumes
membership fresh, targets nonempty, all historical coverage already complete,
no connection switch; refresh-launched membership detaches schedule and changes
foreground count, so20 is not global minimum/maximum. Metadata formula includes
empty and missing identifiers, не максимум3 для произвольного all portfolio.
No DB latency/SQL query count measured. Embedded database подтверждена
`backend/prisma/schema.prisma:7–9`,
`backend/src/prisma/prisma.service.ts:16–21` и `env.validation.ts:35–36`.
Новые indexes/remote DB/cache tier без EXPLAIN/profile не обоснованы.
### 3.7. DTO / filtering / privacy / observed metrics
- Operations фильтрует time/category/instrument на сервере, page bounded;
payload содержит financial details сверх compact row UI, но contract ledger
требует точную запись (`specs/.../brokerage-account-ledger/spec.md:31–44`).
Удалять поля только по одному presentation consumer нельзя.
- Income читает union2–3 periods одним page loader без limit и формирует totals,
comparisons, buckets, instruments, entries (`income-analytics.service.ts:81–93,138–162`).
UI display использует entries/comparisons, не buckets/instruments
(`frontend/src/pages/account-income/model/display.ts:97–129`). Однако все выплаты
сразу и unlimited period — явное требование spec `10,157–179`; pagination или
усечение сумм не являются механическим performance fix, FLOW-009.
- Account preview получает весь events dataset и отображает первые5
(`frontend/src/pages/account/model/previews.ts:75–96`), тогда как operations
уже запрашивает limit5. Полная standalone events page — реальный consumer;
новый preview endpoint без bytes/scale evidence не предлагается.
- Public DTO projections — trust boundary, а не лишняя копия Prisma rows:
`publicInstrument`, `toBond`, `mapBrokerageOperation`, safe error envelope.
Shared instrument/calendar cache не содержит token/user holdings; private
forecasts, coverage, snapshots и cursors ограничены user/revision/account.
- Доступен **исторический synthetic baseline**, не текущий measurement:
`backend/test/fixtures/tbank-read-baseline.json:2–18` (immediate upstream,
limiter/retries excluded, прежнее состояние persistence). Его numbers не
переносились на HEAD. Current benchmark source `test/tbank-read-baseline.e2e-spec.ts:502–541`
тоже исключает transport limiter и создаёт artifacts; он не запускался.
Текущие production logs/trace и RUM в evidence отсутствуют.
## 4. Findings
### FLOW-001 — Personal GET сохраняет незапрошенный import/backfill
- **Decision:** FIX. **Severity:** P2. **Confidence:** High. **Effort:** M.
- **Evidence:** `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:143–151,391–435`;
`backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:46–125`;
`backend/src/modules/investments/operations/presentation/brokerage-operations.controller.ts:22–38`;
`backend/src/modules/investments/operations/presentation/brokerage-operation.dto.ts:36–82`;
`backend/src/modules/investments/income/income-analytics.controller.ts:24–79`;
`backend/src/modules/investments/investment-sync-plan.ts:287–298`.
- **Claim / consumers:** Account overview recent operations+income summary,
AccountOperationsPage and AccountIncomePage ordinary GETs can launch work beyond
the permitted first missing Ledger interval. Income invokes membership;
Ledger also invokes history backfill even with `sync=observe` because its DTO
has no sync field and controller не выбирает observe.
- **Current mechanism:** both production callers pass `history:true` to
`readPlan`; helper computes lifetime history minus shown targets and requests
remaining missing intervals at history priority. Unknown opening falls back
to1970. Income membership TTL miss can fetch GetAccounts, then detached schedule
plans periods and history; no TTL miss still enters reservations/schedule.
- **Impact / actual cost:** `Ledger=Σp(R_T∪R_H)` вместо `Σp(R_T)`;
`Income=m+Σp(R_T∪R_H)` вместо0 personal RPC. Page limit5/100 не ограничивает
backfill месяцы/pages. Полный выбранный период + отсутствующий другой месяц
достаточно для лишнего RPC даже при пустом upstream результате. При failures
повторные reads могут снова резервировать failed work; active work coalesces,
поэтому amplification не равна автоматически3×. **Impact unmeasured.**
- **Why now:** сохранённая intended boundary явно ограничивает исключение
(`specs/openspec/specs/tbank-data-sync/spec.md:9–22`,
`brokerage-account-ledger/spec.md:131–144`); hidden work расходует общий limiter
и source очередь в конкуренции с hourly/online search.
- **Minimum change:** у Ledger выключить outside-scope history branch; у Income
сделать collectRanges чтением независимо от совместимого observe flag.
Сохранить Ledger first-missing plan и complete-result/readiness contract.
Новый endpoint или общий refresh coordinator не нужен.
- **Guarantees at risk:** current revision/account membership, exact interval,
cursor prevalidation, coverage полнота, atomic reconciliation и сохранённые
суммы. Нельзя выдавать отсутствие истории за нулевой income.
- **Counterarguments / trade-offs:** прежний UX preloads всю историю и облегчает
comparisons. Current specs это запрещает; cold history Income может оставаться
incomplete до разрешённой загрузки Ledger. Этот UX нужно согласовать явно,
не восстанавливать covert import. Не удалять loading/history metadata вслепую.
- **Verification or rollback:** isolated seeded real SQLite + mocked T-Bank:
выбранный полный January и отсутствующий December; Ledger GET/no observe/observe
должен дать0 RPC; missing January должен получить толькоJanuary; Income GET
должен не создавать membership/work. Assert requested intervals, not only count.
Existing tests `backend/test/tbank-ledger-local.e2e-spec.ts:368–409` фактически
допускают RPC у observe; income helper `tbank-income-local.e2e-spec.ts:33–43`
по умолчанию тестирует observe. Regression proof нужно поменять, не считать
эти тесты доказательством ordinary GET. Rollback targeted owner diff при нарушении
completeness, без удаления сохранённой БД.
- **Related finding IDs:** FLOW-002, FLOW-008, FLOW-010.
### FLOW-002 — Screen timer вручную обходит enabled и readiness
- **Decision:** FIX. **Severity:** P2. **Confidence:** High. **Effort:** S.
- **Evidence:** `frontend/src/features/tbank-connection/model/screen-sync.ts:7–35,39–51`;
`frontend/src/shared/api/authenticated-query.ts:45–50`;
`frontend/src/entities/bond/api/queries.ts:45–104`;
`frontend/src/pages/bonds/ui/BondsPage/BondsPage.vm.ts:9–13`;
`frontend/src/pages/bond/ui/BondPage/BondPage.vm.ts:23–55`.
- **Claim / consumers:** пустой/короткий search и detail-dependent resources
остаются в resources array даже когда enabled=false; 30-minute interval всё
равно вызывает refetch. Возможна попытка без session или UID. Восемь production
page callers найдено в account/accounts/operations/income/events/bond/bonds/stock.
- **Current mechanism:** interval управляется lifetime page mount, а не Query
key/enable state; ScreenResource даже не требует isEnabled. Refresh/retry
тоже не фильтруют disabled. TanStack ручной refetch исполняется с enabled=false.
- **Impact / actual cost:** BondsPage idle: intended0 → один logical GET на tick;
backend MinLength2 отклоняет его400 без RPC. BondPage без resolved UID на tick:
до6 dependent logical GET attempts вместо0; invalid UUID paths отклоняются до
source, но operations пустой account identifier формирует неправильный URL.
Число successful HTTP attempts зависит от session/abort. **Impact unmeasured.**
- **Why now:** disabled is explicit prerequisite, не спорная freshness policy.
- **Minimum change:** учитывать isEnabled и не отменять уже выполняющийся read
на timer path; либо native Query refetchInterval у owner. Первый минимальный
guard достаточен для дефекта; полная policy migration зависит от FLOW-008.
- **Guarantees at risk:** auth gating, dependent-query ordering, cancellation,
HTTP-only retry и connection reset; не добавлять skip guards к каждому page.
- **Counterarguments / trade-offs:** timer появляется лишь через30мин; backend
validation снижает impact. Native interval может перестать polling background
tab, что надо решить явно. Imperative user retry доступных ресурсов сохранить.
- **Verification or rollback:** fake time+MSW: короткий q, unresolved UID, signed-out
session →0 GET после tick; затем enabled→один GET для same key; timer during
active read не запускает replacement. Existing
`screen-sync.test.tsx:21–80` проверяет manual refresh, не disabled timer.
Использовать App policy, а не retry-disabled test-client как её замену.
- **Related finding IDs:** FLOW-001, FLOW-008, FLOW-010.
### FLOW-003 — Prices ждёт все details без data dependency
- **Decision:** SIMPLIFY. **Severity:** P2. **Confidence:** High. **Effort:** S.
- **Evidence:** `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:295–325`;
`catalog-selection.ts:5–40`;
`backend/src/integrations/tbank/tbank.client.ts:358–379,441–447`;
`frontend/src/pages/bonds/ui/BondsPage/BondsPage.view.tsx:27–109`;
`backend/src/modules/investments/instruments/bonds/application/bond-catalog.service.ts:25–55,92–118`.
- **Claim / consumers:** текущая compact bond card требует nominal/maturity,
frequency/floating/perpetual и logo/exchange; цены требуют только selected UID,
уже известный сразу после FindInstrument. Зависимость prices→all details лишняя.
- **Current mechanism:** search → selected≤30 → await Promise.all(N BondBy) →
await batchGetLastPrices(N UID) → DTO. Сами details уже parallel.
- **Impact / actual cost:** total=`N+2` остаётся; critical path сейчас
`T_search + max(T_detail_i) + T_prices`, включая очереди/retries. Можно сделать
`T_search + max(max(T_detail_i),T_prices)` только при доступном limiter budget.
**Impact unmeasured**, milliseconds неизвестны.
- **Why now:** это независимая зависимость доказана аргументами API, без изменения
полноты карточки/контракта и без нового endpoint.
- **Minimum change:** после selection начать batch prices вместе с detail cohort
через existing promises/Promise.all и existing TbankClient limiter.
- **Guarantees at risk:** same selected UID/prices join, selection/order/ISIN
dedup, bounded result count, valid token handling, отсутствие partial success
вместо принятой whole-request error.
- **Counterarguments / trade-offs:** если detail fails, ранее prices не расходовался,
после parallel может добавиться1 logical RPC. Queue contention может съесть
latency benefit. Сократить N до0 нельзя: InstrumentShort proto `1398–1416` не
содержит используемые bond conditions, тогда как Bond `513–516,535` содержит.
Bulk full catalog другой trade-off и не внедряется без provider/UX proof.
- **Verification or rollback:** controlled mock detail deferred, prices must start
before release; assert exact same DTO/count=`N+2`, empty search=1; detail failure
не возвращает partial success. Synthetic limiter-aware timing отдельно. Если
время не улучшается вне noise или failure-rate cost растёт — вернуть порядок.
- **Related finding IDs:** FLOW-004, FLOW-008, FLOW-010.
### FLOW-004 — Permanent upstream auth failure ошибочно усиливается GET retry
- **Decision:** FIX. **Severity:** P2. **Confidence:** High. **Effort:** S.
- **Evidence:** `backend/src/integrations/tbank/tbank.client.ts:527–565`;
`backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:149–165,227–249,295–317`;
`catalog-enrichment.service.ts:241–267`;
`backend/src/modules/investments/instruments/stocks/application/stock-screener.service.ts:65–68`;
`backend/src/common/filters/http-exception.filter.ts:19–31,50–64`;
`frontend/src/shared/api/client.ts:127–193`.
- **Claim / consumers:** online search/detail/ticker/company не используют
существующий translator. Remote PERMISSION_DENIED/UNAUTHENTICATED после
onlineToken локальной проверки даёт Error →500 InternalServerError → GET retries.
Screener POST тоже теряет public code, но transient transport не повторяет POST.
- **Current mechanism:** invalidToken=true в local row корректно даёт400 до RPC;
finding относится к токену, ещё не известному backend как invalid. gRPC auth
не retries, но application mapping пропущен. Durable enrichment catch — другой
путь с rejectMembershipToken, он не доказывает защиту online path.
- **Impact / actual cost:** Search auth fail на первом FindInstrument:
один queryFn делает3 HTTP/3 upstream RPC вместо1/1. Failure в detail cohort:
до3(1+N) logical RPC, цены не вызываются. Consumer получает InternalServerError
вместо ожидаемого публичного InvalidTbankToken для recovery.
**Impact unmeasured.**
- **Why now:** permanent/transient codes уже разделены frontend и integration;
ложный500 превращает штатную смену credentials в повторную работу.
- **Minimum change:** reuse
`backend/src/integrations/tbank/tbank-token.service.ts:26–35` на online boundary,
сохраняя pending durable attempt policy и safe error envelope.
- **Guarantees at risk:** без raw credential/error disclosure; не маркировать новую
connection invalid по late response старого token; если нужно persistence
invalidation, только revision-aware CAS. Для прекращения retries достаточно
translation, не нужно нового token state machine.
- **Counterarguments / trade-offs:** auth error может означать недостаточные scope,
но existing TbankClient уже сводит её к InvalidToken. Offline known invalid
connection уже защищена; проблема только нового upstream refusal. При transient
unavailable retry сохраняется.
- **Verification or rollback:** configured Nest HTTP с mocked RPC auth status →
400/INVALID_TBANK_TOKEN; MSW через actual client →1 GET, no transient delays.
Separate Search failure after N queued details, company и POST screener.
Existing `tbank.client.spec.ts:230–241` проверяет RPC, а не online HTTP mapping.
- **Related finding IDs:** FLOW-003, FLOW-010.
### FLOW-005 — Bank history N+1 внутри embedded read transaction
- **Decision:** SIMPLIFY. **Severity:** P2. **Confidence:** High. **Effort:** M.
- **Evidence:** `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:41–91,122–165,333–369`;
`backend/src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts:62–72`;
`frontend/src/entities/bank-card/api/queries.ts:120–144`;
`frontend/src/pages/card/ui/CardPage/CardPage.vm.ts:35–45`.
- **Claim / consumers:** list and refund-expense picker используют mapper-loader
одиночной операции; одинаковая карта/category/refund family перечитывается для
каждой строки, хотя page уже загружена.
- **Current mechanism:** base ownership+count+page=3 reads;
row =card1 +target(t) +source(s) +category(c) +refunds(r).
Для корректных видов: income1, transfer2, expense3, refund4. Limit≤100.
Default UI10 expenses →33 reads; page100 refunds →403 reads. Это условные
examples, не статистика production. Corrupt mixed shape может иметь5 row reads.
- **Impact / actual cost:** per-page cost линейна числу rows; refund sums читают
полные sibling lists многократно. `all` frontend складывает эту работу по HTTP
pages. SQLite embedded: **нет** remote DB RTT; materialization/queries остаются.
Promise.all вокруг rows не устраняет model call count. **Impact unmeasured.**
- **Why now:** count структура доказана; list hot path и all-mode существуют.
Runtime ускорение надо доказать перед включением более сложного loader.
- **Minimum change:** сохранить transaction; загрузить distinct cards, source
expenses, categories, refund rows для page одним bounded набором запросов,
затем применить существующую projection. При page empty не грузить relations.
Не менять single-operation/write path ради list optimization.
- **Guarantees at risk:** owner in every batch query, exact bigint sums,
archived/deleted/reference semantics; refund row availableRefund исключает
именно текущую row, expense — нет. Нельзя удалять financial transaction/CAS/
idempotency. `bank-operations.service.ts:181–195` содержит write invariant,
а здесь меняется только read projection.
- **Counterarguments / trade-offs:** default page10 и SQLite могут быть достаточно
быстры; отдельный page loader увеличит maintenance. Existing bounded batch
ориентир3+≤4 relation reads, а не обещание одного SQL. Встроенный include должен
проверяться на фактический SQL/bytes; full unbounded refunds family может всё
ещё доминировать.
- **Verification or rollback:** synthetic page mixed income/transfer/expense/
несколько refunds одной expense, cross-user/deleted cases; DTO равен before,
count не растёт на1 loader на row. Затем query/duration trace на isolated SQLite.
При нейтральной latency и неоправданной сложности — сохранить current loader.
Existing `backend/test/bank-cards.e2e-spec.ts:424–462,550–580,779–791` сохраняет
observable semantics; execution в этом аудите отсутствует.
- **Related finding IDs:** FLOW-009, FLOW-010.
### FLOW-006 — Hourly calendar refresh не обновляет сохранённые calendars
- **Decision:** FIX. **Severity:** P2. **Confidence:** High. **Effort:** M.
- **Evidence:** `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:166–175`;
`backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:182–239`;
`backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:113–125,189–193,596–625,679–692`;
`specs/openspec/specs/hourly-investment-sync/spec.md:8–9`.
- **Claim / consumers:** hourly должен обновлять события текущих holdings, но
requestMany получает force=false; stable успешный calendar не обновляется.
Bond-events не имеет rangeKey, поэтому suppress сохраняется после смены дня;
dividend key changes daily, но внутри дня calendar reuses previous window.
- **Current mechanism:** только quote policy проверяет TTL; dividends/bond-events
stable. New positions/calendars действительно грузятся, а сохранённые нет.
Forecast может выглядеть полным с прежними событиями при новом Portfolio.
- **Impact / actual cost:** warm calendar upstream budget0 не является
корректной экономией, если provider изменил объявленные/отменённые выплаты.
Fix добавляет calls, не уменьшает их. При hourly refresh effective unique
S ранее сохранённых stock windows adds до S GetDividends; B ранее сохранённых
bond calendars adds Σ(c_b+o_b+1) MOEX HTTP при полном refresh, с учётом shared
producers/cache scope. Уже отсутствующие источники и сейчас загружаются.
**Impact unmeasured**, изменённые real calendars не измерялись.
- **Why now:** требование hourly explicitly covers future events; permanent stable
reuse не удовлетворяет его даже без выбора точного TTL общего online справочника.
- **Minimum change:** hourly caller принудительно обновляет именно calendar kinds
или equivalent existing policy; stable detail metadata reuse сохраняется.
Обновление нужного календаря не должно включать quotes/full market history.
- **Guarantees at risk:** shared producer isolation, complete multi-page MOEX,
same portfolio quantities, source version checks и fallback при error. Force
whole source group вслепую может ненужно повторять detail каждого часа.
- **Counterarguments / trade-offs:** calendars меняются редко, но не неизменяемы;
обязательный hourly refresh увеличивает rate budget. Нужно измерить масштабы и
coalescing одного UID между счетами; осторожно повторно не запускать same source
после уже завершённого первого account refresh в этом же walk.
- **Verification or rollback:** two successful controlled hourly source passes
same window, второй upstream calendar изменён/отменён → новое saved version
и forecast; second pass failure сохраняет first complete timestamps. Проверить
2 accounts same UID, no duplicate calendar call внутри одного намеренного pass.
Existing future-events suite запускает manual source refresh, scheduler stub
(`backend/test/upcoming-events.e2e-spec.ts:140–147`), не полный hourly tick.
- **Related finding IDs:** FLOW-007, FLOW-010.
### FLOW-007 — Forecast fallback блокирует новый complete candidate
- **Decision:** FIX. **Severity:** P2. **Confidence:** High. **Effort:** M.
- **Evidence:** `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:152–173,271–275`;
`backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:50–80,99–174`;
`backend/src/modules/investments/portfolio/application/forecast.service.ts:35–65,77–93,98–120`;
`specs/openspec/specs/brokerage-account-upcoming-events/spec.md:9–20,63–76`.
- **Claim / consumers:** next-day hourly get creates new window scope; no exact
previous row + overlapping old forecast → early return old fallback even with
newly computed complete candidate. `findMany` fallback has no recency order.
- **Current mechanism:** resolve conflates saved result identity and requested
filter. It computes candidate in Events GET, takes write fence, then returns
first overlap before validating/publishing new source revisions. No overlap
with old forecast and candidate unavailable instead returns null, although
intended semantics specifies successful empty filtered saved forecast.
- **Impact / actual cost:** upstream/calendar and repeated local calculation могут
не менять visible result; new candidate publication0 despite completed work.
Age/value wrongness is source-inferred conditional, runtime **UNVERIFIED**;
**Impact unmeasured**. Не просто лишний loop: repeated work cannot reach UI.
- **Why now:** hourly rolling windows сдвигаются ежедневно; consumer требует
исходный saved window/calculatedAt и фильтрацию, а не создание identities по GET.
- **Minimum change:** в existing forecast owner отделить selection последнего
complete result от HTTP window filter; candidate publication не блокировать
только из-за другого window key. Read-only selection должен работать и для
непересекающегося запроса. Contract migration scope согласовать перед реализацией.
- **Guarantees at risk:** dependency/source version CAS `80–145`, membership and
connection generation fence, complete-only publish, exact financial totals,
unchanged calculatedAt при read filter, old complete при failed update.
- **Counterarguments / trade-offs:** узкий GET не должен пересчитывать successful
saved forecast или менять timestamp — existing fallback защищает это. Простое
удаление early return сломает такой UX; различить producer update и consumer
selection, а не ослабить проверки. «Latest» требует deterministic order.
- **Verification or rollback:** save complete W0, update quantities/calendar,
hourly produce complete W1 overlapping W0 → W1 доступен; narrow/non-overlap GET
сохраняет исходные time/window и filters; failed W1 сохраняет W0; dependency race
rejects publication. Existing `upcoming-events.e2e-spec.ts:619–641` защищает narrow
filter, его сохранить; добавить distinct producer W0→W1 outcome в isolated DB.
- **Related finding IDs:** FLOW-006, FLOW-009, FLOW-010.
### FLOW-008 — Общая freshness policy требует явного решения
- **Decision:** INVESTIGATE. **Severity:** P3. **Confidence:** Medium. **Effort:** S.
- **Evidence:** `frontend/src/app/ui/App.tsx:13–19`;
`frontend/src/entities/portfolio/api/usePortfolio.ts:3–5`;
`frontend/src/entities/brokerage-account/api/useBrokerageAccounts.ts:7–17`;
`frontend/src/features/tbank-connection/model/screen-sync.ts:20–35`;
`docs/adr/019-hourly-reads-and-online-instrument-reference.md:27–29`;
`frontend/.ai/conventions/data.md:70–74`.
- **Claim / consumers:** source включает focus/reconnect; convention говорит focus
off; ADR «не чаще30мин». StaleTime30s допускает GET после mount/focus/reconnect
через30s, одновременно с interval30min. Факт policy divergence доказан, но
трактовка initial loading/retry/manual actions и freshness UX не утверждена.
- **Current mechanism:** Query defaults+entity freshness+page-native timer;
staleTime не служит timer или строгим throttle; interval не зависит от staleTime.
- **Impact / actual cost:** C и detail могут повторять online RPC; B/D/E повторяют
local work и, пока FLOW-001 не исправлен, scheduling. Request count зависит от
пользователя/visibility; не фиксирован. **Impact unmeasured.**
- **Why now:** выбирать caching наугад может ухудшить cold completeness на30мин.
- **Minimum change:** сначала уточнить правило; затем при подтверждении перенести
периодические GET к existing Query owner, убрать obsolete page polling и `_scope`.
Не вводить общий refresh registry. `ponytail-audit native:` уже установленный
Query interval вместо собственного timer, если его visibility UX подходит.
- **Guarantees at risk:** initial readiness, cached snapshot, connection reset,
user retry, backend hourly freshness distinct from client response freshness.
- **Counterarguments / trade-offs:** более частые local reads ускоряют появление
первого snapshot; online instrument spec требует online-at-request, а не
necessarily durable cache. Background-tab interval и reconnect UX требуют выбора.
- **Verification or rollback:** fake-time App policy test: mount/focus/reconnect/
timer/manual retry до/после chosen budget; evaluate first-null→complete latency
на synthetic sources. Не count test wrapper defaults как production policy.
- **Related finding IDs:** FLOW-001, FLOW-002, FLOW-003.
### FLOW-009 — Full snapshot/analytics DTO cost доказан структурно, benefit не установлен
- **Decision:** INVESTIGATE. **Severity:** P3. **Confidence:** Medium. **Effort:** M.
- **Evidence:** `backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts:25–47`;
`backend/src/modules/investments/portfolio/application/portfolio.service.ts:57–59,79–95`;
`brokerage-accounts/brokerage-accounts.service.ts:17–39`;
`upcoming-events/upcoming-events.service.ts:60–71,206–239`;
`income/income-analytics.service.ts:138–162,233–284`;
`frontend/src/pages/account-income/model/display.ts:97–129`.
- **Claim / consumers:** single-account read loads all account snapshot JSON before
service filter; Accounts summary repeats full snapshot hydration and hourly read;
Events rereads Portfolio/metadata. Income delivers derived buckets/instruments
unused by current UI. Это реальные opportunities, не подтверждённый slow endpoint.
- **Current mechanism:** reuse whole saved dataset for independent HTTP resources;
forecast GET validates/publishes under CAS; income period entries intentionally
unbounded. Нет runtime hydrate/dehydrate registry frontend.
- **Impact / actual cost:** logical repeated reads/JSON work; bytes/CPU/SQLite time
unknown. Removal source calls could change race consistency; **Impact unmeasured**.
- **Why now:** пригодно для дешёвого isolated trace перед новым summary endpoint/cache.
- **Minimum change:** измерить per-route bytes/model SQL/JSON time; кандидат —
передавать account predicate existing repository (single-account read), reuse
`saved.hourly` при Accounts list, сократить unused DTO только с contract proof.
No change допустим при дешёвых local reads. Не добавлять BFF endpoint вслепую.
- **Guarantees at risk:** user/revision/account scope, consistent dependency
projection, last complete forecast/timestamps, exact unlimited-period entries,
stable public artifacts и внешние consumers.
- **Counterarguments / trade-offs:** общий dataset упрощает all aggregation;
independent GETs дают section error isolation. Spec дохода требует все entries
сразу (`spec.md:157–179`) и unlimited duration (`10`), поэтому pagination требует
product/contract decision. «Unused UI field» не доказывает no external consumers.
- **Verification or rollback:** isolated SQLite one account vs many, same data/DTO,
error/isolation race; compare bytes and hydration with query trace. Проверить
всех контрактных consumers до изменения generated snapshot. Если gain neutral,
оставить существующий путь.
- **Related finding IDs:** FLOW-005, FLOW-007, FLOW-010.
### FLOW-010 — Защищающая сложность и достаточное reuse: KEEP
- **Decision:** KEEP. **Severity:** P2. **Confidence:** High. **Effort:** S.
- **Evidence:** `backend/src/integrations/tbank/tbank-access.service.ts:158–169,262–288`;
`backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts:121–133`;
`operations/infrastructure/brokerage-operations.repository.ts:128–144,602–698,703–881`;
`portfolio/infrastructure/forecast.repository.ts:35–46,80–145`;
`instruments/stocks/application/stock-screener.service.ts:19–26,84–109`;
`frontend/src/features/tbank-connection/model/connection-boundary.ts:24–39`;
`frontend/src/features/auth/model/auth.vm.ts:73–90`.
- **Claim / consumers:** финансовые UI, pagination, forecasts и соединения уже
используют meaningful completeness/access/CAS boundaries и cache reuse.
Совпадение keys/SHA/interfaces или цена некоторых reads не оправдывает удаление.
- **Current mechanism:** private user/revision dataset; conditional work claims;
staged complete operation publish; signed cursor filter/version binding;
shared public source coalescing; Query cancel/reset on connection and clear on
logout; bounded upstream queue/deadline; screener snapshot stable pages.
- **Impact / actual cost:** prevents cross-connection publication, wrong money,
duplicate/replayed writes и pagination inconsistency. **Impact unmeasured**,
это protected guarantee, не confirmed performance defect.
- **Why now:** любой optimization выше должен сохранить эту equivalence boundary.
- **Minimum change:** no change to safeguards. Retain search details needed by
actual card, batch prices, SQLite batches400/100 и correct DTO whitelist.
- **Guarantees at risk:** authorization/privacy, secret handling, exact currency/
decimal strings/bigint, rollback/idempotency/CAS, complete-only result,
controlled scheduler lifecycle, public contract stability.
- **Counterarguments / trade-offs:** SQL/JSON validation имеет local cost; alternatives
допустимы только с equal guarantees. Не заменять signed cursor offset у Ledger,
не хранить user portfolios в generic shared instrument cache.
- **Verification or rollback:** retain isolated cross-user/revision/late publication,
cursor, complete-empty/failed-refresh, banking refund/replay/atomicity tests.
Source inspection не доказывает весь real concurrency/security набор; это
необходимый regression plan, не заявление «security audit пройден».
- **Related finding IDs:** FLOW-001, FLOW-002, FLOW-003, FLOW-004, FLOW-005, FLOW-006, FLOW-007, FLOW-009.
### Before/after critical-path outline — две главные проблемы
**1. FLOW-001, ownership/import.**
```text
BEFORE ordinary income, membership fresh/full coverage:
access → readPage → membership reservation(write fence + read)
→ schedule(access → readPage → readPlan(access → coverage)) → readPage
response local; detached import = m + Σp(R_T ∪ R_H), if missing
AFTER intended passive income:
access → readPage → exact totals/comparisons/entries → response
import from this read = 0; hourly and explicitly allowed missing Ledger remain
BEFORE Ledger first missing period:
access → validate cursor if present → readPlan chosen + outside lifetime history
→ local readPage → metadata batch → response
worker queue = pages(chosen gaps) + pages(outside gaps)
AFTER Ledger:
same access/cursor/coverage/DTO protection, request only chosen gaps
worker queue = pages(chosen gaps); no hidden membership/portfolio/calendar refresh
```
Fully warm conditional income foreground model reads20→6; this is static count,
not20/6 speed ratio. Worker total сокращается на `m+Σp(R_H)` плюс выбранные Income gaps, которые
должны принадлежать разрешённому Ledger owner. Нельзя удалить permitted Ledger
first-load или выдавать empty completeness за отсутствие операций.
**2. FLOW-003, search waterfall.**
```text
BEFORE: Token → Search → [Detail1 || … || DetailN] → PricesBatch → DTO → UI
AFTER: Token → Search → [[Detail1 || … || DetailN] || PricesBatch] → DTO → UI
TOTAL: N+2 → N+2 (N>0); empty1 →1
PATH: Tsearch + max(Tdetails) + Tprices
→ Tsearch + max(max(Tdetails), Tprices), subject to limiter contention
```
Do not count N concurrent details as N sequential latency stages. A full failed
detail response remains failed; moving price start earlier can spend1 extra call
on that error path. No latency gain claimed until limiter-aware synthetic proof.
## 5. DELETE / SIMPLIFY / KEEP / INVESTIGATE — summary decision table
| Decision | IDs | Минимальная стратегия | Решение сейчас |
| --- | --- | --- | --- |
| DELETE | — | obsolete outside-scope refresh branches удаляются внутри FLOW-001, отдельного blanket deletion нет | не удалять весь planInvestmentScreen/status/coverage |
| FIX | FLOW-001/002/004 | один source owner; readiness guard; existing token translator | confirmed actionable |
| FIX | FLOW-006/007 | current calendars + producer/consumer forecast distinction | confirmed semantics, regression proof before implementation |
| SIMPLIFY | FLOW-003 | parallel independent prices with details | count unchanged; latency experiment with rollback |
| SIMPLIFY | FLOW-005 | bounded page relation loading instead of row loaders | DB work reduction structure known; runtime benefit unmeasured |
| KEEP | FLOW-010 | native Query sharing, complete snapshots, signed cursor, CAS, transaction, whitelist, limiter, screener snapshot | preserve invariants |
| INVESTIGATE | FLOW-008 | explicit freshness/readiness policy; native Query interval if suitable | no assumed business decision |
| INVESTIGATE | FLOW-009 | local hydration/DTO measurement before new cache/endpoint | no current latency finding |
Ponytail complexity-pass: `native:` Query-owned interval conditional on agreed
policy; `shrink:` убрать независимую последовательность prices; `delete:` убрать
неразрешённые history refresh branches, сохранив source owner и protected guards.
**net: lines not measured; 0 dependency removals established.** Число строк не
выдумывается без prepared diff; новые dependency/registry не предлагаются.
## 6. Cost & benefit
| Strategy / IDs | No change | Expected cost/benefit class | Freshness / UX / rate / maintenance trade-off | Measurement |
| --- | --- | --- | --- | --- |
| ownership FLOW-001 | unrelated history continues on reads | runtime RPC, less source queue work, simpler refresh ownership | cold income completeness remains explicit; chosen Ledger fetch stays; no covert full backfill | not measured; request-set delta proven |
| readiness FLOW-002 | disabled timer requests retained | runtime HTTP + less invalid failures; one shared guard | preserve enabled transitions/session reset/manual retry; avoid cancel/restart storms | not measured; idle tick delta1 or dependent6 inferred |
| parallelism FLOW-003 | current detail→prices order, fewer price calls on detail fail | critical-path latency, small local code change | no total-call reduction, ≤1 extra price on failure; existing limiter still applies | not measured |
| token mapping FLOW-004 | permanent auth500 continues | fewer useless HTTP/RPC retries, correct recovery | safe public code; don't invalidate new generation using old refusal | not measured; first-RPC scenario3→1 |
| batching FLOW-005 | default10 may already be fast | DB/model reads/materialization; some maintenance increase | same transaction/user/refund totals; avoid row N+1, no remote DB claim | not measured |
| calendar freshness FLOW-006 | stable cache saves calls indefinitely | correctness/currentness, MORE upstream calls | hourly calendar-specific scope, multi-account dedup, no full history/quote force | not measured; added source budget explicit |
| forecast FLOW-007 | old overlap can suppress candidate | reliable visible update; avoid wasted upstream/calculation | producer/read filter distinct, full fallback/CAS preserved | not measured |
| cache policy FLOW-008 | inconsistent timers/focus remains | possible HTTP/RPC and cognitive savings | 30min can delay initial complete state; online reference freshness distinct | not measured; INVESTIGATE |
| DTO/hydration FLOW-009 | independent local reads retain error isolation | possible CPU/bytes/agent navigation reduction | unlimited exact income entries required; contract consumers must agree | not measured; INVESTIGATE |
**CI minutes:** не измерялись и не являются целью07. **Agent tokens/cognitive load:**
ожидаемое качественное упрощение ownership, без numeric savings. **Batching search:**
GetLastPrices уже batch; удаление N BondBy без equivalent card fields недопустимо.
**Cache search:** durable reference copy запрещена current online semantics;
ephemeral coalescing/TTL требует product freshness и token/revision-safe key,
сейчас не confirmed action. **Removing obsolete refresh:** FLOW-001 даёт
доказуемый request-set cut; blanket удаление scheduler не эквивалентно.
## 7. Top actions — не более 10
### Quick wins, подтверждённые механизмом
| Priority | Action | Value / risk / effort |
| --- | --- | --- |
| 1 | FLOW-002: shared timer readiness guard, no restart active read | explicit invalid HTTP eliminated; small scope/S |
| 2 | FLOW-004: existing online token translator + HTTP/MSW proof | removes permanent retry loop, correct recovery; S |
| 3 | FLOW-003: overlap prices/detail with controlled-source check | local change/S; revert if limiter/error cost erases benefit |
### Требуют behavioral / DB proof перед выпуском
| Priority | Action | Value / risk / effort |
| --- | --- | --- |
| 4 | FLOW-001: remove outside-scope backfill + passive Income | strongest source-budget reduction; cold history UX/completeness proof/M |
| 5 | FLOW-006: calendar-only hourly refresh with per-walk dedup evidence | current calendar guarantee; increases known upstream budget/M |
| 6 | FLOW-007: distinguish forecast production/read filtering | removes old-candidate suppression; CAS/narrow-read regression/M |
| 7 | FLOW-005: bounded bank list loader only after isolated cost check | per-row DB reads reduced; refund/owner contract and maintenance/M |
ADR нужен только если меняется принятая freshness/cold-history/forecast boundary,
а не как обязательный документ для guard или Promise.all. FLOW-008/009 не входят
в подтверждённые top fixes до ответов и измерений. No change — допустимый результат
измерения для performance-only simplification.
## 8. Cross-audit signals
- **05 Frontend composition:** проверить consumer resource metadata на AccountPage boundary —
`frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:125–135`; проверить
передачу в container/view. Presentation расследование здесь не выполнялось.
- **06 Backend domain style:** отдельные похожие `publicInstrument` projections —
`catalog-discovery.service.ts:499–530`, `catalog-enrichment.service.ts:646–677`;
допустимость объединения owner проверить отдельно, whitelist KEEP.
- **04 Dead code:** production `TbankSyncService.request/dispatch` не найден среди
callers; controller только status — `tbank-sync.controller.ts:31–41`,
`tbank-sync.service.ts:54–78`. Удаление недоступной orchestration — другой audit,
runtime request budget текущих flows от её удаления не меняется.
- **13 Reliability:** scheduler catch/settle/poll и hourly eligibility —
`tbank-hourly.scheduler.ts:108–110,178–218`, `tbank-sync.repository.ts:189–201`;
stalled work/пропущенный срок меняют допустимость следующей работы. Без отдельного
security/reliability расследования в07, finite max walk duration unknown.
- **08 Tests:** Ledger observe assertions допускают scheduling, income helper по
умолчанию observe, scheduler mocked — source refs в FLOW-001/006. Нужен outcome
proof actual ordinary callers, не performance suite вместо regression.
- **15 Specs/docs:** старый Ledger cache/upstream requirement
`brokerage-account-ledger/spec.md:97–110` конфликтует с local/hourly
`131–140`; online-reference addendum docs/bonds `147–151` сосуществует со старым
refresh explanation `119–123`. Не восстанавливать obsolete cache автоматически.
## 9. Open questions / verification plan
### Вопросы, действительно меняющие решение
1. **Cold historical Income после пассивного GET:** пользователь сначала открывает
Ledger, либо требуется иной явный permitted owner? Это меняет UX FLOW-001,
но не делает текущий hidden membership/backfill разрешённым.
2. **30-minute client rule:** применяется к periodic revalidation только или также
focus/mount/reconnect; initial incomplete и user recovery exceptions? Самая
дешёвая проверка — owner decision + fake-time App policy test, FLOW-008.
3. **Calendar budget масштаба:** A, unique held UID, MOEX page counts и one walk
across accounts? Controlled fixtures с repeated UID перед calendar force;
actual production distribution не необходима для correct outcome proof.
4. **Forecast producer identity:** один latest complete snapshot на account либо
поддерживаемые producer windows? Нужен deterministic selection и producer/read
distinction до FLOW-007, без ослабления source CAS.
5. **Bank/DTO масштаб и внешние consumers:** действительно ли local reads/bytes
важны, и кто использует income buckets/instruments/full detail fields? Ответ
меняет FLOW-005 runtime adoption и FLOW-009 contract cut, не надо нового endpoint
ради потенциального gain.
### Non-production verification
| Finding | Самая дешёвая надёжная boundary / expected proof |
| --- | --- |
| FLOW-001 | existing isolated SQLite Ledger/Income suite + mocked broker; selected complete, outside missing, observe/no observe; interval set and zero forbidden work |
| FLOW-002 | existing Vitest/MSW screen-sync test, fake time + real App query options; disabled/session/UID prerequisites; no timer cancel/restart |
| FLOW-003 | controlled service mock deferred details/prices; unchanged DTO/count; optional limiter-aware synthetic timing, no live credentials |
| FLOW-004 | configured Nest HTTP error mapping + MSW authenticatedFetch retry test; permanent1 attempt, transient retries retained |
| FLOW-005 | isolated banking suite, mixed/refund family/cross-user/deleted cases; SQL/model-call trace+timing before adopting batch |
| FLOW-006 | controlled two-pass hourly with shared UID+changed calendar/failure; real SQLite version/timestamp fallback |
| FLOW-007 | W0→W1 producer, narrow/non-overlap passive read, dependency race; real DB CAS outcome |
| FLOW-008/009 | product policy/contract owners + synthetic bytes/read/hydration trace; no release recommendation from intuition |
Future verification commands must use local `nvm use --silent`; inspect script
side effects first. Existing frontend npm test scripts build packages;
SQLite runner migrates disposable DB; они **не исполнялись** в этом read-only
аудите. Новые scripts/frameworks/fixtures tools для самого аудита не создавались.
### Итоговая проверка отчёта и read-only
- Полный prompt и полный отчёт перечитаны; source refs, диапазоны, consumers,
count units/formulas и все required fields проверены статически.
- Semantic/Boy Scout review: найденные возможности не превращены в blanket
removal; public DTO/transaction/CAS/cursor guards сохранены в recommendations;
нет новой application abstraction/dependency/process gate.
- `git diff --no-index --check /dev/null "audit-results/07-api-and-data-flow-efficiency-report.md"`
в root cwd: пустой diagnostic output, exit1 при сравнении нового файла с
`/dev/null`; whitespace errors не выведены. Exit проверен через
`check_exit=$?; typeset -p check_exit`: `typeset check_exit=1`.
- `git diff --check` в root/frontend/backend/specs/docs: пустой output, exit0.
- Итоговые `git rev-parse HEAD && git status --short` всех пяти repo: SHA как в
таблице metadata, tracked/staged/unstaged изменений нет. Новый отчёт root
существует как разрешённый ignored audit artifact; отсутствие его в status
само по себе не проверка содержимого, поэтому выполнен no-index check.
`git check-ignore -v` подтвердил root `.gitignore:2:/*`; ignore не менялся.
- Ограничения: no current runtime measurement/tests, no graph, no fresh OpenAPI
generation, no current production log/trace; version-specific Prisma SQL
expansion unknown. Все latency/throughput выигрыши остаются **UNVERIFIED**.
## Машинная таблица для консолидации
| ID | Decision | Severity | Confidence | Repo | Files | One-line impact | Effort | Related IDs |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| FLOW-001 | FIX | P2 | High | backend | backend/src/modules/investments/operations/application/brokerage-operations.service.ts; backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts; backend/src/modules/investments/income/income-analytics.controller.ts | GET plans m+pages(selected gaps+outside history), violates source ownership | M | FLOW-002,FLOW-008,FLOW-010 |
| FLOW-002 | FIX | P2 | High | frontend | frontend/src/features/tbank-connection/model/screen-sync.ts; frontend/src/entities/bond/api/queries.ts | Manual timer refetch bypasses enabled/session/UID prerequisites | S | FLOW-001,FLOW-008,FLOW-010 |
| FLOW-003 | SIMPLIFY | P2 | High | backend | backend/src/modules/investments/instruments/application/catalog-discovery.service.ts | Search N+2 RPC retains unnecessary details-to-prices waterfall | S | FLOW-004,FLOW-008,FLOW-010 |
| FLOW-004 | FIX | P2 | High | backend+frontend | backend/src/integrations/tbank/tbank.client.ts; backend/src/modules/investments/instruments/application/catalog-discovery.service.ts; frontend/src/shared/api/client.ts | Permanent online broker auth becomes500 and up to3 repeated GET/RPC attempts | S | FLOW-003,FLOW-010 |
| FLOW-005 | SIMPLIFY | P2 | High | backend | backend/src/modules/banking/bank-cards/application/bank-operations.service.ts | Bank history 3+sum(1+t+s+c+r) embedded model reads/page | M | FLOW-009,FLOW-010 |
| FLOW-006 | FIX | P2 | High | backend | backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts; backend/src/modules/investments/upcoming-events/upcoming-events.service.ts; backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts | Hourly stable calendar reuse can suppress updated future events | M | FLOW-007,FLOW-010 |
| FLOW-007 | FIX | P2 | High | backend | backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts; backend/src/modules/investments/portfolio/application/forecast.service.ts | Overlap fallback can suppress newly complete rolling-window candidate | M | FLOW-006,FLOW-009,FLOW-010 |
| FLOW-008 | INVESTIGATE | P3 | Medium | frontend+docs | frontend/src/app/ui/App.tsx; frontend/src/features/tbank-connection/model/screen-sync.ts; docs/adr/019-hourly-reads-and-online-instrument-reference.md | Conflicting30s/30min/focus policy needs readiness and freshness decision | S | FLOW-001,FLOW-002,FLOW-003 |
| FLOW-009 | INVESTIGATE | P3 | Medium | backend+frontend | backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts; backend/src/modules/investments/income/income-analytics.service.ts; frontend/src/pages/account-income/model/display.ts | Full snapshots/derived DTO work exists but runtime benefit and contract cut unverified | M | FLOW-005,FLOW-007,FLOW-010 |
| FLOW-010 | KEEP | P2 | High | backend+frontend | backend/src/integrations/tbank/tbank-access.service.ts; backend/src/modules/investments/operations/infrastructure/brokerage-operations.repository.ts; backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts; frontend/src/features/tbank-connection/model/connection-boundary.ts | Preserve isolation/completeness/CAS/atomicity/typed projection and existing dedup | S | FLOW-001,FLOW-002,FLOW-003,FLOW-004,FLOW-005,FLOW-006,FLOW-007,FLOW-009 |
Все Files в машинной таблице — paths относительно workspace.
Actionable=FIX+SIMPLIFY+DELETE; KEEP и INVESTIGATE исключены из severity totals.