34 KiB
Repository Audit: WealthLedger Frontend
Дата: 2026-08-31
Session ID: 01a05590-9243-7df0-9664-508acc5fc3e9
Invoked skills: repository-audit
Supporting skills invoked: none
External research performed: no
Scope and Confidence
Target: frontend/, Git branch main.
Target commit: 7ab043dce63a3fbacbfc9ac7d15b00dd714a541f
(docs: remove useless).
Initial target state: git status --porcelain=v1 --untracked-files=all был
пуст; целевой репозиторий не содержал tracked или untracked изменений.
Reference: references/frontend-ai-template/. Директория не является
Git-репозиторием, поэтому commit identity недоступна. Идентичность
зафиксирована SHA-256 checksum аудируемого содержимого:
7fced9c6906bdc846847eec40e05aac7c2c59f9fe593acc298eaab4bb29e2a9a.
Checksum получен как SHA-256 отсортированного списка per-file SHA-256 и
относительных путей. Из scope исключены node_modules на любой глубине,
.idea, .playwright-mcp, cache-, build- и coverage-директории. Эталон
использовался только для сравнения эквивалентных обязанностей, не как authority.
Аудит standalone и read-only: OpenSpec/SDD workflow не запускался. Единственная
запись — этот отчёт вне frontend/ и reference. В inventory вошли все 145
tracked-файлов target (117 под src, 22 test-файла) и 220 релевантных файлов
reference после исключения локальных зависимостей и ephemeral artifacts.
Углублённо проверены manifests/lockfile, TypeScript/Vite/Vitest/Biome/Knip/
dependency-cruiser/Husky configuration, repository instructions и frontend
docs, auth/session и API seams, routing/error boundary, representative finance
pages, entities, ViewModels, tests и эквивалентные reference responsibilities.
По всему src выполнены searches импортов, network calls, generated types,
tests и крупных modules; не каждый CSS-файл и не весь generated OpenAPI файл
читались построчно.
Выполненные проверки:
npm run deps— exit 0; 136 modules и 484 dependency edges, нарушений нет;npm run knip— exit 0, findings отсутствуют;- offline generation
openapi-typescriptво временный файл — текущийschema.d.tsбайтово совпадает со snapshot; - controlled invalid-date probe под Node 22 —
RangeError: Invalid time value, подтверждающий F-03.
Полный npm run verify, test:coverage, build, Storybook и E2E не запускались:
они создают или могут изменять coverage, dist, reports и caches, что не
соответствует read-only контракту аудита. Также не выполнялись install,
dependency advisory scan, backend runtime contract test и browser-based visual/
accessibility checks. Поэтому выводы о текущем полном gate, supply-chain
уязвимостях, реальном backend и визуальном поведении не заявляются.
Общая confidence: high для статической архитектуры, tooling и найденных расхождений; medium для production/release risk из-за локальной границы системы и отсутствия runtime/browser evidence.
Preservation check: после всех inspection/validation commands target остался на том же commit с пустым porcelain status; повторный reference checksum совпал с исходным. Временный generated artifact удалён. Единственное сохранённое изменение сессии — этот Markdown report.
Executive Summary
Репозиторий в целом находится в хорошем состоянии: небольшой React/Vite client имеет строгий TypeScript, один typed HTTP boundary, risk-oriented Vitest/MSW tests, coverage floors, dead-code analysis и более сильные dependency rules, чем reference. Критических findings нет.
Наибольшую ценность дадут F-01 и F-02: восстановить детерминированную проверку generated OpenAPI artifact и закрыть реальный login journey существующим test stack. F-03 — небольшой, но уже воспроизводимый trust-boundary defect в URL date validation. F-04 уточняет существенное ограничение заявленного public-API gate: он не видит type-only dependencies и уже пропускает семь внутренних imports. Остальная архитектурная стоимость сосредоточена в двух ownership seams (F-05, F-08) и в документации, которая местами описывает удалённые либо избыточно жёсткие guardrails (F-06, F-07).
Эталон полезен как подтверждение ценности OpenAPI drift check, но его Storybook, browser MSW, Playwright harness, локальный OpenSpec и 17 agent skills не должны переноситься в target: текущая система локальна, а эти поверхности не решают наблюдаемую проблему пропорционально своей стоимости.
Priority Findings
| Severity | Finding | Category | Action | Effort |
|---|---|---|---|---|
| high | F-01. Generated OpenAPI types не защищены от drift | Tooling / API Contract | add | small |
| high | F-02. Основной auth journey не проверяется | Testing / Security | add | small |
| medium | F-03. Некорректная календарная дата бросает исключение | Correctness / Validation | change | small |
| medium | F-04. Type-only dependencies обходят public-API guardrail | Architecture / Tooling | change | small |
| medium | F-05. Ubiquitous Money formatting принадлежит Portfolio | Architecture / Ownership | change | medium |
| medium | F-06. Entry-point docs обещают отсутствующие проверки | AI Readiness / Documentation | change | small |
| low | F-07. File-splitting rule создаёт ложный hard invariant | Complexity / AI Readiness | simplify | small |
| low | F-08. T-Bank status обходит общий authenticated-query seam | Architecture / Maintainability | change | small |
Findings
F-01. Generated OpenAPI types не защищены от drift
Category: Tooling / API Contract
Severity: high
Confidence: high
Action: add
Effort: small
Observed: Frontend хранит и OpenAPI snapshot, и generated
schema.d.ts, но canonical verify не проверяет, что второй детерминированно
получен из первого. Workflow полагается на ручное выполнение
npm run openapi:generate после копирования snapshot.
Evidence: frontend/package.json:17-20 содержит openapi:generate, но
verify состоит только из typecheck, Biome, coverage, Knip,
dependency-cruiser и build. frontend/docs/API-LAYER.md:3-5 прямо описывает
ручные два шага. Backend test:openapi-contract сравнивает runtime contract с
frontend snapshot, но не snapshot с frontend/src/shared/api/generated/schema.d.ts.
Текущий controlled check с генерацией в /private/tmp показал
OPENAPI_GENERATED_MATCH yes, то есть finding относится к отсутствующему
guardrail, а не к текущему рассинхрону. TypeScript и build являются
counter-evidence только для внутренней согласованности кода со stale generated
типами; происхождение этих типов они доказать не могут.
Why it matters: Изменение snapshot без генерации может одновременно пройти
frontend verify и backend runtime-contract check, оставив приложение
скомпилированным против старого публичного контракта. Для auth и финансовых
response shapes это даёт silent contract risk в главном typed boundary.
Reference repository: adopt from reference — capability only. Reference
имеет scripts/check-openapi.mjs, openapi:check и включает его в verify и
pre-commit (reference/package.json:13-28,
reference/scripts/check-openapi.mjs:1-32). Это узкий guardrail с прямой
проблемой и без новой dependency. Остальной reference toolchain для него не
нужен.
Recommended direction: Добавить один non-mutating offline check: с помощью
уже установленного openapi-typescript генерировать temp artifact, сравнивать
его с committed schema.d.ts и включить сравнение в canonical verify.
Не синхронизировать backend и не переписывать tracked files внутри gate.
F-02. Основной auth journey не проверяется
Category: Testing / Security
Severity: high
Confidence: high
Action: add
Effort: small
Observed: Test suite хорошо покрывает восстановление session, Bearer retry,
очистку cache, anonymous redirects и одну отрицательную registration validation,
но ни один тест не выполняет успешный login/register/logout через public UI/
provider boundary. В частности, untested остаётся связка AuthForm →
AuthProvider.authenticate → auth endpoint → in-memory access token → переход
на protected route.
Evidence: Поиск по всем 22 test-файлам нашёл auth assertions только в
src/shared/api/client.test.ts, src/shared/api/authenticated-query.test.tsx,
src/features/auth/model/auth.test.tsx, src/features/auth/ui/AuthForm.test.tsx
и src/app/routes/AppRoutes.test.tsx. auth.test.tsx:11-102 проверяет три
refresh/401 сценария; AuthForm.test.tsx:16-41 — только несовпадающее
подтверждение пароля; route test восстанавливает session через refresh.
Tests не вызывают login, register или logout, не отправляют валидную
форму и не проверяют LoginForm/RegisterForm navigation. Это проверено
repository-wide search, а не выводом из отсутствия отдельного filename.
Why it matters: Auth — security-sensitive и critical user journey по
действующей risk-based testing policy. Независимо покрытые низкоуровневые seams
не обнаружат поломку их композиции: неверный endpoint/body, потерянное session
state update, неработающий returnTo, navigation до завершения auth или
неочищенный UI после logout.
Reference repository: reference approach is unnecessary. Reference имеет Playwright и browser MSW, но его E2E покрывает Todo demo, не auth. Перенос всего browser harness ради этого риска добавит больше lifecycle cost, чем существующий Vitest + Testing Library + MSW boundary.
Recommended direction: Добавить минимальный risk-focused integration test
на существующем stack, который вводит валидные credentials, проходит реальный
AuthProvider/MSW login и наблюдает protected landing/session outcome. Logout
или failure следует добавлять только там, где их риск не покрыт уже имеющимися
provider/client tests; Playwright для этого finding не требуется.
F-03. Некорректная календарная дата бросает исключение
Category: Correctness / Validation
Severity: medium
Confidence: high
Action: change
Effort: small
Observed: isDate сначала проверяет только форму YYYY-MM-DD, затем без
проверки validity вызывает toISOString(). Строка вроде 2026-99-99 проходит
regex и приводит к RangeError, а не к validation result false.
Evidence: src/pages/account-income/ui/AccountIncomePage.vm.ts:35-46 подаёт
isDate в Zod refinements; :51-71 читает даты из untrusted URL search params;
:114-132 использует new Date(...).toISOString() в isDate и затем снова в
validPeriod. Controlled Node 22 probe над этой же операцией вернул
INVALID_DATE_THROW RangeError: Invalid time value. Existing income tests
покрывают analytics, empty bucket, chart и recoverable server error, но не
malformed URL/form dates. Общий RouteErrorBoundary ограничивает blast radius,
но превращает validation error в аварийный route state.
Why it matters: Пользователь или внешняя ссылка может сломать income route одним syntactically-shaped query parameter. Это доступность и корректность на trust boundary; exception bypasses предусмотренное восстановление default period и field-level validation.
Reference repository: neither reflects a directly comparable case.
Reference валидирует numeric route params через Zod safeParse, но не содержит
эквивалентной календарной логики, поэтому переносить его hook не нужно.
Recommended direction: Сделать date predicate total: проверить parsed date
до toISOString либо валидировать год/месяц/день безопасным стандартным
преобразованием, затем зафиксировать malformed query/form date регрессионным
тестом. Новая date library не нужна.
F-04. Type-only dependencies обходят public-API guardrail
Category: Architecture / Tooling / AI Readiness
Severity: medium
Confidence: high
Action: change
Effort: small
Observed: Dependency-cruiser проверяет cycles, layer direction и public
entrypoints только для dependencies, существующих после TypeScript compilation.
Type-only edges не входят в graph, хотя repository docs формулируют public-API
правило без такого исключения. В результате семь consumers импортируют
внутренний generated module @/shared/api/generated/schema напрямую.
Evidence: В frontend/dependency-cruiser.cjs:45-48 задан tsConfig, но нет
tsPreCompilationDeps; установленный dependency-cruiser 18.2.0 устанавливает
его default в false (node_modules/dependency-cruiser/src/main/options/defaults.mjs:9).
frontend/docs/CONVENTIONS.md:9 и frontend/docs/STRUCTURE.md:11-13 требуют
cross-slice imports через index.ts, а frontend/AGENTS.md:34 называет это
dependency-cruiser-enforced boundary. При этом прямые type-only imports
@/shared/api/generated/schema находятся в пяти entity API hooks,
features/auth/model/auth.tsx и
features/tbank-connection/ui/TbankConnectionSection.tsx;
frontend/src/shared/api/index.ts не реэкспортирует paths. Успешный
npm run deps не является counter-evidence: эти edges отсутствуют в
анализируемом graph. Counter-evidence по масштабу риска — остальные найденные
type-only imports идут через public APIs в разрешённом направлении, а текущие
семь generated imports не создают runtime coupling.
Why it matters: Заявленный machine-enforced invariant фактически не распространяется на compile-time architecture. Новый type-only upward import, type cycle или обращение к внутренностям другого slice сможет пройти canonical gate; существующий generated seam уже демонстрирует расхождение code и policy. Severity остаётся medium, потому что текущие edges стираются при compilation и не создают runtime cycle или bundle dependency.
Reference repository: adopt from reference — capability, not exception.
Reference включает tsPreCompilationDeps: true, но исключает generated subtree
из dependency analysis. Target может сохранить более строгий и ясный public API,
не копируя это исключение.
Recommended direction: Реэкспортировать generated paths как type из
shared/api/index.ts, перевести семь consumers на @/shared/api и включить
tsPreCompilationDeps: true в существующем dependency-cruiser config. Если
generated path действительно должен быть публичным исключением, сначала
зафиксировать его явно и всё равно проверять остальные type-only edges. Новая
dependency или второй architecture checker не нужны.
F-05. Ubiquitous Money formatting принадлежит Portfolio
Category: Architecture / Ownership
Severity: medium
Confidence: high
Action: change
Effort: medium
Observed: Общий display formatter formatMoney типизирован через
Portfolio["totalsByCurrency"][number] и экспортируется из Portfolio entity,
хотя используется для Brokerage Account, Ledger Entry, Income Analytics и
Future Cash Event. formatSnapshotTime и instrument logo helper также частично
разделяются через тот же Portfolio public API.
Evidence: Canonical domain glossary определяет Money как самостоятельный
ubiquitous value { currency, value }, который frontend форматирует для
отображения (docs/architecture/domain-glossary.md:20-28). Реализация в
src/entities/portfolio/model/display.ts:1-18 выводит Money type из Portfolio.
Repository-wide usage показывает импорт Portfolio formatter из
src/entities/brokerage-account/ui/BrokerageAccountList.tsx,
src/entities/broker-operation/ui/LedgerEntryRow.tsx, account/events/income
pages и их section views. npm run deps проходит, потому что текущие rules
контролируют direction/public entrypoints, но не смыслового владельца peer
entities. Counter-evidence: formatter один, протестирован и не дублируется;
проблема именно в owner/type dependency, а не в реализации форматирования.
Why it matters: Изменение Portfolio transport shape или реорганизация Portfolio заставляет менять несвязанные account, ledger и income consumers. Граница утверждает, что общий Money concern принадлежит одной product entity, и легализует entity-to-entity coupling, которое dependency tool не различает.
Reference repository: reference approach is unnecessary. Todo-only reference не моделирует shared Money value и не предлагает применимого owner.
Recommended direction: Переместить только действительно ubiquitous
display primitives (в первую очередь Money formatting и, при подтверждении
общего смысла, date formatting) в нейтральный существующий lower-level owner.
Оставить formatShare и portfolio error behavior в Portfolio. Не вводить Money
service, interface hierarchy или новую domain layer ради одного formatter.
F-06. Entry-point docs обещают отсутствующие проверки
Category: AI Readiness / Documentation
Severity: medium
Confidence: high
Action: change
Effort: small
Observed: Человеческий entrypoint сообщает, что verify отклоняет неверный
Node runtime и проверяет repository hygiene, а conventions утверждают, что
pre-commit запускает OpenAPI check. Ни одной из этих команд в текущих scripts/
hook нет.
Evidence: frontend/README.md:25-27 перечисляет runtime и hygiene;
frontend/docs/CONVENTIONS.md:14 перечисляет pre-commit OpenAPI check.
Фактически frontend/package.json:9-22 не содержит runtime:check,
hygiene:check или openapi:check, а .husky/pre-commit выполняет только
npx lint-staged и npm run deps. Counter-evidence: .nvmrc,
engines.node = 22.x и repository-local AGENTS.md корректно требуют
nvm use --silent; canonical config остаётся discoverable. Расхождение
ограничено двумя stale prose claims, а не всей документацией.
Why it matters: Новый разработчик или coding agent получает ложное completion evidence и может считать runtime, env hygiene или generated artifact проверенными. Особенно опасно сочетание с F-01: docs называют guardrail, которого больше нет.
Reference repository: both are acceptable only where config and prose
agree. Reference действительно включает свой openapi:check, но его намного
больший documentation surface не нужен target. Target уже лучше как concise,
canonical entrypoint; ему требуется синхронизация нескольких утверждений, а не
перенос reference docs.
Recommended direction: Привести README и CONVENTIONS к фактическим scripts: описать manual Node activation и реальные hook/gate stages. Если F-01 добавлен, упомянуть только реально включённый OpenAPI check. Не возвращать отдельные runtime/hygiene scripts без нового конкретного failure mode.
F-07. File-splitting rule создаёт ложный hard invariant
Category: Complexity / AI Readiness
Severity: low
Confidence: high
Action: simplify
Effort: small
Observed: UI docs объявляют обязательным отдельный file для каждого
top-level state и каждого именуемого page section, но несколько текущих modules
сознательно хранят cohesive presentation рядом. Самый заметный пример —
388-line AccountIncomePage.view.tsx с loading/error/loaded и шестью semantic
section components.
Evidence: frontend/docs/CONVENTIONS.md:12-13 и
frontend/docs/UI-PATTERN.md:30-35 формулируют правило как Must/«нельзя».
src/pages/account-income/ui/AccountIncomePage.view.tsx:29-380 содержит
AccountIncomePageLoading, AccountIncomePageError, loaded view, summary,
comparisons, trend, breakdown и forecast. Подобная colocated state structure
есть в AccountsPage.view.tsx и небольшом T-Bank view. Counter-evidence:
важная граница container → VM → props-only view в проверенных finance pages
соблюдается; большой income view читается последовательно и покрыт одним
behavior-level page test. Поэтому само количество components/files не является
failure mode.
Why it matters: Непроверяемое и уже нарушенное Must заставляет agents либо игнорировать authority, либо создавать file/ barrel sprawl без улучшения ownership. Оно также маскирует действительно важный invariant — views не должны владеть data fetching и collection derivation.
Reference repository: reference approach is unnecessary. Reference часто создаёт отдельную директорию и file для каждого Todo state; это полезно для component catalog demo, но увеличивает navigation cost для текущего product client и не должно становиться механической нормой.
Recommended direction: Оставить VM/view ownership rule обязательным, а file splitting сделать locality-based guidance: выделять section, когда у неё есть самостоятельная responsibility, reuse, test/change cadence или она мешает пониманию owner. Не добавлять line-count checker и не требовать one-component- per-file.
F-08. T-Bank status обходит общий authenticated-query seam
Category: Architecture / Maintainability
Severity: low
Confidence: high
Action: change
Effort: small
Observed: Все entity queries используют shared useAuthenticatedQuery,
который подписывается на session snapshot. T-Bank connection status вместо
этого импортирует useAuth из соседней feature и вручную задаёт
enabled: Boolean(accessToken) обычному useQuery.
Evidence: src/shared/api/authenticated-query.ts:13-33 владеет session
gating; его используют brokerage account, operation, event, income и portfolio
hooks. src/features/tbank-connection/ui/TbankConnectionSection.vm.ts:1-15
создаёт второй механизм и feature → feature dependency на features/auth.
dependency-cruiser.cjs не запрещает peer-feature dependencies, поэтому
успешный npm run deps не является counterexample. Counter-evidence: текущее
поведение функционально совпадает, access token обновляется через AuthProvider,
а mutations всё равно идут через общий authenticated fetch.
Why it matters: Изменение определения authenticated readiness,
configureAuthClient или session clearing может примениться ко всем entity
queries, но не к T-Bank status. Соседняя feature становится зависимой от auth
implementation detail при уже существующей lower-level seam.
Reference repository: not comparable. Reference не имеет auth/session boundary.
Recommended direction: Переиспользовать existing authenticated-query seam для status query и убрать feature-to-feature dependency. Новая abstraction или расширение dependency rules ради единственного случая не нужны.
Missing Practices
- F-01 — отсутствует доказательство происхождения committed generated API types из committed snapshot.
- F-02 — отсутствует risk-focused integration proof главного auth journey.
- F-03 — trust-boundary date validation не total и не имеет malformed-input regression coverage.
- F-04 — type-only dependencies не входят в заявленный public-API/layer gate; уже существуют семь direct generated-internal imports.
Отсутствие CI/CD и deployment automation не считается finding: canonical
docs/architecture/system-overview.md явно ограничивает текущую поставку
локальной системой, а frontend/AGENTS.md требует отдельного approved change
для CI, Playwright или Storybook.
Over-engineering / Simplification Opportunities
- F-07 — сузить file-splitting Must до правила ownership/locality, сохранив важную VM/view границу.
- Не переносить из reference Storybook, browser MSW mode, Playwright harness,
локальный
openspec/и 17 repository-provided skills: для target нет наблюдаемой проблемы, оправдывающей этот context и maintenance surface. - F-05 следует исправлять переносом маленьких shared primitives, не созданием Money service/interface/factory.
- Не добавлять второй FSD framework или checker: текущий dependency-cruiser уже проверяет cycles, public entrypoints и layer direction лучше reference.
AI Readiness
AI readiness выше средней: AGENTS.md короткий, различает hard guardrails и
review guidance, направляет к специализированным docs и canonical multi-repo
authority; scripts и public APIs предсказуемы; generated seam явно помечена;
dependency-cruiser и Knip снижают необходимость загружать длинные инструкции.
Главные ухудшения — F-04 (type-only blind spot), F-06 (несовпадение prose с
executable truth) и F-07 (непроверяемый structural Must). Reference agent
surface значительно больше и для target был бы регрессом context locality.
What Is Already Good
- Строгая и воспроизводимая основа: Node 22 contract, npm lockfile, exact direct dependency versions, strict TypeScript и Biome.
npm run depsподтверждает отсутствие cycles/layer/public-API violations в анализируемых runtime edges; target имеет отдельные cycle/public-API rules, которых нет в reference, но F-04 ограничивает полноту этого доказательства.- Один HTTP owner: repository-wide search не нашёл прямой network access вне
src/shared/api/client.ts; transport types выводятся из OpenAPI. - Auth design соответствует system/domain authority: access token остаётся в memory, refresh использует cookie и single-flight behavior; Bearer exclusion, refresh retry и cache clearing имеют focused tests.
- 22 colocated test-файла покрывают loading/error/empty/cached snapshot, finance display, filtering/pagination, route boundary и T-Bank mutations.
- Coverage floors, Knip, dependency checks и production build собраны в одном canonical local gate; controlled Knip/dependency runs успешны.
- UI resilience и доступные loading/error regions не только описаны, но и представлены в code/tests; общий route error boundary ограничивает failures.
- Текущий OpenAPI snapshot и generated types фактически синхронны; F-01 — prevention gap, не текущая поломка.
Recommended Order
Do now
- F-01 — добавить non-mutating OpenAPI generated-artifact equivalence в canonical gate.
- F-02 — закрыть успешный auth journey существующим Vitest/MSW stack.
- F-03 — сделать date validation total и закрепить malformed-input case.
- F-04 — включить type-only edges и провести generated types через public API.
- F-06 — синхронизировать README/CONVENTIONS с фактическими scripts и hooks.
Do next
- F-05 — вернуть ubiquitous Money display primitive нейтральному owner без нового service layer.
Consider later
- F-08 — устранить единичный bypass authenticated-query seam при ближайшем изменении T-Bank connection.
- F-07 — упростить file-splitting guidance до решения по locality/ownership.
Do not add
- Storybook, browser MSW mode или Playwright только потому, что они есть в reference; F-02 дешевле и надёжнее закрывается текущим integration stack.
- Локальный OpenSpec store или копии reference agent skills: canonical SDD authority уже находится вне target и entrypoint достаточно короткий.
- CI/CD, deployment или release platform вне отдельного approved scope: as-built system явно локальна.
- Runtime validation каждого OpenAPI response без конкретного наблюдаемого trust-boundary failure; это существенно расширит runtime и schema ownership.
- Новый FSD framework, line-count gate, one-component-per-file rule, Money service/interface или dependency ради findings, которые решаются текущими tools и небольшими ownership corrections.