# Conventions v1: semantic audit Дата: 2026-09-15. Read-only audit production code; conventions изменены отдельно. Snapshot: root `2567a755569e6ef6ba12063b62f202fe52884ce7`, frontend `0244acf429c525b23117f165bbe52ef5403eefd3`, backend `660b250ecd05e38784690b9a9adb381764c0ca7c`. На старте worktrees чистые. ## Scope и метод Прочитаны existing AGENTS/.ai, manifests, TypeScript/Biome/ESLint, dependency rules, test/Storybook setup и OpenAPI flow. Проверены representative paths: auth/refresh, bank cards/operations/summary, portfolio/income page VM, account errors, instrument search и T-Bank/MOEX/NCR boundaries. `rg` использован для callers, HTTP/Prisma/domain imports, raw fetch/SQL, logging, error strings и state patterns. Точечная executable reproduction проверяет ValidationPipe → filter. Это reviewed inventory подтверждённых findings, не полный автоматический счётчик всех файлов. P0 — явное архитектурное нарушение / высокий риск; P1 — неверная ответственность / существенная inconsistency; P2 — cleanup / naming / менее важная consistency issue. Confidence: High / Medium / Low. P0 по проверенной области не выявлены; это не security certification. | Severity | Findings | | --- | --- | | P0 | 0 | | P1 | 4 | | P2 | 3 | ## BE-001 — Validation теряет ошибки полей - **Location:** [configureApp](../../../backend/src/main.ts), строка 28; [HttpExceptionFilter](../../../backend/src/common/filters/http-exception.filter.ts), `catch` / `validationErrors`. - **Convention:** [Errors](../../../backend/.ai/conventions/error-handling.md), [требование](../../../specs/openspec/specs/user-authentication/spec.md#requirement-стандартные-ответы-об-ошибках). - **Current state:** стандартный ValidationPipe формирует `message: string[]`; filter трактует его как `ValidationError[]`, возвращая пустой `errors`. - **Expected state:** invalid request → 400 / VALIDATION_ERROR с применимыми `{ pointer, detail }`; вложенные поля обрабатываются согласно контракту. - **Severity:** P1. **Confidence:** High. - **Reason:** публичный контракт теряет field details; типовой filter unit test и OpenAPI schema parity не проходят реальную validation pipeline. - **Suggested migration:** отдельный behavior fix в Auth/API validation slice: согласовать output pipe с filter; доказать через configured HTTP request. Не добавлять новый validation framework. ## BE-002 — Repository решает business eligibility и HTTP outcome - **Location:** [BankCardsRepository](../../../backend/src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts), `card(active)` и `write`; callers в [BankOperationsService](../../../backend/src/modules/banking/bank-cards/application/bank-operations.service.ts). - **Convention:** [Data](../../../backend/.ai/conventions/data.md), [application boundaries](../../../backend/.ai/architecture.md#ответственность-и-направление-зависимостей). - **Current state:** repository импортирует application `conflict/missing`, решает запрет archived card и переводит общий P2002 в DuplicateCategory. - **Expected state:** repository владеет owner-filtered reads, transaction/retry и техническим DB outcome; application/domain определяет eligibility/public error. - **Severity:** P1. **Confidence:** High. - **Reason:** storage helper знает business policy нескольких callers; P2002 не всегда означает duplicate category. Утечка доступа не утверждается. - **Suggested migration:** Cards/Operations slice: перенести policy и mapping к ответственным сценариям, сохранив lookup/check/write внутри той же transaction, retry budget, idempotency, CAS и действующие public codes. ## FE-001 — Самостоятельные банковские действия находятся в entity UI - **Location:** [BankCardForm](../../../frontend/src/entities/bank-card/ui/BankCardForm.tsx), [CategoryManager](../../../frontend/src/entities/bank-card/ui/CategoryManager.tsx); consumers — CardsPage и CardPage. - **Convention:** [FSD ownership](../../../frontend/.ai/architecture.md#выбор-владельца). - **Current state:** entity UI координирует создание/редактирование карты, idempotency intent, submit, version adoption, создание/редактирование категорий. - **Expected state:** самостоятельные пользовательские workflows имеют feature owner; entity сохраняет модели, queries и presentation понятия карты/категории. - **Severity:** P1. **Confidence:** High. - **Reason:** это полноценные reused capabilities с собственным lifecycle, а не вывод о слое из наличия одной кнопки или mutation. - **Suggested migration:** по одному действию в Cards slice; presentation fields сохранять/reuse, не перемещать всю entity и все её hooks механически. ## FE-002 — Views банковских страниц владеют query state selection - **Location:** [CardPage.view](../../../frontend/src/pages/card/ui/CardPage.view.tsx), [CardsPage.view](../../../frontend/src/pages/cards/ui/CardsPage.view.tsx), соответствующие `CardPage.vm.ts` / `CardsPage.vm.ts` рядом. - **Convention:** [Container/VM/view](../../../frontend/.ai/conventions/ui.md#container-vm-и-view). - **Current state:** VM отдаёт query objects; views выбирают initial/cached/error states через `data/isLoading/isError/isPlaceholderData` и вызывают `refetch`. - **Expected state:** VM выбирает presentation states и retry callbacks, views отображают данные/действия. DOM scroll/focus остаются UI responsibility. - **Severity:** P1. **Confidence:** High. - **Reason:** presentation зависит от query lifecycle и принимает решения, которые existing account-income pattern уже размещает в VM. - **Suggested migration:** Cards slice, сначала один экран: перенести state selection без изменения loading/cached/recovery, URL pagination и scroll anchor. Не строить generic state machine для всех страниц. ## FE-003 — Application navigation находится в shared UI - **Location:** [AppShell](../../../frontend/src/shared/ui/AppShell.tsx), `items`, `useLocation`, `Outlet`, nav rendering; public export в shared/ui. - **Convention:** [FSD и устройство](../../../frontend/.ai/architecture.md). - **Current state:** shared owner содержит конкретные `/accounts`, `/cards`, `/stocks`, `/bonds`, `/settings` и application navigation. - **Expected state:** navigation/composition принадлежит app; shared сохраняет только действительно общие presentation primitives при текущей потребности. - **Severity:** P2. **Confidence:** High. - **Reason:** известное legacy placement уже отмечалось старым convention; dependency graph не доказывает отсутствие business meaning в shared. - **Suggested migration:** отдельный navigation slice вместе с app callers и CSS, без новой оболочки над Mantine и без переразметки пользовательского UI. ## FE-004 — Connection recovery классифицируется повторно - **Location:** [AccountPageError](../../../frontend/src/pages/account/ui/components/AccountPageError.view.tsx), [AccountsPageError](../../../frontend/src/pages/accounts/ui/components/AccountsPageError.view.tsx), [StocksPage.view](../../../frontend/src/pages/stocks/ui/StocksPage.view.tsx), [BondsPage.view](../../../frontend/src/pages/bonds/ui/BondsPage.view.tsx). - **Convention:** [Frontend errors](../../../frontend/.ai/conventions/data.md#errors), [pattern convergence](../../conventions/code-health.md#search--reuse--converge). - **Current state:** несколько owners повторяют `TBANK_NOT_CONNECTED` / `INVALID_TBANK_TOKEN` comparisons; presentation/retry actions различаются. - **Expected state:** общая классификация принадлежит одному допустимому owner, локальный текст/actions остаются у сценария; unknown code сохраняет fallback. - **Severity:** P2. **Confidence:** High. - **Reason:** повторяется техническое decision rule. Различный UI сам по себе не нарушение; generated `code: string` не разрешает выдумывать закрытый enum. - **Suggested migration:** Accounts/Portfolio, затем Instruments/Settings: унифицировать только classification с сохранением всех текущих recovery paths. ## BE-003 — Invalid-token protocol основан на Error.message - **Location:** [TbankClient](../../../backend/src/integrations/tbank/tbank.client.ts), `call` (throw Error); [TbankTokenService](../../../backend/src/integrations/tbank/tbank-token.service.ts), `translateInvalidToken`; [InstrumentMetadataService](../../../backend/src/integrations/tbank/instrument-metadata.service.ts), invalid-token catch. - **Convention:** [Errors](../../../backend/.ai/conventions/error-handling.md), [Integrations](../../../backend/.ai/conventions/integrations.md). - **Current state:** low-level client бросает `Error('INVALID_TBANK_TOKEN')`, два consumers сравнивают строку сообщения для выбора recovery/HTTP mapping. - **Expected state:** один явный local typed/code outcome на integration boundary, public HTTP translation остаётся у существующего owner. - **Severity:** P2. **Confidence:** High. - **Reason:** изменение диагностического текста ломает machine behavior; raw provider exception сейчас уже скрыт, подтверждённой утечки нет. - **Suggested migration:** Instruments/Settings integration slice; заменить marker и всех consumers одной связной правкой, сохранив auth/unavailable distinction и wire codes. Общий enterprise error framework не нужен. ## Что не признано нарушением - Page-local components account/income/events и их flat views допустимы. Размер файла, `map` или handler сами по себе не определяют feature/entity. - Прямые Prisma queries связных auth/banking/connection services допускаются existing data convention; standalone use cases уже используют repository. - URL-applied values и editable draft dates — разные состояния; синхронизация draft после navigation не равна лишнему server-state cache. - Повторы `filter(Boolean).join(" · ")` формируют display metadata, а не CSS classes; это не основание устанавливать `clsx`. - `ProblemDetailsDto.code: string` и typed wire literals — ограничение принятого генератора, не основание создавать ручную копию backend enum. - Stories показывают synthetic presentation; отсутствие story/test у каждого component не считается finding. По выполненным поискам не найдены raw fetch вне shared/api, production console или raw SQL; это не доказательство полноты. ## Evidence BE-001 Из backend после `nvm use --silent`: одноразовый `node -r ts-node/register` загрузил actual CredentialsDto, ValidationPipe и HttpExceptionFilter. Input `{ email: 'bad', password: 'x' }` → exception `message: string[]` → `{ status: 400, code: 'VALIDATION_ERROR', errors: [] }` (assert PASS). DB/порты/внешняя сеть не использовались. Это proof пары pipe/filter; исправление всё равно требует configured HTTP regression test. Immutable baseline этого анализа; статусы закрытия и evidence будущих работ вести в [plan](plan.md), не переписывая snapshot под новый код.