Переносит архив `audits/2026-10-11/` (19 файлов) из codex/audit-remediation-2026-10-11 в develop и добавляет точечный allowlist в .gitignore.
891 lines
88 KiB
Markdown
891 lines
88 KiB
Markdown
# Аудит 03 — Duplication & Reusability
|
||
|
||
## 1. Audit metadata
|
||
|
||
- **Audit ID/name:** 03 / Duplication & Reusability.
|
||
- **Дата:** 2026-10-11.
|
||
- **Workspace:** `/Users/ksv741/Project/vibe/WealthLedger`.
|
||
- **Режим:** независимое read-only исследование; единственный записанный
|
||
артефакт этого исполнителя — настоящий отчёт вне четырёх checkout.
|
||
- **Протокол:** `audit-prompts/03-duplication-and-reusability.md`, прочитан
|
||
целиком. Другие новые отчёты не читались и не перезаписывались.
|
||
- **Skill:** установленный `ponytail-audit` загружен через Skill tool до
|
||
исследования. Его complexity-only подход применён к предложениям
|
||
`SIMPLIFY`; подтверждённое рассогласование validation policy разобрано
|
||
отдельно по контракту аудита, не приписано correctness-проверке skill.
|
||
Дополнительно использованы `feature-sliced-design` для ownership,
|
||
`systematic-debugging` для трассировки причины DRY-001 и
|
||
`verification-before-completion` для проверки результата исследования.
|
||
- **Graphify:** skill загружен. По известному абсолютному пути root
|
||
`graphify-out/graph.json` отсутствует; поиски этого artifact внутри четырёх
|
||
checkout также не дали результатов. Glob может пропускать ignored paths,
|
||
поэтому отсутствие любого скрытого графа не доказано. Доступного existing
|
||
graph для query не обнаружено; анализ продолжен статически. Генерация,
|
||
установка, индексация и создание graphify artifacts не выполнялись.
|
||
|
||
### Репозитории и самостоятельно снятое состояние
|
||
|
||
Все пути в таблице абсолютные. В остальных evidence путь начинается с имени
|
||
checkout и считается относительно workspace.
|
||
|
||
| Repo | Path | HEAD SHA | Начальный `git status --short` | После исследования и сохранения отчёта |
|
||
| --- | --- | --- | --- | --- |
|
||
| root | `/Users/ksv741/Project/vibe/WealthLedger` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | пустой | пустой; отчёт ignored |
|
||
| 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` | пустой | пустой |
|
||
|
||
`AGENTS.md` каждого checkout прочитан до исследования его содержимого.
|
||
Прочитаны локальные architecture/engineering и активированные темы:
|
||
frontend data, TypeScript/React, UI, testing, Storybook; backend HTTP API,
|
||
data, errors, security/auth, integrations, testing. Specs выбирались через
|
||
`AI_INDEX.md`; docs — через README/CONTRIBUTING и тематические страницы.
|
||
Root conventions не применялись к соседним checkout. Локальный FSD profile
|
||
и его реальные import restrictions важнее общего совета о размещении CRUD.
|
||
|
||
### Выполненные проверки и границы исполнения
|
||
|
||
1. В каждом repo самостоятельно выполнены `git rev-parse HEAD` и
|
||
`git status --short`: в начале, после исследования и перед завершением.
|
||
2. Использованы Read/Glob/Grep по конкретным repo paths для поиска символов,
|
||
callers, требований и существующих тестовых сценариев.
|
||
3. Выполнен read-only
|
||
`git diff --no-index --stat backend/openapi.json frontend/src/shared/api/spec/openapi.json`
|
||
из root: различий нет. Это сравнение existing файлов, а не доказательство
|
||
свежести генерации относительно runtime.
|
||
4. Проверены `git diff --check` и отдельно whitespace нового ignored отчёта
|
||
через `git diff --no-index --check /dev/null audit-results/03-duplication-and-reusability-report.md`.
|
||
Проверен `git check-ignore -v` для отчёта; root allowlist сохранён.
|
||
5. Выполнена содержательная сверка отчёта с девятью общими разделами,
|
||
специальными результатами, всеми finding fields и финальной таблицей.
|
||
Read-only `rg --count-matches` подтвердил 9 общих заголовков, 10 DRY sections,
|
||
150 полей (15 на finding, включая Runtime consumer) и 3 Ownership model
|
||
для 3 SIMPLIFY. Агрегатный подсчёт дополнен чтением каждого блока.
|
||
|
||
Тесты не запускались. Node/npm-команды, install, codegen, migrations, build,
|
||
full-suite/verify, commit/PR и обращения к внешним API не выполнялись.
|
||
Исходники, конфигурации, snapshots, dated audits, архивы и references не
|
||
изменялись. Файлы БД, `.env`, секреты и реальные пользовательские данные не
|
||
читались. Два узких installed-library файла прочитаны только для проверки
|
||
семантики `MinLength`, не как кандидаты на ручной DRY cleanup.
|
||
|
||
`audit-results/technical-audit-2026-10-11.md` использован как набор гипотез:
|
||
F12 и F15 независимо подтверждены исходниками и callers. Другие прежние
|
||
рекомендации не принимались автоматически. Числа о LOC, fan-out и времени из
|
||
него не перенесены как собственные измерения.
|
||
|
||
При сводной перепроверке независимо повторён source trace discovery и всех
|
||
production callers `useScreenSync`. Исправлена прежняя ошибка reachability:
|
||
catalog HTTP paths не запускают discovery producer, а возвращаемый screen
|
||
retry не имеет найденных production consumers. DRY-009 сужен до живого
|
||
enrichment и используемых repository fences. Соседние отчёты не читались;
|
||
reachability/deletion discovery переданы audit 04, без требования KEEP этого
|
||
producer. Исходные SHA при перепроверке совпадают с таблицей выше.
|
||
|
||
## 2. Executive verdict
|
||
|
||
1. **Факт — DRY-001:** одна password policy имеет разные единицы в frontend
|
||
и backend. UI допускает не-ASCII пароль, который отклоняет API. Это
|
||
значимое локальное рассогласование, а не основание удалить серверную
|
||
валидацию или создать межрепозиторную библиотеку validators.
|
||
2. **Факт — DRY-002:** одинаковая whitelist-проекция instrument поддерживается
|
||
в двух сервисах одного backend module. У правила один владелец, одинаковые
|
||
типы, ошибки и причины изменения. Локальный mapper даст самый убедительный
|
||
reuse при сохранении самого whitelist.
|
||
3. **Риски сопровождения — DRY-003/004:** frontend повторяет список платёжных
|
||
систем внутри одной формы и один набор access-loss codes в трёх контекстах.
|
||
Сегодня копии согласованы; текущих пользовательских дефектов не доказано.
|
||
Это небольшие P3 cleanup-кандидаты, не обязательная большая миграция.
|
||
4. **Факт — DRY-005–009:** денежные trust boundaries, stock/bond семантика,
|
||
session-aware query wrappers, generated API copies и живой enrichment с
|
||
repository publication fence имеют конкретные гарантии. DRY-009 не требует
|
||
сохранять discovery `request/refresh/drain`: current HTTP consumers их
|
||
обходят, найденный caller `refresh` — только недоступный manual dispatch.
|
||
5. **Непроверенная гипотеза — DRY-010:** часть screen-level periodic-read
|
||
orchestration может перейти к query owners. Наличие нескольких triggers
|
||
подтверждено, но равноценность замены и runtime-экономия — `UNVERIFIED`.
|
||
Все восемь production callers игнорируют hook return; общей кнопки retry
|
||
из этого hook не найдено. Реальный counterargument — mount-based timer phase
|
||
с чтением текущего resource array. Разные `staleTime` не доказывают DRY-долг.
|
||
|
||
**Итог по реестру:** 4 actionable findings: P0 — 0, P1 — 0, P2 — 1,
|
||
P3 — 3; отдельно KEEP — 5, INVESTIGATE — 1. Actionable здесь означает
|
||
`FIX`, `SIMPLIFY` или `DELETE`; DELETE-кандидатов в scope 03 не подтверждено.
|
||
Подсчёт — по уникальным DRY-ID в разделе 4, без повторного подсчёта строк
|
||
Knowledge Register и Cross-audit signals. P2 у KEEP означает значимость
|
||
защищаемого поведения, а не urgency cleanup.
|
||
|
||
## 3. Inventory & coverage
|
||
|
||
### Фактически прослеженные области
|
||
|
||
| Область | Прослеженный flow / объекты | Граница вывода |
|
||
| --- | --- | --- |
|
||
| Auth validation | `AuthForm` → Zod schemas → auth provider/client → `CredentialsDto` через configured pipe → `AuthController` → `AuthService`; spec и docs | Установлены разные единицы длины; HTTP-воспроизведение не запускалось |
|
||
| Catalog projection | Stock/Bond controllers → catalog services → online discovery либо read-only observe; quote/company paths → enrichment section/load | Две одинаковые detail-проекции; catalog HTTP paths не запускают discovery producer |
|
||
| Banking forms | `form.schema.ts`, fields/form VM, generated bank-card types, backend DTO/input/RUB helpers, operation validation/transaction | Разделены локальная копия словаря и необходимое повторение trust-boundary checks |
|
||
| Frontend errors | client permanent classification, resource access-group invalidation, Account/Accounts VM и error views | Совпадает один набор codes; recovery actions и Accounts subset различаются |
|
||
| Stock/Bond UI | Page containers/VM/views, payout components, `instrument-details` model/UI, entity queries | Подтвержден existing reuse и реальная разница dividend/coupon и денежных единиц |
|
||
| Query identity/lifecycle | `apiQueryClient`, authenticated/resource queries, account/portfolio/stock/bond/bank hooks, connection boundary, screen timer | Не обнаружен competing handwritten key для изученных investment queries; банковский `all` — отдельный агрегат |
|
||
| Backend state transitions | Discovery entry-point trace отдельно; живой enrichment reservation/drain → repository publishSource/finish | KEEP касается enrichment/shared fence, не discovery producer; reachability/deletion — audit 04 |
|
||
| Contracts/config | manifests, `main.ts`, `openapi.ts`, frontend dependency-cruiser, API snapshot и точечные generated declarations | Generated files не исследовались как ручные дубли; freshness генерации не доказана |
|
||
| Calendar/money owners | existing investment calendar, instrument-market, shared display, common decimal, T-Bank protobuf decimal | Не предложена общая библиотека на основании похожего имени или арифметики |
|
||
|
||
Выборка risk-/cost-based: приоритет получили публичные проекции, валидируемые
|
||
политики, money/null semantics, recovery и повторяющиеся transitions.
|
||
Тесты читались как свидетельства задуманного поведения и кандидаты для
|
||
будущей проверки, не проводился отдельный аудит их качества или factories.
|
||
|
||
**Метод чисел:** количество копий получено symbol/content search с ручным
|
||
сопоставлением конкретных реализаций и consumers. Для `publicInstrument`
|
||
найдены две definitions и четыре call sites: discovery `:157,323`, enrichment
|
||
`:263,349`. Для access-loss набора — три полных места; transport содержит
|
||
ещё `UNAUTHORIZED`, а Accounts VM использует только два connection codes.
|
||
Для payment systems — две независимо редактируемые frontend runtime lists;
|
||
backend tuple и производные artifacts перечислены отдельно. Глобальные
|
||
проценты дублей, прочитанных файлов, LOC и coverage не вычислялись.
|
||
|
||
Восемь production call sites `useScreenSync` получены поиском символа в
|
||
`frontend/src` с исключением declarations/re-exports/tests и чтением каждого
|
||
полного VM. Во всех случаях вызов — standalone expression, без присваивания,
|
||
destructuring или передачи return дальше; таблица ниже фиксирует полный набор.
|
||
|
||
### Уточнённый consumer trace: discovery и живой enrichment
|
||
|
||
| Вход | Подтверждённая цепочка / evidence | Достигает discovery `request/drain`? |
|
||
| --- | --- | --- |
|
||
| Catalog HTTP endpoints | `backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts:58–82,112–119`; `backend/src/modules/investments/instruments/bonds/presentation/bond.controller.ts:55–89` передают `query.sync === 'observe'`; `backend/src/modules/investments/instruments/stocks/application/stock-catalog.service.ts:22–89` и `backend/src/modules/investments/instruments/bonds/application/bond-catalog.service.ts:25–88` вызывают только `search/detail/byTicker` | Нет при обоих значениях observe |
|
||
| Default `search` | `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:84–86,295–337`: ранний return `onlineSearch`, RPC напрямую через client | Нет |
|
||
| Default `detail` / `byTicker` | Тот же файл `:149–166,227–249`: online branches; `byTicker(false)` вызывает `detail` с default false | Нет |
|
||
| Observe branches | Тот же файл `:86–93,168–190,251–267`: `read(..., true)` и `enrichment.readMany(..., true)`; `read` запускает request только при `!observe` (`:66–70`) | Нет |
|
||
| Discovery producer entry | Тот же файл `:340–386,395–396`: `request` ставит drain, `refresh` вызывает request. Единственный найденный внешний production-text caller refresh — `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:192–201` внутри `dispatch` | Только dead-parent-reachable по найденному src trace, не текущий HTTP flow |
|
||
| Parent entry point | `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:54–78`: dispatch запускается только из request; `backend/src/modules/investments/tbank-sync/tbank-sync.controller.ts:31–41` вызывает только status; `backend/src/modules/investments/tbank-sync/tbank-sync.module.ts:23–25` регистрирует service, экспортирует scheduler, но не запускает request | Production consumer request не найден; DI registration не является вызовом |
|
||
| Реальный enrichment producer | `backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts:122–129` → `backend/src/modules/investments/instruments/stocks/application/stock-market.service.ts:34–45` → `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:234–241,269–289,113–125,189–225,497–577` | Это другой, живой producer: `quote(false)` → `section` → `readMany(false)` → при missing/stale `requestMany` → drain → publishSource |
|
||
|
||
В таблице «тот же файл» означает указанный выше полный path discovery service.
|
||
Поиск ссылок `CatalogDiscoveryService`/`discovery.refresh` и
|
||
`TbankSyncService` в `backend/src` не обнаружил другого producer entry point.
|
||
Прямые вызовы `read(false)`/`request` существуют в
|
||
`backend/test/tbank-catalog-discovery.e2e-spec.ts:86,184–187,202–203`; они
|
||
подтверждают test reachability, но не production reachability. Удаление
|
||
неживой producer-ветки и точные границы compatibility — предмет audit 04.
|
||
|
||
### Production callers `useScreenSync`: return не используется
|
||
|
||
| Call site | Фактический consumer |
|
||
| --- | --- |
|
||
| `frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:78–84` | Standalone вызов для пяти resources; return отброшен |
|
||
| `frontend/src/pages/accounts/ui/AccountsPage/AccountsPage.vm.ts:23` | Standalone вызов для accounts; return отброшен |
|
||
| `frontend/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.vm.ts:67–75` | Standalone вызов для events; return отброшен |
|
||
| `frontend/src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.vm.ts:66–69` | Standalone вызов для analytics; return отброшен |
|
||
| `frontend/src/pages/account-operations/ui/AccountOperationsPage/AccountOperationsPage.vm.ts:96–99` | Standalone вызов для operations; return отброшен |
|
||
| `frontend/src/pages/stock/ui/StockPage/StockPage.vm.ts:45–55` | Standalone вызов для stock sections; return отброшен |
|
||
| `frontend/src/pages/bond/ui/BondPage/BondPage.vm.ts:45–55` | Standalone вызов для bond sections; return отброшен |
|
||
| `frontend/src/pages/bonds/ui/BondsPage/BondsPage.vm.ts:13` | Standalone вызов для search result; return отброшен |
|
||
|
||
Только test harness сохраняет return и вызывает retry/refresh:
|
||
`frontend/src/features/tbank-connection/model/screen-sync.test.tsx:37–50,68–78`.
|
||
Реальный production side effect hook — timer
|
||
`frontend/src/features/tbank-connection/model/screen-sync.ts:22–35`, не его
|
||
return `:53–61`. Timer effect имеет `[]`: его clock начинается при mount,
|
||
не перезапускается из-за resource array change; callback читает актуальный ref.
|
||
|
||
**Вне охвата:** полный clone detector по всему tree, generated/vendor/proto
|
||
duplication, зависимости как предмет отдельного аудита, dead code (04),
|
||
крупные module boundaries (01), эффективность API (07), качество tests (08),
|
||
test infrastructure/factories (09), docs/SDD duplication (15). Root release/CI
|
||
и похожие configs самостоятельных repos не объявлены DRY-долгом: они не
|
||
должны становиться общей обязательной tooling-зависимостью.
|
||
|
||
### Knowledge Duplication Register
|
||
|
||
| Rule/behavior | Copies | Divergence risk | Single owner? | Extraction trade-off | Decision |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Password 12–72 UTF-8 bytes | FE `auth.schema.ts:10–11,36–43`; BE `auth.dto.ts:16–17,43–74`; snapshot `:5246–5250` | Нижняя граница уже разошлась по единицам | Да: auth policy; server владеет enforcement, spec фиксирует требование | Согласовать две независимые проверки; TS generator не реализует byte validation | FIX / DRY-001 |
|
||
| Detail instrument whitelist | `catalog-discovery.service.ts:499–531`; `catalog-enrichment.service.ts:646–678` | Новый field/filter требует двух правок | Да: instruments boundary | Один локальный mapper; не объединять online и durable execution | SIMPLIFY / DRY-002 |
|
||
| Runtime payment systems формы | FE `form.schema.ts:65–69`; `BankCardFields.vm.ts:19–22`; BE tuple `bank-card.dto.ts:45` | FE schema и selectable options могут расходиться | Да внутри FE bank-card; BE отдельно владеет wire vocabulary | Tuple у существующей schema; generated union проверяет допустимость, но не даёт runtime list | SIMPLIFY / DRY-003 |
|
||
| Четыре access-loss codes | FE `client.ts:162–169`; `resource-query.ts:42–51`; `AccountPage.vm.ts:59–64` | Добавление code может изменить retry, purge и page state по-разному | Да: API code taxonomy; actions имеют разных owners | Один predicate, сохранить отдельные действия и дополнительный auth code | SIMPLIFY / DRY-004 |
|
||
| Independent banking validation | FE `form.schema.ts:8–42`, `OperationForm.schema.ts:53–131`; BE `rub-money.ts:12–30`, `bank-operations.service.ts:94–210` | Значения policy требуют согласования; implementations специально различаются | Server — authority, browser — UX snapshot | Shared runtime validator не заменит ownership/CAS/transaction и ухудшит repo autonomy | KEEP / DRY-005 |
|
||
| Dividend/coupon forecast и UI | BE stock `stock-income.service.ts:114–178`, bond `bond-income.service.ts:145–201`; FE payout views | Опасность скрыть payment/record/date и per-share/per-bond differences | Нет для всего процесса; общие primitives уже имеют owners | Generic payout с mode/flags объединит разные причины изменения | KEEP / DRY-006 |
|
||
| Stock/Bond query declarations | FE `stock/api/queries.ts:21–95`, `bond/api/queries.ts:21–104` | Малое локальное повторение, разные endpoints/accessGroup/contracts | Два entity owners; transport общий | Оставить endpoint-specific generated options, не строить string-templated query factory | KEEP / DRY-006 |
|
||
| Query wrapper / Mantine primitives | `authenticated-query.ts:34–59`, `resource-query.ts:37–67,84–149`; UI packages | Удаление defaults потеряет session/access/resource/ARIA поведение | Да для каждой существующей focused boundary | Нативные controls плюс project defaults уже используются; passthrough переименование не предлагается | KEEP / DRY-007 |
|
||
| OpenAPI → snapshot → TS aliases | BE DTO/Swagger, FE snapshot/generated, `bank-card/api/types.ts`, `auth-context.ts` | Freshness artifact остаётся отдельным вопросом | Backend editable source; FE локальный consumer artifact | Сохранить offline generation, не создавать shared DTO package/runtime validator | KEEP / DRY-008 |
|
||
| Known UI errors vs open wire string | FE `problem-details.ts:1–28`; generated `schema.d.ts:731–747` | Unknown code должен сохранять fallback | Server code owners и FE known subset имеют разные задачи | Не превращать UI enum в закрытый весь wire contract | KEEP / DRY-008 |
|
||
| Живой enrichment vs похожий discovery producer | BE enrichment `:113–125,189–225,497–593`; discovery `:66–70,84–86,149–166,227–267,340–495` | Generic extraction из неживой ветки добавит framework; изменение enrichment может потерять batching/fences | Enrichment владеет текущим producer; общая repository fence уже есть | Сохранить только используемые enrichment guarantees; discovery reachability/deletion — audit 04, не KEEP | KEEP / DRY-009 (узкий scope) |
|
||
| Periodic reads и freshness triggers | FE `screen-sync.ts:22–35`; восемь standalone page calls; `App.tsx:13–20`; entity stale times | Триггеры распределены; return/retry не используются, runtime waste не измерен | Scheduling owner требует решения; freshness не равно interval | Query-owned interval требует проверки mount phase/latest-resource semantics, не гипотетической общей кнопки | INVESTIGATE / DRY-010 |
|
||
|
||
В таблице короткие имена относятся к точным полным paths из findings ниже.
|
||
Скопированные generated значения не посчитаны независимо editable знаниями.
|
||
|
||
### Intentional Duplication to Keep
|
||
|
||
| Место / evidence | Runtime consumer | Почему повторение оправдано | Что нельзя потерять |
|
||
| --- | --- | --- | --- |
|
||
| `frontend/src/entities/bank-card/model/money.ts:4–23` и `backend/src/modules/banking/bank-cards/domain/rub-money.ts:12–30` | Form draft → normalized request → server application input | UI допускает пробелы/запятую/leading zeros; server принимает канонический RUB decimal и проверяет storage range | bigint precision, positive checks, ошибки вместо округления |
|
||
| `frontend/src/pages/card/ui/OperationForm/OperationForm.schema.ts:53–131` и `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:94–210` | Draft validation и create/update operation | UI проверяет cached список; server повторно читает authoritative user-owned records в transaction | Cross-user isolation, refund CAS/limit, rollback, replay |
|
||
| `frontend/src/entities/stock/api/queries.ts:21–95`; `frontend/src/entities/bond/api/queries.ts:21–104`; `backend/src/modules/investments/instruments/stocks/application/stock-income.service.ts:114–178`; `backend/src/modules/investments/instruments/bonds/application/bond-income.service.ts:145–201` | Stock/Bond route и HTTP resource | Сходная композиция, разные payloads, null/date rules, quote units; общие chart/position/layout уже переиспользуются | Не смешать проценты номинала и Money, registry и payment date |
|
||
| `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:113–125,189–225,497–577`; `backend/src/modules/investments/instruments/infrastructure/catalog-discovery.repository.ts:154–179` | Живой quote(false) → section → readMany(false) → enrichment producer → publishSource | Local enrichment использует batching/observers, repository уже разделяет authorization/publication checks; generic queue из сходства с неживым discovery не нужна | Authorized producer, private work handles, conditional publication только используемого enrichment; discovery producer не входит в KEEP |
|
||
| `backend/src/common/money.types.ts:1`; `backend/src/common/money.dto.ts:3–10`; `frontend/src/shared/api/generated/schema.d.ts:763–795` | Swagger/ValidationPipe и typed API client | Runtime metadata и независимые репозитории требуют разных форм представления одного контракта | Offline frontend input, open unknown errors, отсутствие Swagger в pure domain |
|
||
|
||
Эта выборка показывает несколько разных оснований KEEP, а не исключения,
|
||
добавленные после обязательного требования «всё объединить».
|
||
|
||
## 4. Findings
|
||
|
||
### DRY-001 — Password policy расходится по единицам, а не по месту проверки
|
||
|
||
- **Decision:** FIX.
|
||
- **Severity:** P2.
|
||
- **Confidence:** High.
|
||
- **Evidence:** `frontend/src/features/auth/model/auth.schema.ts:10–11,36–43`;
|
||
`backend/src/modules/auth/presentation/auth.dto.ts:16–17,43–74`;
|
||
`backend/src/main.ts:31–39`;
|
||
`specs/openspec/specs/user-authentication/spec.md:9–10,109–110`.
|
||
Installed semantics независимо проверены в
|
||
`backend/node_modules/class-validator/cjs/decorator/string/MinLength.js:14–29`
|
||
и `backend/node_modules/validator/lib/isLength.js:23–26`.
|
||
- **Claim:** две проверки одного принятого правила имеют несовместимый
|
||
минимум: frontend — bytes, backend — character length.
|
||
- **Current mechanism:** browser считает обе границы TextEncoder-ом;
|
||
`CredentialsDto` использует `MinLength(12)` плюс только upper-byte validator.
|
||
Например, `абвгде` — шесть кириллических символов / двенадцать UTF-8 байтов:
|
||
проходит FE правило 12–72, не проходит BE минимум 12 characters. Это вывод
|
||
по коду и encoding, не результат выполненного HTTP-теста.
|
||
- **Runtime consumer:** `frontend/src/features/auth/ui/AuthForm/AuthForm.tsx:24–40`
|
||
выбирает обе schemas; provider отправляет password без изменения
|
||
(`frontend/src/features/auth/model/auth.vm.ts:14–24,121–132`). Register/login
|
||
принимают тот же DTO (`backend/src/modules/auth/presentation/auth.controller.ts:55–79`),
|
||
далее password поступает в bcrypt (`backend/src/modules/auth/application/auth.service.ts:35–78`).
|
||
- **Impact / actual cost:** UI принимает draft, API отказывает; AuthForm
|
||
показывает общий requestError вместо успешного auth. Impact unmeasured:
|
||
частота таких паролей и пользовательских отказов неизвестна.
|
||
- **Why now:** уже доказан drift единицы policy; изменение локально у auth
|
||
owner и не требует нового validator framework.
|
||
- **Minimum change:** обе byte-границы проверять существующим
|
||
`PasswordBytesConstraint`, убрать несовместимый character minimum.
|
||
Согласовать Swagger описание bytes: snapshot сейчас содержит character
|
||
`minLength: 12` (`frontend/src/shared/api/spec/openapi.json:5246–5250`), что
|
||
не описывает byte-policy. При реализации явно исправить metadata и передать
|
||
обновлённый artifact потребителю, не править generated TS вручную.
|
||
- **Guarantees at risk:** server-side validation, bcrypt 72-byte ceiling,
|
||
отсутствие trim/normalization password, совместимость login и error pointers.
|
||
- **Counterarguments / trade-offs:** character minimum может быть отдельным
|
||
security/product решением, но такого изменения не подтверждают текущая spec
|
||
и docs (`docs/domains/authentication/overview.md:14–21`). Independent
|
||
validation нужна; общего исполняемого пакета между repos не нужно.
|
||
- **Verification or rollback:** будущая минимальная проверка — frontend schema
|
||
и configured HTTP DTO boundary на 11/12/72/73 bytes, ASCII/кириллице/emoji,
|
||
password whitespace без изменения. Existing DTO checks находятся в
|
||
`backend/src/common/dto-validation.spec.ts:13–30`; HTTP flow должен сохранить
|
||
400/VALIDATION_ERROR и success auth. Не менять stored hashes; rollback —
|
||
возврат DTO/metadata/artifact совместно. В этом аудите проверки не запускались.
|
||
- **Effort:** M.
|
||
- **Related finding IDs:** DRY-005, DRY-008.
|
||
|
||
### DRY-002 — Одна instrument detail projection имеет две идентичные копии
|
||
|
||
- **Decision:** SIMPLIFY.
|
||
- **Severity:** P3.
|
||
- **Confidence:** High.
|
||
- **Evidence:** `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:499–531`;
|
||
`backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:646–678`.
|
||
- **Claim:** `publicInstrument` копирует один и тот же allowlist для одного
|
||
`TbankBond | TbankShare` input, включая одинаковый brand.logoName whitelist.
|
||
- **Current mechanism:** две чистые функции без side effects или различающейся
|
||
error/null политики. Обе сохраняют optional/undefined fields; отсутствие
|
||
instrument обрабатывают callers, а не функция.
|
||
- **Runtime consumer:** online detail/search —
|
||
`backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:143–165,295–325`;
|
||
company online и durable detail/bond-current load —
|
||
`backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:241–267,339–350`.
|
||
Ответы преобразуют
|
||
`backend/src/modules/investments/instruments/stocks/application/stock-catalog.service.ts:59–89`
|
||
и `backend/src/modules/investments/instruments/bonds/application/bond-catalog.service.ts:58–88`;
|
||
сохранение проходит через
|
||
`backend/src/modules/investments/instruments/infrastructure/catalog-discovery.repository.ts:154–223,252–276`.
|
||
- **Impact / actual cost:** изменение разрешённого field требует двух правок;
|
||
drift может сделать online и saved representation разными. Текущего drift
|
||
этих функций нет. Impact unmeasured; экономия maintenance/cognitive load.
|
||
- **Why now:** существует реальный reuse внутри одного module и нет
|
||
необходимости менять execution model ради него.
|
||
- **Minimum change:** оставить одну чистую функцию у instruments boundary,
|
||
например `instruments/infrastructure/public-instrument.mapper.ts`, и два
|
||
локальных imports. Не создавать Nest provider, generic mapper или общий
|
||
cross-repo package.
|
||
- **Ownership model:** этот module-owned mapper вызывают discovery и
|
||
enrichment application services; он зависит только от integration record
|
||
types. Application → infrastructure уже используется этими владельцами;
|
||
новых cross-module exports или forbidden imports не требуется.
|
||
- **Guarantees at risk:** explicit allowlist, отсутствие private quantities
|
||
и owner fields, nested brand whitelist, UID check, null/undefined semantics,
|
||
publication fences и оригинальное fetchedAt.
|
||
- **Counterarguments / trade-offs:** online и saved execution действительно
|
||
различаются. Общая projection не должна объединять их orchestration. Skinny
|
||
discovery payload
|
||
`backend/src/modules/investments/instruments/infrastructure/catalog-discovery.repository.ts:358–387`
|
||
имеет другие обязанности и
|
||
`update: {}`; это другой shape, не цель reuse полной detail projection.
|
||
Reachability/deletion этого discovery publication path относится к audit 04;
|
||
DRY-002 не требует сохранять его или discovery producer.
|
||
- **Verification or rollback:** future check одного share и bond с extra
|
||
private fields, unknown/null optional fields и лишним brand field через
|
||
обе paths; сохранить UID mismatch guard и CAS. Existing discovery assertions
|
||
`backend/test/tbank-catalog-discovery.e2e-spec.ts:119–177,197–229` защищают
|
||
близкую boundary через direct service calls, но не доказывают production
|
||
reachability discovery producer или полный online/enrichment parity.
|
||
Rollback — вернуть два локальных вызова/definitions без schema migration.
|
||
- **Effort:** S.
|
||
- **Related finding IDs:** DRY-006, DRY-009.
|
||
|
||
### DRY-003 — Select options и Zod enum дублируют словарь одной bank-card формы
|
||
|
||
- **Decision:** SIMPLIFY.
|
||
- **Severity:** P3.
|
||
- **Confidence:** High.
|
||
- **Evidence:** `frontend/src/entities/bank-card/model/form.schema.ts:65–69`;
|
||
`frontend/src/entities/bank-card/ui/BankCardForm/BankCardFields.vm.ts:19–25`;
|
||
`backend/src/modules/banking/bank-cards/presentation/bank-card.dto.ts:45,80–83,245–246`;
|
||
`frontend/src/shared/api/generated/schema.d.ts:767–795`.
|
||
- **Claim:** в frontend одну runtime vocabulary вручную поддерживают и schema,
|
||
и selectable options. Здесь одинаковы owner, причина изменения и semantics.
|
||
- **Current mechanism:** schema содержит `""` и три payment-system значения;
|
||
fields VM независимо содержит те же три значения. `""` — UI sentinel,
|
||
преобразуемый в `null`, а не четвёртая платёжная система.
|
||
- **Runtime consumer:** fields VM передаёт options форме; form VM использует
|
||
schema и отправляет `values.paymentSystem || null`
|
||
(`frontend/src/entities/bank-card/ui/BankCardForm/BankCardForm.vm.ts:30–39,50–65`).
|
||
- **Impact / actual cost:** при изменении словаря можно получить selectable,
|
||
но невалидное значение или валидное, но недоступное для выбора. Сегодня
|
||
списки совпадают. Impact unmeasured; малая maintenance cost.
|
||
- **Why now:** один локальный export может удалить вторую editable копию без
|
||
новых layers, dependencies или API change.
|
||
- **Minimum change:** существующая `form.schema.ts` владеет typed const tuple
|
||
реальных payment systems; Zod добавляет пустой sentinel, VM строит labels
|
||
из этого tuple. Проверять tuple через generated `BankCard`/request union
|
||
аналогично existing `model/operation-kinds.ts:1–8`.
|
||
- **Ownership model:** schema и fields VM остаются в `entities/bank-card`;
|
||
VM импортирует local schema export. Не нужен `shared/constants`, новый slice
|
||
или import backend checkout. `satisfies` проверяет допустимость values,
|
||
но не является доказательством exhaustive покрытия future enum.
|
||
- **Guarantees at risk:** clearable Select, `null` в API, `""` в form draft,
|
||
сохранение выбранного значения, перевод labels и independent server checks.
|
||
- **Counterarguments / trade-offs:** словарь маленький и меняется редко;
|
||
это optional cleanup при работе над формой. Generated TS union не является
|
||
runtime list; вводить второй generator ради этих трёх values невыгодно.
|
||
- **Verification or rollback:** existing visible selection/save case
|
||
`frontend/src/entities/bank-card/ui/BankCardForm/BankCardForm.test.tsx:86–105`,
|
||
clearing-to-null `:55–84`; при реализации проверить каждый option и empty
|
||
sentinel, затем local type/dependency checks. Rollback локальный, API прежний.
|
||
- **Effort:** S.
|
||
- **Related finding IDs:** DRY-005, DRY-008.
|
||
|
||
### DRY-004 — Access-loss taxonomy повторяется, recovery actions объединять не надо
|
||
|
||
- **Decision:** SIMPLIFY.
|
||
- **Severity:** P3.
|
||
- **Confidence:** High.
|
||
- **Evidence:** `frontend/src/shared/api/resource-query.ts:37–67`;
|
||
`frontend/src/shared/api/client.ts:162–179`;
|
||
`frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:59–64,86–96`.
|
||
- **Claim:** один набор `FORBIDDEN`, `INVALID_TBANK_TOKEN`, `NOT_FOUND`,
|
||
`TBANK_NOT_CONNECTED` независимо перечислен для resource loss, permanent
|
||
transport failure и блокирующего account state.
|
||
- **Current mechanism:** resource отменяет/очищает access group; transport
|
||
запрещает retry и отдельно включает `UNAUTHORIZED`; Account VM выбирает
|
||
error вместо snapshot. Совпадает membership классификации, не actions.
|
||
- **Runtime consumer:** `useResourceQuery` применяет classifier в effect
|
||
(`frontend/src/shared/api/resource-query.ts:99–102`); `fetchAppRead` вызывает
|
||
transient classifier (`frontend/src/shared/api/client.ts:127–150,172–179`);
|
||
`frontend/src/pages/account/ui/AccountPage/AccountPage.tsx:9–12` использует VM state.
|
||
- **Impact / actual cost:** новая access-loss причина потребует синхронно
|
||
изменить три места, иначе retry/cache/page state могут разойтись. Сейчас
|
||
набор согласован. Impact unmeasured; maintenance risk, не доказанный дефект.
|
||
- **Why now:** existing owner `shared/api/problem-details.ts` уже владеет
|
||
известными codes и unknown fallback; классификацию можно расширить там.
|
||
- **Minimum change:** один predicate именно для этих четырёх codes у этого
|
||
owner; transport добавляет свой `UNAUTHORIZED`, resource и Account VM
|
||
сохраняют прежние actions. Не создавать error registry или общий UI handler.
|
||
- **Ownership model:** `client.ts`/`resource-query.ts` используют local import;
|
||
Account VM — public `@/shared/api`. Никаких dependencies из shared в
|
||
features/entities/pages. Это API error taxonomy, не банковский расчёт.
|
||
- **Guarantees at risk:** unknown fallback, auth refresh отдельно от broker
|
||
access, cached data при temporary failures и clearing после confirmed loss.
|
||
- **Counterarguments / trade-offs:**
|
||
`frontend/src/pages/accounts/ui/AccountsPage/AccountsPage.vm.ts:16–19` намеренно
|
||
имеет subset из двух connection codes; расширять его до четырёх нельзя.
|
||
Error views имеют разные settings/back actions
|
||
(`frontend/src/pages/account/ui/AccountPage/components/AccountPageError.view.tsx:15–43`,
|
||
`frontend/src/pages/accounts/ui/AccountsPage/components/AccountsPageError.view.tsx:22–38`);
|
||
эти branches остаются у pages. Если название helper скрывает это различие,
|
||
оставить текущие короткие списки дешевле плохого generic handler.
|
||
- **Verification or rollback:** future MSW checks для membership four codes,
|
||
special `UNAUTHORIZED`, `TBANK_UNAVAILABLE`, unknown/missing code; разные
|
||
actions остаются. Existing boundary tests:
|
||
`frontend/src/shared/api/resource-query.test.tsx:174–209`,
|
||
`frontend/src/shared/api/client.test.ts:261–281`. Rollback — вернуть predicates,
|
||
не менять error wire values или response shapes.
|
||
- **Effort:** S.
|
||
- **Related finding IDs:** DRY-007, DRY-008.
|
||
|
||
### DRY-005 — Banking validation и точные money representations оставить на обеих границах
|
||
|
||
- **Decision:** KEEP.
|
||
- **Severity:** P2.
|
||
- **Confidence:** High.
|
||
- **Evidence:** `frontend/src/entities/bank-card/model/money.ts:4–23`;
|
||
`frontend/src/entities/bank-card/model/form.schema.ts:8–42`;
|
||
`frontend/src/pages/card/ui/OperationForm/OperationForm.schema.ts:53–131`;
|
||
`backend/src/modules/banking/bank-cards/domain/rub-money.ts:12–38`;
|
||
`backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:94–210`.
|
||
- **Claim:** повторение validation защищает разные trust boundaries; money
|
||
helpers также не являются одинаковым public interface.
|
||
- **Current mechanism:** UI draft понимает comma/trim/leading zeros, возвращает
|
||
null/field errors и проверяет cached данные. Server требует canonical RUB
|
||
decimal, storage range и positive operation amount; затем authoritative
|
||
records, ownership и refunds проверяются в write transaction.
|
||
- **Runtime consumer:** BankCardForm и OperationForm → typed mutation →
|
||
DTO/input → `createCard`/`createOperation`. Server write boundary:
|
||
`backend/src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts:35–59`;
|
||
replay fingerprint и ownership:
|
||
`backend/src/modules/banking/bank-cards/application/bank-cards.service.ts:74–95`,
|
||
`backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:214–239`.
|
||
- **Impact / actual cost:** дополнительное сопровождение policy values есть,
|
||
но удаление server validation позволяет обойти browser checks, а reuse
|
||
UI normalizer изменит server semantics. Impact unmeasured.
|
||
- **Why now:** DRY-001 показывает риск расхождения значений, но не оправдывает
|
||
универсальный shared validator или замену bigint на floating point.
|
||
- **Minimum change:** сохранить независимые implementations; согласовывать
|
||
конкретные policy values и примеры при изменении границы. Existing
|
||
`backend/src/common/decimal.ts:19–83` уже обслуживает server arithmetic;
|
||
`backend/src/integrations/tbank/decimal.ts:7–13` занимается protobuf units/nano
|
||
отдельно.
|
||
- **Guarantees at risk:** signed-64-bit bounds input, precision сумм сверх
|
||
storage range, positive/zero distinction, user ownership, CAS, transaction
|
||
rollback и replay protection.
|
||
- **Counterarguments / trade-offs:** параллельные constants требуют parity
|
||
review; frontend/backend не должны делить исполняемые business modules.
|
||
`rubMoney` форматирует вычисленную сумму и не обязан ограничивать её input
|
||
storage range, в отличие от `parseRubMoney`.
|
||
- **Verification or rollback:** future changes проверять existing boundary
|
||
cases `frontend/src/entities/bank-card/model/form-validation.test.ts:13–48`
|
||
и `backend/src/modules/banking/bank-cards/domain/rub-money.spec.ts:4–56`.
|
||
Финансовые changes дополнительно требуют реальной изолированной transaction
|
||
proof. В аудите ничего не удалено, rollback не требуется.
|
||
- **Effort:** S.
|
||
- **Related finding IDs:** DRY-001, DRY-003, DRY-008.
|
||
|
||
### DRY-006 — Stock/Bond сходство не оправдывает generic page/query/payout pipeline
|
||
|
||
- **Decision:** KEEP.
|
||
- **Severity:** P2.
|
||
- **Confidence:** High.
|
||
- **Evidence:** `frontend/src/pages/stock/ui/StockPage/StockPage.vm.ts:23–98`;
|
||
`frontend/src/pages/bond/ui/BondPage/BondPage.vm.ts:23–98`;
|
||
`backend/src/modules/investments/instruments/stocks/application/stock-income.service.ts:114–178`;
|
||
`backend/src/modules/investments/instruments/bonds/application/bond-income.service.ts:145–201`.
|
||
- **Claim:** одинаковая композиция закрывает разные business semantics;
|
||
действительно общие primitives уже переиспользуются.
|
||
- **Current mechanism:** dividends фильтруют cancelled и выбирают timing по
|
||
paymentDate с recordDate fallback; coupons несут number/type/periodDays и
|
||
timing по paymentDate. Quotes stock — Money, bond — percent of nominal,
|
||
clean/dirty/ACI. Страницы используют общий instrument-details layout,
|
||
section, chart, position и operation rows, но разные company/metrics/payouts.
|
||
- **Runtime consumer:** route containers
|
||
`frontend/src/pages/stock/ui/StockPage/StockPage.tsx:10–21` и
|
||
`frontend/src/pages/bond/ui/BondPage/BondPage.tsx:10–21`;
|
||
reused composition —
|
||
`frontend/src/pages/stock/ui/StockPage/StockPage.view.tsx:38–72`,
|
||
`frontend/src/pages/bond/ui/BondPage/BondPage.view.tsx:134–205`.
|
||
HTTP consumers —
|
||
`backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts:170–181`,
|
||
`backend/src/modules/investments/instruments/bonds/presentation/bond.controller.ts:126–134`.
|
||
- **Impact / actual cost:** текущая локальная duplication проще universal
|
||
hook с динамическим endpoint, type casts и flags для income kind/company/
|
||
metrics/price unit. Impact unmeasured; savings от такой extraction не доказаны.
|
||
- **Why now:** защищает от ложного DRY на основании похожего JSX и одинакового
|
||
UUID regex. У entity hooks разные wire contracts и accessGroup usage.
|
||
- **Minimum change:** сохранить endpoint-specific entity query declarations
|
||
и page composition. Existing `features/instrument-details` остаётся общим
|
||
владельцем focused presentation, без объединения stock/bond entities.
|
||
- **Guarantees at risk:** exact decimal currency/percent semantics, unknown
|
||
amount vs zero, registry/payment dates, cancelled dividends, enabled inputs,
|
||
stable generated query keys и отсутствие запрещённых sibling imports.
|
||
- **Counterarguments / trade-offs:** horizon constants и часть grouping похожи;
|
||
отдельное маленькое правило может заслужить reuse при реальном совместном
|
||
изменении. Это не основание сейчас переносить весь orchestration или делать
|
||
boolean-driven payout component.
|
||
- **Verification or rollback:** future common changes должны сохранять
|
||
`backend/src/modules/investments/instruments/stocks/application/stock.spec.ts:10–37,45–69`,
|
||
`backend/src/modules/investments/instruments/bonds/application/bond.spec.ts:27–113,139–176`
|
||
и видимые stock/bond
|
||
payout fields. Local import boundaries проверяет
|
||
`frontend/dependency-cruiser.cjs:42–64`. В аудите изменения нет.
|
||
- **Effort:** S.
|
||
- **Related finding IDs:** DRY-002, DRY-007, DRY-010.
|
||
|
||
### DRY-007 — Query wrappers и UI primitives несут project responsibility
|
||
|
||
- **Decision:** KEEP.
|
||
- **Severity:** P2.
|
||
- **Confidence:** High.
|
||
- **Evidence:** `frontend/src/shared/api/authenticated-query.ts:34–59`;
|
||
`frontend/src/shared/api/resource-query.ts:37–67,84–149`;
|
||
`frontend/packages/ui/src/LoadingSkeleton/LoadingSkeleton.tsx:5–17`;
|
||
`frontend/packages/ui/src/StateView/StateView.tsx:5–27`.
|
||
- **Claim:** эти wrappers не просто воспроизводят API библиотеки под новым
|
||
названием; они задают конкретные session/resource/ARIA/presentation defaults.
|
||
- **Current mechanism:** authenticated query совмещает session и caller
|
||
enabled; resource wrapper очищает access group, проецирует envelope data/meta,
|
||
сохраняет transport status и refetch envelope. LoadingSkeleton задаёт busy
|
||
status/label, StateView — единый alert/layout; EntryCard — общую композицию,
|
||
tone и действия (`frontend/packages/ui/src/EntryCard/EntryCard.tsx:24–81`).
|
||
- **Runtime consumer:** portfolio query
|
||
`frontend/src/entities/portfolio/api/usePortfolio.ts:19–25`, banking query
|
||
`frontend/src/entities/bank-card/api/queries.ts:28–44`; instrument sections
|
||
используют LoadingSkeleton
|
||
(`frontend/src/features/instrument-details/ui/InstrumentSection.view.tsx:43–54`),
|
||
Account/Accounts error views используют StateView.
|
||
- **Impact / actual cost:** удаление wrappers размножит session gates,
|
||
resource access handling и accessibility defaults по callers. Impact
|
||
unmeasured; независимая стоимость текущих abstractions не измерялась.
|
||
- **Why now:** ponytail ladder о library/native API не означает удаления
|
||
useful project defaults. Mantine controls уже используются напрямую там,
|
||
где нет дополнительной ответственности
|
||
(`frontend/src/features/auth/ui/AuthForm/AuthForm.tsx:49–83`).
|
||
- **Minimum change:** сохранить focused wrappers и primitives. Не добавлять
|
||
собственные Button/Select passthrough wrappers. DRY-004 может упростить
|
||
классификацию внутри existing owner, не заменить whole wrapper.
|
||
- **Guarantees at risk:** no fetch before session, combining enabled, unknown
|
||
resource при успешном HTTP, access-loss cache clearing, сохранение cached
|
||
snapshot и доступные loading/error states.
|
||
- **Counterarguments / trade-offs:** wrapper вручную выбирает часть Query
|
||
return API; при новом consumer нужно расширять осмысленно. Observation
|
||
compatibility может быть отдельным dead-code вопросом audit 04, но её
|
||
возможная ненужность не доказывает ненужность wrapper целиком.
|
||
- **Verification or rollback:** existing resource boundary assertions
|
||
`frontend/src/shared/api/resource-query.test.tsx:72–138,174–209`
|
||
различают transport success,
|
||
projection и clearing. Future UI changes сохраняют keyboard/ARIA basics;
|
||
tests приложения не заменяют browser proof. В аудите изменений нет.
|
||
- **Effort:** S.
|
||
- **Related finding IDs:** DRY-004, DRY-006, DRY-010.
|
||
|
||
### DRY-008 — Generated contracts и known-error subset не являются competing DTO truths
|
||
|
||
- **Decision:** KEEP.
|
||
- **Severity:** P3.
|
||
- **Confidence:** High.
|
||
- **Evidence:** `backend/src/openapi.ts:4–10`;
|
||
`backend/.ai/conventions/http-api.md:69–81`;
|
||
`frontend/package.json:22–23`;
|
||
`frontend/src/entities/bank-card/api/types.ts:1–34`;
|
||
`frontend/src/features/auth/model/auth-context.ts:14–15`;
|
||
`frontend/src/shared/api/generated/schema.d.ts:731–795`.
|
||
- **Claim:** reviewed transport types выводятся из provided snapshot; не
|
||
найдена независимая handwritten DTO-копия для рассмотренных bank/auth flows.
|
||
- **Current mechanism:** editable contract — BE Nest DTO/Swagger; provided
|
||
snapshot — вход FE offline generation; aliases используют `paths`.
|
||
Form values выводятся из schemas. `MoneyDto implements Money`
|
||
(`backend/src/common/money.dto.ts:3–10`) сохраняет runtime Swagger metadata
|
||
отдельно от pure `backend/src/common/money.types.ts:1`.
|
||
- **Runtime consumer:** `frontend/src/shared/api/client.ts:1–6` импортирует
|
||
generated paths; form/auth/bank query callers используют derived aliases.
|
||
Known UI enum `frontend/src/shared/api/problem-details.ts:1–28` обслуживает
|
||
recovery, в то время как
|
||
generated ProblemDetails.code остаётся открытой строкой.
|
||
- **Impact / actual cost:** artifact freshness требует сопровождения, но
|
||
удаление snapshots или общий исполняемый DTO package нарушит independent
|
||
repo inputs. Impact unmeasured; текущего file-to-file drift OpenAPI нет.
|
||
- **Why now:** наличие нескольких файлов с одной shape не означает несколько
|
||
редактируемых источников значения. DRY-001 и DRY-003 касаются runtime policies,
|
||
которые TypeScript declarations не реализуют автоматически.
|
||
- **Minimum change:** оставить существующий contract flow и local aliases;
|
||
согласовывать metadata при DRY-001. Сохранить runtime known subset ошибок с
|
||
fallback, не закрывать wire string ради auto-generated exhaustive enum.
|
||
- **Guarantees at risk:** compatible wire contract, offline FE generation,
|
||
независимость CI/checkout, unknown-code fallback, domain без Swagger.
|
||
- **Counterarguments / trade-offs:** равенство existing OpenAPI файлов не
|
||
подтверждает runtime freshness, а typed `satisfies` не проверяет все future
|
||
enum values. Это ограничение существующего flow, не доказательство нужности
|
||
ещё одного hash/validator registry. Fresh generation parity — UNVERIFIED.
|
||
- **Verification or rollback:** при будущих API changes — предусмотренные
|
||
backend contract checks и frontend offline `openapi:check`, без чтения
|
||
соседнего source как обязательного входа. В аудите выполнено только сравнение
|
||
existing artifacts; generation/build не запускались.
|
||
- **Effort:** S.
|
||
- **Related finding IDs:** DRY-001, DRY-003, DRY-004, DRY-005.
|
||
|
||
### DRY-009 — Живой enrichment/shared fence: не вводить generic queue из сходства с discovery
|
||
|
||
- **Decision:** KEEP.
|
||
- **Severity:** P2.
|
||
- **Confidence:** High.
|
||
- **Evidence:** `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:113–125,189–225,497–577`;
|
||
`backend/src/modules/investments/instruments/infrastructure/catalog-discovery.repository.ts:154–179,280–318`;
|
||
`backend/src/modules/investments/instruments/stocks/application/stock-market.service.ts:34–45`.
|
||
Reachability counterexample:
|
||
`backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:66–70,84–93,149–190,227–267`;
|
||
`backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:54–78,192–201`;
|
||
`backend/src/modules/investments/tbank-sync/tbank-sync.controller.ts:31–41`.
|
||
- **Claim:** KEEP касается используемых enrichment batching/observer/publication
|
||
guarantees и общей repository fence. Сходство с discovery не оправдывает
|
||
generic queue и не является основанием сохранять discovery producer.
|
||
- **Current mechanism:** enrichment `requestMany` при missing/stale sources
|
||
резервирует work и ставит drain; price batch ограничен одной user/revision,
|
||
поздние observers используют `publishSource` с initiating attempt. Repository
|
||
отдельно claim-ит initiator и observer. Discovery default HTTP branches online,
|
||
observe branches read-only; `read(true)` пропускает request.
|
||
- **Runtime consumer:** `backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts:122–129`
|
||
→ `backend/src/modules/investments/instruments/stocks/application/stock-market.service.ts:34–45`
|
||
→ enrichment `section('quote', false)`
|
||
(`backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:234–241,269–289`)
|
||
→ `readMany(false)` (`:113–125`)
|
||
→ при missing/stale `requestMany` (`:189–225`) → drain/publishSource (`:497–577`).
|
||
Bond quote имеет тот же живой entry point
|
||
(`backend/src/modules/investments/instruments/bonds/presentation/bond.controller.ts:92–99`;
|
||
`backend/src/modules/investments/instruments/bonds/application/bond-market.service.ts:38–49`).
|
||
Ни default catalog search/detail/byTicker, ни observe calls discovery producer
|
||
не запускают. Единственный найденный внешний production-text refresh caller
|
||
находится в manual dispatch без найденного production request consumer.
|
||
- **Impact / actual cost:** generic queue ради устранения сходства с
|
||
dead-parent-reachable кодом добавит callbacks/flags вместо устранения
|
||
неиспользуемого пути. Изменение живого enrichment может потерять authorization
|
||
и publication fences. Impact unmeasured; runtime savings не измерены.
|
||
- **Why now:** исправлен ложный runtime consumer прежней редакции. Наличие
|
||
definitions, DI registration и direct tests не доказывает producer reachability.
|
||
- **Minimum change:** не вводить generic queue; сохранить гарантии живого
|
||
enrichment и используемые им `reserve/publishSource/finish`. DRY-002 извлекает
|
||
stateless projection независимо. Discovery `request/refresh/drain` не входит
|
||
в KEEP; reachability/deletion и compatibility reads — предмет audit 04.
|
||
- **Guarantees at risk:** connection revision/attempt identity, isolation
|
||
initiating credentials, private observer handles, late-publication fences,
|
||
same-owner batching и conditional finish именно живого enrichment.
|
||
- **Counterarguments / trade-offs:** discovery producer в source действительно
|
||
похож и исполняется из direct tests, но current catalog HTTP paths его
|
||
обходят; это аргумент за отдельный dead-code разбор, не за сохранение двух
|
||
queues. KEEP не распространяется на весь CatalogDiscoveryService/repository,
|
||
unused methods или каждый completion helper enrichment. Равноценность общей
|
||
queue и снижение total cost — UNVERIFIED.
|
||
- **Verification or rollback:** при изменении живой boundary нужны controlled
|
||
quote/enrichment scenarios с late replace/delete, invalid initiating token,
|
||
batch/observer isolation и conditional publication. Direct discovery tests
|
||
(`backend/test/tbank-catalog-discovery.e2e-spec.ts:184–187,202–203`) не являются
|
||
proof текущего HTTP flow. Реализация в этом аудите не изменена; rollback
|
||
source не требуется. Audit 04 определяет deletion scope отдельно.
|
||
- **Effort:** S.
|
||
- **Related finding IDs:** DRY-002.
|
||
|
||
### DRY-010 — Scheduling ownership требует проверки, глобальная freshness policy преждевременна
|
||
|
||
- **Decision:** INVESTIGATE.
|
||
- **Severity:** P2.
|
||
- **Confidence:** Medium.
|
||
- **Evidence:** `frontend/src/features/tbank-connection/model/screen-sync.ts:20–35,53–61`;
|
||
`frontend/src/app/ui/App.tsx:13–20`;
|
||
`frontend/src/entities/portfolio/api/usePortfolio.ts:3–5,19–25`;
|
||
`frontend/src/entities/brokerage-account/api/useBrokerageAccounts.ts:3–18`;
|
||
`specs/openspec/specs/brokerage-accounts/spec.md:68,96–98`;
|
||
`specs/openspec/specs/portfolio-view/spec.md:122–124`.
|
||
- **Claim:** у одного HTTP-read flow есть page timer, query freshness и app
|
||
refetch triggers. Возможность уменьшить собственную coordination реальна,
|
||
но их полное объединение не является доказанным behavior-preserving cleanup.
|
||
- **Current mechanism:** screen hook каждые 1 800 000 ms вызывает refetch
|
||
текущего resource array; отдельные hooks задают 30 seconds для portfolio и
|
||
30 minutes для accounts; app включает focus/reconnect. Spec специально
|
||
различает freshness и periodic interval, а focus refetch запрещает.
|
||
- **Runtime consumer:** Account VM передаёт пять resources
|
||
(`frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:78–84`);
|
||
Stock/Bond — свои section queries; BondsPage
|
||
передаёт search result (`frontend/src/pages/bonds/ui/BondsPage/BondsPage.vm.ts:10–13`).
|
||
Все восемь production call sites перечислены в разделе 3: ни один не
|
||
сохраняет/передаёт return hook. `refresh/retry` используются только test
|
||
harness (`frontend/src/features/tbank-connection/model/screen-sync.test.tsx:37–50,68–78`),
|
||
не кнопкой текущего production UI.
|
||
- **Impact / actual cost:** maintenance scheduling policy распределено по
|
||
owners; runtime число лишних GET, interval phase, background-tab behavior и
|
||
эффект от native query-owned interval не измерены. Impact unmeasured.
|
||
- **Why now:** важно не заменять все staleTime на один constant ради DRY.
|
||
Неиспользуемый return/retry не является основанием сохранять timer;
|
||
его reachability/deletion относится к audit 04.
|
||
- **Minimum change:** сначала проверить семантику interval относительно
|
||
последнего успешного read, focus/reconnect, disabled inputs и активных
|
||
observers. Затем решить, достаточно ли существующих query options для
|
||
periodic reads. До подтверждения не создавать global query factory или
|
||
universal policy object.
|
||
- **Guarantees at risk:** caller enabled/session gates, canonical cache key,
|
||
cleanup при unmount, HTTP-only periodic read, сохранение snapshot и отсутствие
|
||
hidden personal-import commands.
|
||
- **Counterarguments / trade-offs:** существующий timer реально имеет общую
|
||
mount-based фазу: effect с `[]` не перезапускается при смене resource array,
|
||
а callback читает актуальный ref
|
||
(`frontend/src/features/tbank-connection/model/screen-sync.ts:22–35`). Query-owned timers
|
||
могут изменить фазу, поведение при смене inputs или background/observer
|
||
semantics. Общая кнопка retry из return не является counterargument —
|
||
production consumer не найден. Разные 30 sec/30 min в specs не противоречат
|
||
друг другу. UNVERIFIED: equivalence замены, необходимость общего clock
|
||
как требования и фактические savings.
|
||
- **Verification or rollback:** дешевейшее подтверждение — controlled-time
|
||
MSW сценарий существующего query/page owner, assert на видимые данные и GET
|
||
при mount/focus/interval/unmount/disabled search, затем сверка native options
|
||
со штатной документацией используемой версии. Никакой реальной сети и
|
||
нагрузочного benchmark для первого решения не нужно. Future rollback —
|
||
вернуть только scheduling options/timer, сохранив generated keys.
|
||
- **Effort:** M.
|
||
- **Related finding IDs:** DRY-006, DRY-007.
|
||
|
||
## 5. DELETE / SIMPLIFY / KEEP / INVESTIGATE
|
||
|
||
| Decision | IDs / область | Итог |
|
||
| --- | --- | --- |
|
||
| DELETE | — | Подтверждённых удалений в scope 03 нет; недоступные flows относятся к audit 04 |
|
||
| SIMPLIFY | DRY-002 | Один module-local instrument projection mapper; whitelist сохраняется |
|
||
| SIMPLIFY | DRY-003 | Один typed runtime tuple у текущей bank-card schema |
|
||
| SIMPLIFY | DRY-004 | Один code-membership predicate у existing API error owner; actions остаются разными |
|
||
| FIX | DRY-001 | Согласовать единицу password minimum и API metadata, сохранить две trust-boundary checks |
|
||
| KEEP | DRY-005 | Money precision, server validation, transaction/CAS/refund/replay protections |
|
||
| KEEP | DRY-006 | Stock/Bond contracts и business/render semantics; existing focused reuse достаточен |
|
||
| KEEP | DRY-007 | Authenticated/resource wrappers и доступные UI primitives |
|
||
| KEEP | DRY-008 | Provider-owned OpenAPI, consumer artifacts, derived aliases, open-code fallback |
|
||
| KEEP | DRY-009 | Только гарантии живого enrichment и используемая repository fence; discovery producer — audit 04, не KEEP |
|
||
| INVESTIGATE | DRY-010 | Scheduling simplification только после проверки эквивалентности |
|
||
|
||
### Minimal ownership model для reuse
|
||
|
||
| Правило | Единственная implementation / value | Callers | Запрещённое направление / лишняя abstraction |
|
||
| --- | --- | --- | --- |
|
||
| Detail projection / DRY-002 | Локальный `instruments/infrastructure/public-instrument.mapper.ts` | Discovery/enrichment application внутри instruments | Не в global common/utils, не integration → business DTO, не DI provider |
|
||
| FE payment list / DRY-003 | Typed tuple в existing `entities/bank-card/model/form.schema.ts` | Zod enum и bank-card fields VM | Не frontend → backend checkout, не shared → entity, не новый generator |
|
||
| Access-loss membership / DRY-004 | Predicate в existing `shared/api/problem-details.ts` | Local client/resource; Account VM через `shared/api/index.ts` | Не shared → features/pages, не generic recovery component |
|
||
| Password policy / DRY-001 | BE CredentialsDto validator — enforcement; FE auth schema — UX implementation той же spec | Register/login boundaries | Не удалять server check, не shared cross-repo executable validator |
|
||
| Shared instrument presentation / KEEP | Existing `features/instrument-details` | Stock/Bond pages | Не entity sibling import, не generic boolean-driven page |
|
||
|
||
Сравнение вариантов для всех `SIMPLIFY`:
|
||
|
||
- **Оставить копии:** допустимо для P3, если область не меняется; сохраняется
|
||
несколько мест правки. Для DRY-002 benefit выше: одна visibility projection.
|
||
- **Перенести к owner:** предпочтительно для DRY-002–004; число business
|
||
owners не меняется, imports локальны или уже разрешены.
|
||
- **Генерировать:** полезно для wire types и уже выполняется; byte validators,
|
||
recovery actions и runtime UI lists нельзя получить из erased TS declarations
|
||
автоматически. Дополнительный generator здесь дороже tuple/predicate.
|
||
- **Сделать shared:** допустим только existing focused API code owner DRY-004.
|
||
Межрепозиторный shared package и огромный `shared/utils` создадут release,
|
||
build, trust-boundary и layer coupling без подтверждённой потребности.
|
||
|
||
## 6. Cost & benefit
|
||
|
||
| ID | Класс ожидаемой пользы | Фактическое измерение | Обязательная regression boundary |
|
||
| --- | --- | --- | --- |
|
||
| DRY-001 | Reliability/UX и maintenance согласованной policy | not measured; несовместимость доказана по source | UTF-8 границы, HTTP validation, password без преобразования, published metadata |
|
||
| DRY-002 | Cognitive load, maintenance; один visibility owner | not measured; две definitions подтверждены | Whitelist/nested brand, optional fields, online/saved parity, UID/CAS |
|
||
| DRY-003 | Небольшая maintenance/cognitive экономия | not measured; две local lists подтверждены | Все options, empty sentinel и null request |
|
||
| DRY-004 | Maintenance при изменении code taxonomy | not measured; три полных набора подтверждены | Unknown/temporary errors, auth special case, cache clearing и разные actions |
|
||
| DRY-005–009 | Избежать лишнего coupling и удаления используемой защиты | not measured | Money precision, server authority, live enrichment publication/session boundaries, accessibility; discovery producer исключён |
|
||
| DRY-010 | Возможная экономия собственных scheduling constructs | UNVERIFIED / not measured | Mount timer phase/latest-resource ref, enabled, active observers, cached snapshot, HTTP-only reads; unused return/retry не защищается |
|
||
|
||
Никаких claims об ускорении, CI minutes, bundle size или agent-token savings
|
||
не сделано. Для выбранных cleanup нет подтверждённого кандидата на удаление
|
||
dependency. Net LOC не вычислялся: нет implementation diff, а helper imports,
|
||
public exports и необходимые regression checks также стоят кода.
|
||
|
||
### Complexity-only pass в формате ponytail-audit
|
||
|
||
- `shrink:` убрать вторую `publicInstrument`; одна instruments boundary projection. [DRY-002; backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:499–531; backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:646–678]
|
||
- `shrink:` убрать повторные полные access-loss списки; один predicate у existing API code owner, actions отдельно. [DRY-004; frontend/src/shared/api/client.ts:162–169; frontend/src/shared/api/resource-query.ts:42–51; frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:59–64]
|
||
- `shrink:` убрать вторую runtime payment-system list формы; typed tuple existing schema. [DRY-003; frontend/src/entities/bank-card/model/form.schema.ts:65–69; frontend/src/entities/bank-card/ui/BankCardForm/BankCardFields.vm.ts:19–22]
|
||
|
||
`net: -N lines, -0 deps possible.` Здесь `N = not measured`: сокращение строк
|
||
без подготовленного и проверенного diff не оценено; 0 относится к предложенным
|
||
удалениям deps, не к исчерпывающему dependency audit. DRY-001 и защищаемые
|
||
инварианты не включены в complexity-only cuts.
|
||
|
||
## 7. Top actions
|
||
|
||
Все четыре шага ниже подтверждены source analysis; ранжирование учитывает
|
||
value/risk/effort, а не число одинаковых строк.
|
||
|
||
### Требует behavior/contract подтверждения
|
||
|
||
1. **DRY-001 / M:** восстановить согласованный byte minimum у auth validator,
|
||
согласовать metadata и artifact; проверить один набор UTF-8 границ на
|
||
frontend и configured backend boundary. Новый ADR нужен только при решении
|
||
действительно изменить принятую password policy, а не при её восстановлении.
|
||
|
||
### Quick wins при работе над соответствующим owner
|
||
|
||
2. **DRY-002 / S:** один local projection mapper с сохранением allowlist,
|
||
null/undefined и используемых enrichment publication fences. Это не требует
|
||
сохранения discovery producer; его deletion scope — audit 04. Максимальный
|
||
подтверждённый reuse benefit среди structural кандидатов этого аудита.
|
||
3. **DRY-003 / S:** использовать один typed runtime tuple для schema и options;
|
||
сохранить пустое form value и API null. Никакой tooling migration.
|
||
4. **DRY-004 / S:** один predicate для четырёх codes; сохранить отдельный auth
|
||
`UNAUTHORIZED`, Accounts subset и page recovery actions. Выполнять только
|
||
с boundary proof сохранения temporary-error snapshot.
|
||
|
||
DRY-010 остаётся verification question о timer phase, не подтверждённым шагом
|
||
migration и не защитой неиспользуемого return/retry. DRY-009 не требует
|
||
сохранения discovery `request/refresh/drain`; их reachability/deletion — audit 04.
|
||
KEEP findings не превращены в backlog на дополнительные layers/tests/rules.
|
||
|
||
## 8. Cross-audit signals
|
||
|
||
- **04 / Dead Code & Legacy:** observation compatibility в `frontend/src/shared/api/resource-query.ts:69–127`; проверить actual production consumers перед удалением, не удалять весь resource wrapper.
|
||
- **04 / Dead Code & Legacy:** `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:66–70,84–86,149–190,227–267,340–495` и `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:54–78,192–201`; default paths online, observe read-only, внешний refresh caller только в manual dispatch без найденного production request entry point; DRY-009 не защищает этот producer от удаления.
|
||
- **04 / Dead Code & Legacy:** return/refresh/retry `frontend/src/features/tbank-connection/model/screen-sync.ts:37–61` потребляется только tests `screen-sync.test.tsx:37–50,68–78`; все восемь production calls отбросили return; deletion scope отдельно от живого timer `:22–35`.
|
||
- **13 / Reliability & Security:** online catalog calls `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:149–165,295–317` и existing token translator `backend/src/integrations/tbank/tbank-token.service.ts:26–35`; проверить публичное invalid-token recovery, здесь отдельный error/security аудит не проводился.
|
||
- **05 / Frontend Architecture:** ownership периодического чтения и app focus/reconnect `frontend/src/features/tbank-connection/model/screen-sync.ts:20–35`, `frontend/src/app/ui/App.tsx:13–20`; связан с DRY-010, не повод унифицировать разные staleTime.
|
||
- **08 / Test Quality:** proof для DRY-001 должен различать byte/character limits; рассмотренный `backend/src/common/dto-validation.spec.ts:13–30` использует ASCII happy path, полноценный аудит auth tests не выполнялся.
|
||
- **15 / SDD, Specs & Documentation:** `docs/adr/019-hourly-reads-and-online-instrument-reference.md:27–29`, `specs/openspec/specs/portfolio-view/spec.md:122–124`, `specs/AI_INDEX.md:15–31`; различать общую формулировку read interval и endpoint-specific freshness, не объявлять docs копии автоматически лишними.
|
||
|
||
## 9. Open questions / verification plan
|
||
|
||
| Вопрос, реально меняющий решение | Дешевейшая проверка | Что изменит ответ |
|
||
| --- | --- | --- |
|
||
| Есть ли принятое изменение password minimum с bytes на characters? | Проверить accepted auth decision/requirement у owner; existing spec и docs уже говорят о byte-policy и непринятом drift | Изменяет целевую policy DRY-001; без нового решения восстановить bytes |
|
||
| Четыре codes по-прежнему имеют одинаковое membership во всех трёх contexts? | Existing MSW boundaries плюс unknown/temporary/auth code cases, без network | Если meanings расходятся, DRY-004 становится KEEP коротких local predicates |
|
||
| Periodic reads должны синхронно повторять весь экран или каждый active resource после собственного успешного read? | Controlled-time MSW scenario mount/focus/reconnect/interval/unmount; сопоставить Accounts/Portfolio specs | Определяет, нужна ли screen clock и эквивалентен ли query-owned interval в DRY-010 |
|
||
| Как native interval действует при disabled query и background tab в установленной версии? | Сверка документации версии и targeted existing owner test с disabled search, без install | Определяет безопасность и размер scheduling cleanup; пока UNVERIFIED |
|
||
|
||
### Финальная проверка полноты и ограничений
|
||
|
||
- Присутствуют все девять общих разделов, Knowledge Duplication Register,
|
||
Intentional Duplication to Keep, minimal ownership model и сравнение вариантов
|
||
local copies / owner / generated / shared.
|
||
- Для каждого из десяти DRY-ID заполнены Decision, Severity, Confidence,
|
||
Evidence, Claim, Current mechanism, Runtime consumer, Impact / actual cost,
|
||
Why now, Minimum change, Guarantees at risk, Counterarguments / trade-offs,
|
||
Verification or rollback, Effort и Related finding IDs. Для каждого
|
||
SIMPLIFY дополнительно указан owner и допустимое направление imports.
|
||
- Четыре actionable выводы отделены от пяти KEEP и одного INVESTIGATE.
|
||
Неизмеренные savings и непроверенная эквивалентность явно обозначены.
|
||
- Повторный consumer trace отделяет живой enrichment/shared fence DRY-009
|
||
от discovery producer без найденного production entry point; восемь
|
||
standalone `useScreenSync` calls не используют return. Связанные register,
|
||
intentional KEEP, summary/top/minimum change и final table согласованы.
|
||
- Boy Scout review отчёта: не добавлены competing policy values, generic
|
||
shared owner, duplicate findings для одного механизма или обязательная
|
||
tooling-зависимость от соседнего checkout. Исходники не правились.
|
||
- После записи проверены собственный новый текст, whitespace и повторные
|
||
HEAD/status всех пяти repos. Tracked/staged/unstaged source изменений нет.
|
||
Отчёт ignored root allowlist-ом и существует только в разрешённом месте.
|
||
- Статический audit не является runtime bug reproduction, успешным запуском
|
||
tests, подтверждением DB atomicity, свежей генерацией OpenAPI или измерением
|
||
производительности. Эти ограничения сохраняются после clean Git status.
|
||
|
||
## Machine-readable consolidation table
|
||
|
||
| ID | Decision | Severity | Confidence | Repo | Files | One-line impact | Effort | Related IDs |
|
||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||
| DRY-001 | FIX | P2 | High | backend,frontend | backend/src/modules/auth/presentation/auth.dto.ts:43–74; frontend/src/features/auth/model/auth.schema.ts:36–43; frontend/src/shared/api/spec/openapi.json:5246–5250 | Разные единицы password minimum дают UI/API отказ | M | DRY-005,DRY-008 |
|
||
| DRY-002 | SIMPLIFY | P3 | High | backend | backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:499–531; backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:646–678 | Две одинаковые detail projections требуют двух правок whitelist | S | DRY-006,DRY-009 |
|
||
| DRY-003 | SIMPLIFY | P3 | High | frontend | frontend/src/entities/bank-card/model/form.schema.ts:65–69; frontend/src/entities/bank-card/ui/BankCardForm/BankCardFields.vm.ts:19–22 | Schema и selectable options вручную повторяют один словарь | S | DRY-005,DRY-008 |
|
||
| DRY-004 | SIMPLIFY | P3 | High | frontend | frontend/src/shared/api/client.ts:162–179; frontend/src/shared/api/resource-query.ts:37–67; frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:59–64 | Три набора access-loss codes могут расходиться; actions оставить локальными | S | DRY-007,DRY-008 |
|
||
| DRY-005 | KEEP | P2 | High | backend,frontend | frontend/src/entities/bank-card/model/money.ts:4–23; frontend/src/pages/card/ui/OperationForm/OperationForm.schema.ts:53–131; backend/src/modules/banking/bank-cards/domain/rub-money.ts:12–38; backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:94–210 | Independent validation сохраняет precision и server trust boundary | S | DRY-001,DRY-003,DRY-008 |
|
||
| DRY-006 | KEEP | P2 | High | backend,frontend | frontend/src/pages/stock/ui/StockPage/StockPage.vm.ts:23–98; frontend/src/pages/bond/ui/BondPage/BondPage.vm.ts:23–98; backend/src/modules/investments/instruments/stocks/application/stock-income.service.ts:114–178; backend/src/modules/investments/instruments/bonds/application/bond-income.service.ts:145–201 | Generic pipeline скроет разные dividend/coupon и quote semantics | S | DRY-002,DRY-007,DRY-010 |
|
||
| DRY-007 | KEEP | P2 | High | frontend | frontend/src/shared/api/authenticated-query.ts:34–59; frontend/src/shared/api/resource-query.ts:37–149; frontend/packages/ui/src/LoadingSkeleton/LoadingSkeleton.tsx:5–17; frontend/packages/ui/src/StateView/StateView.tsx:5–27 | Wrappers несут session/resource/accessibility defaults | S | DRY-004,DRY-006,DRY-010 |
|
||
| DRY-008 | KEEP | P3 | High | backend,frontend | backend/src/openapi.ts:4–10; frontend/src/entities/bank-card/api/types.ts:1–34; frontend/src/shared/api/generated/schema.d.ts:731–795; frontend/src/shared/api/problem-details.ts:1–28 | Generated copies сохраняют независимые repo inputs и open-code fallback | S | DRY-001,DRY-003,DRY-004,DRY-005 |
|
||
| DRY-009 | KEEP | P2 | High | backend | backend/src/modules/investments/instruments/stocks/application/stock-market.service.ts:34–45; backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:113–125,189–225,497–577; backend/src/modules/investments/instruments/infrastructure/catalog-discovery.repository.ts:154–179 | Сохранить live enrichment guarantees/shared fence; discovery producer не входит в KEEP, deletion — audit 04 | S | DRY-002 |
|
||
| DRY-010 | INVESTIGATE | P2 | Medium | frontend | frontend/src/features/tbank-connection/model/screen-sync.ts:22–35,53–61; frontend/src/app/ui/App.tsx:13–20; frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:78–84; frontend/src/entities/portfolio/api/usePortfolio.ts:3–5 | Проверить equivalence timer phase; восемь production callers не используют return/retry, runtime savings UNVERIFIED | M | DRY-006,DRY-007 |
|