Переносит архив `audits/2026-10-11/` (19 файлов) из codex/audit-remediation-2026-10-11 в develop и добавляет точечный allowlist в .gitignore.
92 KiB
Аудит 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
- Факт: сильная тестовая основа существует. Денежные cases используют независимо заданные строки и границы
>2^53/signed64; auth concurrency проходит реальный SQLite CAS; operations rollback выполняет часть записи до отказа следующего интервала. Их нельзя заменять одними mocks или удалять как «дублирующие coverage». - Главный риск: новый владелец обновления почти не проверен как выполняемый flow. В inventory не найдены прямые проверки
tick,reserveHourly,settleHourly; три integration suites явно заменяют scheduler. Проверки готовогоmetaне доказывают запуск, сроки, завершение и результат реального обхода. Это единственный P1 finding о пробеле защиты. - Факт: backend late-portfolio test не создаёт заявленную гонку. Deferred установлен на RPC, но stimulus — локальный GET; PUT тоже не запускает Portfolio owner. Тест не проверяет отклонение поздней публикации. Отдельные соседние тесты также сохранили названия failed refresh/recovery при прямой подстановке DB-state.
- Риск: frontend race assertions не всегда стоят после завершения проигравшего. StocksPage проверяет уже видимый новый результат сразу после освобождения старого ответа. В resource tests ожидается promise затвора, не всего HTTP/query lifecycle. Отсутствие надёжного completion barrier подтверждено; фактический false-green мутанта не запускался.
- Факт: часть test oracles противоречит действующим поведенческим требованиям. Portfolio tests требуют отсутствия предупреждения/retry; Events tests закрепляют фильтры и отсутствие retry; backend Events ожидает
data: nullдля непересекающегося сохранённого прогноза. Это не просто недостаток покрытия: такие ожидания защищают другую модель UX/данных. - Факт: важные negative boundaries остаются неразличимыми. Тест «publish fails» отклоняет upstream до начала публикации, а не запись внутри transaction. Auth cases не ловят различие UTF-8 bytes/characters и не проверяют refresh token как Bearer. Отсутствие теста не доказывает production-эксплуатацию.
- Непроверенная семантика: отсутствующий блок 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 и awaitwork.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 startedassertion или ожидания завершения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/testtest 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 в
srcunit и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:
getPortfoliorejects внутри 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/totests не удалять автоматически: 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
- TQ-001: заменить unused-deferred late-portfolio stimulus existing source request, проверить started и await work.done. Empty membership оставить отдельной observable гарантией.
- TQ-002: добавить completion barrier real old operation в StocksPage/resource race cases; уже true new-result assertion не использовать как barrier.
- TQ-004: обновить existing route/MSW oracle: source metadata + сохранённые totals + warning + click HTTP retry; дождаться actual query error, не request counter.
- TQ-007: добавить independently expected multibyte boundaries и refresh-as-Bearer HTTP case; расширить cookie assertions на существующем success response.
- TQ-008: честно назвать upstream-failure test и добавить один post-first-write rollback case с snapshot + points, используя isolated SQLite.
Нужны подтверждённый behavioral oracle или решение владельца
- TQ-003: построить минимальный executable proof current hourly owner: reservation time table и один controlled partial/error walk. Сначала закрепить missed-term semantics; новый framework не нужен.
- TQ-005: согласовать latest saved forecast selection; заменить null expectation на successful empty fallback и добавить shifted-window/new-complete candidate distinction. Existing CAS/fallback tests KEEP.
- TQ-010: вместе с screen owner упростить Events test set под read-only current UX. API
from/to, event meaning и source-error fallback не удалять вместе с filter UI. - TQ-006: решить missing-block semantics по trusted offline payload/контракту; затем разделить absence/malformed unit cases и сохранить persistence negative proof.
- 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 |