Files
main/audits/2026-10-11/reports/09-test-architecture-and-infrastructure-report.md
T
ksv741 2b63bde934
Conventional commits / commits (pull_request) Successful in 13s
docs: archive technical audit 2026-10-11 in develop (#36)
2026-10-17 19:30:38 +03:00

441 lines
74 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.
# Аудит 09 — Test Architecture & Infrastructure
## 1. Audit metadata
**Дата:** 2026-10-11. **Режим:** read-only аудит тестовой инфраструктуры; записан только этот отчёт в родительском workspace.
| Область | Путь относительно workspace | Точный HEAD | Состояние при начале аудита |
| --- | --- | --- | --- |
| Workspace | `.` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | Чистый, `release/0.2.1` |
| Frontend | `frontend/` | `c8821200ce699dab1ce65360d1968d5877a2e78b` | Чистый, `codex/fix-stock-screener-race` |
| Backend | `backend/` | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` | Чистый, `codex/add-stock-screener` |
| Specs | `specs/` | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` | Чистый, `codex/add-stock-screener` |
| Docs | `docs/` | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` | Чистый, `codex/add-stock-screener` |
Абсолютный корень: `/Users/ksv741/Project/vibe/WealthLedger`. Прочитаны локальные `AGENTS.md`, engineering/testing conventions и относящиеся к трассировке architecture/data источники. Conventions использованы для установления задуманного устройства, а не как доказательство полезности каждого правила.
**Skill:** установленный `ponytail-audit` загружен через Skill tool и применён к сложности test support. Его complexity-pass отделён от проверки isolation/lifecycle. Исправления не применялись. `technical-audit-2026-10-11.md` прочитан как список прежних гипотез; выводы ниже проверены по текущему checkout.
### Выполненные проверки
- В каждом репозитории: `git rev-parse HEAD`, `git status --porcelain=v1 --branch`.
- Статический inventory через Glob/Read/Grep и `rg`: runner configuration, imports test support, lifecycle hooks, mocks/timers, локальные factories, consumers fixtures и зависимости subprocess.
- Backend, после загрузки существующего NVM и активации местной `.nvmrc`:
```sh
source "/Users/ksv741/.nvm/nvm.sh" && nvm use --silent && npm test -- --runInBand --no-cache --runTestsByPath src/modules/banking/bank-cards/domain/rub-money.spec.ts
```
**PASS: 1 suite, 22 tests; Jest сообщил 0.379 s.** Это время одного локального запуска pure unit suite, не оценка CI или всего набора. Проверка подтверждает работоспособность дешёвой unit boundary без Nest bootstrap, SQLite и внешней сети. Coverage и Jest cache не создавались.
- Первая попытка той же команды остановилась на `nvm: command not found`: NVM не был загружен в tool shell, сам тест не запускался. После `source` проверка прошла. Это ограничение оболочки аудита, не finding проекта.
- Одна поисковая команда сначала включала отсутствующий frontend-каталог `test/`; inventory затем выполнен по фактическим `src/test/`, `src/`, `packages/`. Данных эта ошибка не меняла.
**Не запускались:** install, build, codegen, миграции, full-suite, coverage suite, `verify`, SQLite/HTTP suites, browser tests, performance/durability/container/regression runners. Реальные банковские и брокерские API не вызывались. Изменения исходников, тестов, конфигурации, lockfiles и Git history отсутствуют.
**Ограничение доказательств:** дефекты организации и возможные failure modes выведены из исходников. Частота flaky-сбоев, время bootstrap/migrations и экономия CI **не измерены**. Не воспроизведённые runtime-сценарии помечены отдельно; зелёный unit test не подтверждает исправность HTTP/SQLite инфраструктуры.
## 2. Executive verdict
1. **Основа достаточно проста и оправданна.** Backend разделяет colocated Jest unit, реальные SQLite suites и configured HTTP; frontend использует небольшие query/resource helpers, RTL/MSW и два browser layout-файла. Причин создавать общий test framework, builders DSL или cross-repo runtime не найдено.
2. **Подтверждена межтестовая зависимость банковской suite — TI-001, P2.** Данные одного owner живут между `it`; restart-case проверяет переименование категории, сделанное другим case. Это непосредственно мешает изолированному запуску и перестановке сценариев.
3. **Подтверждён неконтролируемый фоновой lifecycle — TI-002, P2.** В 10 функциональных HTTP suites зарегистрирован настоящий минутный scheduler. В части из них есть активные connection fixtures; длительный запуск способен начать работу вне Arrange/Act. Отдельный benchmark тоже запускает scheduler, что мешает изолированному измерению read budget.
4. **Синхронизация фоновых сценариев местами привязана к скорости машины — TI-003, P2.** Sleeps на 5–10 ms используются как признак начала работы; для portfolio unit tests уже существует `work.done`, но вместо него опрашиваются mock calls. Это подтверждённый механизм риска, а не измеренная flaky-rate.
5. **Targeted SQLite path допускает смешение source и build — TI-004, P2.** Crash producer загружается из `dist`, основной driver — через ts-jest из `src`; прямой runner не собирает build. Штатный `test:e2e` защищён предварительным build. Фактическое устаревание текущего `dist` — **UNVERIFIED**.
6. **Cleanup небольшой и необязательный.** Opt-in regression самого runner выполняет пять полных функциональных проходов — TI-005, P3; в bond setup есть один option без consumer — TI-006, P3. Ни одна находка не оправдывает массовую перестройку тестов.
Всего: **6 findings — 4 P2 и 2 P3. P0/P1 не установлены.** Самые важные изменения уменьшают скрытое состояние и делают выполнение управляемым, сохраняя реальные DB/HTTP guarantees.
## 3. Inventory & coverage
### 3.1. Фактический стек и размеры области
| Объект | Результат | Метод и граница подсчёта |
| --- | --- | --- |
| Frontend test files | 63 | `rg --files src packages -g '*.test.ts' -g '*.test.tsx' \| wc -l`; файлы, не expanded test cases |
| Frontend browser files | 2 | По найденным `*.browser.test.tsx`: `EntryCard` и `position-layout`; оба запускаются в двух viewport instances |
| Frontend JSDOM files | 61 | 63 минус 2 browser-файла; разграничение подтверждено `vitest.config.ts:26–65` |
| Consumers `@/test/query` | 27 test files | `rg -l 'from "@/test/query"' src packages` с теми же test globs; прямые imports, не число вызовов |
| Consumers `@/test/resource` | 20 test files | Аналогичный подсчёт прямых imports; дополнительно helper использует `src/test/handlers.ts:3` |
| Consumers `@/test/server` | 27 test files | Аналогичный подсчёт прямых imports; lifecycle setup рассматривается отдельно |
| Backend unit files | 39 | `rg --files src -g '*.spec.ts' \| wc -l` |
| Backend e2e/integration files | 20 | `rg --files test -g '*.e2e-spec.ts' \| wc -l` |
| Функциональные suites в `--all` | 19 | 20 файлов минус явно исключённый performance-file, `test/sqlite.cjs:7–17` |
| Suites с `AppModule` | 14, из них 13 функциональных | `rg -l 'imports: \[AppModule\]' test -g '*.e2e-spec.ts' \| wc -l`; просмотр multiline imports и consumer каждого результата |
| Overrides hourly scheduler | 3 suites | `portfolio-structure`, `tbank-value-history`, `upcoming-events`; остальные 10 функциональных AppModule suites запускают lifecycle настоящего provider |
| Backend локальные `deferred<T>` | 4 определения | По `function deferred` в `test/`: local-reads, operations-sync, metadata-persistence, catalog-discovery |
Frontend manifest: Vitest `5.0.0`, RTL `16.3.3`, MSW `2.15.0`, Playwright `1.63.0`, TanStack Query `5.102.8`, JSDOM `30.0.1` (`frontend/package.json:64–116`). Backend: Jest `30.5.1`, ts-jest `29.4.12`, Nest testing `12.0.1`, Supertest `7.2.2`, Prisma `7.10.0`, better-sqlite3 `13.0.3` (`backend/package.json:40–105`). Это manifest versions; переустановка зависимостей не выполнялась.
### 3.2. Test Support Inventory
| Support / owner | Consumer / выполняемый поток | Назначение и оценка |
| --- | --- | --- |
| `frontend/vitest.config.ts:26–65` | Unit project; browser project | Разные environments и взаимоисключающие globs. Browser geometry не дублируется в JSDOM — **KEEP** |
| `frontend/src/test/setup.ts:1–48` | Каждый JSDOM test file | jest-dom, i18n, strict MSW listener, RTL cleanup, auth reset, handler reset, browser API shims — **KEEP** |
| `frontend/src/test/server.ts:1–7`, `handlers.ts:10–82` | Setup и 27 напрямую импортирующих suites | Один process-local MSW server и synthetic defaults. Нет browser worker или backend dependency — **KEEP** |
| `frontend/src/test/query.tsx:5–24` | 27 files | Fresh QueryClient, query/mutation retries off, wrapper сохраняет тот же client при rerender — **KEEP** |
| `frontend/src/test/resource.ts:1–27` | 20 files + handlers | Resource envelope, typed Meta и explicit overrides. Не ходит в сеть, не нормализует деньги — **KEEP** |
| `frontend/src/pages/account/model/positions.fixtures.ts:1–73` | `positions.test.ts:6`, `position-groups.test.tsx:7`, `AccountPage.vm.test.tsx:14` | Три прямых consumers; typed data с null, mixed currencies и negative quantity — **KEEP** |
| Page-local `renderPage` / `setup` | Account, Card, OperationForm, Stock, Bond suites | Явная provider composition, локальные handlers/controls. Практически один suite-local уровень orchestration; не общий DSL — **KEEP**, TI-006 для одного flag |
| Auth test wrapper `features/auth/model/auth.test.tsx:20–28` | AuthProvider suite | QueryClientProvider + настоящий AuthProvider. Сценарии изменения identity живут у auth owner — **KEEP** |
| `frontend/src/test/storybook.test.tsx:19–57` | Composed stories из явно выбранного набора | Проверяет isolation от network/storage/cookies, а не работу factory как таковой — **KEEP** |
| Browser-local `afterEach(cleanup)` | Два browser-файла | Browser project не подключает JSDOM setup. Их cleanup обязателен, не дублирование global hook — **KEEP** |
| `backend/package.json:123–139` | `npm test`, watch/cov/debug | Unit Jest: `rootDir=src`, colocated specs, node environment — **KEEP** |
| `backend/test/jest-e2e.json:1–13` | SQLite runner → Jest | E2E config, isolated-env gate, ts-jest, `.js` import mapper — **KEEP** |
| `backend/test/sqlite.cjs:6–95` | `test:e2e`, `test:sqlite`, OpenAPI/performance entries | Suite selection, свежий temp DB, штатные migrations, отдельный Jest process, signals и cleanup — **KEEP**; TI-004 для build prerequisite |
| `backend/test/e2e-env.cjs:1–17` | Каждая e2e suite и crash subprocess | Проверка runner-owned URL/temp path до DB work; synthetic JWT/encryption config — **KEEP** |
| Local DB constructors | Пять service/repository suites: operations-persistence, operations-sync, metadata, catalog, candles | Реальная SQLite + конкретный service без Nest app и HTTP. Это уже реализованный меньший scope — **KEEP** |
| Suite-local DB seeds | Connections, memberships, portfolios, operations/coverage | Seed responsibility локальна; current revision, token и financial inputs доступны тесту. Глобального seed framework нет — **KEEP** |
| Local poll helpers | `state`, `settled`, `requestPortfolio`, `load` | Разные смыслы: конкретный work state, dependency group, await existing handle. Нельзя объединять исключительно по похожему loop; TI-003 касается readiness sleeps |
| `backend/test/fixtures/tbank-sync-crash.cjs:1–86` | `tbank-operations-sync.e2e-spec.ts:424–499` | Child process + IPC checkpoint + SIGKILL на durable boundaries. Обычный mock не заменяет crash — **KEEP**, TI-004 |
| `backend/test/sqlite-regression.cjs:20–96` | Явный opt-in command в `README.md:250–254` | Уникальные DB, внешний sentinel, migration failure, SIGINT/SIGTERM cleanup — **KEEP protections / SIMPLIFY workload**, TI-005 |
| `backend/test/bank-cards-durability.cjs:10–23,120–215` | `test:durability -- <baseline>` | Независимая historical build, migration prefix, restart, rollback, backup/restore. Explicit external artifact, не shared helper dependency — **KEEP** |
| `backend/test/container-smoke.cjs:19–73`, `runtime-check.cjs:1–8` | Container test / build-runtime проверка | Packaging/native modules/proto/read-only image boundaries; не дополнительный business test framework — **KEEP** |
| `backend/test/tbank-read-baseline.e2e-spec.ts` | Отдельный performance command | Большой synthetic dataset и read-budget measurement, исключён из `--all` — **KEEP separation**, TI-002 для scheduler |
| `backend/test/fixtures/tbank-read-baseline.json` | Benchmark, `tbank-read-baseline.e2e-spec.ts:522–529` | Historical RPC comparison input; не текущий CI timing — **KEEP** |
| `backend/test/fixtures/tbank-read-persistent.json:1–20` | Runtime consumer в просмотренных test/config/docs roots не найден | Historical measurement evidence, не executable factory. **KEEP как evidence**; отсутствие import не доказывает ненужность исторического результата |
Custom builders/classes, собственные matchers/`expect.extend`, snapshot serializers и concurrent-case режим в просмотренной test architecture не найдены. Policy/tooling tests `test/eslint-config.cjs`, `scripts/test-eslint.mjs`, `scripts/test-pre-commit.mjs` отмечены в inventory, но их содержательная целесообразность относится к аудитам 10/11.
### 3.3. Representative cases и границы чтения
Карта имён/config/support охватывает все найденные test files. Углублённая выборка risk-based, а не построчное чтение всех assertions.
| Выборка | Реально рассмотренные файлы / участок |
| --- | --- |
| 5 сложных frontend suites | `AccountPage.test.tsx` — оба render helpers, fixtures, state/route setup; `OperationForm.test.tsx` — весь файл; `CardPage.test.tsx` — setup, pending-page/spy teardown и consumers options; `StockPage.test.tsx`, `BondPage.test.tsx` — options, handlers, controls и все consumers |
| Дополнительные RTL/MSW flows | `StocksPage.test.tsx`, `resource-query.test.tsx`, `client.test.ts`, `auth.test.tsx`, `AccountPage.vm.test.tsx` — async barriers, retry/time policy и wrapper ownership |
| 5 простых frontend suites | `AuthForm.test.tsx`, `calendar.test.ts`, `period.test.ts`, `SourceSyncStatus.test.tsx`, `EntryCard.browser.test.tsx`; дополнительно `position-layout.browser.test.tsx` |
| 5 сложных backend suites | `bank-cards.e2e-spec.ts` — helper closure, seeds, global counts, category dependency, restart; `upcoming-events.e2e-spec.ts` — bootstrap, cleanup, dependency polling, revision-aware seed; `tbank-operations-sync.e2e-spec.ts` — весь файл; metadata/catalog persistence — construction, hooks, producer/observer races и deferred closure |
| 5 простых backend suites | `rub-money.spec.ts`, `bank-card.calculations.spec.ts`, `decimal.spec.ts`, `all-accounts.e2e-spec.ts`, `stocks.e2e-spec.ts` — unit/HTTP boundary и setup scope |
| Дополнительный backend infrastructure | `app.e2e-spec.ts`, `openapi-contract.e2e-spec.ts`, `portfolio.service.spec.ts`, candle/local-reads/connection/history bootstrap, все собственные test runners и crash fixture |
| Public intent / supporting docs | Местные testing conventions/README; `specs/.../frontend-ui-catalog/spec.md:40–88,97–105`; `docs/architecture/cross-cutting/data-freshness.md:63–83` |
**Вне scope:** правильность каждого assertion, полнота продуктовых regressions, общий coverage score и legacy сценарии как таковые — аудит 08; CI triggers — 11; production SRP — 01/06; non-test scripts — 10. Не исследовались реальные внешние API, performance profile всего приложения и архивная OpenSpec история.
### 3.4. Fixture Complexity Matrix
`Arrange` ниже — число явно видимых инфраструктурных операций в вызывающем тесте, не количество DB queries/framework internals. Глубина — последовательные project-owned helper calls; provider tree не считается цепочкой helper abstractions.
| Fixture/helper → consumers | Responsibility | Hidden side effects / сложность | Alternatives и решение |
| --- | --- | --- | --- |
| `createTestQueryClient` / `createQueryWrapper` → 27 files | Client lifecycle / wrapper | Один factory call; wrapper → factory — максимум два support calls. Fresh client, retries off; сетевых вызовов при construction нет | Сохранить; общий client вместо per-case создаст новую isolation проблему |
| `resource(data, meta)` → 20 files + handlers | Envelope только | Один call; generic data, fixed resource key/date/revision, shallow Meta override | Сохранить. Для stale/failure/connection-sensitive scenarios metadata задавать явно; не применять runtime validator ко всем fixtures |
| `positions.fixtures` → 3 files | Typed scenario data | Module-level objects, nested references при spread; consumers используют projections/overrides, общей мутации в рассмотренных путях не найдено | Сохранить; deep-freeze/clone framework без доказанной mutation не нужен |
| `OperationForm.setup` → собственная suite | Auth + categories handler + render | Три инфраструктурных шага; fresh client внутри render, close spy возвращается. Data-only operation передаётся отдельно; assertions в setup нет | Сохранить локально: category endpoint не перекрывает тестовые operation handlers |
| `CardPage.setup` → собственная suite | Auth + handler bundle + render + request capture | Четыре options, каждый имеет consumer; mutable request arrays принадлежат одному invocation. 1 helper, без builder layers | Сохранить; новые options добавлять по реальному сценарию |
| `StockPage.setup` → собственная suite | Несколько endpoint handlers + providers + failure controls | Девять options, examined consumers используют все; handlers содержат domain-specific payloads. Большой объём — данные/API sections, а не глубина DSL | Не объединять со BondPage: metric/rating/coupon contracts различаются |
| `BondPage.setup` → собственная suite | Аналогичная page composition | Семь options; шесть используются, `failBond` не имеет consumer; controls локальны | Удалить только unused option — TI-006; общий instrument test framework не нужен |
| Banking `createCard` → `auth` → `token`, `op`, `createOp` | Happy-path HTTP arrange / payload creation | Максимум три локальных helper calls; `createCard/createOp` делают HTTP/DB write и ожидают успешный status. Проверки ошибок используют `auth(...).expect(...)` напрямую | Сохранить короткие HTTP helpers: скрытая happy-path assertion здесь намеренная, negative path управляемый |
| Banking module-level owner/DB → все `it` | Shared suite state | До первого case users seed, после каждого case cleanup нет; category state пересекает границы сценариев | FIX TI-001: case-owned data и explicit prerequisites |
| Upcoming `portfolio` / `refresh` / `settled` → собственная suite | Seed / launch / await dependencies | Три разные обязанности уже разделены; `portfolio` явно повышает dataRevision и использует user-specific revision. `settled` включает quiet interval | KEEP разделение; не превращать в `givenPortfolioReadyAndAssert` DSL |
| Metadata/catalog `createService`, `seedConnection`, `deferred`, `settled` → по одной suite | Composition / seed / RPC gate / completion | Четыре небольших помощника с разными ролями, не четыре обязательных layers; RPC readiness иногда заменён sleep | FIX TI-003 для sequencing. Небольшие duplicated constructors не требуют общего DI container |
| Portfolio unit polling → два launch cases | Ожидание publish mock | Два одинаковых loop по десять `setImmediate`; тест игнорирует доступный `work.done` | Использовать existing completion promise — TI-003, без нового helper |
| Crash subprocess → три checkpoint cases | Настоящая потеря процесса | Контроль RPC/repository checkpoint, отдельный `dist` runtime, IPC, timeout и child cleanup | KEEP; согласовать freshness build — TI-004 |
| Runner regression → 3 sequential + 2 parallel full runs и failure/signal cases | Isolation/lifecycle infrastructure | Полный product набор является payload каждого успешного infrastructure check | SIMPLIFY TI-005: маленький existing workload с нужными suite/process boundaries |
### 3.5. Проверка SRP / DRY / KISS / YAGNI / ISP / DIP
- **SRP:** в изученной выборке нет общего helper, который одновременно скрыто генерирует финансовый набор, мутирует DB, делает HTTP и проверяет весь business outcome. Banking happy-path helpers объединяют HTTP action и success status; negative cases обходят их напрямую, поэтому разбиение ради SRP увеличило бы ceremony.
- **DRY:** семантически одинаковые freshness/reset patterns важнее одинаковых object literals. Provider composition повторяется, но QueryClient factory уже общая. Backend auth/JWT seed wrappers короткие и локальные; общего runtime между frontend/backend нет.
- **KISS:** максимальная установленная project-owned цепочка arrange в рассмотренных support paths — три коротких banking helper calls. Цепочек по 5–10 factory layers не найдено. Большие stock/bond setup — плоские bundles handlers, не builders framework.
- **YAGNI:** подтверждён один unused option TI-006 и избыточный объём product workload в opt-in infrastructure regression TI-005. Оснований удалять основные factories, MSW server, crash helper или DB runner нет.
- **ISP/DIP:** mocks в основном структурные, ограничены используемыми boundary methods; interfaces/fake hierarchies не вводятся. `as never` ослабляет compile-time проверку fake, но само по себе не доказывает runtime поломку и не оправдывает написание полного fake Prisma. DB guarantees проверяются на настоящей SQLite.
- **Lifecycle:** frontend global hook очищает DOM/auth/handlers; fake timers обычно восстанавливаются локально. Backend per-suite fresh DB не равен per-case isolation — TI-001; настоящий background provider мешает deterministic setup — TI-002; producer gates требуют explicit readiness/release — TI-003.
- **Financial defaults:** `resource` не округляет деньги; fixtures и сценарии явно показывают null, zero, negative/fractional quantities, UTF-8 names, разные revision/user, signed64 и >2^53. Доказательств, что builder скрывает финансовый corner case, нет. Fixed metadata не доказывает fresh/stale policy: такие tests должны задавать значимые поля явно.
- **Heavy bootstrap:** чистые money/calendar/domain tests используют imports напрямую, без AppModule. Пять persistence suites уже создают concrete services без HTTP. Stocks/bonds HTTP contracts стартуют весь AppModule; меньшая composition возможна, но сохранение настоящих guard/pipe/filter/route registration важнее немедленного сокращения setup. Это **INVESTIGATE**, не обязательный rewrite.
- **Factory-internal tests:** отдельные self-tests `resource`, `createQueryWrapper`, builders не найдены. Storybook isolation и SQLite runner regression имеют реальные границы защиты: первый запрещает network/storage, второй защищает пользовательскую БД и child-process cleanup.
## 4. Findings
### TI-001 — Банковская suite использует состояние предыдущих test cases
**Decision = FIX · Severity = P2 · Confidence = High · Effort = M**
**Evidence:** `backend/test/bank-cards.e2e-spec.ts:23–39,77–105,106–145,371–390,710–753`; consumers: все `it` в `describe('Manual bank cards configured HTTP and real SQLite')`.
**Claim.** Per-suite fresh SQLite не обеспечивает самостоятельность банковских scenarios: часть cases требует данных/изменений, выполненных предыдущими `it`.
**Current mechanism.** Один module-level owner и stranger создаются в `beforeAll`; app/DB повторно используются, `beforeEach/afterEach` cleanup отсутствует. Case `renames default category...` меняет default category на «Остальное» (`371–386`). Case `preserves history... across app restart` сам создаёт карту/операции, но после restart ожидает ту же строку «Остальное» (`728–730`), не выполняя переименование. Case rename также читает default category до собственного `createCard` (`372–373`); её появление зависит от прежней записи. Constraint-case получает существующую category (`743`).
**Impact / actual cost.** Порядок тестов становится скрытым prerequisite. Targeted run одного rename/restart/constraint case на новой БД не воспроизводит исходные условия полной suite. Добавление нового раннего case меняет shared category/card state. **Impact unmeasured:** стоимость диагностики и частота падений не измерялись; runtime isolated run в этом аудите не выполнялся.
**Why now.** Дешёвые целевые проверки нужны для финансовых изменений; текущая организационная зависимость лишает их надёжности независимо от корректности business assertions.
**Minimum change.** Каждый независимый case создаёт owner/stranger или свой явно очищенный набор с FK-aware cleanup. Rename и constraint cases сами создают prerequisite card/categories. Restart-case сам переименовывает category до restart. App можно оставить suite-scoped; не нужна отдельная БД или migration на каждый `it`.
**Guarantees at risk.** Не удалять реальную SQLite, concurrent writes, replay fingerprints/tombstones, archive/version checks и restart внутри одного case. Смена owner требует обновить auth helpers, а cleanup — соблюдать FK order.
**Counterarguments / trade-offs.** Shared DB снижает bootstrap cost, случайные card IDs изолируют многие записи, некоторые global assertions сравнивают before/after вместо абсолютного значения. Это хорошие локальные меры, но они не устраняют найденную category dependency. Намеренный sequential journey допустим внутри одного `it`; здесь scenarios оформлены как независимые cases.
**Verification or rollback.** На disposable SQLite выполнить rename и restart отдельно через Jest `--testNamePattern`, а затем полную банковскую suite. Для filtered запуска сохранить runner-owned env: обычный runner сейчас не пересылает произвольные Jest flags. Проверить отсутствие зависимости от чужого case, затем real concurrency/replay outcomes. Rollback — вернуть только изменение test setup; production/schema не затрагивать.
**Related finding IDs:** TI-002, TI-003.
### TI-002 — Настоящий scheduler запускается в suites, не управляющих его работой
**Decision = FIX · Severity = P2 · Confidence = High · Effort = S**
**Evidence:**
- Composition/lifecycle: `backend/src/app.module.ts:18–36`, `backend/src/modules/investments/tbank-sync/tbank-sync.module.ts:13–25`, `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:19–21,48–86,119–175`.
- Consumers: `backend/test/all-accounts.e2e-spec.ts:30–61`, `backend/test/tbank-connection.e2e-spec.ts:30–54`, `backend/test/tbank-local-reads.e2e-spec.ts:100–141`, `backend/test/tbank-income-local.e2e-spec.ts:58–100`, `backend/test/tbank-ledger-local.e2e-spec.ts:44–78`.
- Benchmark: `backend/test/tbank-read-baseline.e2e-spec.ts:199–247,355–357,410–485`.
- Контрпримеры: `backend/test/upcoming-events.e2e-spec.ts:140–152`, `backend/test/portfolio-structure.e2e-spec.ts:40–49`, `backend/test/tbank-value-history.e2e-spec.ts:47–56`.
**Claim.** Тестовый app lifecycle не отделён от настоящего фонового scheduler в части HTTP/read suites и benchmark.
**Current mechanism.** `AppModule` включает `TbankSyncModule`, provider создаёт `setInterval(..., 60_000)` на `app.init/listen`. `tick` читает connections, резервирует hourly work и запускает membership/portfolio/operations/events. Из 13 функциональных AppModule suites только три override scheduler; в остальных 10 таймер активен. Пять перечисленных connection-oriented functional suites создают connection fixtures. Benchmark держит app во время большой DB population, допускает длительный setup/test и собирает upstream counters.
**Impact / actual cost.** При достижении минутного срока данные/metadata и counters могут меняться вне тестового Act; возможны неожиданные upstream mock calls и новые work rows. В benchmark фоновые writes/refresh конкурируют с измеряемыми reads. **Impact unmeasured:** длительность текущих suites, факт срабатывания tick и flaky frequency не измерены. Фактический внешний сетевой запрос не установлен.
**Why now.** Isolation не должна зависеть от того, уложился ли runner в минуту. Особенно это важно для утверждений «GET не запускает upstream» и измерения read-only budget.
**Minimum change.** В не-scheduler suites применить уже имеющийся `.overrideProvider(TbankHourlyScheduler).useValue(...)` без lifecycle таймера. Benchmark также отключает scheduler и явно запускает только необходимую подготовку. Для scheduler behavior нужен отдельный управляемый time/lifecycle case; не добавлять production env flag ради tests.
**Guarantees at risk.** Сохранить настоящие controllers/guards/pipes, migrations, DB publication fences и ручной запуск source owners в relevant scenarios. Scheduler tests должны сохранять реальный `tick`/reservation behavior; override не должен стать способом скрыть их ошибки.
**Counterarguments / trade-offs.** Большинство функциональных suites, вероятно, заканчивается раньше первого tick; auth/cards/stocks/bonds/OpenAPI обычно не создают connections, поэтому там интервал может быть inert. `app.close` очищает interval. Эти гарантии уменьшают вероятность, но не делают connection suites deterministic; cleanup timer не отменяет уже запущенную работу. Три существующих overrides показывают, что меньшая boundary уже принята.
**Verification or rollback.** После override отдельно проверить один local-read HTTP case и benchmark setup с временем за минутной границей: новых hourly work/upstream calls быть не должно. Затем relevant functional suites. Проверки самого scheduler — с controlled clock. Rollback — только provider override; production scheduler не меняется.
**Related finding IDs:** TI-001, TI-003, TI-005.
### TI-003 — Readiness фоновой работы зависит от sleeps и polling вместо доступного completion handle
**Decision = FIX · Severity = P2 · Confidence = High · Effort = M**
**Evidence:**
- Consumers readiness sleeps: `backend/test/tbank-metadata-persistence.e2e-spec.ts:130–178`, `backend/test/tbank-catalog-discovery.e2e-spec.ts:179–225`, `backend/test/tbank-operations-sync.e2e-spec.ts:152–181,183–210,523–550`.
- Async launch mechanism: `backend/src/integrations/tbank/instrument-metadata.service.ts:75–108,111–129`, `backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:157–185,233–275`.
- Consumers polling и существующий handle: `backend/src/modules/investments/portfolio/application/portfolio.service.spec.ts:100–110,150–154`, `backend/src/modules/investments/portfolio/application/portfolio.service.ts:234–306`.
- Контрпримеры: `backend/test/tbank-local-reads.e2e-spec.ts:69–77`, `backend/test/portfolio-structure.e2e-spec.ts:81–100`.
**Claim.** Некоторые Arrange/Act sequences не имеют явного сигнала «producer дошёл до управляемого RPC», а часть tests не ждёт уже существующую completion promise.
**Current mechanism.** Metadata/catalog/operations services после reservation запускают async drain через `setImmediate` и затем выполняют DB/access awaits. Tests ждут `delay(5)` или `delay(10)` и считают producer начавшимся; после этого меняют connection или присоединяют другую работу. Два portfolio unit cases опрашивают `repository.publish.mock.calls` десять event-loop turns, хотя `service.request` возвращает `{id, done}`. В metadata coalescing-case assertions стоят перед `blocked.resolve`; при failure до release нет `finally`, который освободил бы producer.
**Impact / actual cost.** Sequencing зависит от CPU/SQLite scheduling. На медленном запуске возможен другой interleaving или падение mock call assertion. При раннем failure gate может остаться открытым до teardown и затруднить диагностику. Polling publish не выражает полный lifecycle задачи. **Impact unmeasured:** нет воспроизведённого flaky-run и оценки времени; речь о доказанном способе координации.
**Why now.** Race/connection scenarios должны контролировать точки начала/завершения, сохраняя реальный repository/CAS путь. Увеличение sleep не устанавливает ordering guarantee.
**Minimum change.** Mock upstream отмечает `started` и блокируется на существующем gate; тест ждёт этот сигнал перед replace/join. Release помещается в `finally`, когда case может выйти раньше. Где есть `work.done`, await именно его. Где completion представлен только durable work row, оставить bounded state polling и meaningful error; не переводить SQLite целиком на fake timers и не создавать универсальную race DSL.
**Guarantees at risk.** Барьер должен стоять на внешнем RPC или явной publication boundary, не заменять transaction/repository fake. Не сериализовать сами конкурирующие операции, не удалять проверки stale revision/attempt и final state.
**Counterarguments / trade-offs.** Настоящая SQLite может делать короткий polling самым простым reliable completion способом. Awaited `service.read` иногда даёт producer время на запуск; на обычной машине sleep может работать стабильно. Поэтому не удаляются все `delay` и `settled`: distinction между state polling и readiness sleep принципиален. Existing `work.done` consumers подтверждают более простой путь для portfolio без нового API.
**Verification or rollback.** Запустить только затронутые cases в среде с локальными портами и disposable DB. Продемонстрировать, что replace/join выполняется после `started`, а case заканчивается после release/completion; проверить failure-path cleanup. Для portfolio unit достаточно двух existing launch cases. Повторять тесты для оценки flaky-rate только при оставшемся сомнении, не заменять correctness многократным зелёным запуском.
**Related finding IDs:** TI-001, TI-002, TI-004.
### TI-004 — Targeted runner не задаёт freshness prerequisite для crash subprocess из `dist`
**Decision = FIX · Severity = P2 · Confidence = High · Effort = S**
**Evidence:** `backend/test/sqlite.cjs:7–19,71–85`; `backend/test/jest-e2e.json:7–12`; `backend/test/tbank-operations-sync.e2e-spec.ts:424–499`; `backend/test/fixtures/tbank-sync-crash.cjs:1–17,59–82`; `backend/package.json:28–34`; `backend/README.md:250–254`.
**Claim.** Допустимый targeted launch может проверять исходники основного driver и другой build crash producer одновременно.
**Current mechanism.** Runner принимает любой existing e2e filename и запускает ts-jest. Operations-sync suite fork-ит fixture, которая требует services/repository/Prisma из `dist`. Прямой `node test/sqlite.cjs tbank-operations-sync.e2e-spec.ts` и передача такого filename в `test:sqlite` не выполняют build. `test:e2e`, performance и OpenAPI manifest commands предварительно собирают приложение.
**Impact / actual cost.** Если `dist` отсутствует, child не достигает checkpoint; если build старый, parent и producer могут иметь разную reservation/publication семантику. Возможны ложные failure или недостоверная green proof относительно текущего source. **UNVERIFIED:** текущий `dist` не проверялся на freshness; доказан разрешённый launch path без prerequisite, а не наличие устаревшего build сегодня. **Impact unmeasured.**
**Why now.** Самый дешёвый targeted SQLite path должен честно сообщать свои inputs; именно crash tests используют иной runtime artifact.
**Minimum change.** В existing README/test command guidance явно связать dist-dependent targeted suite с `npm run build && node test/sqlite.cjs tbank-operations-sync.e2e-spec.ts`. У runner сделать понятной build prerequisite для этой suite, если пользователи продолжают запускать её напрямую. Не добавлять checksum framework и не делать build обязательным для всех pure/DB-only tests; наличие файла само по себе не доказывает freshness.
**Guarantees at risk.** Сохранить реальный отдельный child process, SIGKILL, checkpoint semantics, migrated temporary DB и сравнение durable rows после recovery. Замена crash helper на mocked restart уменьшит доказательность.
**Counterarguments / trade-offs.** Штатный full e2e flow уже безопаснее: build выполняется перед всеми suites. Default `test:sqlite` выбирает только auth/cards и не использует crash fixture. Проблема локальна targeted/custom invocation. Повторный build стоит времени; достаточно explicit prerequisite, без усложнения общей схемы запусков.
**Verification or rollback.** Проверить две existing entry boundaries: targeted suite без build даёт понятное ограничение; после fresh build её три crash checkpoints работают с текущими services. Эта runtime проверка отложена, потому что требует build/migrations. Rollback — отменить только guidance/preflight change, не child protocol.
**Related finding IDs:** TI-003, TI-005.
### TI-005 — Opt-in regression runner использует слишком большой product workload
**Decision = SIMPLIFY · Severity = P3 · Confidence = High · Effort = M**
**Evidence:** `backend/test/sqlite-regression.cjs:20–76,77–91`; `backend/test/sqlite.cjs:7–17,58–89`; consumer: `backend/README.md:250–254`.
**Claim.** Regression isolation/cleanup самого runner повторяет полный functional product набор там, где для механизма достаточно меньшего существующего workload.
**Current mechanism.** Каждый `check` вызывает `test/sqlite.cjs --all`; три последовательных и два параллельных normal passes проверяют allocated directories, uniqueness, cleanup и неизменность sentinel. Затем отдельно запускаются migration failure и signals. При прохождении всех normal checks текущий workload — **5 × 19 = 95 functional suite invocations**, каждая со свежими migrations/Jest; failure/signal attempts в число 95 не включены.
**Impact / actual cost.** Maintenance/check cost связан со всеми business suites; чужая продуктовая регрессия может остановить проверку runner mechanics. Количество повторных suite invocations установлено статически. **Impact unmeasured:** wall time, CPU и CI minutes не измерялись; command opt-in, не утверждается его запуск на каждом PR.
**Why now.** Механизмы защиты DB и cleanup стоит проверять достаточно дешёво, чтобы ими действительно пользовались после изменения runner.
**Minimum change.** Использовать существующие небольшие SQLite suites как workload: один normal проход должен сохранить переход между минимум двумя suites; parallel checks должны сохранить минимум два одновременных runner process. Failure/signal/sentinel assertions оставить. Full product e2e уже имеет собственный entrypoint. Новая искусственная suite или новый test framework не требуются.
**Guarantees at risk.** Уникальность DB между suites и процессами, штатные migrations, реальная child failure, неизменность sentinel, SIGINT/SIGTERM nonzero exit и cleanup. Нельзя сократить workload до пустого mocked subprocess, который перестаёт обращаться к SQLite.
**Counterarguments / trade-offs.** Полные проходы дополнительно проверяют все suites на внешнее shared state и дают broad stress evidence. Поэтому сокращение не срочное; имеет смысл после маленького доказательства эквивалентности runner guarantees. Three identical sequential passes сами по себе не доказывают, что пять полных product проходов — минимально нужный объём.
**Verification or rollback.** Сопоставить assertions старого и малого workload: минимум две suite directories, два parallel runners, sentinel и failure/signal cleanup. Один измеренный запуск старого/нового workload даст baseline для экономии; пока не заявлять ускорение. Rollback — вернуть workload selection в opt-in runner.
**Related finding IDs:** TI-002, TI-004.
### TI-006 — `failBond` — unused flexibility в локальном frontend setup
**Decision = DELETE · Severity = P3 · Confidence = High · Effort = S**
**Evidence:** `frontend/src/pages/bond/ui/BondPage/BondPage.test.tsx:88–107,294–404`; consumers `setup` — `296,337,345,353,364,371,383`.
**Claim.** Option `failBond` и его endpoint branch существуют без выполняющего их scenario.
**Current mechanism.** Local `setup` не экспортируется. `failBond` определяется в options и читается только внутри default bond handler; ни один callsite не передаёт его. Остальные options имеют реальные consumers. В соседней stock suite `failStock` используется в recovery case (`frontend/src/pages/stock/ui/StockPage/StockPage.test.tsx:473–481`), поэтому удалять симметрично оба flags нельзя.
**Impact / actual cost.** Небольшая лишняя ветвь чтения и сопровождения helper, создающая видимость поддерживаемого scenario. **Impact unmeasured;** это необязательный cleanup, не дефект продукта и не требование добавить тест ради покрытия ветви.
**Why now.** Только при ближайшей правке этой suite; отдельная migration ради одного flag не оправданна.
**Minimum change.** Удалить option и conditional response, оставить happy-path handler. Если появляется настоящий unavailable-bond scenario, case может явно добавить соответствующий MSW handler.
**Guarantees at risk.** Не удалять ambiguity handler, failure coupons/rating и recovery UI production logic; находка относится только к невызванной test-support ветви.
**Counterarguments / trade-offs.** Flag мог готовиться под будущий сценарий; это не current consumer. Если такой scenario уже нужен, owner должен добавить его по продуктовому риску — решение о assertions относится к аудиту 08.
**Verification or rollback.** Static dependency closure уже прослежена в полном файле. При реализации достаточно existing targeted BondPage suite и typecheck; не писать test на отсутствие option. Rollback — вернуть локальный flag при появлении consumer.
**Related finding IDs:** —.
## 5. DELETE / SIMPLIFY / KEEP / INVESTIGATE
| Decision | Кандидат / механизм | Основание и граница |
| --- | --- | --- |
| FIX | Banking per-case state — TI-001 | Самостоятельные prerequisites, app может оставаться общим |
| FIX | Hourly provider в не-scheduler suites — TI-002 | Existing override; scheduler behavior сохраняется в своей boundary |
| FIX | Readiness/completion — TI-003 | Explicit RPC start/release и existing `done`; не увеличивать sleeps |
| FIX | Dist-dependent targeted command — TI-004 | Согласовать build input; не менять все runner entrypoints |
| SIMPLIFY | Workload runner regression — TI-005 | Малый real SQLite payload при сохранённых safety assertions |
| DELETE | `failBond` test option — TI-006 | Нулевой consumer подтверждён; production behavior не удаляется |
| KEEP | Real SQLite migrations + runner env gate + temp cleanup | `sqlite.cjs:60–89`, `e2e-env.cjs:1–13`; native constraint/transaction proof и защита пользовательской DB |
| KEEP | Refresh CAS, financial concurrency и replay tests | `app.e2e-spec.ts:161–213`, `bank-cards.e2e-spec.ts:118–164,391–423,462–524,622–708`; mocks не равносильны реальной записи |
| KEEP | Revision/attempt publication и crash process | `tbank-migration.e2e-spec.ts:150–168,194–231`, operations-sync crash cases; не заменять rollback/CAS обычными mocks |
| KEEP | Pure money/calendar boundary | `rub-money.spec.ts`, `bank-card.calculations.spec.ts`, `decimal.spec.ts`; никаких app/DB dependencies ради вычислений |
| KEEP | QueryClient factory + strict MSW + resource envelope | Shared technical policy уже имеет consumers; singleton client и cross-repo framework ухудшат isolation |
| KEEP | Explicit page providers и domain fixtures | Повторение короткого arrange делает local expectations видимыми |
| KEEP | Два browser viewport instances | Проверяют настоящую geometry, которой нет в JSDOM; это не suite duplication |
| KEEP | Storybook network/storage isolation | Прямое требование synthetic catalog; не «тест factory ради factory» |
| KEEP | Build/container/durability boundaries | Packaging/crash/backup risks не воспроизводятся pure assertions |
| KEEP | Локальные `deferred` сейчас | Backend `target=ES2023` (`tsconfig.json:13`) не предоставляет Promise.withResolvers в default lib. Не копировать frontend ES2024 решение, меняя TS config ради четырёх коротких helpers |
| INVESTIGATE | Узкий HTTP module для stocks/bonds | AppModule bootstrap/migrations time **not measured**; сначала сохранить реальные guards/pipes/filter/routes и проверить цену |
| INVESTIGATE | Explicit QueryClient teardown для незавершённых requests | Fresh clients и RTL cleanup уже есть; утечка/мешающий GC timer **UNVERIFIED**. Не вводить client registry или `gcTime` policy без воспроизведения |
### Ponytail complexity-pass
- `shrink:` уменьшить product workload opt-in runner regression; использовать existing small SQLite suites с теми же lifecycle/sentinel checks. [`backend/test/sqlite-regression.cjs:20–91`, TI-005]
- `shrink:` заменить два portfolio publish-poll loops на existing `work.done`. [`backend/src/modules/investments/portfolio/application/portfolio.service.spec.ts:100–110,150–154`, часть TI-003]
- `delete:` убрать unused `failBond` option и conditional handler branch; replacement — обычный success handler. [`frontend/src/pages/bond/ui/BondPage/BondPage.test.tsx:88–107`, TI-006]
**net: -3 lines, -0 deps possible.** Это консервативный подсчёт только конкретного удаления `failBond`: одна option-строка плюс замена трёх строк conditional expression одной (`1 + (3 − 1)`). Patch не применялся; сокращение polling/runner LOC не оценивалось. Кандидатов на удаление dependency не установлено.
## 6. Cost & benefit
| Действие | Ожидаемый класс пользы | Что измерено | Обязательные regressions / safety boundary |
| --- | --- | --- | --- |
| TI-001: case-owned data | Cognitive load, время targeted diagnosis, maintenance | Static dependency category rename → restart; время **not measured** | Money, FK order, concurrent refunds/refresh, replay/tombstones, restart |
| TI-002: controlled scheduler | Reliability, reproducibility; чистота benchmark measurement | 10 functional AppModule suites без override; runtime влияние **not measured** | HTTP/DB остаются настоящими; scheduler проверяется управляемо |
| TI-003: readiness / `done` | Flakiness risk, ясность Arrange/Act, меньше incidental polling | Concrete sleep/launch/completion paths; flaky frequency **not measured** | Не подменить CAS; awaited release и terminal state, failure cleanup |
| TI-004: fresh build для crash path | Достоверность proof, diagnostic clarity | Source/dist split; actual stale artifact **UNVERIFIED** | Actual separate process/SIGKILL/current services/temp DB |
| TI-005: меньший runner workload | Local verification time; CI minutes только если command используется в CI | 95 suite invocations в успешных normal checks; wall time **not measured** | Две suite boundaries, два parallel processes, sentinel, миграции, signals/failure |
| TI-006: unused flag | Очень небольшая maintenance/cognitive экономия | Dependency closure, минимальный подсчёт удаления выше | Existing BondPage behavior; новые tests ради флага не нужны |
Локальная pure unit проверка заняла сообщённые Jest **0.379 s** 2026-10-11; её нельзя экстраполировать на HTTP/SQLite или приписывать предложенному cleanup.
Исторические benchmark JSON содержат application-read metrics и различный hardware/profile (`test/fixtures/tbank-read-baseline.json:1–19`, `tbank-read-persistent.json:1–20`). Это **не CI timing**; даты измерения в просмотренных headers не указаны. Их latency не использована для расчёта экономии инфраструктуры и не представлена как актуальное состояние.
Нет доказанных процентов ускорения, объёма agent-token экономии или необходимости убрать зависимости.
## 7. Top actions
### Quick wins
1. **TI-002:** применить existing scheduler override в connection/read HTTP suites и benchmark. Большая польза isolation при маленьком diff; real time остаётся только в scheduler-specific proof.
2. **TI-001:** сделать rename/restart/constraint cases self-contained. Сначала добавить явные prerequisites, затем убрать shared owner state между независимыми cases. Полный финансовый suite остаётся необходимой regression boundary.
3. **TI-003:** в двух portfolio unit launch cases ждать `work.done`. Уже существующий handle короче и точнее нового polling helper.
4. **TI-004:** записать существующую build + targeted crash command как правильный путь. Понятное ограничение предпочтительнее проверки freshness через новый hash/manifest механизм.
5. **TI-006:** удалить unused flag при ближайшей правке BondPage suite; самостоятельная cleanup-кампания не нужна.
### Решения с тестовым подтверждением
6. **TI-003:** убрать readiness sleeps в metadata/catalog/operations producer races; поставить gate на реальную upstream boundary, гарантировать release/settlement при выходе из case. Сохранить real SQLite.
7. **TI-005:** сократить opt-in infrastructure workload после доказательства сохранности suite/process isolation, failure/signals и sentinel assertions. Измерить один before/after запуск; не объявлять неподтверждённое ускорение.
Новая ADR для этих локальных исправлений не нужна. ADR/отдельное решение потребуется только при смене базовой testing boundary, backend compiler lib ради новых APIs или принятии нового общего HTTP harness; таких изменений аудит не требует.
### Minimal Test Infrastructure Baseline
| Граница | Минимально достаточный baseline | KEEP / REMOVE / CONSOLIDATE |
| --- | --- | --- |
| Pure domain | Existing Jest/Vitest imports, explicit financial/calendar data | **KEEP**; не стартовать app/DB |
| Service orchestration | Concrete service + узкие external mocks + управляемые gates | **KEEP** real completion handles; **REMOVE** readiness sleeps там, где нужен ordering |
| Backend DB | Existing isolated SQLite runner, production migrations, case-owned state | **KEEP** per-suite DB и env gate; **CONSOLIDATE** launch guidance, не DB mocks |
| Backend HTTP | ConfigureApp + настоящие guard/pipe/filter + controlled external boundaries | **KEEP**; **REMOVE** автоматический scheduler lifecycle из не-scheduler suites |
| Crash/rollback/restore | Existing subprocess/baseline artifact/backup runners | **KEEP** независимые boundaries; **CONSOLIDATE** build prerequisite с explicit command |
| Frontend API/UI | Fresh test client + strict MSW + explicit providers + RTL cleanup | **KEEP**; page fixtures остаются локальными |
| Layout | Existing Vitest browser project на narrow/wide | **KEEP**; не имитировать dimensions в JSDOM |
| Catalog | Synthetic stories + network/storage isolation check | **KEEP**; отдельная story-fixture DSL не нужна |
| Runner regression | Small existing SQLite workload + sentinel/failure/signal assertions | **CONSOLIDATE** mechanics checks вокруг малого workload; full product e2e отдельно |
| Cross-repo reuse | Публичные contract artifacts, без runtime imports helpers | **KEEP** автономность frontend/backend/specs/docs |
## 8. Duplication vs Readability Trade-off
| Повторение | Что совпадает семантически | Решение |
| --- | --- | --- |
| Query/Mantine/Router provider trees | Framework composition; маршруты/auth requirements различаются | Factory QueryClient уже общая. Не делать omnibus `renderWithEverything` с десятком switches |
| Stock/Bond handlers | Похожая topology; financial metrics/rating/coupons, identifiers и outcomes различаются | Local ownership важнее визуального DRY. Удалить только доказанный dead option |
| Backend JWT/connection seeds | Несколько строк config; revision/account/lifecycle цели различаются | Не делить между репозиториями и не строить users/builders framework |
| `settled` loops | Metadata/catalog ждут отсутствие running work; operations ждёт конкретное state; forecast ждёт dependency group и quiet period | Не унифицировать все loops по синтаксису. При изменении owner сделать условия/диагностику явными |
| Portfolio unit publish polling | Два одинаковых обхода вместо уже предоставленного completion | Сократить через `work.done`, не извлекать `waitForPublish` |
| Banking happy-path helpers | HTTP/auth/expected success повторяются реально | Сохранить helpers; negative status assertions оставить в test body |
| Четыре `deferred<T>` | Promise control похож; часть поддерживает reject | Цена мала, lib compatibility различается с frontend. Не менять compiler contract ради DRY; отдельно устранять sequencing недостатки |
| Restart/crash/durability scenarios | Слова «restart» совпадают, failure boundaries разные | Nest provider restart, SIGKILL процесса и old/new-build rollback **не взаимозаменяемы** |
| Runner regression full passes | Повторяются все product cases, а assertions проверяют runner directories/sentinel | Малый real workload может сохранить именно infrastructure proof — TI-005 |
## 9. Cross-audit signals
- **08:** соответствие named race исходному data flow и ожидание позднего response — `backend/test/tbank-local-reads.e2e-spec.ts:489–515`, `frontend/src/pages/stocks/ui/StocksPage/StocksPage.test.tsx:175–237`; assertions здесь не переаудировались.
- **08:** cache/error scenarios с изменением handler без следующего Act — `frontend/src/pages/bond/ui/BondPage/BondPage.test.tsx:363–369`, `src/pages/stock/ui/StockPage/StockPage.test.tsx:463–471`; вопрос содержательной regression proof.
- **08/12:** coverage include захватывает `src/pages/account/model/positions.fixtures.ts`, exclude ограничен test/stories/support globs — `frontend/vitest.config.ts:15–24`; оценить смысл coverage input, не добавлять tests на fixture.
- **06/13:** termination уже начавшейся hourly work при app shutdown — `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:48–54,84–86,197–218`; production lifecycle не расследовался сверх необходимого для TI-002.
- **10/11:** policy-tooling tests и стоимость repeated package builds — frontend `package.json:18–26`, `scripts/test-eslint.mjs`; backend `test/eslint-config.cjs`; их обязательность/trigger budget вне этого аудита.
## 10. Open questions / verification plan
| Вопрос, реально меняющий решение | Текущее состояние | Самый дешёвый следующий шаг |
| --- | --- | --- |
| Должна banking suite быть sequential journey или набором независимых cases? | Формат `it` и отдельные titles указывают на самостоятельные cases; category dependency доказана | Запустить rename/restart отдельно на disposable DB. Если journey намеренный — держать dependent steps внутри одного scenario, а не скрывать dependency между `it` |
| Срабатывает ли hourly tick на обычном runner до конца connection suites? | **UNVERIFIED**, registration/connection path доказан | Один controlled-clock/instrumented targeted run. Отсутствие срабатывания сегодня не отменяет полезность deterministic override |
| Какие targeted SQLite команды реально используют разработчики для crash cases? | Full command собирает build; direct runner принимает dist-dependent suite | Проверить usage/guidance и выполнить один targeted launch после fresh build. Отдельная freshness registry не нужна |
| Достаточен ли маленький workload для safety regression runner? | Product повторение доказано; эквивалентность ещё не проверена | Малый prototype на existing suites; сверить directory/process/sentinel/failure/signal assertions и один before/after timing |
| Есть ли dangling frontend requests/GC timers после cleanup? | **UNVERIFIED**; fresh clients, auth generation fence и cleanup уменьшают риск | Только при воспроизведении pending-after-test симптома снять handles/requests для одной suite; затем локальный teardown. Не добавлять глобальный registry заранее |
| Стоит ли уменьшать stocks/bonds AppModule bootstrap? | Framework/DB bootstrap присутствует; цена **not measured**, pipeline coverage имеет ценность | Измерить setup одной existing HTTP suite; прототип narrow module только при значимой цене и с сохранёнными guard/pipe/filter outcomes |
### Основания для library-specific решений
- Vitest lifecycle/file isolation и default stack hooks сверены с документацией через Context7 `/vitest-dev/vitest/v5.0.3`, `docs/guide/lifecycle.md`, `docs/config/sequence.md`. В проекте manifest `5.0.0`; документация ближайшей версии использована для общих lifecycle semantics, не как proof прогона текущего runner. File isolation не сбрасывает все resources после каждого case автоматически.
- TanStack Query testing/cache guidance сверено через Context7 `/tanstack/query`, testing guide и core GC implementation. Fresh client/retry-off соответствует текущей задаче. Совет `gcTime=Infinity` относится к определённому Jest handle symptom и не является основанием без воспроизведения добавлять его в frontend Vitest.
- Decision о сохранении маленьких backend deferred helpers основан на local `tsconfig.json:13` и реальных consumers, а не на недоступном для default lib API.
### Финальная проверка отчёта
- Findings ограничены audit 09; проверены owner/consumer, контрпримеры, минимальная правка и сохраняемые guarantees.
- Числа — file/import counts, static workload selection и один explicitly logged targeted unit run; runtime экономия не приписывалась.
- Проверены source references и существование цитируемых путей; новый файл просмотрен отдельно, выполнен whitespace check.
- Финальная проверка HEAD/status четырёх репозиториев подтверждает исходные SHA и отсутствие staged/unstaged/untracked изменений. Отчёт находится вне них; root ignore/allowlist не менялись.
## Таблица для консолидации
| ID | Decision | Severity | Confidence | Repo | Files | One-line impact | Effort | Related IDs |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| TI-001 | FIX | P2 | High | backend | test/bank-cards.e2e-spec.ts | Межтестовая category dependency мешает самостоятельному запуску финансовых scenarios | M | TI-002, TI-003 |
| TI-002 | FIX | P2 | High | backend | src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts; test/all-accounts.e2e-spec.ts; test/tbank-connection.e2e-spec.ts; test/tbank-local-reads.e2e-spec.ts; test/tbank-income-local.e2e-spec.ts; test/tbank-ledger-local.e2e-spec.ts; test/tbank-read-baseline.e2e-spec.ts | Фоновый tick способен менять fixtures/counters вне Act и загрязнять read benchmark | S | TI-001, TI-003, TI-005 |
| TI-003 | FIX | P2 | High | backend | test/tbank-metadata-persistence.e2e-spec.ts; test/tbank-catalog-discovery.e2e-spec.ts; test/tbank-operations-sync.e2e-spec.ts; src/modules/investments/portfolio/application/portfolio.service.spec.ts | Readiness sleeps и ignored done делают lifecycle зависимым от scheduling | M | TI-001, TI-002, TI-004 |
| TI-004 | FIX | P2 | High | backend | test/sqlite.cjs; test/fixtures/tbank-sync-crash.cjs; test/tbank-operations-sync.e2e-spec.ts; package.json; README.md | Targeted crash path допускает source/dist mismatch без fresh-build prerequisite | S | TI-003, TI-005 |
| TI-005 | SIMPLIFY | P3 | High | backend | test/sqlite-regression.cjs; test/sqlite.cjs; README.md | Opt-in infrastructure regression выполняет 95 functional suite invocations в normal checks | M | TI-002, TI-004 |
| TI-006 | DELETE | P3 | High | frontend | src/pages/bond/ui/BondPage/BondPage.test.tsx | Unused failBond option добавляет ветвь helper без consumer | S | — |