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

74 KiB
Raw Permalink Blame History

Аудит 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:

    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-кампания не нужна.

Решения с тестовым подтверждением

  1. TI-003: убрать readiness sleeps в metadata/catalog/operations producer races; поставить gate на реальную upstream boundary, гарантировать release/settlement при выходе из case. Сохранить real SQLite.
  2. 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 —