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

92 KiB
Raw Blame History

Аудит 08 — Test Quality & Effectiveness

1. Audit metadata

  • Дата: 2026-10-11.
  • Задача: проверить, какие тесты различают сохранённые бизнес-инварианты и реальные регрессии, а какие проверяют другой сценарий, слишком раннее состояние или устаревший контракт.
  • Протокол: audit-prompts/08-test-quality-and-effectiveness.md.
  • Workspace: /Users/ksv741/Project/vibe/WealthLedger; это родительская папка четырёх самостоятельных репозиториев. Единственный создаваемый artifact — этот отчёт вне них.
Checkout Абсолютный путь Точный HEAD Начальное состояние
root /Users/ksv741/Project/vibe/WealthLedger dc030f84c979cedb2628ef4bd97c62d6c63a9256 git status --short: пусто
frontend /Users/ksv741/Project/vibe/WealthLedger/frontend c8821200ce699dab1ce65360d1968d5877a2e78b чисто
backend /Users/ksv741/Project/vibe/WealthLedger/backend 0ad84ff4f0b4712abc7e69dc299856f14af6eadd чисто
specs /Users/ksv741/Project/vibe/WealthLedger/specs fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121 чисто
docs /Users/ksv741/Project/vibe/WealthLedger/docs e6fd4f64b81dd73b33959b845a1babcfac1891c8 чисто

Инструкции и skills. Прочитаны локальные AGENTS.md, engineering/testing conventions и необходимые data/auth/integration правила. Установленный ponytail-audit загружен и применён к стоимости и кандидатам удаления тестов. Его фактический scope — over-engineering; корректность assertions исследована по протоколу аудита 08, а не приписана этому skill. graphify загружен; существующего graphify-out/graph.json нет. Построение графа не выполнялось, поскольку оно создаёт дополнительные artifacts и противоречит read-only контракту. Subagents не использовались.

Предыдущие сведения. audit-results/technical-audit-2026-10-11.md прочитан как реестр гипотез. Его ссылки на race tests, Portfolio и MOEX перепроверены по текущему вызовному пути. Совпадение HEAD с предыдущим аудитом не использовано как доказательство.

Выполнено: статическое чтение, поиски имён/consumers/assertions, git rev-parse HEAD, git status --short, одноразовый inventory через rg --files, проверка отчёта и diff. Дополнительно выполнен один целевой запуск двух чистых денежных suites:

source "$HOME/.nvm/nvm.sh"
nvm use --silent
npm test -- --runInBand --no-cache --coverage=false --runTestsByPath src/integrations/tbank/decimal.spec.ts src/modules/banking/bank-cards/domain/rub-money.spec.ts

Working directory: backend. Результат: 2 suites PASS, 28 tests PASS, 0 snapshots; Jest сообщил 0.342 s. Это время одного локального запуска, не измерение CI или общей эффективности. npm выдал предупреждение о пользовательской настройке //registry; выполнение завершилось успешно. Эти suites импортируют денежные функции, не открывают DB и не обращаются к upstream.

Не выполнялось: install, codegen, migrations, build, full-suite, verify, SQLite/HTTP suites, browser suites, coverage collection, mutation/removal исходников, mutation framework, обращения к брокерским/банковским API, commit/PR. Runtime-прохождение остальных тестов и предполагаемые исходы мутантов обозначаются отдельно. Исходники, тесты, конфигурации и четыре checkout не изменены.

2. Executive verdict

  1. Факт: сильная тестовая основа существует. Денежные cases используют независимо заданные строки и границы >2^53/signed64; auth concurrency проходит реальный SQLite CAS; operations rollback выполняет часть записи до отказа следующего интервала. Их нельзя заменять одними mocks или удалять как «дублирующие coverage».
  2. Главный риск: новый владелец обновления почти не проверен как выполняемый flow. В inventory не найдены прямые проверки tick, reserveHourly, settleHourly; три integration suites явно заменяют scheduler. Проверки готового meta не доказывают запуск, сроки, завершение и результат реального обхода. Это единственный P1 finding о пробеле защиты.
  3. Факт: backend late-portfolio test не создаёт заявленную гонку. Deferred установлен на RPC, но stimulus — локальный GET; PUT тоже не запускает Portfolio owner. Тест не проверяет отклонение поздней публикации. Отдельные соседние тесты также сохранили названия failed refresh/recovery при прямой подстановке DB-state.
  4. Риск: frontend race assertions не всегда стоят после завершения проигравшего. StocksPage проверяет уже видимый новый результат сразу после освобождения старого ответа. В resource tests ожидается promise затвора, не всего HTTP/query lifecycle. Отсутствие надёжного completion barrier подтверждено; фактический false-green мутанта не запускался.
  5. Факт: часть test oracles противоречит действующим поведенческим требованиям. Portfolio tests требуют отсутствия предупреждения/retry; Events tests закрепляют фильтры и отсутствие retry; backend Events ожидает data: null для непересекающегося сохранённого прогноза. Это не просто недостаток покрытия: такие ожидания защищают другую модель UX/данных.
  6. Факт: важные negative boundaries остаются неразличимыми. Тест «publish fails» отклоняет upstream до начала публикации, а не запись внутри transaction. Auth cases не ловят различие UTF-8 bytes/characters и не проверяют refresh token как Bearer. Отсутствие теста не доказывает production-эксплуатацию.
  7. Непроверенная семантика: отсутствующий блок MOEX. Код и tests считают его failure, spec — нормальным отсутствием данных. Конфликт подтверждён, но без согласования provider semantics нельзя просто ослабить malformed-response guards. Решение — INVESTIGATE.

Итог: 10 actionable findings: 7 FIX, 1 SIMPLIFY, 1 условный DELETE и 1 INVESTIGATE; отдельно 3 KEEP записи о критических гарантиях. P0 findings нет. Цель — заменить слабые стимулы и неверные ожидания, а не увеличить число tests или coverage threshold.

3. Inventory & coverage

3.1. Метод и границы подсчёта

Inventory выполнен один раз по исходным test-file patterns; уточняющие команды разделили первоначальные суммы по директориям:

# backend
rg --files src -g '*.spec.ts' | rg -c '\.spec\.ts$'
rg --files test -g '*.e2e-spec.ts' | rg -c '\.e2e-spec\.ts$'
# frontend
rg --files src -g '*.test.ts' -g '*.test.tsx' | rg -c '\.test\.tsx?$'
rg --files packages scripts -g '*.test.ts' -g '*.test.tsx' -g '*test*.mjs'
Область Inventory Что означает число
backend src 39 *.spec.ts файлы colocated unit/component tests
backend test 20 *.e2e-spec.ts HTTP/persistence/migration suites, включая отдельный read benchmark
frontend src 57 *.test.ts(x) test files, включая position-layout.browser.test.tsx
frontend packages 6 *.test.ts(x) UI/icons tests, включая EntryCard.browser.test.tsx
frontend scripts 2 *test*.mjs tooling tests; только инвентаризированы, вне глубокого анализа 08

Поиск учитывает ignore rules rg, исключает dependencies/generated/build outputs. Рабочие деревья были чистыми. Это число файлов, не число сценариев: it.each и loops не развёртывались для метрик. Test-case total и проценты покрытия не вычислялись. 28 PASS относятся только к двум явно запущенным suites.

Конфигурации уровней сверены по backend/package.json:123–139 и frontend/vitest.config.ts:26–65: Jest Node для backend unit, отдельные SQLite/HTTP suites, frontend JSDOM и настоящий Chromium для browser layout. Здесь не оценивались стоимость runner/setup, CI minutes или целесообразность их архитектуры.

3.2. Risk-based sampling matrix

Flow Исследованные тестовые owners Production/contract boundary Почему выбрано
Exact money decimal, RUB parser, bank-card HTTP, bank summary sorting BigInt decimal → application → SQLite/HTTP → frontend comparator финансовая точность и независимо заданные edge values
Auth/CAS/replay AuthService unit, auth HTTP, frontend client/AuthProvider JWT verification, conditional update, cookie response, session generation серьёзный эффект удаления одного guard; сильные и слабые proofs
Connection publication local reads, connection HTTP, value history local GET vs source request; generation/FK/conditional publish отделить реальные fences от несостоявшихся races
Async/read lifecycle StocksPage, resource query, screen sync, initial availability HTTP promises, query cancellation, request identity, timer boundaries completion ordering, retries и ложные negative assertions
Forecast/calendar upcoming-events HTTP, ForecastRepository, MOEX parsing saved forecast/window, dependency versions, complete source publication неверный oracle, malformed-vs-empty и реальные stale-data guarantees
Frontend screens AccountPage/VM, EventsPage/filters, RouteErrorBoundary route → VM → view, cached snapshot/error/recovery проверить видимый пользовательский исход и integration seam
API boundaries configured auth/events HTTP, DTO pipe, OpenAPI, TbankClient mapping guards/validation/status/schema/proto dates различить wire contract и простое mock wiring
Scheduler все test paths по names + metadata cases tick → reserveHourly → walk → settleHourly новый критический owner, явный пробел executable coverage
Browser fit position-layout browser test + PositionSection настоящий layout 375/1280 px полезный тест геометрии, которую JSDOM не доказывает

Shortlist отбирался по риску денежной/identity регрессии, смене владельца обновления, подозрительной асинхронности и стоимости устаревших ожиданий. Выборка намеренно включает хорошие примеры. Она не статистическая; findings не экстраполируются на каждый файл inventory. Большие suites прочитаны в релевантных диапазонах, с callers и setup, влияющими на stimulus.

Вне глубокого охвата: полный набор банковских UI-сценариев, все instrument/candle/ledger permutations, весь NCR parser, все theme/icons/package tests, scripts, миграционная совместимость/durability, browser cookie enforcement, actual gRPC queue scheduling/deadlines и длительность CI. Для них не заявляется отсутствие дефектов. Specs/docs использованы как независимые требования/решения, а не как исполняемые гарантии или автоматически правильные conventions.

3.3. Test Value Matrix

В этой таблице B = backend/, F = frontend/. Остальная часть каждого пути точная. «Ловит» означает вывод из stimulus/assertions, не результат запущенной мутации. Метка PASS используется только для реального целевого запуска.

Test case / evidence Claimed invariant Given → executed stimulus Observable assertion Regression caught? Action
B src/integrations/tbank/decimal.spec.ts:5–22 signed nano и большие суммы точны 9007199254740993 + nano; отрицательная дробь; subtraction/multiply точные ожидаемые decimal strings да: Number coercion/sign/scale; PASS KEEP TQ-012
B src/modules/banking/bank-cards/domain/rub-money.spec.ts:4–57 storage bounds, no rounding, positive input independently заданные крайние kopecks и invalid strings → parse/format точный BigInt/строка или throw да: off-by-one signed64, дробная копейка, zero-positive; PASS KEEP TQ-012
B test/bank-cards.e2e-spec.ts:146–165 точность проходит storage и HTTP create/read карты >2^53, topup за край storage value, expenses 0.10/0.20 точные balance strings в HTTP да: serialization/storage/application truncation KEEP TQ-012
F src/entities/bank-card/api/summary.test.ts:9–37 сортировка не теряет копейки и source order huge amounts с разницей 0.01, ties, negatives → sortBankSummary IDs в независимом порядке; source unchanged да: Number comparator, mutation массива KEEP TQ-012
B src/modules/auth/application/auth.service.spec.ts:95–148 один refresh winner, loser не отзывает session два callers читают old hash через barrier → refresh; mock CAS один fulfillment/401; expected hash передан; no revoke да: application CAS/error wiring; не DB atomicity KEEP TQ-011
B test/app.e2e-spec.ts:161–213 реальный CAS, replay rejection, usable winner два HTTP refresh блокируются до CAS; Promise.all обоих statuses 200/401, no loser cookie, старый token 401, winner 200, одна session да: unconditional update/revocation/cookie clobber KEEP TQ-011
F src/shared/api/client.test.ts:92–123 old identity retry не возвращает чужие данные дождаться второго GET → generation change → release → await rejection итоговый Promise отклонён AbortError да: удаление post-retry generation check KEEP
F src/features/auth/model/auth.test.tsx:189–275 auth-cookie writes сериализованы в этой вкладке pending refresh → logout/login → release → await всех операций итоговая identity и порядок реальных MSW endpoints да: late refresh overwrites login/logout; не browser cookie attributes KEEP
B test/tbank-local-reads.e2e-spec.ts:489–514 late portfolio fenced after replacement deferred RPC установлен → только local GET → PUT → release неиспользованного promise data null, DB count 0 нет stimulus публикации, тест этого fence не различает FIX TQ-001
B test/tbank-local-reads.e2e-spec.ts:299–325 failed refresh сохраняет old totals seed успешного owner result → ручной failed work-state → local GET old data; lastAttempt none; RPC count 1 ловит часть read projection, не failed owner refresh уточнить scope / заменить stimulus; TQ-001
B test/tbank-local-reads.e2e-spec.ts:328–379 recovery/coalescing concurrent discovery abandoned row → 20 local GET; RPC forbidden; work завершён самим тестом accounts видимы; no RPC ловит passive reads, не recovery producer и не coalescing работы переименовать/убрать unused deferred; TQ-001
F src/pages/stocks/ui/StocksPage/StocksPage.test.tsx:175–236 новый search переживает late page page GET + новый search → новый результат → release old новый текст уже существует; старого ещё нет completion loser не доказан; false-green mutant UNVERIFIED FIX TQ-002
F src/shared/api/resource-query.test.tsx:139–172 observed result побеждает older GET old handler started → await observe → release; await только затвора current payload равен snapshot часть cancellation path выполнена; end-to-end settlement old HTTP UNVERIFIED FIX barrier TQ-002
F src/shared/api/resource-query.test.tsx:174–209 access loss очищает pending sibling initial a loaded → a 404 → b release; await затвора a/b data undefined; a isError initial access reset различает; late sibling application не доказана FIX barrier TQ-002
F src/features/tbank-connection/model/screen-sync.test.tsx:57–79 resources refresh together один accounts resource → manual refresh GET count 2 ловит manual refetch одного owner; не multiple resources/timer/unmount KEEP узкую гарантию; wider claim не подтверждён
F src/shared/lib/initial-availability.test.tsx:7–37,76–101 wait budget и identity reset fake time 99+1; rerender new identity loading/unavailable/ready да: deadline off-by-one, stale identity selection KEEP
F src/pages/account/ui/AccountPage/AccountPage.test.tsx:751–855 snapshot сохраняется «тихо» после 503 initial data → invalidation → request count → следующий invalidation суммы остаются, warning/retry отсутствуют да для totals; неправильный oracle для recovery; timestamp selector устарел FIX TQ-004
B test/upcoming-events.e2e-spec.ts:321–340 иной forecast range не выдаётся за прежний saved forecast Feb → query Mar newRange.data === null различает range, но отвергает required successful empty fallback FIX TQ-005
B test/upcoming-events.e2e-spec.ts:619–640 narrow filter не обновляет calculation time saved Feb window → Feb 1–10 query тот же calculatedAt; одна event; no RPC да для overlap; не non-overlap/latest-success/shifted-window publication KEEP и дополнить TQ-005
B test/upcoming-events.e2e-spec.ts:344–368 dependency CAS отказывает stale candidate qty=3 candidate → реальный qty=9 write в seam перед resolve first result old; next result 90.000000009 да: удаление portfolio revision fence KEEP TQ-013
B src/integrations/moex/moex.client.spec.ts:121–191 malformed calendar не становится complete valid empty blocks + coupons: undefined/bad columns/rows → parser MOEX_UNAVAILABLE да, но отсутствие блока спорно объединено с malformed INVESTIGATE TQ-006; malformed KEEP
B src/integrations/moex/moex.client.spec.ts:223–274 все cursor pages, no repeated index controlled response pages/index → реальный parser две точные events или failure; starts 0/1 да: потерянная page/repeated cursor KEEP
B test/upcoming-events.e2e-spec.ts:540–599 source failure сохраняет complete forecast сохранить реальный parsed calendar → force invalid payload → await durable works DB calendar/forecast и time прежние; failure observable да для persistence fallback; семантика missing block спорна KEEP malformed arm; TQ-006
B src/common/dto-validation.spec.ts:13–29 credentials required, email normalized missing fields / ASCII-valid password → configured local ValidationPipe reject или normalized DTO да для этих inputs; не UTF-8 minimum/maximum semantics FIX gap TQ-007
F src/features/auth/ui/AuthForm/AuthForm.test.tsx:9–31 confirmation mismatch не отправляется заполнить несовпадающие пароли → click submit видимая field error и onSubmit не вызван да: removal confirmation guard; callback — допустимый seam KEEP; TQ-007 не закрывает
B test/tbank-value-history.e2e-spec.ts:316–333 «publish fails» сохраняет snapshot/points existing points → getPortfolio rejects → await owner done points прежние; snapshot не asserted да для upstream failure; не transaction rollback FIX TQ-008
B test/tbank-operations-persistence.e2e-spec.ts:186–246 group publication atomic successful a/b → изменить staged a, оставить b incomplete → publish rejection; прежние rows и coverage times; later success/revision stability да: first interval writes rollback после incomplete b KEEP TQ-013
B test/tbank-operations-persistence.e2e-spec.ts:74–126 separate reader видит old/new group real parallel publish + 20 reads; reconnect reader только 10/10 или 20/20; reopened 20/20 полезно, но reader не гарантированно попадает внутрь любого write interleaving KEEP вместе с deterministic rollback, не считать exhaustive race proof
B test/bank-cards.e2e-spec.ts:391–422 replay не дублирует деньги/не оживляет tombstone same intent дважды + другой expense → edit/delete/replay balance 700/800, 409 stale version, 410 deleted replay да: duplicate debit, lost independent write, resurrected operation KEEP TQ-013
B test/bank-cards.e2e-spec.ts:622–653 refunds limited, rejected locks rollback HTTP refund vs expense reduction; после settlement invalid refund date один успех; исходная version после rejection; refund sum ≤ expense да: race unsafe limit/transaction removal KEEP TQ-013
B src/modules/investments/tbank-sync/tbank-screen.spec.ts:4–73 manual screen normalization/validation pure normalizeScreen с descriptors/dates normalized object или throw да для функции, но у текущего request-flow нет запускающего consumer conditional DELETE TQ-009
B src/modules/investments/upcoming-events/upcoming-events.controller.spec.ts:8–18 authenticated sub/account/query forwarded прямой controller call с arbitrary sub → mock service точные service arguments да для seam mapping; не guards/HTTP/DTO/runtime response KEEP малую seam-проверку
B src/integrations/tbank/tbank.client.spec.ts:174–239 bounded retry, queue admission, auth failure mock transport/queue, real call policy + fake 999/1 ms request count и resolved/rejected result да: policy bypass/retry excess; не реальный queue scheduler KEEP, корректно назвать boundary
F src/app/routes/RouteErrorBoundary/RouteErrorBoundary.test.tsx:133–148 безопасный fallback и navigation reset throwing route → click healthy navigation no technical details; healthy content да: boundary reset/navigation regression KEEP
F src/pages/account/ui/AccountPage/position-layout.browser.test.tsx:17–95 stocks/bonds vertical, other responsive real render at configured 375/1280 px → geometry read ненулевая геометрия и relative positions да: ошибочное число колонок; не весь visual/a11y contract KEEP browser level
F src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.filters.test.tsx:229–290 filters apply/reset/history real route/MSW → picker/apply/back/reset URL, period text, expected GET params да для старого filter UI, которого current spec не требует SIMPLIFY TQ-010
F src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.test.tsx:172–190 «quiet» error без retry mocked error resource → rerender Reset видим, Retry отсутствует закрепляет противоположное required HTTP recovery SIMPLIFY/FIX TQ-010

3.4. Critical Behavior Gaps

Critical behavior Что уже проверяется Что остаётся без надёжного distinguishing proof Самая дешёвая responsible boundary
Hourly owner/lifecycle pure metadata projection; individual source owners реальные сроки/overlap/restart/partial failure/terminal settlement controlled tick с fake upstream и настоящим repository; SQLite для reservation, TQ-003
Поздний portfolio старого подключения connection replacement/FK; прямой old-attempt rejection действительно started owner → replace → complete late owner existing local-reads suite с await started + work.done, TQ-001
Late frontend result новая search identity; initial access reset assertion после завершения проигравшей операции, а не его затвора существующие RTL/MSW suites, TQ-002
Cached Portfolio + visible metadata/retry сохранение сумм; standalone status widget route/container не теряет source; 503 warning и HTTP-only retry существующий AccountPage integration test, TQ-004
Forecast filter/latest result overlap filtering, same-scope CAS non-overlap empty fallback и публикация нового полного shifted window существующая SQLite/HTTP Events suite, TQ-005
Empty vs malformed source malformed cursor/rows и сохранение старого complete смысл отсутствующего блока, отдельно от damaged block unit parser + один integration arm после решения, TQ-006
Auth trust boundary valid access, absent access, refresh replay/CAS UTF-8 boundary; refresh-as-Bearer; cookie flags; JWT/DB expiry в актуальном suite существующий configured auth HTTP и local DTO/schema tests, TQ-007
Portfolio transaction rollback upstream failure preserves points ошибка после первого успешного write сохраняет snapshot и points вместе SQLite repository publication со failure injection внутри transaction, TQ-008
Current events UX старая форма периода и Reset forecast calculation time, read-only list и Retry как reread того же resource RTL/MSW existing Events suite, TQ-010

Отдельно: два screen-sync tests не продвигают 30-минутный timer и используют только один resource. Нельзя считать interval/multi-resource/unmount поведение проверенным. Это ограничение выборки, а не основание автоматически строить новый scheduler harness или отдельный finding.

4. Findings

Для KEEP severity означает ущерб от удаления защищающей гарантии, не текущий дефект. Effort для KEEP — стоимость сохранить/правильно классифицировать proof. Impact unmeasured означает отсутствие измерения инцидентов, flaky frequency или затрат CI.

TQ-001 — Late-portfolio test не запускает защищаемую публикацию

  • Decision: FIX. Severity: P2. Confidence: High. Effort: S. Related finding IDs: TQ-003, TQ-013.
  • Evidence: backend/test/tbank-local-reads.e2e-spec.ts:489–514,69–76; backend/src/modules/investments/portfolio/presentation/portfolio.controller.ts:25–31; backend/src/modules/investments/portfolio/application/portfolio.service.ts:55–59,113–231,234–306; backend/src/modules/investments/tbank-connection/tbank-connection.service.ts:31–76; backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts:121–146.
  • Claim: deferred getPortfolio не потребляется stimulus теста. Нет source-owner request, late candidate и вызова publication fence, который якобы проверяется.
  • Current mechanism: GET вызывает portfolio.read, а тот читает access/saved data/metadata; PUT валидирует token и транзакционно заменяет connection, без запуска PortfolioService. Deferred освобождается, затем ожидается 20 ms и проверяется по-прежнему пустая DB. Scheduler зарегистрирован в этой suite, но тест не вызывает tick и не синхронизируется с минутным timer.
  • Impact / actual cost: удаление portfolio conditional-publication guard не обязано менять outcome этого case: publisher им вообще не вызван. Подтверждён структурный пробел stimulus; запуск такого мутанта не выполнялся. Impact unmeasured.
  • Why now: suite уже использует явный portfolioService.request в других cases; данный тест сохранил предположение, что обычный GET начинает работу.
  • Minimum change: сохранить отдельный полезный empty-membership assertion. До replacement вызвать existing request с текущей revision; потребовать ненулевой work и подтверждённый старт upstream; после replacement освободить RPC и await work.done. Затем проверить отсутствующую old-generation publication и неизменность new-generation data.
  • Guarantees at risk: connection isolation, empty ≠ missing, late financial writes. Не заменять локальный GET импортом ради восстановления старого теста.
  • Counterarguments / trade-offs: FK и old-attempt tests уже защищают некоторые случаи (tbank-connection.e2e-spec.ts:113–161, tbank-value-history.e2e-spec.ts:264–313). Они не делают этот case полноценной owner race. Если минутный tick случайно случится, это не контролируемый proof, а неявная зависимость от времени suite.
  • Verification or rollback: targeted existing SQLite suite; порядок started → replace → release → done → assertion обязателен. Откат правки теста не меняет production. Проверка удаления fence в рамках этого аудита запрещена и не выполнялась.

TQ-002 — Frontend late-response assertions не имеют completion barrier проигравшего

  • Decision: FIX. Severity: P2. Confidence: Medium. Effort: S. Related finding IDs: TQ-001, TQ-011.
  • Evidence: frontend/src/pages/stocks/ui/StocksPage/StocksPage.test.tsx:175–236; frontend/src/pages/stocks/ui/StocksPage/StocksPage.vm.ts:65–100; frontend/src/shared/api/resource-query.test.tsx:139–209; frontend/src/shared/api/resource-query.ts:104–126; frontend/src/shared/api/client.ts:205–247.
  • Claim: освобождение MSW затвора не эквивалентно завершению fetch body, continuation и React/query application. Финальные assertions допускают проверку состояния до этого завершения.
  • Current mechanism: после «Новый» stock test вызывает oldPage.resolve, затем waitFor на уже выполненное условие. Нет old request started assertion или ожидания завершения changePage. Resource tests ждут blocked, который разрешён самими tests; generated query/fetch продолжает собственную цепочку. act может обработать часть работы, но это не явное ожидание полного сетевого Promise.
  • Impact / actual cost: stale request guard/cancellation regression может не наблюдаться в момент assertion. Точное false-green расписание и mutant outcome — UNVERIFIED; частота flakiness не измерялась. Impact unmeasured.
  • Why now: эти cases названы regression tests именно late completion, а production имеет отдельные guards на success/error/finally и собственный response-body lifecycle.
  • Minimum change: в existing test probe сохранить awaited operations/settled marker для реального вызываемого Promise, либо использовать существующую наблюдаемую lifecycle boundary, подтверждающую завершение. Начало старого handler проверить до нового search; после release ждать old operation settlement и React flush; затем сравнить data/error/loading. MSW handler-return marker сам по себе ещё не гарантирует client application.
  • Guarantees at risk: request identity, cancellation/access-group cleanup; не добавлять глобальные epochs/HTTP registries ради теста.
  • Counterarguments / trade-offs: текущие act/MSW scheduling иногда доведут response до конца; correctness source guards присутствуют. Поэтому не заявляется, что existing test обязательно false-green. Сильный альтернативный образец — client.test.ts:115–123: ожидается итоговый request rejection, не только освобождение затвора.
  • Verification or rollback: targeted StocksPage/resource-query tests без сборки нового harness; финальное условие должно стать невозможным до settlement old operation. Source mutation не нужна для правки ordering; удаление guard — лишь описанный мысленный regression.

TQ-003 — Нет executable proof часового координатора и durable reservation policy

  • Decision: FIX. Severity: P1. Confidence: High. Effort: M. Related finding IDs: TQ-001, TQ-005.
  • Evidence: backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:48–110,113–189,199–218; backend/src/modules/investments/tbank-sync/tbank-sync.repository.ts:174–242; backend/test/upcoming-events.e2e-spec.ts:140–147; backend/test/tbank-value-history.e2e-spec.ts:47–53; backend/test/portfolio-structure.e2e-spec.ts:40–45; backend/src/modules/investments/investment-resource.mapper.spec.ts:45–117; specs/openspec/specs/hourly-investment-sync/spec.md:8–40.
  • Claim: 59 inventoried backend test files не содержат прямого exercise tick/reserveHourly/settleHourly с assertion требуемого lifecycle. Полезные projection tests получают уже готовый hourly result.
  • Current mechanism: поиск reserveHourly|settleHourly|\.tick\(|TbankHourlyScheduler по src/test test patterns нашёл только три imports/overrides scheduler. Real owner сначала резервирует работу, запускает walk через setImmediate, затем ждёт источники и публикует result. Ordinary read/source tests проверяют другую boundary.
  • Impact / actual cost: регрессии часовых сроков, overlap, missed term, first launch и неверного successful settlement не имеют прямого regression oracle. Например, общий catch вызывает settle с прежним failures, а reserve policy сравнивает start с now: это конкретные ветви, которые нужен отличающий stimulus. Их production-исправление относится к другому аудиту. Impact unmeasured.
  • Why now: этот owner заменяет read-driven refresh для всех личных investment resources, то есть пробел влияет на основной способ обновления данных, а не опциональный helper.
  • Minimum change: одна таблица сценариев реального SQLite reservation с управляемым временем: first/due/not-due, running same process, interrupted other process, пропуск срока долгим обходом. Один controlled coordinator scenario: два accounts, успех одного и failure другого, сохранённый успешный snapshot и terminal partial result. Добавить error arm вне локального source catch; дождаться реальной terminal row. Mocks — только upstream/контролируемые внешние owners, не проверяемая reservation.
  • Guarantees at risk: single attempt, skip/no catch-up, сохранность partial successes, connection ownership, scheduler lifecycle. Не заменять durable CAS mock-map и не убирать isolation overrides в unrelated suites только ради coverage.
  • Counterarguments / trade-offs: indirect timer может сработать в длинном HTTP suite; отдельные source/metadata tests полезны. Без явных assertions результата обхода это не proof. Не требуется exhaustive scheduler permutation suite, inter-process lease или real upstream.
  • Verification or rollback: новое различающее поведение исполнять на existing isolated SQLite boundary; fake clock должен контролировать проверяемую eligibility, а work completion — явный terminal barrier. Согласовать long-walk policy до утверждения oracle. Не запускавшаяся в аудите проверка остаётся UNVERIFIED.

TQ-004 — Portfolio tests защищают отсутствие recovery и не проверяют передачу source metadata

  • Decision: FIX. Severity: P2. Confidence: High. Effort: S. Related finding IDs: TQ-010.
  • Evidence: frontend/src/pages/account/ui/AccountPage/AccountPage.test.tsx:751–855,859–895; frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:125–135; frontend/src/pages/account/ui/AccountPage/AccountPage.tsx:24–34; frontend/src/pages/account/ui/AccountPage/components/AccountPageLoaded.view.tsx:55–59; frontend/src/shared/ui/SourceSyncStatus/SourceSyncStatus.tsx:31–47; frontend/src/shared/ui/SourceSyncStatus/SourceSyncStatus.test.tsx:23–26; specs/openspec/specs/frontend-data-resilience/spec.md:11–28.
  • Claim: valuable cached-total assertions смешаны с противоположным требованиям oracle: предупреждение и Repeat должны отсутствовать. Route-level proof передачи source отсутствует.
  • Current mechanism: VM возвращает source; container его не передаёт; view умеет показывать timestamp/stale/attempt. Test проверяет отсутствие «Данные получены», тогда как current widget использует «Данные на». Поэтому этот negative regexp даже не различает появление актуального status widget. После 503 ожидание requestCount=2 не доказывает, что query уже перешёл в error.
  • Impact / actual cost: omission VM → view остаётся незамеченным, а требуемое предупреждение/retry может восприниматься как regression теста. Информация о stale/failed result теряется рядом с денежными суммами. Impact unmeasured.
  • Why now: существует нужный owner status widget и точная requirement о cached snapshot плюс неблокирующее предупреждение и HTTP-only retry.
  • Minimum change: переиспользовать existing integration case: fixture с осмысленными lastSuccessfulAt/freshness/lastAttempt; positive assertions текущего source label/state. 503 дождаться как terminal query outcome; суммы должны сохраниться, warning и Retry — появиться. Click Retry должен читать тот же GET и обновить data; source sync request недопустим.
  • Guarantees at risk: snapshot не исчезает и не превращается в initial skeleton; HTTP error не переписывает server isStale; identity switch не сохраняет чужие данные.
  • Counterarguments / trade-offs: «quiet» намерение могло означать отсутствие блокирующего loader или ручного импорта; это разумно. Оно не обосновывает скрытие freshness и HTTP recovery. Standalone widget test не ловит пропущенный prop в container.
  • Verification or rollback: RTL/MSW на реальном AccountPage route; не ограничиваться VM-only или presentation-only render. Production/UI согласовать с текущим behavioral requirement; исходники в этом аудите не менялись.

TQ-005 — Forecast range oracle требует missing вместо сохранённого успешного empty result

  • Decision: FIX. Severity: P2. Confidence: High. Effort: M. Related finding IDs: TQ-003, TQ-006, TQ-013.
  • Evidence: backend/test/upcoming-events.e2e-spec.ts:321–339,619–640; backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:152–173; backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:50–79; backend/src/modules/investments/portfolio/application/forecast.service.ts:76–93,98–120; specs/openspec/specs/brokerage-account-upcoming-events/spec.md:12–24,63–76.
  • Claim: тест «never relabels another range» считает null правильным при непересечении сохранённого окна. Требование предписывает успешный пустой data с исходными calculation/source metadata. Сохранить исходную идентичность не означает потерять подтверждённый forecast.
  • Current mechanism: test сначала сохраняет February forecast, затем спрашивает March и ожидает null. Repository ищет fallback только среди пересекающихся scopes. Scope includes window; branch !previous && fallback возвращает fallback и при новом candidate. Narrow-window test проверяет только overlap и тот же timestamp.
  • Impact / actual cost: исправление non-overlap behavior ломает текущий oracle; новый полный candidate shifted window не имеет отдельного positive proof и может уступить старому. Пользователь получает missing/старую расчётную basis после успешной работы. Impact unmeasured.
  • Why now: с часовым обновлением default window движется; разные windows — обычный сценарий, а не только ручной date filter.
  • Minimum change: разделить два сценария на existing SQLite/HTTP boundary: (a) нет нового полного candidate, запрос вне saved window → empty events с прежними calculatedAt/source identity; (b) появился новый полный candidate с другой quantity/source version и shifted window → новый forecast с новой расчётной basis. Не переименовывать старый forecast новым scope/time.
  • Guarantees at risk: membership/portfolio/source revision CAS, точные amounts, fallback only complete, отсутствие fake recalculation time.
  • Counterarguments / trade-offs: old test правильно запрещает relabelling; это KEEP гарантия, которую надо сохранить новым expected shape. Выбор «последнего полного» требует явного ordering; внедрение новой repository abstraction не требуется. HTTP route spelling в spec местами старое — finding основан на содержательной semantics окна, не на совпадении имени endpoint.
  • Verification or rollback: оба сценария должны различать правильный итог независимо от ORM/query order; existing CAS test TQ-013 остаётся. Expected contract уточнить до source fix; mutations в аудите не выполнялись.

TQ-006 — Missing MOEX block объединён с malformed response в двух уровнях тестов

  • Decision: INVESTIGATE. Severity: P2. Confidence: High для конфликта; provider semantics UNVERIFIED. Effort: S. Related finding IDs: TQ-005, TQ-013.
  • Evidence: backend/src/integrations/moex/moex.client.spec.ts:101–118,121–191,223–274; backend/src/integrations/moex/moex.client.ts:119–158,182–197; backend/test/upcoming-events.e2e-spec.ts:540–599; specs/openspec/specs/brokerage-account-upcoming-events/spec.md:78–84.
  • Claim: unit table и настоящий-parser integration arm оба требуют failure для отсутствующего блока; spec требует normal no-data. Тесты не являются независимым подтверждением нужности этого правила.
  • Current mechanism: parseRows(undefined) падает так же, как non-array columns/data или damaged row. Calendar owner сохраняет предыдущую DB source/forecast на failure. Empty arrays и 404 уже трактуются отдельно как normal absence.
  • Impact / actual cost: зелёные suites закрепляют противоречащий spec outcome; обратная механическая правка может ошибочно объявить неполный calendar подтверждённо пустым. Impact unmeasured.
  • Why now: distinction определяет, можно ли безопасно заменить complete calendar/forecast новым результатом, а не только текст ошибки.
  • Minimum change: согласовать missing-block semantics на сохранённом provider payload/первичном контракте без real banking calls. После решения выделить отсутствующий block в свой case; malformed columns/rows/cursor оставить failure. Отразить принятое ожидание и unit, и persistence level.
  • Guarantees at risk: полная pagination, no partial-success, сохранность последнего complete forecast, no fabricated zero payments.
  • Counterarguments / trade-offs: текущий failure может быть сознательной защитой от изменения ISS shape. Тогда меняется behavioral requirement, а не ослабляются guards. Наличие обоих уровней оправдано: unit доказывает parser, integration — сохранность DB fallback.
  • Verification or rollback: offline fixture подтверждает форму missing block; targeted unit и один real-parser integration arm. До решения обе защиты KEEP; production не изменён.

TQ-007 — Auth suite не различает важные входные и token-role boundaries

  • Decision: FIX. Severity: P2. Confidence: High. Effort: S. Related finding IDs: TQ-011.
  • Evidence: backend/src/common/dto-validation.spec.ts:13–29; backend/test/app.e2e-spec.ts:51–90,161–214,216–261; backend/src/modules/auth/application/auth.service.spec.ts:60–148; backend/src/modules/auth/presentation/auth.dto.ts:43–54,66–74; backend/src/modules/auth/infrastructure/jwt.strategy.ts:13–25; backend/src/modules/auth/presentation/auth.controller.ts:113–137; frontend/src/features/auth/model/auth.schema.ts:36–56; frontend/src/features/auth/ui/AuthForm/AuthForm.test.tsx:9–31; specs/openspec/specs/user-authentication/spec.md:10,43–60,83–96.
  • Claim: фактический auth shortlist не проверяет UTF-8 byte boundaries, запрещённый refresh-as-Bearer и ключевые cookie attributes; удаление соответствующего application/config guard не различается current assertions.
  • Current mechanism: credentials case использует ASCII password; frontend case — confirmation mismatch. Backend минимум задан MinLength, maximum — byte validator; frontend обе границы считает TextEncoder. Ровно шесть кириллических букв дают 12 UTF-8 bytes, но не 12 символов. HTTP tests используют valid access и absent access; cookie проверяется для rotation/clear, не HttpOnly/SameSite/path/host-only flags. Поиск auth expiry/token-role/cookie assertions в src unit и test/*.ts не нашёл соответствующих negative cases.
  • Impact / actual cost: существующее несовпадение password semantics не имеет regression case. Пропущенные role/expiry/cookie tests — риск будущей регрессии, не доказательство существующего обхода auth: production type guard и expiry config присутствуют. Impact unmeasured.
  • Why now: это конкретные trust-boundary distinctions, явно требуемые current auth spec; ASCII happy path не различает конкурирующие правила.
  • Minimum change: добавить маленькую boundary table в existing DTO/schema tests: 11/12 и 72/73 UTF-8 bytes с multibyte fixture и сохранением исходного password. В существующем configured HTTP auth flow использовать настоящий refresh token как Bearer → 401. На уже имеющемся successful response проверить cookie flags/path/no Domain. Следующим risk-driven шагом — signed-expired access/refresh и DB-expired session; не создавать тест каждого decorator.
  • Guarantees at risk: server runtime validation остаётся authoritative; refresh не становится access; bad refresh не очищает winner cookie; maximum не допускает bcrypt truncation semantics.
  • Counterarguments / trade-offs: JWT/bcrypt/class-validator имеют собственные tests, но application выбирает role/expiry/options и связывает два независимых validators. Проверять весь framework не нужно; byte-vs-character fixture и application token-role case оправданы именно собственным контрактом. Browser enforcement cookie attributes этим HTTP test полностью не доказывается.
  • Verification or rollback: local boundary table + targeted configured auth HTTP. Подтверждение wire/runtime ошибки byte minimum доступно самым дешёвым DTO case; в этом аудите он не создавался/не запускался. Production исправляется отдельным изменением с согласованным contract.

TQ-008 — «Publish fails» отклоняет upstream до transaction и не проверяет snapshot

  • Decision: FIX. Severity: P2. Confidence: High. Effort: S. Related finding IDs: TQ-001, TQ-012, TQ-013.
  • Evidence: backend/test/tbank-value-history.e2e-spec.ts:99–111,316–333; backend/src/modules/investments/portfolio/application/portfolio.service.ts:265–291; backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts:121–183.
  • Claim: имя утверждает сохранность snapshot и points при publish failure, но публикация вообще не начинается. Assertions сравнивают только value points.
  • Current mechanism: getPortfolio rejects внутри Promise.all; execution переходит в fail branch до normalize/repository.publish. Реальная transaction сначала claim-ит work, пишет Portfolio, затем history/points. Именно частичный write/rejection этот test не осуществляет.
  • Impact / actual cost: removal transaction wrapper либо failure после Portfolio upsert может повредить связность snapshot/points без изменения этого upstream-failure case. Это мысленная регрессия; actual mutant не запускался. Impact unmeasured.
  • Why now: value history добавляет несколько persisted outputs к одному publish; тест уже заявляет их совместную сохранность, но наблюдает только один.
  • Minimum change: оставить и честно назвать existing upstream-failure case. Один новый SQLite publication case должен выполнить первый реальный write и затем вызвать контролируемую ошибку внутри transaction (например, temporary DB trigger, удаляемый в teardown). Сравнить previous Portfolio payload/dataRevision/time, history points и согласованный work outcome. Не mock-ить весь publish.
  • Guarantees at risk: атомарность snapshot + points, dedup attempt/currency, original source time, fail-state без частично обновлённых данных.
  • Counterarguments / trade-offs: FK/transaction в production есть; дефект их работы не утверждается. Другие bank/operations rollback tests используют другие repository owners и не заменяют этот proof. Existing upstream failure case полезен и не удаляется.
  • Verification or rollback: isolated SQLite suite; fault обязан происходить после подтверждённой записи внутри tx, затем independently прочитать DB/reopen при необходимости. Удалить fault в teardown; не использовать application DB и не добавлять audit executable script.

TQ-009 — Manual-screen normalization tests обслуживают недоступный request-flow

  • Decision: DELETE после подтверждения retirement владельцем. Severity: P3. Confidence: High для текущего call graph. Effort: S. Related finding IDs: TQ-003.
  • Evidence: backend/src/modules/investments/tbank-sync/tbank-screen.spec.ts:1–75; backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:54–78; backend/src/modules/investments/tbank-sync/tbank-sync.controller.ts:31–40; backend/test/openapi-contract.e2e-spec.ts:27–55; specs/openspec/specs/tbank-data-sync/spec.md:9–14.
  • Claim: normalizeScreen unit cases проверяют manual-command descriptor boundary, у которой в текущем source/test consumer graph нет запускающего production caller.
  • Current mechanism: normalizeScreen вызывается только TbankSyncService.request; все references самого сервиса — declaration/provider/controller status. Controller предоставляет GET status, не request; OpenAPI test прямо требует отсутствия collection command path. Hourly owner использует другую reservation/flow.
  • Impact / actual cost: поддерживается тестовая модель запрещённой manual sync capability. Runtime/CI экономия not measured; минимум снижается cognitive/maintenance load при окончательном retirement.
  • Why now: новые нужные tests должны защищать hourly contract, а не расширять прежний manual request vocabulary.
  • Minimum change: согласовать retirement с production owner из audit 04/06; удалить именно tbank-screen.spec.ts, не заменяя тестами того же недоступного flow. Source retirement — отдельная задача, не выполненная этим аудитом.
  • Guarantees at risk / что перестанет проверяться: нормализация query/ticker; canonical categories/order; rejection отсутствующих identities/лишних descriptor fields; inclusive event calendar и invalid/default-period rejection в manual descriptor API. Это не active HTTP query validation, не auth, не hourly reservation и не Ledger planner.
  • Counterarguments / trade-offs: если request — явно поддерживаемый programmatic API с consumer вне проверенного дерева, DELETE заменяется KEEP. Такого consumer в src/test не найдено. Нельзя по этой находке удалять planInvestmentScreen, у него иной active consumer.
  • Verification or rollback: повторить consumer search перед удалением, сохранить existing HTTP/calendar/owner tests; восстановление test file из Git полностью обратимо. До подтверждённого retirement эта рекомендация условная.

TQ-010 — Events tests закрепляют старую форму периода и отсутствие HTTP retry

  • Decision: SIMPLIFY. Severity: P2. Confidence: High для расхождения. Effort: M. Related finding IDs: TQ-004, TQ-005.
  • Evidence: frontend/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.filters.test.tsx:159–291; frontend/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.test.tsx:63–79,172–190; frontend/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.vm.ts:19–33,62–75,92–123; frontend/src/pages/account-events/ui/AccountEventsPage/components/AccountEventsPageLoaded.view.tsx:59–98; frontend/src/pages/account-events/ui/AccountEventsPage/components/AccountEventsPageError.view.tsx:13–23; specs/openspec/specs/brokerage-account-upcoming-events/spec.md:86–107.
  • Claim: наиболее развитые current tests защищают интерактивный filter UI и Reset вместо Retry, тогда как existing spec требует read-only forecast list без фильтров и с HTTP-only recovery.
  • Current mechanism: VM хранит applied/draft periods и maxDate, отдаёт events, но не exposes calculatedAt/original forecast window; view показывает date picker/Apply/Reset. Error test явно отвергает «Повторить». MSW filter tests доказывают старые URL transitions, но не current forecast presentation.
  • Impact / actual cost: UX migration удерживается устаревшими assertions; required timestamp/coverage explanation/retry не имеют route-level oracle. Поддерживается периодная state machine, которая не нужна сохранённому current screen contract. Impact unmeasured.
  • Why now: spec уже различает query window API и read-only screen. Нет необходимости переиспользовать Ledger-подобную форму периода для этого экрана.
  • Minimum change: вместе с согласованной screen migration заменить filter-interaction сценарии несколькими existing RTL/MSW cases: sorted saved list и calculatedAt/original window; empty complete; missing/error; complete fallback + failed attempt; Retry читает тот же GET. Сохранить semantic event amount/offer/identity и доступное status text. Transport from/to tests не удалять автоматически: API filtering остаётся отдельным контрактом.
  • Guarantees at risk: deep-link account scope, inclusive UTC filtering API, cached full forecast, no manual import, accessible statuses. Удаление формы не должно удалить эти гарантии вместе с test file.
  • Counterarguments / trade-offs: если продукт сознательно сохраняет filters, сначала согласовать изменение current spec и forecast-source UX. Сам факт слова «MUST» не доказывает оптимальность screen decision; однако code/test противоречие уже факт. Глубокий UI/source redesign относится к audit 05, здесь проверен только test oracle/coverage.
  • Verification or rollback: existing route/MSW tests должны проверять видимый контент, а не private VM field shape. Reuse existing cases/handlers; новый browser runner не нужен. Ошибка source и transport должны оставаться раздельными.

TQ-011 — KEEP: реальные auth CAS и session-generation proofs дополняют unit tests

  • Decision: KEEP. Severity: P1. Confidence: High. Effort: S. Related finding IDs: TQ-002, TQ-007.
  • Evidence: backend/test/app.e2e-spec.ts:161–213; backend/src/modules/auth/application/auth.service.spec.ts:95–148; backend/src/modules/auth/application/auth.service.ts:81–114,166–200; frontend/src/shared/api/client.test.ts:92–123; frontend/src/features/auth/model/auth.test.tsx:189–275; frontend/src/features/auth/model/auth.vm.ts:41–115,121–140.
  • Claim: unit mock CAS, real SQLite HTTP race и frontend identity/cookie-write ordering защищают разные failure modes; это не бесполезное повторение одного сценария.
  • Current mechanism: unit barrier обеспечивает два reads и проверяет expected hash/error handling. HTTP barrier пропускает оба real sign/refresh paths к SQLite CAS, ждёт обоих responses и последовательно проверяет replay/winner. Frontend tests ждут реальные operation promises и проверяют identity после late completion.
  • Impact / actual cost: ловятся unconditional rotation, session revocation loser-ом, loser Set-Cookie и resurrection old user. Стоимость исполнения этих suites not measured; runtime assertions по статике значимы.
  • Why now: simplification и тестовые gaps TQ-002/TQ-007 не должны вести к замене сильного proof более дешёвым, но неэквивалентным mock wiring.
  • Minimum change: сохранить cases/барьеры. При переименовании прямо обозначать DB vs orchestration vs frontend generation scope. Не добавлять cross-tab protocol, которого current contract не требует.
  • Guarantees at risk: один winner, reusable winner session, no cookie clobber, in-memory identity isolation.
  • Counterarguments / trade-offs: JWT signature boundary — уместный test seam для ordering, не assertion private call order. Это same-process HTTP race, не доказательство multi-process deployment или реального browser cookie enforcement.
  • Verification or rollback: targeted auth HTTP plus existing frontend cases при изменении этого owner. Аудит их runtime не запускал; hypothetical removing expected-hash predicate изменяет содержательный outcome, а не только mock invocation.

TQ-012 — KEEP: money tests имеют независимые oracles и правильные complementary levels

  • Decision: KEEP. Severity: P1. Confidence: High. Effort: S. Related finding IDs: TQ-007, TQ-008, TQ-013.
  • Evidence: backend/src/integrations/tbank/decimal.spec.ts:5–37; backend/src/modules/banking/bank-cards/domain/rub-money.spec.ts:4–57; backend/src/common/decimal.ts:19–48,59–83; backend/src/integrations/tbank/decimal.ts:9–13; backend/src/modules/banking/bank-cards/domain/rub-money.ts:12–38; backend/test/bank-cards.e2e-spec.ts:146–165; frontend/src/entities/bank-card/api/summary.test.ts:9–37; frontend/src/entities/bank-card/api/queries.ts:13–25.
  • Claim: финансовые expected results не вычислены тем же production helper; границы точно отличают float/coercion/range regressions. Unit + real DB/HTTP + frontend comparator не взаимозаменяемы.
  • Current mechanism: explicit BigInt/string fixtures, boundary values ±signed64, forbidden fractional kopecks, zero/negative operation guard, computed sum beyond storage bounds. HTTP проверяет persist/serialize/application, frontend comparator различает копейки больших чисел и non-mutating ties.
  • Impact / actual cost: сохраняются точные деньги и no rounding/no fake zero; две pure suites реально прошли, 28 tests. Стоимость remaining layers not measured.
  • Why now: эти small tests дают больше защиты, чем рост coverage на passthrough DTOs; сокращать их из-за похожих inputs нельзя.
  • Minimum change: оставить existing tables. Новую property-testing dependency или suite для каждого arithmetic helper текущие findings не обосновывают. Finance regression добавлять по отличающему risk, не ради 100% coverage.
  • Guarantees at risk: precision, nanos/sign, storage input bound, вычисления beyond individual stored-value bound, per-currency semantics.
  • Counterarguments / trade-offs: Number допустим для округляемого display percentage (frontend/src/entities/portfolio/model/display.ts:7–24), это не тот же инвариант, что exact money sorting. Длинный fraction case проверяет собственный formatter; его runtime cost отдельно не выделялся.
  • Verification or rollback: фактический targeted command записан в metadata; он не подтверждает всю финансовую систему. При persistence change сохранить HTTP exact-value proof отдельно от pure unit result.

TQ-013 — KEEP: deterministic rollback, dependency CAS и replay проверяют реальные данные

  • Decision: KEEP. Severity: P1. Confidence: High. Effort: S. Related finding IDs: TQ-001, TQ-005, TQ-006, TQ-008, TQ-012.
  • Evidence: backend/test/tbank-operations-persistence.e2e-spec.ts:186–246,292–350; backend/src/modules/investments/operations/infrastructure/brokerage-operations.repository.ts:703–818,820–839,932–944; backend/test/upcoming-events.e2e-spec.ts:344–368; backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:80–145; backend/test/bank-cards.e2e-spec.ts:391–422,622–653; backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:152–195,214–240; backend/src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts:35–59.
  • Claim: это tests собственных transaction/publication/idempotency invariants, не просто проверка Prisma/SQLite «работает».
  • Current mechanism: в operations publish первая изменённая группа реально записывается, затем incomplete marker второй группы throws; independently прочитанные rows/coverage должны остаться старыми. Forecast case меняет DB revision в контролируемом seam перед original resolve. Banking HTTP replay проверяет persisted balance/tombstone, а invalid refund date возникает после version-lock write внутри transaction.
  • Impact / actual cost: rollback failure, stale calculated publication, duplicate debit/replay resurrection становятся наблюдаемыми. Impact runtime/CI not measured; не утверждается exhaustive coverage всех interleavings.
  • Why now: похожие «failed refresh» names не делают все failure tests равноценными. Эти cases обеспечивают реальные данные/порядок, которых нет в TQ-001/TQ-008.
  • Minimum change: сохранить existing cases и asserted financial outcomes. Private seam в forecast допустим как контролируемая интервенция, потому что test вызывает original repository и наблюдает HTTP/DB итог. Не заменять conditional publication проверкой publish called.
  • Guarantees at risk: group atomicity, revision stability, connection/member/source binding, exact reconciliation, replay protection и refund limit.
  • Counterarguments / trade-offs: отдельный reader loop не гарантирует попасть в середину каждого scheduling interleaving; claim должен быть ограничен. Deterministic post-first-write rollback и controlled revision-change cases дают более сильный distinguishing proof и сохраняются независимо от этого reader sample.
  • Verification or rollback: existing isolated SQLite suites при соответствующем source change. Их запуск не выполнен в аудите; вывод основан на точном control flow до throw и независимых observed records.

5. DELETE / SIMPLIFY / KEEP / INVESTIGATE

Decision Объект Обоснование и условие Что обязательно сохраняется
DELETE tbank-screen.spec.ts, TQ-009 текущий normalization consumer — недоступная manual command; только после retirement confirmation active HTTP date/access tests, hourly owner и Ledger planner
SIMPLIFY Events filter-interaction tests, TQ-010 заменить прежний UX oracle current read-only forecast behavior event meaning/amount, account scope, API date semantics, cached result/retry
FIX race stimulus/barriers, TQ-001/002 started producer и completed loser вместо unused deferred/early negative generation/identity guards и real observed data
FIX hourly proof, TQ-003 actual coordinator/reservation/result boundary durable reservation, no overlap/catch-up, partial-success preservation
FIX Portfolio/forecast/auth/rollback oracles, TQ-004/005/007/008 existing suites можно сделать различающими без нового framework monetary accuracy, HTTP read-only, CAS, validation, transaction
KEEP exact money layers, TQ-012 разные failure modes: pure math/storage/wire/sorting большие/отрицательные значения и независимо заданные expected results
KEEP real auth CAS + frontend generations, TQ-011 две операции реально начаты и дожданы; winner проверен после replay conditional update/no loser cookie/no resurrected identity
KEEP transaction/CAS/replay cases, TQ-013 actual DB writes перед failure или изменением dependency old complete group, work/source versions, tombstones, refund limits
KEEP controller argument seam test 20-line arbitrary-user mapping проверяет собственный adapter; HTTP guards другим уровнем authenticated sub/account/query routing
KEEP TbankClient retry/date seam tests собственная calendar/protocol/retry policy, даже при mock transport inclusive → half-open UTC, retry bound, no auth retry
KEEP real browser layout case actual geometry различает колонку/row; JSDOM не даёт эквивалентный oracle 375/1280 responsive behavior
INVESTIGATE missing MOEX block, TQ-006 semantic conflict, no trusted provider payload in audit malformed/cursor/partial-calendar safeguards

Применение ponytail-audit: delete: manual-screen normalization tests после retirement недоступного command-flow; replacement: ничего для прежней capability. [backend/src/modules/investments/tbank-sync/tbank-screen.spec.ts]

Размер этого условного кандидата — 75 физических строк, по numbered Read существующего файла. Для него отдельно: net: -75 lines, -0 deps possible. Это размер только удаляемого test file, не прогноз net diff всех рекомендаций; source cleanup, replacement tests и CI savings не измерялись. Массовое удаление controller/DTO/mock tests только по внешнему виду не обосновано.

High-value KEEP examples

  • Auth HTTP race: обе операции проходят barrier, SQLite выбирает winner, обе завершены до assertion; replay старого cookie и последующий winner refresh различают «одна ошибка» и «повреждена победившая session».
  • Operations group rollback: incomplete second interval проверяется после обработки первого, поэтому removal transaction меняет visible financial group/coverage, а не только количество calls.
  • Forecast dependency CAS: DB quantity/revision меняются до original resolve; old fallback и next exact amount отличают отказ stale candidate от просто вызванного repository method.
  • Money: expected maximum kopecks и decimal strings независимо заданы; roundtrip отдельно проверяет BigInt и output, не только format(parse(x)) == x.
  • Bank replay/tombstone: одинаковый intent и независимый expense одновременно не должны ни двойно списывать деньги, ни терять второй intent; replay после delete не оживляет operation.
  • Browser layout: реальные ненулевые bounds и относительное положение карточек; это оправданный browser test своего layout, а не тест декларации Mantine.

6. Cost & benefit

Рекомендация Ожидаемый класс пользы Измерение Обязательная regression boundary
TQ-001/002 меньше ложной уверенности/недетерминированного расследования races false-green rate, flaky rate, agent tokens: not measured started + completed real operation и observed data
TQ-003 предотвращение потери refresh/lifecycle regressions incidents/CI minutes: not measured; suite может добавить время DB reservation + terminal hourly result, fake upstream
TQ-004/005/010 меньше поддержки устаревшего поведения; лучше user-visible guarantees maintenance time/UX impact: not measured full route/resource result, original calculation metadata, HTTP-only retry
TQ-006 избежать неверного confirmed-empty либо ложного failure provider failure frequency: not measured offline payload semantics + real persistence fallback
TQ-007/008 закрытие конкретных trust/storage boundary blind spots security/storage failure frequency: not measured configured HTTP/DTO и post-write SQLite rollback
TQ-009 cognitive/maintenance load; удаление orphan scenario candidate file: 75 физических строк; время исполнения not measured повторная проверка consumer graph, active boundaries KEEP
TQ-011/012/013 сохранить наиболее ценные distinguishing proofs только 2 pure suites/28 PASS; остальные runtime UNVERIFIED complementary unit + true integration; не mock-only replacement

Runtime production оптимизация этим аудитом не предлагалась. Проценты ускорения/сокращения покрытия/CI или общий net LOC не прогнозируются. Новые dependencies, runner scripts, generic factories и mutation framework не нужны для рекомендованных минимальных изменений.

7. Top actions

Максимум десять шагов; порядок — value/risk/effort, а не число замечаний.

Quick wins

  1. TQ-001: заменить unused-deferred late-portfolio stimulus existing source request, проверить started и await work.done. Empty membership оставить отдельной observable гарантией.
  2. TQ-002: добавить completion barrier real old operation в StocksPage/resource race cases; уже true new-result assertion не использовать как barrier.
  3. TQ-004: обновить existing route/MSW oracle: source metadata + сохранённые totals + warning + click HTTP retry; дождаться actual query error, не request counter.
  4. TQ-007: добавить independently expected multibyte boundaries и refresh-as-Bearer HTTP case; расширить cookie assertions на существующем success response.
  5. TQ-008: честно назвать upstream-failure test и добавить один post-first-write rollback case с snapshot + points, используя isolated SQLite.

Нужны подтверждённый behavioral oracle или решение владельца

  1. TQ-003: построить минимальный executable proof current hourly owner: reservation time table и один controlled partial/error walk. Сначала закрепить missed-term semantics; новый framework не нужен.
  2. TQ-005: согласовать latest saved forecast selection; заменить null expectation на successful empty fallback и добавить shifted-window/new-complete candidate distinction. Existing CAS/fallback tests KEEP.
  3. TQ-010: вместе с screen owner упростить Events test set под read-only current UX. API from/to, event meaning и source-error fallback не удалять вместе с filter UI.
  4. TQ-006: решить missing-block semantics по trusted offline payload/контракту; затем разделить absence/malformed unit cases и сохранить persistence negative proof.
  5. TQ-009: подтвердить отсутствие внешнего programmatic consumer manual command; после retirement удалить orphan normalization test. При наличии такого consumer — KEEP и явно определить контракт.

Полноценный ADR нужен только если меняется принятое продуктовое решение (например, сохраняются event filters вопреки текущей spec) или provider completeness semantics. Для исправления test stimulus, ожидания Promise и independent boundary fixture отдельный ADR не нужен.

8. Cross-audit signals

  • Audit 06/13: hourly catch/settlement/polling и first-launch/missed-term поведение — backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:48–110,178–218, backend/src/modules/investments/tbank-sync/tbank-sync.repository.ts:174–219; здесь исследован только отсутствующий regression proof.
  • Audit 05: Portfolio metadata/recovery и Events filter UI owner — frontend/src/pages/account/ui/AccountPage/AccountPage.tsx:24–34, frontend/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.vm.ts:19–123; полноценный UI redesign не выполнялся.
  • Audit 04/06: retirement недоступного manual command — backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:54–78, backend/src/modules/investments/tbank-sync/tbank-sync.controller.ts:31–40; тестовый кандидат TQ-009 зависит от решения owner.
  • Audit 09/11: отсутствие timer/multi-resource proof не решать изменением runner/CI; frontend/src/features/tbank-connection/model/screen-sync.test.tsx:21–79 — отдельная quality boundary, стоимость инфраструктуры здесь не оценивалась.
  • Audit 15: accepted ADR 019 и current behavioral specs расходятся с частью UI/parser/test expectations; docs/adr/019-hourly-reads-and-online-instrument-reference.md:9–29, Events/Auth specs — нужна reconciliation решений, не дополнительный обязательный workflow.

9. Open questions / verification plan

Вопрос, меняющий решение Current evidence Самый дешёвый способ проверки Что изменить в выводе
Есть ли поддерживаемый consumer TbankSyncService.request вне checkout? в src/test найден только unused request owner; controller status-only спросить владельца capability / повторить consumer search перед retirement external consumer → TQ-009 KEEP вместо DELETE
Missing ISS block означает supported absence или повреждение/изменение API? tests/source failure vs spec no-data trusted сохранённый JSON/первичный provider contract offline; никаких real personal APIs выбрать unit/integration oracle TQ-006 и при необходимости обновить решение
Принят ли current Events read-only UX или filters намеренно остаются? current spec no filters/retry vs source/tests filters/reset решение screen owner по existing spec/accepted ADR TQ-010 simplification либо explicit amended contract; negative retry не оставлять молча
Как выбирать последний complete forecast между окнами? fallback ищет overlap, candidate может уступить old scope existing SQLite suite с двумя independently distinguishable scopes/amounts/times TQ-005 positive selection oracle без relabelling
Какой long-walk/missed-term outcome действителен? spec 10:00 → после 11:00 completion → следующий ≥12:00 table reserveHourly на real isolated DB с контролируемыми start/finish/due TQ-003 expected timing; конфликт source не исправлять по случайному аналогичному test
При удалении stale guard текущий frontend test действительно false-green? completion old operation не ожидается явно при отдельной test-fix задаче сначала explicit started/settled instrumentation existing test; mutation только по отдельному разрешению сейчас Medium risk; не превращать статическую гипотезу в измеренный факт
Какие suites актуально проходят в runtime? запущены только две денежные pure suites owners запускают соответствующие existing targeted suites после изменений current report не означает all tests PASS

Минимальный порядок последующей верификации: safe pure auth/decimal boundary → targeted RTL/MSW owner tests → isolated SQLite publication/reservation/auth HTTP → browser только для изменяемой browser-specific гарантии. Использовать локальные .nvmrc и existing runner safeguards. Полный verify нужен при будущих code changes по affected repository conventions, а не как побочный эффект read-only аудита.

Проверка завершения аудита: новый отчёт просмотрен на структуру, exact evidence и границы claims; git diff --check в checkout не выявил whitespace errors. Для нового ignored-файла отдельно выполнен git diff --no-index --check /dev/null audit-results/08-test-quality-and-effectiveness-report.md; замечаний нет. Подсчёт заголовков ^### TQ-[0-9]{3} — дал 13 записей, соответствующих итоговой таблице. Повторный git status --short в четырёх checkout пустой; их HEAD не изменены. git check-ignore подтвердил, что отчёт — ignored локальный artifact в root audit-results, не изменение исходников/test/config четырёх репозиториев.

10. Машинно-считываемая таблица для консолидации

ID Decision Severity Confidence Repo Files One-line impact Effort Related IDs
TQ-001 FIX P2 High backend test/tbank-local-reads.e2e-spec.ts Late-publication case never starts the protected producer S TQ-003,TQ-013
TQ-002 FIX P2 Medium frontend src/pages/stocks/ui/StocksPage/StocksPage.test.tsx;src/shared/api/resource-query.test.tsx Late-result assertions lack full loser-completion barrier S TQ-001,TQ-011
TQ-003 FIX P1 High backend src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts;src/modules/investments/tbank-sync/tbank-sync.repository.ts;test/upcoming-events.e2e-spec.ts Critical hourly lifecycle and reservation lack direct executable proof M TQ-001,TQ-005
TQ-004 FIX P2 High frontend src/pages/account/ui/AccountPage/AccountPage.test.tsx;src/pages/account/ui/AccountPage/AccountPage.tsx Portfolio oracle forbids recovery and misses source-metadata wiring S TQ-010
TQ-005 FIX P2 High backend test/upcoming-events.e2e-spec.ts;src/modules/investments/portfolio/infrastructure/forecast.repository.ts Range oracle rejects complete-empty fallback and misses new-window publication M TQ-003,TQ-006,TQ-013
TQ-006 INVESTIGATE P2 High backend src/integrations/moex/moex.client.spec.ts;test/upcoming-events.e2e-spec.ts Missing source block semantics conflict with behavioral requirement S TQ-005,TQ-013
TQ-007 FIX P2 High backend,frontend backend/src/common/dto-validation.spec.ts;backend/test/app.e2e-spec.ts;frontend/src/features/auth/ui/AuthForm/AuthForm.test.tsx UTF-8 and token-role/cookie boundaries lack distinguishing cases S TQ-011
TQ-008 FIX P2 High backend test/tbank-value-history.e2e-spec.ts Publish-failure case fails upstream before transaction and omits snapshot assertion S TQ-001,TQ-012,TQ-013
TQ-009 DELETE P3 High backend src/modules/investments/tbank-sync/tbank-screen.spec.ts Orphan manual-command tests removable after owner confirms retirement S TQ-003
TQ-010 SIMPLIFY P2 High frontend src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.filters.test.tsx;src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.test.tsx Old filter/reset oracles obstruct current read-only forecast and retry UX M TQ-004,TQ-005
TQ-011 KEEP P1 High backend,frontend backend/test/app.e2e-spec.ts;backend/src/modules/auth/application/auth.service.spec.ts;frontend/src/shared/api/client.test.ts;frontend/src/features/auth/model/auth.test.tsx Preserve real refresh CAS, usable winner and late-identity isolation S TQ-002,TQ-007
TQ-012 KEEP P1 High backend,frontend backend/src/integrations/tbank/decimal.spec.ts;backend/src/modules/banking/bank-cards/domain/rub-money.spec.ts;backend/test/bank-cards.e2e-spec.ts;frontend/src/entities/bank-card/api/summary.test.ts Preserve independent exact-money unit, persistence and sorting proofs S TQ-007,TQ-008,TQ-013
TQ-013 KEEP P1 High backend test/tbank-operations-persistence.e2e-spec.ts;test/upcoming-events.e2e-spec.ts;test/bank-cards.e2e-spec.ts Preserve deterministic rollback, dependency CAS and replay invariants S TQ-001,TQ-005,TQ-006,TQ-008,TQ-012