Переносит архив `audits/2026-10-11/` (19 файлов) из codex/audit-remediation-2026-10-11 в develop и добавляет точечный allowlist в .gitignore.
88 KiB
Аудит 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.
Выполненные проверки и границы исполнения
- В каждом repo самостоятельно выполнены
git rev-parse HEADиgit status --short: в начале, после исследования и перед завершением. - Использованы Read/Glob/Grep по конкретным repo paths для поиска символов, callers, требований и существующих тестовых сценариев.
- Выполнен read-only
git diff --no-index --stat backend/openapi.json frontend/src/shared/api/spec/openapi.jsonиз root: различий нет. Это сравнение existing файлов, а не доказательство свежести генерации относительно runtime. - Проверены
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 сохранён. - Выполнена содержательная сверка отчёта с девятью общими разделами,
специальными результатами, всеми 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
- Факт — DRY-001: одна password policy имеет разные единицы в frontend и backend. UI допускает не-ASCII пароль, который отклоняет API. Это значимое локальное рассогласование, а не основание удалить серверную валидацию или создать межрепозиторную библиотеку validators.
- Факт — DRY-002: одинаковая whitelist-проекция instrument поддерживается в двух сервисах одного backend module. У правила один владелец, одинаковые типы, ошибки и причины изменения. Локальный mapper даст самый убедительный reuse при сохранении самого whitelist.
- Риски сопровождения — DRY-003/004: frontend повторяет список платёжных систем внутри одной формы и один набор access-loss codes в трёх контекстах. Сегодня копии согласованы; текущих пользовательских дефектов не доказано. Это небольшие P3 cleanup-кандидаты, не обязательная большая миграция.
- Факт — 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 их обходят, найденный callerrefresh— только недоступный manual dispatch. - Непроверенная гипотеза — 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 сейчас содержит characterminLength: 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 | TbankShareinput, включая одинаковый 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 через generatedBankCard/request union аналогично existingmodel/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 queryfrontend/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 отдельно от purebackend/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 enumfrontend/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→ enrichmentsection('quote', false)(backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:234–241,269–289) →readMany(false)(:113–125) → при missing/stalerequestMany(: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 независимо. Discoveryrequest/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 подтверждения
- DRY-001 / M: восстановить согласованный byte minimum у auth validator, согласовать metadata и artifact; проверить один набор UTF-8 границ на frontend и configured backend boundary. Новый ADR нужен только при решении действительно изменить принятую password policy, а не при её восстановлении.
Quick wins при работе над соответствующим owner
- DRY-002 / S: один local projection mapper с сохранением allowlist, null/undefined и используемых enrichment publication fences. Это не требует сохранения discovery producer; его deletion scope — audit 04. Максимальный подтверждённый reuse benefit среди structural кандидатов этого аудита.
- DRY-003 / S: использовать один typed runtime tuple для schema и options; сохранить пустое form value и API null. Никакой tooling migration.
- 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потребляется только testsscreen-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 translatorbackend/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
useScreenSynccalls не используют 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 |