# Аудит 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` | 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 -- ` | Независимая 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` | 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 | — |