Files
docs/audits/audit-13-08-2026.md
T
2026-08-24 10:33:29 +03:00

237 lines
26 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
Дата: 2026-08-13
Обновление: 2026-08-14 — повторная проверка находок после доработок `align-frontend-foundation` и `align-frontend-ui-patterns`. Статус каждой находки помечен: `[done]` закрыто, `[partial]` частично, `[open]` не тронуто.
Статус: снимок текущей рабочей копии; исправления и OpenSpec-артефакты не создавались.
## Цель и охват
Проведён тщательный аудит `frontend`, `backend`, `specs`, `docs`, корневых файлов координации и `.opencode`. Папка `references/` исключена. Оценивались качество кода, архитектура, безопасность, обработка ошибок, целостность данных, производительность, accessibility, качество сценариев тестирования, документация, OpenSpec и артефакты для AI-агентов.
Текущая рабочая копия является источником фактов. История Git использовалась только для контекста жизненного цикла OpenSpec. Проценты и пороги test coverage намеренно не оценивались.
## Методика и ограничения
Аудит сопоставляет локальные правила из `AGENTS.md`, OpenSpec, feature docs, ADR и фактический код. Проверялись критичные цепочки: регистрация и сессии, transport API, подключение T-Bank, счета и портфель, а также путь «спецификация -> реализация -> документация -> инструкция агенту».
Ограничения:
- Не проводились нагрузочные тесты, анализ production-конфигурации, live-проверки T-Bank, TLS/proxy и multi-instance развёртывания.
- `references/` не анализировалась.
- В `specs` присутствуют staged additions/deletions артефактов двух изменений; это нестабильное состояние рабочей копии, а не подтверждённый стабильный релиз.
- Команда `opsx` отсутствует в среде, поэтому обязательная lifecycle-проверка `opsx-verify` не была выполнена.
## Карта зрелости
| Область | Оценка | Основание |
| --- | --- | --- |
| Код backend | Требует приоритетных исправлений | Сильные криптография и денежная арифметика, но есть риски ротации и отзыва сессий. |
| Код frontend | Удовлетворительно | Типизированный API и UI-состояния устойчивы; A-11/A-12 закрыты, но остаётся production fallback (A-03). |
| Тестовые сценарии | Требует усиления | Frontend покрыт (transport + UI resilience); backend concurrent E2E отсутствует. |
| Документация | Требует исправлений | Feature docs полезны, но часть устарела; architecture/development пока заглушки. |
| OpenSpec | Удовлетворительно с риском процесса | Основные спецификации качественные и валидны, но lifecycle и traceability между репозиториями недостаточны. |
| Артефакты для AI-агентов | Удовлетворительно с риском процесса | Границы репозиториев заданы хорошо, но OpenSpec context и multi-repo verification не формализованы. |
## Приоритетный backlog
| Приоритет | ID | Область | Владелец | Краткое действие | Статус | Зависимости |
| --- | --- | --- | --- | --- | --- | --- |
| High | A-01 | Безопасность сессий | backend | Сделать refresh rotation атомарной. | open | Prisma conditional update/transaction. |
| High | A-02 | Отзыв сессий | backend | Проверять активность `sid` либо документировать ограниченную модель отзыва access token. | open | Решение о DB lookup/cache в guard. |
| High | A-03 | Production transport | frontend | Убрать fallback на `localhost` для production; использовать явный origin или same-origin `/api`. | open | Контракт reverse proxy и env. |
| High | A-04 | Документация T-Bank | docs | Исправить устаревшее ограничение про счета и портфели. | open | Нет. |
| High | A-05 | OpenSpec lifecycle | specs | Атомарно завершить архивирование либо восстановить активные артефакты. | partial | Подтверждение намеренного lifecycle. |
| High | A-06 | Операционная документация | docs | Добавить архитектурную карту и workflow разработки/верификации. | partial | Подтверждение команд и границ от frontend/backend. |
| Medium | A-07 | Регистрация | backend | Преобразовать unique-constraint race в `EMAIL_ALREADY_REGISTERED`. | open | Prisma error type/code. |
| Medium | A-08 | API errors | backend | Исправить сериализацию validation errors. | open | Нет. |
| Medium | A-09 | Сессии и cache | backend | Ввести retention/index для sessions и bounded TTL/LRU cache для portfolio metadata. | open | Политика retention и лимиты. |
| Medium | A-10 | Большие портфели | backend | Ограничить параллелизм получения metadata ниже лимита очереди клиента. | open | Целевой latency limit. |
| Medium | A-11 | Auth transport | frontend | Синхронизировать восстановленный token с `accessTokenRef`. | done | Нет. |
| Medium | A-12 | FSD | frontend | Перевести внешние импорты слайсов на public API. | done | Нет. |
| Medium | A-13 | Тестовые сценарии | frontend/backend | Добавить конкурентные auth E2E и behavior-тесты T-Bank UI. | partial | A-01, A-07; React Query test wrapper. |
| Medium | A-14 | ADR и OpenSpec context | docs/specs/root | Актуализировать ADR, заполнить OpenSpec context и multi-repo evidence. | open | Согласование общей политики. |
## Находки
### High
#### A-01. Ротация refresh token не атомарна — `[open]`
- Доказательство: `backend/src/auth/auth.service.ts:75-96,148-166` выполняет read/verify/update в отдельных действиях.
- Обновление 2026-08-14: код не менялся; риск сохраняется.
- Риск: два параллельных запроса с одним cookie могут оба пройти проверку и получить access token; итоговый hash зависит от последней записи.
- Минимальное действие: обновлять с условием по `id`, `refreshTokenHash`, активности и сроку жизни; считать нулевое число обновлений отказом.
- Владелец: backend. Зависимости: Prisma conditional update/transaction.
#### A-02. Отзыв сессии не прекращает действие уже выданного access JWT — `[open]`
- Доказательство: `backend/src/auth/jwt.strategy.ts:19-23`, `backend/src/auth/jwt-auth.guard.ts:4-5`, `backend/src/auth/auth.service.ts:89-94,114-117` проверяют подпись, срок и тип token, но не состояние `sid`.
- Обновление 2026-08-14: backend не менялся; риск сохраняется.
- Минимальное действие: проверять активный неистёкший `sid` в guard/strategy либо явно принять и документировать bounded-revocation модель.
- Владелец: backend. Зависимости: решение о lookup/cache.
#### A-03. Production fallback направляет bearer token на localhost пользователя — `[open]`
- Доказательство: `frontend/src/shared/api/config.ts:1-2` использует `http://localhost:3000` при отсутствии build-time URL; `frontend/src/shared/api/client.ts:54-57` направляет туда bearer-authenticated requests.
- Обновление 2026-08-14: fallback `?? "http://localhost:3000"` перенесён в `frontend/src/shared/api/client.ts:6`; риск сохраняется.
- Минимальное действие: требовать явный production API origin либо использовать same-origin `/api`; завершать build/start при отсутствии допустимой настройки.
- Владелец: frontend. Зависимости: env и reverse-proxy contract.
#### A-04. Документация T-Bank противоречит реализованным функциям — `[open]`
- Доказательство: `docs/features/tbank-connection.md:38-40` откладывает счета и портфели, но `docs/features/brokerage-accounts.md:1-55` и `docs/features/portfolio-view.md:1-59` описывают их как поставленные.
- Обновление 2026-08-14: не исправлено.
- Минимальное действие: оставить в limitations только реально отложенные возможности.
- Владелец: docs. Зависимости: нет.
#### A-05. Активные OpenSpec changes находятся в неполном переходном состоянии — `[partial]`
- Доказательство: `git status` в `specs` показывает staged additions/deletions `design.md`, delta-spec и `tasks.md` для `add-brokerage-accounts` и `add-portfolio-view`; правила требуют полный набор в `specs/AGENTS.md:16-17`.
- Обновление 2026-08-14: `align-frontend-foundation` и `align-frontend-ui-patterns` архивированы в `archive/` и закоммичены в HEAD, но в индексе остаются 16 staged-записей `AD` (включая новые изменения); `git status` не чист, требуется `git reset` для завершения lifecycle.
- Минимальное действие: одним коммитом завершить archive/move либо восстановить активные артефакты.
- Владелец: specs. Зависимости: подтверждение lifecycle.
#### A-06. Архитектурная и development-документация фактически отсутствуют — `[partial]`
- Доказательство: `docs/architecture/README.md:1-3` и `docs/development/README.md:1-3` содержат только заглушки, хотя их назначение закреплено в `docs/AGENTS.md:15-17`.
- Обновление 2026-08-14: добавлена `docs/development/frontend-workflow.md` (Node 22, `npm run verify`, `openapi:sync`); `docs/architecture/README.md` остаётся заглушкой.
- Минимальное действие: добавить актуальную component/data-flow карту и руководство по setup, generation, verification и выпуску изменений.
- Владелец: docs. Зависимости: подтверждение команд и границ frontend/backend.
### Medium
#### A-07. Конкурентная регистрация возвращает 500 вместо контрактного 409 — `[open]`
- Доказательство: `backend/src/auth/auth.service.ts:29-42` делает preflight `findUnique`, не обрабатывая unique-constraint вокруг `create`.
- Риск: один из параллельных запросов с одинаковым email завершится не `EMAIL_ALREADY_REGISTERED`.
- Минимальное действие: перехватывать Prisma unique-constraint и возвращать `ConflictException`.
- Владелец: backend. Зависимости: Prisma error type/code.
#### A-08. Формат validation error не соответствует фактическому Nest payload — `[open]`
- Доказательство: `backend/src/common/filters/http-exception.filter.ts:23-38,58-66` трактует `message: string[]` как `ValidationError[]`.
- Риск: публичный `VALIDATION_ERROR` содержит пустой `errors`, клиент не получает указания на неверные поля.
- Минимальное действие: задать `exceptionFactory` с `ValidationError[]` или корректно сериализовать строки Nest.
- Владелец: backend. Зависимости: нет.
#### A-09. Сессии и portfolio cache не имеют границ роста — `[open]`
- Доказательство: `backend/src/auth/auth.service.ts:137-165`, `backend/prisma/schema.prisma:28-39` не очищают sessions и не содержат expiry index; `backend/src/tbank/portfolio.service.ts:54-55,67-98,229-273` держит неограниченные process-local maps.
- Риск: рост SQLite-таблицы и памяти при длительной эксплуатации.
- Минимальное действие: retention/cleanup сессий и index `expiresAt`; bounded TTL/LRU с периодической очисткой cache.
- Владелец: backend. Зависимости: политика retention, лимиты cache.
#### A-10. Большой портфель может терять metadata из-за burst concurrency — `[open]`
- Доказательство: `backend/src/tbank/portfolio.service.ts:131-133,229-272` запускает все lookups одновременно, `backend/src/tbank/tbank.client.ts:208-209` отклоняет новые вызовы после 50 queued.
- Риск: часть позиций получает fallback FIGI/`Unknown instrument`, хотя данные доступны upstream.
- Минимальное действие: bounded concurrency ниже лимита очереди либо очередь без hard rejection.
- Владелец: backend. Зависимости: допустимая latency для крупных портфелей.
#### A-11. Восстановленный access token не синхронизируется с transport ref — `[done]`
- Доказательство: `frontend/src/features/auth/model/auth.tsx:66-75` обновляет state, но не `accessTokenRef.current`; `:100-110` настраивает shared client только из ref.
- Обновление 2026-08-14: исправлено. `auth.tsx` синхронизирует `accessTokenRef.current` при bootstrap/refresh/login/logout/clear (строки 54,74,87,96,108); transport — единственный владелец Bearer header, покрыт тестами в `client.test.ts`.
- Минимальное действие: синхронно обновлять ref при восстановлении token; затем удалить дублирующиеся headers.
- Владелец: frontend. Зависимости: нет.
#### A-12. Нарушается FSD public API boundary — `[done]`
- Доказательство: `frontend/src/app/routes/AppRoutes.tsx:2-9`, `src/pages/accounts/ui/AccountsPage.tsx:5-10`, `src/pages/settings/ui/SettingsPage.tsx:4-6`, `src/features/tbank-connection/ui/TbankConnectionSection.tsx:13-16` импортируют внутренности слайсов вопреки `frontend/docs/STRUCTURE.md:11`.
- Обновление 2026-08-14: исправлено. Импорты идут через public `index.ts`, добавлен `dependency-cruiser`; `npm run deps` проходит без нарушений (84 modules).
- Минимальное действие: экспортировать нужные элементы через `index.ts` и импортировать только public API.
- Владелец: frontend. Зависимости: нет.
#### A-13. Критичные сценарии не защищены тестами — `[partial]`
- Доказательство: `backend/test/app.e2e-spec.ts:36-85` не тестирует параллельные refresh/registration; `frontend/src/features/tbank-connection/ui/TbankConnectionSection.tsx:27-169` не имеет focused tests.
- Обновление 2026-08-14: frontend-часть закрыта — transport-тесты (`client.test.ts`), `RouteErrorBoundary.test.tsx`, `AccountPage.test.tsx`, `TbankConnectionSection.view.test.tsx`; `vitest run` 30/30. Backend-часть (concurrent auth E2E) не добавлена — backend не менялся.
- Минимальное действие: добавить concurrent E2E для auth и behavior tests для replace/delete/error/pending/invalidation UI.
- Владелец: backend и frontend. Зависимости: A-01, A-07; React Query test wrapper.
#### A-14. Контекст и verification evidence не адаптированы к multi-repo SDD — `[open]`
- Доказательство: `specs/openspec/config.yaml:1-20` оставлен примером; `.opencode/skills/openspec-verify-change/SKILL.md:70-115` не требует evidence matrix по `frontend`, `backend`, `docs`; архивные tasks, например `specs/openspec/changes/archive/2026-08-13-add-portfolio-view/tasks.md:13-15,23-32`, не содержат commit SHA и результаты проверок.
- Обновление 2026-08-14: не менялось; `config.yaml` всё ещё пример.
- Минимальное действие: задать OpenSpec context/rules и completion record: репозиторий, SHA, версия/ревизия контракта, дата и результат проверок.
- Владелец: specs и root. Зависимости: соглашение frontend/backend/docs.
### Low
#### A-15. ADR отражают план, а не реализованное состояние — `[open]`
- Доказательство: `docs/adr/003-tbank-server-side-grpc-boundary.md:19-21`, `004-encrypted-per-user-tbank-token.md:13-15`, `006-auth-session-policy.md:13-15` описывают будущую реализацию; feature docs фиксируют поставленные функции.
- Риск: читатель не отличит accepted decision от delivery status.
- Минимальное действие: дополнить consequences статусом реализации и ссылкой на feature doc; обновить конфликтующий policy в `docs/adr/007-react-query-fetch-policy.md:9-15`.
- Владелец: docs. Зависимости: согласование frontend/backend policy.
#### A-16. Локальные frontend guides не имеют явного статуса в общей документации — `[open]`
- Доказательство: `frontend/AGENTS.md:19-24` требует читать пять внутренних документов, а корневой `AGENTS.md:17,23` описывает только durable docs.
- Риск: агенту неясно, какая документация авторитетна и как поддерживать локальные convention guides.
- Минимальное действие: отметить их как frontend-local conventions и связать с development index.
- Владелец: root. Зависимости: согласование frontend/docs.
#### A-17. Небольшие UX/i18n несоответствия — `[open]`
- Доказательство: `frontend/src/features/auth/ui/AuthForm.tsx:84-99` не содержит `autoComplete`; `src/entities/brokerage-account/ui/BrokerageAccountList.tsx:33-38`, `src/entities/portfolio/model/display.ts:20-25`, `src/features/tbank-connection/ui/TbankConnectionSection.tsx:99-104` жёстко используют `ru-RU` при наличии i18n.
- Риск: хуже работа password manager и некорректный формат дат при добавлении другой локали.
- Минимальное действие: добавить semantic autocomplete и использовать текущий язык i18n в formatters.
- Владелец: frontend. Зависимости: решение о дополнительных локалях для второй части.
#### A-18. Непоследовательное оформление основной OpenSpec — `[open]`
- Доказательство: `specs/openspec/specs/user-authentication/spec.md:1-5` начинается с `## Purpose`, в отличие от остальных спецификаций, например `specs/openspec/specs/tbank-connection/spec.md:1-7`.
- Риск: небольшая навигационная и tooling-неоднозначность.
- Минимальное действие: добавить `# user-authentication Specification`.
- Владелец: specs. Зависимости: нет.
## Подтверждённые сильные стороны
- T-Bank tokens шифруются AES-256-GCM с уникальным nonce и authentication tag: `backend/src/tbank/tbank.crypto.ts:16-43`.
- Tokens валидируются до сохранения и не возвращаются connection status API: `backend/src/tbank/tbank-connection.service.ts:34-61`, `backend/src/tbank/tbank-connection.controller.ts:35-55`.
- JWT algorithm закреплён, а для неизвестного пользователя используется dummy bcrypt hash: `backend/src/auth/jwt.strategy.ts:11-16`, `backend/src/auth/auth.service.ts:50-58`.
- Денежная арифметика использует `bigint`, не floating point: `backend/src/tbank/decimal.ts:1-44`.
- Frontend использует централизованный типизированный OpenAPI client, не хранит token в persistent storage, а маршруты и страницы имеют loading/error/empty patterns.
- Основные OpenSpec scenario-based, охватывают ошибки, cache и безопасность; feature docs содержат пользовательские потоки, API, ограничения и Mermaid diagrams.
- Корневые и локальные `AGENTS.md` ясно фиксируют независимые репозитории и SDD intent.
## Результаты воспроизводимых проверок
Проверки выполнены 2026-08-13 без запуска `test:cov` и без анализа процентов покрытия. Повторные проверки frontend — 2026-08-14.
| Область | Команда | Результат |
| --- | --- | --- |
| frontend | `./node_modules/.bin/biome check .` | Успешно: 64 файла, замечаний нет. |
| frontend | `./node_modules/.bin/tsc -b --noEmit` | Успешно. |
| frontend | `./node_modules/.bin/vitest run` | Успешно: 8 files, 25 tests. |
| frontend | `./node_modules/.bin/tsc -b && ./node_modules/.bin/vite build` | Успешно; Vite предупреждает о JS chunk 582.99 kB (176.19 kB gzip) после minification. |
| frontend | `! rg -i todo src --glob '*.{ts,tsx}'` | Успешно: TODO не найдено. |
| backend | `rtk lint "{src,apps,libs,test}/**/*.ts"` | Успешно: ESLint без замечаний. |
| backend | `npm test` | Успешно: 8 suites, 22 tests. |
| backend | `rtk npm run build` | Успешно: Prisma generate и Nest build. |
| specs | `npx openspec validate --all --strict` | Успешно: 7 specs, 0 failures. |
| specs | `opsx validate --all --strict` | Не выполнено: `opsx: command not found`. |
## Повторные проверки 2026-08-14
| Область | Команда | Результат |
| --- | --- | --- |
| frontend | `npm run typecheck` | Успешно. |
| frontend | `./node_modules/.bin/biome check .` | Успешно: 81 файл, замечаний нет. |
| frontend | `npm run openapi:check` | Успешно: generated artifact соответствует committed snapshot. |
| frontend | `npm run deps` | Успешно: `dependency-cruiser`, 84 modules, нарушений нет. |
| frontend | `npm run knip` | Успешно. |
| frontend | `vitest run` | Успешно: 30 tests (11 files). |
| specs | `npx openspec validate --all --strict` | Успешно: 8 specs (добавлен `frontend-ui-resilience`), 0 failures. |
| specs | `git status` | Грязный: 16 staged-записей `AD` в `openspec/changes/` — ожидает `git reset`. |
## Рекомендуемый порядок исправлений
1. Создать одну security-focused change для A-01, A-02, A-03 и A-07; добавить связанные concurrent tests из A-13 (backend-часть). Это единственный незакрытый High-блок.
2. Завершить lifecycle A-05: выполнить `git reset` в `specs`, чтобы убрать 16 staged-записей `AD` и вернуть воспроизводимый OpenSpec store.
3. Исправить A-04, A-06, A-14 и A-15 одним documentation/process change, включив multi-repo evidence matrix.
4. Выполнить A-08, A-09 и A-10 отдельными backend-изменениями.
5. Завершить Low-улучшения (A-16, A-17, A-18) после согласования локалей и статуса локальных руководств.