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

439 lines
59 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Аудит WealthLedger — 20.08.2026
## Исследовательская база: актуальные практики AI-агентов
| Практика | Первичный источник | Следствие для аудита |
| --- | --- | --- |
| Делить постоянные инструкции по области: общий контракт в корне, узкие правила рядом с подсистемой; ближайший файл имеет приоритет. | [Codex: AGENTS.md](https://learn.chatgpt.com/docs/agent-configuration/agents-md), [AGENTS.md standard](https://agents.md/), [GitHub Copilot](https://docs.github.com/en/copilot/how-tos/copilot-on-github/customize-copilot/add-custom-instructions/add-repository-instructions) | Проверить, что корневой `AGENTS.md` задаёт только межрепозиторные правила, а `frontend/`, `backend/` и `specs/` имеют собственные, непротиворечивые инструкции там, где различаются команды и нормы. |
| Ограничивать объём постоянного контекста; переносить локальные процедуры и правила в scoped/JIT-слои. | [Codex: 32 KiB по умолчанию](https://learn.chatgpt.com/docs/agent-configuration/agents-md), [Claude: цель — <200 строк](https://code.claude.com/docs/en/memory), [Copilot: инструкции не длиннее двух страниц](https://docs.github.com/en/copilot/how-tos/copilot-on-github/customize-copilot/add-custom-instructions/add-repository-instructions), [Gemini: JIT-context](https://geminicli.com/docs/cli/gemini-md/) | Измерить размер и дублирование инструкций; исключить из постоянного слоя длинные справочники, task-specific шаги и нерелевантные правила. |
| Формулировать инструкции конкретно и так, чтобы их можно было проверить: точные пути, команды, предусловия и ожидаемый результат. | [Claude: effective instructions](https://code.claude.com/docs/en/memory), [Copilot: validate bootstrap/build/test/lint commands](https://docs.github.com/en/copilot/how-tos/copilot-on-github/customize-copilot/add-custom-instructions/add-repository-instructions), [AGENTS.md standard](https://agents.md/) | Проверить наличие единственного детерминированного local bootstrap и явно перечисленных non-mutating проверок для каждого независимого репозитория. |
| Держать runnable acceptance criteria рядом со спецификацией и трассировать изменение от контракта к проверкам. | [Copilot: документировать последовательность validation](https://docs.github.com/en/copilot/how-tos/copilot-on-github/customize-copilot/add-custom-instructions/add-repository-instructions), [Codex: instructions with review rules](https://learn.chatgpt.com/docs/agent-configuration/agents-md) | Проверить, что OpenSpec change содержит требования, задачи и исполнимые локальные gates, а инструкции связывают реализацию с `opsx-verify` до завершения работы. |
| Сохранять воспроизводимое окружение: версии рантайма и инструментов, порядок bootstrap, необходимые переменные и подтверждённые обходы. | [Copilot: bootstrap и версии](https://docs.github.com/en/copilot/how-tos/copilot-on-github/customize-copilot/add-custom-instructions/add-repository-instructions), [Gemini sandbox: reproducibility](https://geminicli.com/docs/cli/sandbox/) | Проверить, можно ли на чистой локальной машине повторить установку и gates без угадывания версий, скрытых сервисов или порядка команд. |
| Разделять поведенческие советы и жёсткое принуждение: критичные запреты должны выполняться permissions/hooks/sandbox, а не только prose. | [Claude: settings/hook enforcement vs context](https://code.claude.com/docs/en/memory), [Claude security](https://code.claude.com/docs/en/security), [Gemini policy engine](https://geminicli.com/docs/reference/policy-engine/) | Выявить правила, нарушение которых опасно (границы записи, секреты, destructive-команды), и оценить, есть ли для них machine-enforced локальный контроль. |
| Работать с минимальными правами: read-only для исследования, узкие writable roots/allow-rules для реализации, явное подтверждение сети и выхода за границы. | [Codex sandboxing](https://learn.chatgpt.com/docs/sandboxing), [Claude security](https://code.claude.com/docs/en/security), [Gemini trusted folders](https://geminicli.com/docs/cli/trusted-folders/) | Проверить, что agent workflow не требует broad permissions и что необязательные внешние действия не входят в local read-only MVP. |
| Обеспечивать независимый review после локальных gates: reviewer получает явные критерии и не должен вносить изменения вместо проверки. | [GitHub Copilot code review](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/request-a-code-review/use-code-review), [Claude agent teams: independent lenses](https://code.claude.com/docs/en/agent-teams) | Проверить, что workflow предписывает отдельный review реализации по OpenSpec, diff, тестам и отсутствию лишних изменений после успешной самопроверки. |
| Минимизировать rework: versioned инструкции должны фиксировать только подтверждённые команды, известные ограничения и архитектурные точки входа. | [Claude: когда добавлять знания в CLAUDE.md](https://code.claude.com/docs/en/memory), [Copilot CLI: не перегружать постоянные инструкции](https://docs.github.com/en/copilot/concepts/agents/copilot-cli/comparing-cli-features) | Проверить, что документация не заставляет агента повторно искать уже известные команды и не содержит устаревших либо конфликтующих указаний. |
Ограничения доказательности:
- Использованы только официальные документация вендоров и сайт открытого стандарта `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](https://github.com/advisories/GHSA-2w69-qvjg-hvjx); registry предлагает переход на 7.18.2 вне текущего exact range;
- backend: `deepmerge-ts@7.1.5` через `prisma -> @prisma/config` попадает в [GHSA-ggr8-5vv4-36mx](https://github.com/advisories/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. После этого проект станет существенно ближе к требованию «агент выполняет задачу практически без ошибок и без переделки».