Files
docs/audits/audit-20-08-2026.md
T

59 KiB
Raw Blame History

Аудит WealthLedger — 20.08.2026

Исследовательская база: актуальные практики AI-агентов

Практика Первичный источник Следствие для аудита
Делить постоянные инструкции по области: общий контракт в корне, узкие правила рядом с подсистемой; ближайший файл имеет приоритет. Codex: AGENTS.md, AGENTS.md standard, GitHub Copilot Проверить, что корневой AGENTS.md задаёт только межрепозиторные правила, а frontend/, backend/ и specs/ имеют собственные, непротиворечивые инструкции там, где различаются команды и нормы.
Ограничивать объём постоянного контекста; переносить локальные процедуры и правила в scoped/JIT-слои. Codex: 32 KiB по умолчанию, Claude: цель — <200 строк, Copilot: инструкции не длиннее двух страниц, Gemini: JIT-context Измерить размер и дублирование инструкций; исключить из постоянного слоя длинные справочники, task-specific шаги и нерелевантные правила.
Формулировать инструкции конкретно и так, чтобы их можно было проверить: точные пути, команды, предусловия и ожидаемый результат. Claude: effective instructions, Copilot: validate bootstrap/build/test/lint commands, AGENTS.md standard Проверить наличие единственного детерминированного local bootstrap и явно перечисленных non-mutating проверок для каждого независимого репозитория.
Держать runnable acceptance criteria рядом со спецификацией и трассировать изменение от контракта к проверкам. Copilot: документировать последовательность validation, Codex: instructions with review rules Проверить, что OpenSpec change содержит требования, задачи и исполнимые локальные gates, а инструкции связывают реализацию с opsx-verify до завершения работы.
Сохранять воспроизводимое окружение: версии рантайма и инструментов, порядок bootstrap, необходимые переменные и подтверждённые обходы. Copilot: bootstrap и версии, Gemini sandbox: reproducibility Проверить, можно ли на чистой локальной машине повторить установку и gates без угадывания версий, скрытых сервисов или порядка команд.
Разделять поведенческие советы и жёсткое принуждение: критичные запреты должны выполняться permissions/hooks/sandbox, а не только prose. Claude: settings/hook enforcement vs context, Claude security, Gemini policy engine Выявить правила, нарушение которых опасно (границы записи, секреты, destructive-команды), и оценить, есть ли для них machine-enforced локальный контроль.
Работать с минимальными правами: read-only для исследования, узкие writable roots/allow-rules для реализации, явное подтверждение сети и выхода за границы. Codex sandboxing, Claude security, Gemini trusted folders Проверить, что agent workflow не требует broad permissions и что необязательные внешние действия не входят в local read-only MVP.
Обеспечивать независимый review после локальных gates: reviewer получает явные критерии и не должен вносить изменения вместо проверки. GitHub Copilot code review, Claude agent teams: independent lenses Проверить, что workflow предписывает отдельный review реализации по OpenSpec, diff, тестам и отсутствию лишних изменений после успешной самопроверки.
Минимизировать rework: versioned инструкции должны фиксировать только подтверждённые команды, известные ограничения и архитектурные точки входа. Claude: когда добавлять знания в CLAUDE.md, Copilot CLI: не перегружать постоянные инструкции Проверить, что документация не заставляет агента повторно искать уже известные команды и не содержит устаревших либо конфликтующих указаний.

Ограничения доказательности:

  • Использованы только официальные документация вендоров и сайт открытого стандарта AGENTS.md, актуальные на 20.08.2026; это не эмпирическое сравнение качества моделей.
  • Часть возможностей зависит от конкретного клиента и режима, поэтому аудит оценивает tool-agnostic контракт репозитория, а не предполагает поддержку каждого механизма всеми агентами.
  • Scope ограничен локальным read-only MVP: CI/CD, облачное исполнение, deployment и масштабирование намеренно не оцениваются.

Согласованная рамка

Аудит оценивает текущий локальный read-only MVP и качество рабочей области для AI-агентов. Охват: корневые файлы координации, .opencode, frontend, backend, specs и docs. Каталог references/ полностью исключён: его содержимое не читалось и не использовалось как доказательство.

Снимок репозиториев:

Репозиторий Branch Commit Состояние до аудита
frontend main 5168870cf2d5 clean
backend main e0055414d694 clean
specs main 601a4989913f clean
docs main 680fc03bec7f clean

Не оцениваются и не получают severity: CI/CD, Docker, deployment, hosted security controls, PostgreSQL, HA и масштабирование. Проценты coverage не считаются главной метрикой. Проверяются рискованные сценарии, соответствие OpenSpec, корректность контрактов, воспроизводимость локальных gates и способность независимого агента завершить задачу без повторной переделки.

Статусы доказательности:

  • confirmed — воспроизведено командой либо однозначно следует из кода и контракта;
  • likely — сильное структурное доказательство, но runtime-сценарий не исполнялся;
  • needs verification — гипотеза или применимость внешнего advisory требует отдельной проверки.

Severity присваивается только confirmed-находкам:

  • P0 — немедленная потеря/раскрытие данных либо блокировка основного сценария без обхода;
  • P1 — неверное поведение основного сценария или системный дефект процесса, регулярно направляющий агента к неверному результату;
  • P2 — существенный риск корректности, безопасности, UX или воспроизводимости с ограниченным обходом;
  • P3 — локальный долг, hygiene, accessibility или ранний сигнал архитектурного ухудшения.

Итог

Фундамент проекта технически здравый: типизированный OpenAPI-путь, явная T-Bank seam на backend, защищённое хранение токена, структурированные логи, нормальная FSD-декомпозиция и валидный основной OpenSpec store. Однако рабочая область пока не обеспечивает почти безошибочную автономную работу агента. Причина не в объёме инструкций — они компактны — а в том, что несколько ключевых правил существуют только как текст и не совпадают с исполняемыми gates.

P0 не обнаружены. Подтверждены 5 P1, 13 P2 и 8 P3. Самые важные:

  1. frontend использует не те React Query cache keys, которые затем обновляет и инвалидирует;
  2. strict TypeScript заявлен, но не включён, поэтому штатный typecheck пропускает 16 ошибок;
  3. два OpenSpec-root дают разные ответы об активных changes;
  4. архивирование допускает незавершённые tasks, и такой архив уже существует;
  5. обязательная backend-проверка lint изменяет исходники, а отдельные typecheck/e2e gates сейчас неработоспособны.

Матрица состояния

Область Оценка Основание
Frontend weak Сильная структура и тестовая база, но P1 cache-key bug и отсутствующий фактический strict mode.
Backend adequate Хорошие security/logging seams и unit tests; есть concurrency/cache риски и неработающие read-only gates.
API contract weak Snapshot и generated types воспроизводимы, но auth status не совпадает с runtime, а freshness относительно backend не проверяется одной локальной командой.
Risk-based tests weak 46 frontend и 51 backend unit tests проходят; backend e2e не стартует, а ключевые concurrency/cache scenarios отсутствуют.
Security local MVP adequate Сильные env validation, bcrypt, JWT typing, AES-256-GCM и redaction; остаются concurrency и dependency risks.
Архитектура adequate Основные seams осмысленны; cache identity и verification/OpenSpec selection остаются распределёнными интерфейсами.
SDD и документация weak 12/12 основных specs валидны, но source-of-truth раздвоен, lifecycle допускает неполный архив, docs частично устарели.
AI-agent readiness weak Scoped инструкции хороши, но нет единого read-only verify, машинного enforcement части правил и обязательного независимого review.

Сильные стороны

Frontend

  • FSD-направление app -> pages -> features -> entities -> shared, публичные index.ts, alias @/, dependency-cruiser, Knip и Biome создают хорошую навигацию.
  • OpenAPI snapshot и generated types закоммичены; openapi:check детерминированно работает без backend и пишет только во временный каталог (frontend/scripts/openapi-check.mjs:6-35).
  • Auth client хранит access token только в памяти, использует cookie credentials, сериализует refresh и повторяет защищённый запрос один раз (frontend/src/shared/api/client.ts:20-59).
  • MSW запрещает необработанные запросы; 15 test files / 46 tests проходят.
  • frontend/AGENTS.md короткий (2 509 bytes), правильно маршрутизирует агента к JIT-документам API/UI/structure/testing.

Backend

  • Конфигурация проверяет SQLite URL, origin, port, JWT secret, 32-byte encryption key, T-Bank endpoint/rate и log level (backend/src/config/env.validation.ts:13-97).
  • Пароли хэшируются bcrypt cost 12; refresh token хранится как SHA-256 hash; T-Bank token шифруется AES-256-GCM (backend/src/auth/auth.service.ts:25-59, backend/src/tbank/tbank.crypto.ts:16-44).
  • TbankClient, TbankCrypto, StructuredLogger, RequestContextService и PrismaService — полезные глубокие модули: сложность находится за небольшими интерфейсами и используется несколькими flows.
  • HTTP errors централизованно нормализуются в application/problem+json без внутренних деталей (backend/src/common/filters/http-exception.filter.ts:11-79).
  • Логи используют allowlist и исключают authorization, cookie и request body; 17 suites / 51 unit tests проходят.

SDD, docs и contracts

  • Основной store wealth-ledger-specs валиден: openspec validate --all --store wealth-ledger-specs — 12 passed, 0 failed.
  • CONTEXT.md даёт компактный словарь Portfolio, Position, Instrument, Ledger и Future Cash Event.
  • Feature docs обычно содержат пользовательский поток, API, ограничения и Mermaid-сценарии.
  • Статический cross-repo срез показал согласованность 10 controller paths с frontend OpenAPI snapshot и typed usages.

Подтверждённые находки P1

WL-P1-01 — Frontend обновляет неиспользуемые React Query cache keys

Статус: confirmed. Область: correctness / frontend.

В entity hooks пользовательский queryKey указывается до spread apiQueryClient.queryOptions(...), например frontend/src/entities/brokerage-account/api/useBrokerageAccounts.ts:11-17, frontend/src/entities/portfolio/api/usePortfolio.ts:20-28 и frontend/src/entities/brokerage-event/api/useBrokerageEvents.ts:14-23. Установленная библиотека возвращает собственный queryKey (frontend/node_modules/openapi-react-query/src/index.ts:211-219), поэтому последний spread перезаписывает ключ. Strict compiler прямо сообщает TS2783 для пяти hooks.

После connect/delete T-Bank код пишет в ["tbank-connection"] и инвалидирует ["brokerage-accounts"] (frontend/src/features/tbank-connection/ui/TbankConnectionSection.tsx:50-67), тогда как активные queries используют ключи вида ["get", path, init]. UI может остаться в состоянии «не подключено», а счета — не обновиться. Сценарного теста cache update/invalidation нет.

Remediation: сделать cache identity частью единственного интерфейса entity-модуля: получать/экспортировать реальный key из queryOptions либо передавать custom key последним supported-аргументом; добавить integration tests connect/delete → status/accounts refresh.

WL-P1-02 — Заявленный strict TypeScript не является исполняемым инвариантом

Статус: confirmed. Область: correctness / agent gate.

frontend/AGENTS.md:29 требует strict TypeScript, но frontend/tsconfig.app.json:2-25 и frontend/tsconfig.node.json:2-20 не включают strict. Обычный npm run typecheck проходит; npx tsc -p tsconfig.app.json --noEmit --strict воспроизводимо выдаёт 16 ошибок: пять overwritten queryKey, nullable query data, string | undefined и (string | null)[].

Это показательный agent failure: агент может выполнить все написанные команды, получить зелёный gate и передать функционально неверный код.

Remediation: включить strict в обоих tsconfig, исправить все 16 ошибок, оставить диагностический regression test/config check и сделать невозможным расхождение между AGENTS.md и compiler options.

WL-P1-03 — Два OpenSpec-root дают взаимоисключающую картину работы

Статус: confirmed. Область: SDD / source of truth.

Корневое правило называет source of truth specs/openspec (AGENTS.md:5-17), но существует второй openspec/config.yaml:1 и пустой openspec/changes/add-brokerage-account-ledger/. Из корня:

  • openspec list --json показывает активный add-brokerage-account-ledger и root workspace;
  • openspec list --store wealth-ledger-specs --json показывает 0 активных changes и root specs.

Даже качественные .opencode инструкции вынуждены повторять правило выбора store во многих командах; selection complexity протекает к каждому caller вместо одного надёжного seam.

Remediation: удалить/перенаправить phantom root; сделать один workspace entrypoint, всегда выбирающий wealth-ledger-specs; добавить machine check, что unscoped и canonical context не расходятся.

WL-P1-04 — Lifecycle позволяет архивировать change с незавершёнными tasks

Статус: confirmed. Область: SDD / evidence integrity.

Архивированный specs/openspec/changes/archive/2026-08-17-redesign-position-cards/tasks.md:33-37 оставляет unchecked archive и cross-repo commit tasks. Это противоречит specs/openspec/specs/project-documentation/spec.md:23-28 и MVP.md:26-28. Причина структурная: .opencode/commands/opsx-archive.md:63-77 разрешает продолжить после подтверждения при незавершённых artifacts/tasks.

Для агента архив перестаёт быть доказательством завершённости; последующий агент не может отличить исключение от процессной ошибки.

Remediation: incomplete tasks должны hard-fail archive либо сам archive должен атомарно закрывать только свои post-actions и записывать проверяемое evidence; исторический change нужно отдельно привести к согласованному состоянию.

WL-P1-05 — Обязательная backend-верификация изменяет пользовательский код

Статус: confirmed. Область: agent safety / reproducibility.

backend/AGENTS.md:7-13 предписывает npm run lint как проверку, но backend/package.json:15 запускает ESLint с --fix. Read-only audit или reviewer, буквально следующий инструкции, может изменить файлы без явного решения. Без --fix ESLint сейчас действительно находит ошибку formatting в backend/src/tbank/upcoming-events.service.ts:40.

Remediation: lint/lint:check сделать non-mutating, автоисправление вынести в lint:fix; добавить один backend verify, который не пишет source/generated output и отчётливо разделяет unit, typecheck, migration и e2e.

Подтверждённые находки P2

WL-P2-01 — Operations cache не имеет capacity/eviction

backend/src/tbank/brokerage-operations.service.ts:33-36 хранит страницы в постоянном Map; stale entry только игнорируется при чтении (:56-59), но не удаляется, а каждый уникальный запрос записывает новый entry (:79, :104). Cache key включает клиентские query/cursor (:56), cursor допускает 4 096 символов, limit — до 1 000 (backend/src/tbank/brokerage-operation.dto.ts:46-58). Даже в локальном MVP ошибочный/зацикленный клиент может неограниченно наращивать память процесса.

Remediation: bounded LRU/TTL с удалением stale entries, лимит веса/числа страниц, canonical upstream cursor и тест на большое число уникальных ключей.

WL-P2-02 — Параллельная регистрация может вернуть 500 вместо 409

backend/src/auth/auth.service.ts:30-42 делает check-then-create, а unique constraint находится в backend/prisma/schema.prisma:11-18. Две регистрации одного email могут одновременно пройти lookup; проигравший create выбросит Prisma P2002, который общий filter преобразует в 500. Последовательный e2e (backend/test/app.e2e-spec.ts:62-80) гонку не проверяет.

Remediation: считать unique constraint источником истины, маппить P2002 в 409 EMAIL_ALREADY_REGISTERED, добавить parallel integration test.

WL-P2-03 — Refresh rotation не атомарна

Два запроса могут прочитать одинаковый hash (backend/src/auth/auth.service.ts:75-96), затем оба записать разные новые hashes (:148-166). Token из ответа, проигравшего last-write, будет воспринят как reuse и отзовёт Session. Ограничение явно документировано (docs/features/authentication.md:17-19), поэтому это не скрытое нарушение MVP, но остаётся техническим риском correctness.

Remediation: compare-and-swap updateMany по session.id + oldHash либо транзакционный adapter; ровно один refresh должен победить, второй — получить определённый idempotent/unauthorized результат без случайного отзыва победителя.

WL-P2-04 — OpenAPI и runtime расходятся по success status auth endpoints

Register/login/refresh помечены @ApiOkResponse (backend/src/auth/auth.controller.ts:51-76) и committed snapshot содержит только 200, но Nest POST без @HttpCode(200) возвращает 201; e2e ожидает 201 (backend/test/app.e2e-spec.ts:34-37, :51-52). Frontend types строятся по ошибочному contract.

Remediation: выбрать 200 или 201, согласовать controller decorator, tests, OpenAPI snapshot и frontend response types; добавить runtime-vs-snapshot contract check.

WL-P2-05 — Backend e2e gate не запускает Jest

npm run test:e2e (backend/package.json:20) воспроизводимо падает на prisma migrate deploy с Schema engine error до старта тестов. Unit tests не компенсируют отсутствие реального HTTP/DB gate для auth, migrations и contract.

Remediation: отдельно диагностировать Prisma/SQLite schema-engine failure; e2e должен использовать уникальную временную БД, сам создавать и удалять её и возвращать диагностируемую ошибку.

WL-P2-06 — Backend не имеет проходящего standalone typecheck

В scripts отсутствует typecheck; npx tsc --noEmit падает, потому что backend/src/tbank/tbank-connection.service.spec.ts:20-27 передаёт неполный mock как TbankClient. Build исключает specs, поэтому production build может быть зелёным при типовых ошибках тестов.

Remediation: добавить отдельные app/test tsconfig либо typed adapter fake; включить оба в non-mutating verify.

WL-P2-07 — Background refetch скрывает валидный cached snapshot

frontend/src/pages/accounts/ui/AccountsPage.vm.ts:7-15 возвращает loading при любом isFetching. frontend/src/pages/account/ui/AccountPage.vm.ts:16-23 ставит error/fetching выше существующих data. Во время background refresh пользователь теряет последний валидный финансовый снимок; при refetch error вместо stale data получает full error state, хотя docs описывают stale-snapshot UX.

Remediation: initial state определять через isPending/absence of data; cached data оставлять видимыми, а background error показывать как stale/non-blocking state.

WL-P2-08 — Cold-start refresh оставляет unhandled rejection

frontend/src/features/auth/model/auth.tsx:66-76 запускает promise через void, имеет .then и .finally, но не .catch. Transport exception превращается в unhandled rejection; ready всё равно становится true без контролируемой диагностики. Тест frontend/src/features/auth/model/auth.test.tsx:11-35 покрывает только успешное восстановление.

Remediation: явно нормализовать network failure в unauthenticated state, исключить unhandled rejection и добавить cold-start failure test.

WL-P2-09 — Локальный contract gate проверяет snapshot только против generated types

frontend/scripts/openapi-check.mjs:6-35 сравнивает generated schema с committed snapshot. Текущий backend документ извлекается только ручным mutating openapi:sync (frontend/scripts/openapi-sync.mjs:6-41). Поэтому auth status drift уже существует, хотя frontend openapi:check зелёный.

Remediation: workspace-level read-only command должен поднять backend с детерминированным test env, получить /api/docs-json, canonicalize и сравнить со snapshot, ничего не обновляя.

WL-P2-10 — Onboarding и постоянная документация заметно отстают от кода

  • frontend/README.md:1-77 — Vite template, описывающий ESLint вместо фактического Biome/FSD/OpenAPI workflow;
  • backend/README.md:24-98 — Nest template с npm install, без env/Prisma/API map;
  • docs/architecture/README.md:1-3 и docs/development/README.md:1-3 — placeholders;
  • docs/features/tbank-connection.md:38-40 утверждает, что реализована только проверка токена, хотя accounts/portfolio/ledger/events уже документированы и реализованы;
  • MVP.md:18-24 не отражает ledger/events и использует future-tense для уже работающей T-Bank seam.

Stale docs повышают rework: следующий агент вынужден повторно устанавливать факты либо принимает неверное ограничение за source of truth.

Remediation: agent-oriented entrypoints для frontend/backend, фактическая architecture map и полный local workflow; при закрытии change проверять stale future-tense и delivery map.

WL-P2-11 — Reproducibility frontend и backend асимметрична

Frontend фиксирует Node 22 в frontend/package.json:6-8 и .nvmrc; backend не имеет engines/.nvmrc, а README предлагает npm install (backend/README.md:28-32) вместо lockfile-safe npm ci. Проверка проходила на Node v22.21.0, npm 10.9.4, но repo не гарантирует это следующему агенту.

Remediation: единая Node/npm policy, npm ci, перечисленные env/migration/bootstrap steps и machine check версии.

WL-P2-12 — Актуальный npm audit сообщает high advisories

Онлайн-проверка 20.08.2026:

  • frontend: прямой react-router@7.9.6 (frontend/package.json:40) попадает в high advisories, включая GHSA-2w69-qvjg-hvjx; registry предлагает переход на 7.18.2 вне текущего exact range;
  • backend: deepmerge-ts@7.1.5 через prisma -> @prisma/config попадает в GHSA-ggr8-5vv4-36mx; npm audit считает цепочку тремя high findings.

Уязвимые версии подтверждены. Применимость exploit требует проверки: frontend — client-only SPA без SSR/RSC и с внутренними routes; backend chain в основном Prisma tooling/config, а не user-controlled runtime merge.

Remediation: отдельный dependency change с анализом применимости и regression tests; не использовать автоматический npm audit fix --force без проверки breaking changes.

WL-P2-13 — Нет единого интерфейса локальной верификации для четырёх Git-root

Корень не является Git-репозиторием (AGENTS.md:19-22), а каждый subrepo имеет отдельный AGENTS.md. В разных agent clients instruction discovery начинается от выбранного project/Git root; запуск из workspace и запуск из subrepo не гарантируют одинаковую цепочку root + local guidance. Единой read-only команды, которая выбирает canonical OpenSpec store, запускает изменённые repo gates, cross-contract check и проверяет лишний diff, нет.

Remediation: тонкий tool-agnostic workspace verifier/manifest: явная карта репозиториев, canonical store, non-mutating команды, changed-repo selection, expected artifacts и финальный clean-diff check. Tool-specific skills остаются адаптерами, а не вторым источником правил.

Подтверждённые находки P3

WL-P3-01 — Unknown request fields молча отбрасываются

ValidationPipe({ transform: true, whitelist: true }) в backend/src/main.ts:28 не включает forbidNonWhitelisted. Опечатка поля может выглядеть как успешный запрос. Для typed clients это редкость, но для ручной/agent диагностики ухудшает обратную связь.

Remediation: включить forbidNonWhitelisted после contract test на ожидаемый problem response либо явно документировать permissive policy.

WL-P3-02 — В shared i18n есть import cycle

frontend/src/shared/i18n/index.ts:13 re-export-ит ./zod, а frontend/src/shared/i18n/zod.ts:3 импортирует .. Graph analysis подтвердил двухфайловый cycle; текущий dependency-cruiser проверяет только направление слоёв (frontend/dependency-cruiser.cjs:1-27), но не cycles.

Remediation: импортировать нужный leaf напрямую и добавить no-circular rule в dependency-cruiser.

WL-P3-03 — Public API правило FSD уже нарушено

frontend/AGENTS.md:27 требует cross-slice imports через public index.ts, но frontend/src/features/auth/ui/AuthForm.tsx:9 использует относительный deep path ../../../shared/i18n. Текущий deps gate это пропускает.

Remediation: перейти на public alias и расширить dependency rule так, чтобы deep cross-slice imports не проходили.

WL-P3-04 — Базовая accessibility metadata не соответствует русскому UI

frontend/index.html:2 задаёт lang="en"; T-Bank loading — обычный Text без status/live semantics (frontend/src/features/tbank-connection/ui/TbankConnectionSection.view.tsx:4-7).

Remediation: задать фактический язык документа и общий accessible loading/status primitive.

WL-P3-05 — Один frontend chunk уже превышает стандартный warning threshold

Production Vite build прошёл, но сформировал один JS chunk около 620 kB minified / 186 kB gzip и выдал warning >500 kB. Все pages импортируются статически (frontend/src/app/routes/AppRoutes.tsx:3-10), vite.config.ts:7-10 не задаёт splitting. Для локального MVP это не блокер, но дальнейший рост ухудшит startup.

Remediation: lazy-load route boundaries и закрепить измеримый bundle regression budget без преждевременного дробления vendor chunks.

WL-P3-06 — Coverage запускается без regression thresholds

frontend/package.json:16,22 запускает coverage, но frontend/vitest.config.ts:7-11 содержит только reporters. Coverage не должен быть главной метрикой, однако минимальный baseline полезен как сигнал внезапного исчезновения проверяемых branches.

Remediation: после добавления рискованных сценариев зафиксировать умеренный baseline/ratchet; решения о качестве по-прежнему принимать по scenarios, не по одному проценту.

WL-P3-07 — Frontend .env закоммичен и не игнорируется

frontend/.env tracked и содержит только локальный VITE_API_BASE_URL; секрета сейчас нет, а Vite vars всё равно публичны. Но .gitignore не исключает .env, поэтому следующий агент может случайно закоммитить новое значение вместо изменения .env.example.

Remediation: оставить tracked .env.example, игнорировать рабочие .env* с явным исключением examples и добавить secret/hygiene check.

WL-P3-08 — Backend хранит tracked пустой runtime DB вне ignore pattern

backend/dev.db tracked, имеет размер 0 bytes. .gitignore:61-62 исключает только prisma/*.db, поэтому root dev.db не защищён от будущего заполнения user/session/token data.

Remediation: удалить пустой artifact отдельным согласованным change, игнорировать DB files во всех runtime locations и проверять, что fixtures создаются во временном каталоге.

Архитектурная оценка

Для оценки использовалась модель deep modules: хороший модуль скрывает заметную сложность за небольшим устойчивым интерфейсом; слабый seam заставляет каждого caller знать детали выбора, последовательности или внутреннего состояния.

Удачные seams

  • TbankClient скрывает gRPC transport, metadata, rate/queue policy и преобразование upstream errors; замена транспорта локализована в одном adapter boundary.
  • TbankCrypto предоставляет малый интерфейс encrypt/decrypt поверх корректного authenticated encryption и не разносит формат ciphertext по feature-коду.
  • StructuredLogger и RequestContextService централизуют allowlist/redaction и correlation context.
  • PrismaService изолирует жизненный цикл DB adapter; repository-specific data access остаётся в сервисах предметной области.
  • Frontend apiClient централизует bearer token, cookie credentials, single-flight refresh и retry; generated OpenAPI types дают единую транспортную модель.

Слишком распределённые интерфейсы

Интерфейс Где протекает сложность Последствие
React Query cache identity Каждый entity hook комбинирует custom key с generated queryOptions, а mutation UI отдельно угадывает ключи. Уже привело к WL-P1-01; refactor или новый endpoint легко повторит дефект.
Выбор OpenSpec store Canonical store задаётся prose и флагом, тогда как второй root остаётся распознаваемым CLI. Результат зависит от cwd/команды, а не от предметного состояния проекта.
Workspace verification Frontend имеет широкий verify, backend — набор неодинаковых scripts, cross-contract — ручной, Git roots независимы. Агент не может получить один однозначный read-only verdict.
Completion evidence Tasks, archive command, Git commits и результаты gates не образуют одного проверяемого record. Архив не гарантирует, что работа закончена и проверена.

TbankConnectionModule пока не следует механически дробить: один adapter seam для одного провайдера глубже, чем россыпь формальных abstractions. Разделение оправдано только при появлении независимых вариантов transport/provider или если модуль нельзя тестировать без знания нескольких несвязанных политик. Практический deletion test: удаление T-Bank должно затронуть feature/module wiring и клиентские T-Bank slices, но не auth, logging, portfolio domain primitives или общий transport.

Структурный граф

Graphify-анализ выполнен по 319 файлам рабочей области после исключения references/, .git, node_modules, build/coverage outputs, env, сертификатов и DB. Итоговый граф содержит 1 307 nodes, 1 816 edges и 92 communities. Самые связные прикладные узлы — TbankClient, TbankCrypto, StructuredLogger, PrismaService, RequestContextService и frontend useAuth; это подтверждает, что transport/security/context seams являются реальными центрами системы, а не только заявлены в документации.

Единственный подтверждённый source-level cycle вне generated Prisma-кода — frontend/src/shared/i18n/index.ts -> zod.ts -> index.ts (WL-P3-02). Generated Prisma nodes намеренно не используются как основание для архитектурного refactor: их центральность является свойством генератора. Семантическая связь OpenAPI change/spec с frontend API layer — полезный сигнал трассируемости, но сейчас она не подкреплена runtime-vs-snapshot gate (WL-P2-09).

Готовность к работе AI-агентов

Что уже помогает агенту

  • Корневой AGENTS.md коротко объясняет четыре Git-root, роль каждого репозитория и запрет смешивать commits.
  • Frontend instructions хорошо используют progressive disclosure: базовые правила остаются короткими, конкретные convention guides читаются по задаче.
  • OpenSpec specs scenario-based и дают гораздо лучший intent, чем issue title или список файлов.
  • Generated API types, deterministic frontend snapshot check, MSW strict mode, dependency-cruiser и Knip уменьшают пространство скрытых ошибок.
  • Локальная модель прав не требует сети или внешних credentials для большинства статических проверок.

Почему агент всё ещё будет ошибаться

Ожидание Текущая реальность Типичная ошибка агента
Инструкция соответствует gate «strict» написан, но compiler strict не запускается. Агент передаёт 16 скрытых type errors и функциональный cache bug.
Verification не изменяет diff Backend lint всегда использует --fix. Reviewer незаметно меняет пользовательский код и смешивает audit с implementation.
Source of truth один CLI видит два OpenSpec roots. Change создаётся/проверяется не в том store.
Archived означает complete Archive допускает unchecked tasks. Новый агент принимает незавершённую работу за поставленную.
Все обязательные gates runnable Backend typecheck отсутствует, e2e падает до Jest, contract freshness ручная. Агент либо сдаёт непроверенный результат, либо импровизирует несовместимую команду.
Docs описывают фактическое состояние README/templates и feature/MVP содержат устаревшие сведения. Агент повторно исследует проект или реализует уже существующую возможность.
Review независим Обязательной fresh-agent проверки по spec и standards нет. Авторская ошибка проходит вместе с авторской интерпретацией acceptance criteria.

Целевой локальный workflow

  1. Агент из одного entrypoint определяет canonical OpenSpec store и затронутые Git-root; двусмысленность считается ошибкой.
  2. До реализации существует согласованный change: requirements, scenarios, design decisions, task-to-repo map и рискованные acceptance tests.
  3. Реализация затрагивает только перечисленные repos; generated artifacts меняются только явной generate-командой.
  4. Один read-only verifier выбирает нужные checks: format/lint check, app+test typecheck, unit/risk tests, OpenSpec validation, runtime-vs-snapshot contract и clean-diff check.
  5. Fresh agent получает OpenSpec, diff и evidence и отдельно проверяет две оси: Spec (все requirements/scenarios реализованы) и Standards (архитектура, безопасность, repo rules, лишние изменения). Для auth/crypto/data-loss changes добавляется security lens.
  6. Archive разрешён только после зелёных machine gates, закрытых tasks и записанного evidence по каждому Git-root. Reviewer ничего автоматически не исправляет.

Этот workflow не требует hosted/CI инфраструктуры: он является локальным контрактом команд и артефактов. Главное свойство — одинаковый результат у автора и у независимого агента, а не количество текста в инструкциях.

Предлагаемые будущие OpenSpec changes

Ниже — группировка для будущего планирования; changes в рамках аудита не создавались.

Предлагаемый change Находки Impact / effort Минимальные acceptance criteria
stabilize-frontend-query-and-strict-contracts P1-01, P1-02, P2-07, P2-08 high / M Реальные query keys имеют одного владельца; connect/delete обновляют status/accounts; strict включён для app и tooling; cold-start/refetch scenarios проходят.
harden-openspec-authority-and-lifecycle P1-03, P1-04 high / S–M CLI из workspace и specs выбирает один store; phantom root отсутствует; incomplete tasks блокируют archive; completion evidence проверяется машинно.
make-local-verification-agent-safe P1-05, P2-05, P2-06, P2-09, P2-11, P2-13 high / M Один non-mutating workspace command; backend lint/check разделён с fix; app+test typecheck и e2e проходят; backend OpenAPI сравнивается со snapshot; финальный diff содержит только ожидаемые файлы.
harden-auth-and-cache-concurrency P2-01, P2-02, P2-03 high / M Bounded cache; parallel registration стабильно даёт один success и один 409; refresh CAS допускает одного победителя без случайного revoke.
align-api-contract-and-client-resilience P2-04, P2-07, P2-08 high / M Runtime/status/OpenAPI/types совпадают; stale data остаются видимыми; transport failures не создают unhandled rejection.
refresh-project-documentation-and-agent-entrypoints P2-10, P2-11, P2-13 medium / M README не являются templates; architecture/data-flow и exact bootstrap актуальны; authoritative/JIT docs явно разделены; команды проверены на чистой локальной среде.
address-dependency-advisories-and-repo-hygiene P2-12, P3-07, P3-08 medium / S–M Applicability обоих advisories документирована; безопасные upgrades имеют regression tests; .env/DB policy защищена ignore/check без удаления нужных examples.
tighten-frontend-quality-guardrails P3-02–P3-06 low–medium / S–M Нет source cycles/deep imports; lang и loading semantics корректны; route splitting имеет измеримый критерий; coverage threshold используется только как regression signal.

Зависимости: сначала устранить двусмысленность OpenSpec lifecycle, затем закрепить единый verifier; feature/security changes должны опираться уже на эти два process seams. P3 hygiene можно выполнять независимо, но не смешивать с функциональным change без явной причины.

Результаты воспроизводимых проверок

Среда аудита: Node v22.21.0, npm 10.9.4, OpenSpec 1.9.0.

Область Команда Результат
frontend npm run typecheck pass; текущий non-strict gate зелёный.
frontend npx tsc -p tsconfig.app.json --noEmit --strict fail: 16 type errors; в том числе 5 TS2783 по queryKey.
frontend npm run lint pass.
frontend npm run openapi:check pass; generated types совпадают с committed snapshot.
frontend npm run test:run pass: 15 files, 46 tests.
frontend npm run knip pass.
frontend npm run deps pass: 97 modules, 293 dependencies, violations нет.
frontend npm run build pass; JS chunk около 620 kB / 186 kB gzip, warning >500 kB.
backend npm test -- --runInBand pass: 17 suites, 51 tests.
backend npx eslint "{src,apps,libs,test}/**/*.ts" fail: одна Prettier error в upcoming-events.service.ts:40; source не изменён.
backend npx tsc --noEmit fail: incomplete typed TbankClient mock в test compile.
backend npm run test:e2e fail до Jest: Prisma Schema engine error.
backend npx prisma validate pass.
backend node test/runtime-check.cjs pass.
specs openspec validate --all --store wealth-ledger-specs pass: 12 specs, 0 failures.
workspace openspec list --json vs openspec list --store wealth-ledger-specs --json fail consistency: phantom active change vs 0 canonical changes.
dependencies npm audit --json отдельно в frontend/backend frontend: 1 high package; backend: 3 high findings; применимость помечена needs verification.
repositories git status --short в четырёх subrepos clean на входе в аудит.

Зелёные unit tests доказывают только покрытые сценарии; они не отменяют красные strict/typecheck/e2e/contract gates. npm audit выполнялся с сетью только для получения актуального advisory snapshot и не менял lockfiles.

Изменения относительно audit.md от 13–14.08.2026

Новый отчёт является самостоятельным snapshot, а не дописыванием старого.

  • Старые утверждения о staged AD в specs и отсутствии OpenSpec/opsx tooling больше не верны: все четыре subrepo clean, OpenSpec 1.9.0 доступен, canonical store проходит 12/12.
  • A-01 (неатомарный refresh), A-04 (устаревшая T-Bank документация), A-06/A-14 (architecture/workflow/evidence) и A-07 (registration race) сохраняются и переоценены как WL-P2-03, P2-10/P2-13 и P2-02.
  • Hosted-specific A-03 намеренно не переоценивался: по согласованной рамке это локальный MVP, hosted/deployment требования исключены.
  • Lifecycle-риск A-05 изменил форму: Git index теперь clean, но canonical store раздвоен с phantom root, а один архив содержит unchecked tasks.
  • Frontend test/FSD foundation стала сильнее, однако строгая компиляция впервые выявила cache-key defect и 16 пропущенных типовых ошибок.
  • Впервые отдельно проверены backend read-only gates, runtime-vs-snapshot contract, актуальные dependency advisories и пригодность workflow для независимого AI review.

Приоритетный backlog

Порядок Действие Находки Impact Effort
1 Исправить query identity и реально включить strict TypeScript вместе со сценарными тестами. P1-01, P1-02 high M
2 Установить один OpenSpec root и запретить archive с незавершёнными tasks. P1-03, P1-04 high S–M
3 Сделать backend/workspace verification полностью read-only и проходящим. P1-05, P2-05, P2-06, P2-09, P2-13 high M
4 Закрыть registration/refresh races и ограничить operations cache. P2-01–P2-03 high M
5 Синхронизировать runtime OpenAPI и улучшить cached/cold-start UX. P2-04, P2-07, P2-08 high M
6 Обновить README, architecture/development docs, MVP и agent evidence workflow. P2-10, P2-11, P2-13 medium M
7 Проверить advisories и закрепить .env/DB hygiene. P2-12, P3-07, P3-08 medium S–M
8 Закрыть локальные frontend guardrails. P3-02–P3-06 low–medium S–M

Ограничения и needs verification

  • Не выполнялись live-вызовы T-Bank, browser E2E, нагрузочные тесты и destructive race experiments на пользовательских данных.
  • Причина Prisma schema-engine failure не диагностировалась: аудит подтверждает красный gate, но не приписывает ему неподтверждённый root cause.
  • Runtime-влияние React Router и deepmerge-ts advisories на текущие call paths не доказано; подтверждены только версии и advisory ranges.
  • Cross-repo path alignment проверен статически; полный backend-runtime-to-generated-client contract test отсутствует и сам является находкой.
  • Graphify semantic extraction использовалась как навигация и cross-check, а не как единственное доказательство severity. Точная token-cost метрика subagents была недоступна, поэтому не оценивалась.
  • references/ не читалась и не индексировалась. Hosted, deployment, CI/CD, Docker, PostgreSQL, HA и scale остаются вне рамки.

Заключение

WealthLedger — не технически плохой проект: его основные domain/transport/security seams лучше среднего для локального MVP, а typed contract, FSD, unit tests и scenario-based specs дают хороший фундамент. Главный разрыв находится между заявленным процессом и машинно проверяемым процессом. Пока агент может выполнить написанные команды и всё равно получить неверные cache updates, пропустить strict errors, выбрать не тот OpenSpec root или изменить код во время review.

Первая цель улучшений — не добавлять больше prose, а углубить три интерфейса: один владелец query/cache identity, один canonical OpenSpec lifecycle и один non-mutating local verifier с независимым review. После этого проект станет существенно ближе к требованию «агент выполняет задачу практически без ошибок и без переделки».