13 KiB
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, строка 28;
HttpExceptionFilter,
catch/validationErrors. - Convention: Errors, требование.
- 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,
card(active)иwrite; callers в BankOperationsService. - Convention: Data, application boundaries.
- 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, CategoryManager; consumers — CardsPage и CardPage.
- Convention: FSD ownership.
- 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,
CardsPage.view,
соответствующие
CardPage.vm.ts/CardsPage.vm.tsрядом. - Convention: 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,
items,useLocation,Outlet, nav rendering; public export в shared/ui. - Convention: FSD и устройство.
- 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, AccountsPageError, StocksPage.view, BondsPage.view.
- Convention: Frontend errors, pattern convergence.
- Current state: несколько owners повторяют
TBANK_NOT_CONNECTED/INVALID_TBANK_TOKENcomparisons; 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,
call(throw Error); TbankTokenService,translateInvalidToken; InstrumentMetadataService, invalid-token catch. - Convention: Errors, Integrations.
- 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, не переписывая snapshot под новый код.