Переносит архив `audits/2026-10-11/` (19 файлов) из codex/audit-remediation-2026-10-11 в develop и добавляет точечный allowlist в .gitignore.
431 lines
92 KiB
Markdown
431 lines
92 KiB
Markdown
# Аудит 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:
|
||
|
||
```sh
|
||
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; уточняющие команды разделили первоначальные суммы по директориям:
|
||
|
||
```sh
|
||
# 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 или решение владельца
|
||
|
||
6. **TQ-003:** построить минимальный executable proof current hourly owner: reservation time table и один controlled partial/error walk. Сначала закрепить missed-term semantics; новый framework не нужен.
|
||
7. **TQ-005:** согласовать latest saved forecast selection; заменить null expectation на successful empty fallback и добавить shifted-window/new-complete candidate distinction. Existing CAS/fallback tests KEEP.
|
||
8. **TQ-010:** вместе с screen owner упростить Events test set под read-only current UX. API `from/to`, event meaning и source-error fallback не удалять вместе с filter UI.
|
||
9. **TQ-006:** решить missing-block semantics по trusted offline payload/контракту; затем разделить absence/malformed unit cases и сохранить persistence negative proof.
|
||
10. **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 |
|